mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-07-12 12:45:56 +00:00
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / windows-qt (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / macos-qt (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-qt (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / linux-qt-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* Changed symbol bindings to their names * Fixed kakposfos' requests on GitHub * Fine, I'll do it myself. --------- Co-authored-by: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com>
457 lines
14 KiB
C++
457 lines
14 KiB
C++
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#pragma once
|
|
|
|
#include <array>
|
|
#include <map>
|
|
#include <string>
|
|
#include <unordered_set>
|
|
|
|
#include "SDL3/SDL_events.h"
|
|
#include "SDL3/SDL_timer.h"
|
|
|
|
#include "common/logging/log.h"
|
|
#include "common/types.h"
|
|
#include "core/libraries/pad/pad.h"
|
|
#include "fmt/format.h"
|
|
#include "input/controller.h"
|
|
|
|
// +1 and +2 is taken
|
|
#define SDL_MOUSE_WHEEL_UP SDL_EVENT_MOUSE_WHEEL + 3
|
|
#define SDL_MOUSE_WHEEL_DOWN SDL_EVENT_MOUSE_WHEEL + 4
|
|
#define SDL_MOUSE_WHEEL_LEFT SDL_EVENT_MOUSE_WHEEL + 5
|
|
#define SDL_MOUSE_WHEEL_RIGHT SDL_EVENT_MOUSE_WHEEL + 7
|
|
|
|
#define SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT SDL_GAMEPAD_BUTTON_COUNT + 1
|
|
#define SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER SDL_GAMEPAD_BUTTON_COUNT + 2
|
|
#define SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT SDL_GAMEPAD_BUTTON_COUNT + 3
|
|
|
|
// idk who already used what where so I just chose a big number
|
|
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
|
|
|
|
#define LEFTJOYSTICK_HALFMODE 0x00010000
|
|
#define RIGHTJOYSTICK_HALFMODE 0x00020000
|
|
#define BACK_BUTTON 0x00040000
|
|
|
|
#define KEY_TOGGLE 0x00200000
|
|
#define MOUSE_GYRO_ROLL_MODE 0x00400000
|
|
|
|
#define SDL_UNMAPPED UINT32_MAX - 1
|
|
|
|
namespace Input {
|
|
using Input::Axis;
|
|
using Libraries::Pad::OrbisPadButtonDataOffset;
|
|
|
|
struct AxisMapping {
|
|
u32 axis;
|
|
s16 value;
|
|
AxisMapping(SDL_GamepadAxis a, s16 v) : axis(a), value(v) {}
|
|
};
|
|
|
|
enum class InputType { Axis, KeyboardMouse, Controller, Count };
|
|
const std::array<std::string, 4> input_type_names = {"Axis", "KBM", "Controller", "Unknown"};
|
|
|
|
class InputID {
|
|
public:
|
|
InputType type;
|
|
u32 sdl_id;
|
|
InputID(InputType d = InputType::Count, u32 i = SDL_UNMAPPED) : type(d), sdl_id(i) {}
|
|
bool operator==(const InputID& o) const {
|
|
return type == o.type && sdl_id == o.sdl_id;
|
|
}
|
|
bool operator!=(const InputID& o) const {
|
|
return type != o.type || sdl_id != o.sdl_id;
|
|
}
|
|
bool operator<=(const InputID& o) const {
|
|
return type <= o.type && sdl_id <= o.sdl_id;
|
|
}
|
|
bool IsValid() const {
|
|
return *this != InputID();
|
|
}
|
|
std::string ToString() {
|
|
return fmt::format("({}: {:x})", input_type_names[static_cast<u8>(type)], sdl_id);
|
|
}
|
|
};
|
|
|
|
class InputEvent {
|
|
public:
|
|
InputID input;
|
|
bool active;
|
|
s8 axis_value;
|
|
|
|
InputEvent(InputID i = InputID(), bool a = false, s8 v = 0)
|
|
: input(i), active(a), axis_value(v) {}
|
|
InputEvent(InputType d, u32 i, bool a = false, s8 v = 0)
|
|
: input(d, i), active(a), axis_value(v) {}
|
|
};
|
|
|
|
// i strongly suggest you collapse these maps
|
|
const std::map<std::string, u32> string_to_cbutton_map = {
|
|
{"triangle", SDL_GAMEPAD_BUTTON_NORTH},
|
|
{"circle", SDL_GAMEPAD_BUTTON_EAST},
|
|
{"cross", SDL_GAMEPAD_BUTTON_SOUTH},
|
|
{"square", SDL_GAMEPAD_BUTTON_WEST},
|
|
{"l1", SDL_GAMEPAD_BUTTON_LEFT_SHOULDER},
|
|
{"r1", SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER},
|
|
{"l3", SDL_GAMEPAD_BUTTON_LEFT_STICK},
|
|
{"r3", SDL_GAMEPAD_BUTTON_RIGHT_STICK},
|
|
{"pad_up", SDL_GAMEPAD_BUTTON_DPAD_UP},
|
|
{"pad_down", SDL_GAMEPAD_BUTTON_DPAD_DOWN},
|
|
{"pad_left", SDL_GAMEPAD_BUTTON_DPAD_LEFT},
|
|
{"pad_right", SDL_GAMEPAD_BUTTON_DPAD_RIGHT},
|
|
{"options", SDL_GAMEPAD_BUTTON_START},
|
|
|
|
// these are outputs only (touchpad can only be bound to itself)
|
|
{"touchpad_left", SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT},
|
|
{"touchpad_center", SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER},
|
|
{"touchpad_right", SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT},
|
|
{"leftjoystick_halfmode", LEFTJOYSTICK_HALFMODE},
|
|
{"rightjoystick_halfmode", RIGHTJOYSTICK_HALFMODE},
|
|
|
|
// this is only for input
|
|
{"back", SDL_GAMEPAD_BUTTON_BACK},
|
|
{"lpaddle_high", SDL_GAMEPAD_BUTTON_LEFT_PADDLE1},
|
|
{"lpaddle_low", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
|
|
{"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
|
|
{"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2},
|
|
{"mouse_gyro_roll_mode", MOUSE_GYRO_ROLL_MODE},
|
|
};
|
|
|
|
const std::map<std::string, AxisMapping> string_to_axis_map = {
|
|
{"axis_left_x_plus", {SDL_GAMEPAD_AXIS_LEFTX, 127}},
|
|
{"axis_left_x_minus", {SDL_GAMEPAD_AXIS_LEFTX, -127}},
|
|
{"axis_left_y_plus", {SDL_GAMEPAD_AXIS_LEFTY, 127}},
|
|
{"axis_left_y_minus", {SDL_GAMEPAD_AXIS_LEFTY, -127}},
|
|
{"axis_right_x_plus", {SDL_GAMEPAD_AXIS_RIGHTX, 127}},
|
|
{"axis_right_x_minus", {SDL_GAMEPAD_AXIS_RIGHTX, -127}},
|
|
{"axis_right_y_plus", {SDL_GAMEPAD_AXIS_RIGHTY, 127}},
|
|
{"axis_right_y_minus", {SDL_GAMEPAD_AXIS_RIGHTY, -127}},
|
|
|
|
{"l2", {SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 127}},
|
|
{"r2", {SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 127}},
|
|
|
|
// should only use these to bind analog inputs to analog outputs
|
|
{"axis_left_x", {SDL_GAMEPAD_AXIS_LEFTX, 127}},
|
|
{"axis_left_y", {SDL_GAMEPAD_AXIS_LEFTY, 127}},
|
|
{"axis_right_x", {SDL_GAMEPAD_AXIS_RIGHTX, 127}},
|
|
{"axis_right_y", {SDL_GAMEPAD_AXIS_RIGHTY, 127}},
|
|
};
|
|
const std::map<std::string, u32> string_to_keyboard_key_map = {
|
|
// alphanumeric
|
|
{"a", SDLK_A},
|
|
{"b", SDLK_B},
|
|
{"c", SDLK_C},
|
|
{"d", SDLK_D},
|
|
{"e", SDLK_E},
|
|
{"f", SDLK_F},
|
|
{"g", SDLK_G},
|
|
{"h", SDLK_H},
|
|
{"i", SDLK_I},
|
|
{"j", SDLK_J},
|
|
{"k", SDLK_K},
|
|
{"l", SDLK_L},
|
|
{"m", SDLK_M},
|
|
{"n", SDLK_N},
|
|
{"o", SDLK_O},
|
|
{"p", SDLK_P},
|
|
{"q", SDLK_Q},
|
|
{"r", SDLK_R},
|
|
{"s", SDLK_S},
|
|
{"t", SDLK_T},
|
|
{"u", SDLK_U},
|
|
{"v", SDLK_V},
|
|
{"w", SDLK_W},
|
|
{"x", SDLK_X},
|
|
{"y", SDLK_Y},
|
|
{"z", SDLK_Z},
|
|
{"0", SDLK_0},
|
|
{"1", SDLK_1},
|
|
{"2", SDLK_2},
|
|
{"3", SDLK_3},
|
|
{"4", SDLK_4},
|
|
{"5", SDLK_5},
|
|
{"6", SDLK_6},
|
|
{"7", SDLK_7},
|
|
{"8", SDLK_8},
|
|
{"9", SDLK_9},
|
|
|
|
// symbols
|
|
{"grave", SDLK_GRAVE},
|
|
{"tilde", SDLK_TILDE},
|
|
{"exclamation", SDLK_EXCLAIM},
|
|
{"at", SDLK_AT},
|
|
{"hash", SDLK_HASH},
|
|
{"dollar", SDLK_DOLLAR},
|
|
{"percent", SDLK_PERCENT},
|
|
{"caret", SDLK_CARET},
|
|
{"ampersand", SDLK_AMPERSAND},
|
|
{"asterisk", SDLK_ASTERISK},
|
|
{"lparen", SDLK_LEFTPAREN},
|
|
{"rparen", SDLK_RIGHTPAREN},
|
|
{"minus", SDLK_MINUS},
|
|
{"underscore", SDLK_UNDERSCORE},
|
|
{"equals", SDLK_EQUALS},
|
|
{"plus", SDLK_PLUS},
|
|
{"lbracket", SDLK_LEFTBRACKET},
|
|
{"rbracket", SDLK_RIGHTBRACKET},
|
|
{"lbrace", SDLK_LEFTBRACE},
|
|
{"rbrace", SDLK_RIGHTBRACE},
|
|
{"backslash", SDLK_BACKSLASH},
|
|
{"pipe", SDLK_PIPE},
|
|
{"semicolon", SDLK_SEMICOLON},
|
|
{"colon", SDLK_COLON},
|
|
{"apostrophe", SDLK_APOSTROPHE},
|
|
{"quote", SDLK_DBLAPOSTROPHE},
|
|
{"comma", SDLK_COMMA},
|
|
{"less", SDLK_LESS},
|
|
{"period", SDLK_PERIOD},
|
|
{"greater", SDLK_GREATER},
|
|
{"slash", SDLK_SLASH},
|
|
{"question", SDLK_QUESTION},
|
|
|
|
// special keys
|
|
{"escape", SDLK_ESCAPE},
|
|
{"printscreen", SDLK_PRINTSCREEN},
|
|
{"scrolllock", SDLK_SCROLLLOCK},
|
|
{"pausebreak", SDLK_PAUSE},
|
|
{"backspace", SDLK_BACKSPACE},
|
|
{"delete", SDLK_DELETE},
|
|
{"insert", SDLK_INSERT},
|
|
{"home", SDLK_HOME},
|
|
{"end", SDLK_END},
|
|
{"pgup", SDLK_PAGEUP},
|
|
{"pgdown", SDLK_PAGEDOWN},
|
|
{"tab", SDLK_TAB},
|
|
{"capslock", SDLK_CAPSLOCK},
|
|
{"enter", SDLK_RETURN},
|
|
{"lshift", SDLK_LSHIFT},
|
|
{"rshift", SDLK_RSHIFT},
|
|
{"lctrl", SDLK_LCTRL},
|
|
{"rctrl", SDLK_RCTRL},
|
|
{"lalt", SDLK_LALT},
|
|
{"ralt", SDLK_RALT},
|
|
{"lmeta", SDLK_LGUI},
|
|
{"rmeta", SDLK_RGUI},
|
|
{"lwin", SDLK_LGUI},
|
|
{"rwin", SDLK_RGUI},
|
|
{"space", SDLK_SPACE},
|
|
{"up", SDLK_UP},
|
|
{"down", SDLK_DOWN},
|
|
{"left", SDLK_LEFT},
|
|
{"right", SDLK_RIGHT},
|
|
|
|
// keypad
|
|
{"kp0", SDLK_KP_0},
|
|
{"kp1", SDLK_KP_1},
|
|
{"kp2", SDLK_KP_2},
|
|
{"kp3", SDLK_KP_3},
|
|
{"kp4", SDLK_KP_4},
|
|
{"kp5", SDLK_KP_5},
|
|
{"kp6", SDLK_KP_6},
|
|
{"kp7", SDLK_KP_7},
|
|
{"kp8", SDLK_KP_8},
|
|
{"kp9", SDLK_KP_9},
|
|
{"kpperiod", SDLK_KP_PERIOD},
|
|
{"kpcomma", SDLK_KP_COMMA},
|
|
{"kpslash", SDLK_KP_DIVIDE},
|
|
{"kpasterisk", SDLK_KP_MULTIPLY},
|
|
{"kpminus", SDLK_KP_MINUS},
|
|
{"kpplus", SDLK_KP_PLUS},
|
|
{"kpequals", SDLK_KP_EQUALS},
|
|
{"kpenter", SDLK_KP_ENTER},
|
|
|
|
// mouse
|
|
{"leftbutton", SDL_BUTTON_LEFT},
|
|
{"rightbutton", SDL_BUTTON_RIGHT},
|
|
{"middlebutton", SDL_BUTTON_MIDDLE},
|
|
{"sidebuttonback", SDL_BUTTON_X1},
|
|
{"sidebuttonforward", SDL_BUTTON_X2},
|
|
{"mousewheelup", SDL_MOUSE_WHEEL_UP},
|
|
{"mousewheeldown", SDL_MOUSE_WHEEL_DOWN},
|
|
{"mousewheelleft", SDL_MOUSE_WHEEL_LEFT},
|
|
{"mousewheelright", SDL_MOUSE_WHEEL_RIGHT},
|
|
|
|
// no binding
|
|
{"unmapped", SDL_UNMAPPED},
|
|
};
|
|
|
|
void ParseInputConfig(const std::string game_id);
|
|
|
|
class InputBinding {
|
|
public:
|
|
InputID keys[3];
|
|
InputBinding(InputID k1 = InputID(), InputID k2 = InputID(), InputID k3 = InputID()) {
|
|
// we format the keys so comparing them will be very fast, because we will only have to
|
|
// compare 3 sorted elements, where the only possible duplicate item is 0
|
|
|
|
// duplicate entries get changed to one original, one null
|
|
if (k1 == k2 && k1 != InputID()) {
|
|
k2 = InputID();
|
|
}
|
|
if (k1 == k3 && k1 != InputID()) {
|
|
k3 = InputID();
|
|
}
|
|
if (k3 == k2 && k2 != InputID()) {
|
|
k2 = InputID();
|
|
}
|
|
// this sorts them
|
|
if (k1 <= k2 && k1 <= k3) {
|
|
keys[0] = k1;
|
|
if (k2 <= k3) {
|
|
keys[1] = k2;
|
|
keys[2] = k3;
|
|
} else {
|
|
keys[1] = k3;
|
|
keys[2] = k2;
|
|
}
|
|
} else if (k2 <= k1 && k2 <= k3) {
|
|
keys[0] = k2;
|
|
if (k1 <= k3) {
|
|
keys[1] = k1;
|
|
keys[2] = k3;
|
|
} else {
|
|
keys[1] = k3;
|
|
keys[2] = k1;
|
|
}
|
|
} else {
|
|
keys[0] = k3;
|
|
if (k1 <= k2) {
|
|
keys[1] = k1;
|
|
keys[2] = k2;
|
|
} else {
|
|
keys[1] = k2;
|
|
keys[3] = k1;
|
|
}
|
|
}
|
|
}
|
|
// copy ctor
|
|
InputBinding(const InputBinding& o) {
|
|
keys[0] = o.keys[0];
|
|
keys[1] = o.keys[1];
|
|
keys[2] = o.keys[2];
|
|
}
|
|
|
|
inline bool operator==(const InputBinding& o) {
|
|
// InputID() signifies an unused slot
|
|
return (keys[0] == o.keys[0] || keys[0] == InputID() || o.keys[0] == InputID()) &&
|
|
(keys[1] == o.keys[1] || keys[1] == InputID() || o.keys[1] == InputID()) &&
|
|
(keys[2] == o.keys[2] || keys[2] == InputID() || o.keys[2] == InputID());
|
|
// it is already very fast,
|
|
// but reverse order makes it check the actual keys first instead of possible 0-s,
|
|
// potenially skipping the later expressions of the three-way AND
|
|
}
|
|
inline int KeyCount() const {
|
|
return (keys[0].IsValid() ? 1 : 0) + (keys[1].IsValid() ? 1 : 0) +
|
|
(keys[2].IsValid() ? 1 : 0);
|
|
}
|
|
// Sorts by the amount of non zero keys - left side is 'bigger' here
|
|
bool operator<(const InputBinding& other) const {
|
|
return KeyCount() > other.KeyCount();
|
|
}
|
|
inline bool IsEmpty() {
|
|
return !(keys[0].IsValid() || keys[1].IsValid() || keys[2].IsValid());
|
|
}
|
|
std::string ToString() { // todo add device type
|
|
switch (KeyCount()) {
|
|
case 1:
|
|
return fmt::format("({})", keys[0].ToString());
|
|
case 2:
|
|
return fmt::format("({}, {})", keys[0].ToString(), keys[1].ToString());
|
|
case 3:
|
|
return fmt::format("({}, {}, {})", keys[0].ToString(), keys[1].ToString(),
|
|
keys[2].ToString());
|
|
default:
|
|
return "Empty";
|
|
}
|
|
}
|
|
|
|
// returns an InputEvent based on the event type (keyboard, mouse buttons/wheel, or controller)
|
|
static InputEvent GetInputEventFromSDLEvent(const SDL_Event& e);
|
|
};
|
|
|
|
class ControllerOutput {
|
|
static GameController* controller;
|
|
|
|
public:
|
|
static void SetControllerOutputController(GameController* c);
|
|
static void LinkJoystickAxes();
|
|
|
|
u32 button;
|
|
u32 axis;
|
|
// these are only used as s8,
|
|
// but I added some padding to avoid overflow if it's activated by multiple inputs
|
|
// axis_plus and axis_minus pairs share a common new_param, the other outputs have their own
|
|
s16 old_param;
|
|
s16* new_param;
|
|
bool old_button_state, new_button_state, state_changed, positive_axis;
|
|
|
|
ControllerOutput(const u32 b, u32 a = SDL_GAMEPAD_AXIS_INVALID, bool p = true) {
|
|
button = b;
|
|
axis = a;
|
|
new_param = new s16(0);
|
|
old_param = 0;
|
|
positive_axis = p;
|
|
}
|
|
ControllerOutput(const ControllerOutput& o) : button(o.button), axis(o.axis) {
|
|
new_param = new s16(*o.new_param);
|
|
}
|
|
~ControllerOutput() {
|
|
delete new_param;
|
|
}
|
|
inline bool operator==(const ControllerOutput& o) const { // fucking consts everywhere
|
|
return button == o.button && axis == o.axis;
|
|
}
|
|
inline bool operator!=(const ControllerOutput& o) const {
|
|
return button != o.button || axis != o.axis;
|
|
}
|
|
std::string ToString() const {
|
|
return fmt::format("({}, {}, {})", (s32)button, (int)axis, old_param);
|
|
}
|
|
inline bool IsButton() const {
|
|
return axis == SDL_GAMEPAD_AXIS_INVALID && button != SDL_GAMEPAD_BUTTON_INVALID;
|
|
}
|
|
inline bool IsAxis() const {
|
|
return axis != SDL_GAMEPAD_AXIS_INVALID && button == SDL_GAMEPAD_BUTTON_INVALID;
|
|
}
|
|
|
|
void ResetUpdate();
|
|
void AddUpdate(InputEvent event);
|
|
void FinalizeUpdate();
|
|
};
|
|
class BindingConnection {
|
|
public:
|
|
InputBinding binding;
|
|
ControllerOutput* output;
|
|
u32 axis_param;
|
|
InputID toggle;
|
|
|
|
BindingConnection(InputBinding b, ControllerOutput* out, u32 param = 0, InputID t = InputID()) {
|
|
binding = b;
|
|
axis_param = param;
|
|
output = out;
|
|
toggle = t;
|
|
}
|
|
bool operator<(const BindingConnection& other) const {
|
|
// a button is a higher priority than an axis, as buttons can influence axes
|
|
// (e.g. joystick_halfmode)
|
|
if (output->IsButton() &&
|
|
(other.output->IsAxis() && (other.output->axis != SDL_GAMEPAD_AXIS_LEFT_TRIGGER &&
|
|
other.output->axis != SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))) {
|
|
return true;
|
|
}
|
|
if (binding < other.binding) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
InputEvent ProcessBinding();
|
|
};
|
|
|
|
// Updates the list of pressed keys with the given input.
|
|
// Returns whether the list was updated or not.
|
|
bool UpdatePressedKeys(InputEvent event);
|
|
|
|
void ActivateOutputsFromInputs();
|
|
|
|
} // namespace Input
|