// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include #include #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 // 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 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 input_type_names = {"Axis", "KBM", "Controller", "Unknown"}; class InputID { public: InputType type; u32 sdl_id; InputID(InputType d = InputType::Count, u32 i = (u32)-1) : 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[(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 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", SDL_GAMEPAD_BUTTON_TOUCHPAD}, {"leftjoystick_halfmode", LEFTJOYSTICK_HALFMODE}, {"rightjoystick_halfmode", RIGHTJOYSTICK_HALFMODE}, // this is only for input {"back", SDL_GAMEPAD_BUTTON_BACK}, }; const std::map 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 string_to_keyboard_key_map = { {"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}, {"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}, {"comma", SDLK_COMMA}, {"period", SDLK_PERIOD}, {"question", SDLK_QUESTION}, {"semicolon", SDLK_SEMICOLON}, {"minus", SDLK_MINUS}, {"underscore", SDLK_UNDERSCORE}, {"lparenthesis", SDLK_LEFTPAREN}, {"rparenthesis", SDLK_RIGHTPAREN}, {"lbracket", SDLK_LEFTBRACKET}, {"rbracket", SDLK_RIGHTBRACKET}, {"lbrace", SDLK_LEFTBRACE}, {"rbrace", SDLK_RIGHTBRACE}, {"backslash", SDLK_BACKSLASH}, {"dash", SDLK_SLASH}, {"enter", SDLK_RETURN}, {"space", SDLK_SPACE}, {"tab", SDLK_TAB}, {"backspace", SDLK_BACKSPACE}, {"escape", SDLK_ESCAPE}, {"left", SDLK_LEFT}, {"right", SDLK_RIGHT}, {"up", SDLK_UP}, {"down", SDLK_DOWN}, {"lctrl", SDLK_LCTRL}, {"rctrl", SDLK_RCTRL}, {"lshift", SDLK_LSHIFT}, {"rshift", SDLK_RSHIFT}, {"lalt", SDLK_LALT}, {"ralt", SDLK_RALT}, {"lmeta", SDLK_LGUI}, {"rmeta", SDLK_RGUI}, {"lwin", SDLK_LGUI}, {"rwin", SDLK_RGUI}, {"home", SDLK_HOME}, {"end", SDLK_END}, {"pgup", SDLK_PAGEUP}, {"pgdown", SDLK_PAGEDOWN}, {"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}, {"kpperiod", SDLK_KP_PERIOD}, {"kpcomma", SDLK_KP_COMMA}, {"kpdivide", SDLK_KP_DIVIDE}, {"kpmultiply", SDLK_KP_MULTIPLY}, {"kpminus", SDLK_KP_MINUS}, {"kpplus", SDLK_KP_PLUS}, {"kpenter", SDLK_KP_ENTER}, {"kpequals", SDLK_KP_EQUALS}, {"capslock", SDLK_CAPSLOCK}, }; 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