shadPS4/src/input/input_handler.h
kalaposfos13 c4bfaa6031
Added keyboard and mouse input remapping, mouse movement to joystick logic, GUI and more (#1356)
* added support for loading keyboard config from file

* final minor update before pull request

* fix messing up the merge

* fix waitEvent to correctly handle mouse inputs

* add license

* Applied coding style fixes

* clang-format fucked up the .ini file

* actually fix clang changing ini syntax
use relative path for the ini file

* remove big commented out code blocks,
and fixed platform-dependent code

* fix windows hating me

* added mouse config option

* added toggle for mouse movement input (f7)

* fix license and style

* add numpad support i accidentally left out

* added support for mouse wheel (to buttons only)

* if keyboard config doesn't exist, autogenerate it

* added keybinds for "walk mode"

* Mouse movement input is now off by default

* code cleanup and misc fixes

* delete config file since it is now autogenerated

* F6 = F7 + F9

* added better mouse handling with config options

* Added capslock support

* fix clang-format

* Added support for mod key toggle key

* F6 and F7 are removed, F9 captures and enables the mouse

* Encapsulated globals and new classes in a new namespace

* Added mouse side button support

* Added per-game config

* relocated input parser to the new namespace

* changed parser parameters to make it possible to use it from the gui

* added home, end, pgup and pgdown

* Resolved merge conflict and refactored code

* Updated default keybindings

* Changed input handling to be single-threaded

* General code cleanup

* Start working on new backend

* Mouse polling, CMakeLists, and basic framework

* Output update handling, and reworked file creating, reading and parsing

* Parsing works now

* Single key button inputs work now

* Axis outputs work now

* Wheel works now (for me), l2/r2 handling improvements, and misc bugfixes

* Downgraded prints to log_debug, and implemented input hierarchy

* Implemented key toggle

* Added mouse parameter parsing

* clang-format

* Fixed clang and added a const keyword for mac

* Fix input hierarchy

* Fixed joysick halfmodes, and possibly the last update on input hierarchy

* clang-format

* Rewrote the default config to reflect new changes

* clang

* Update code style

* Updated sorting to accomodate for that one specific edge case

* Fix default config and the latest bug with input hiearchy

* Fix typo

* Temporarily added my GUI

* Update cmakelists

* Possible fix for Gravity Rush

* Update Help text, default config, and clang

* Updated README with the new keybind info

* okay so maybe the gravity rush fix might have slightly broken the joystick halfmode and key toggle

* Fixed mistakenly overwriting the last opened config with the default one if the GUI is opened multiple times in a session

* Updated Help descriptions and fixed mouse movement default parameters

* Fix crash if the Help dialog was opened a second time
If it's closed with the top right close button instead of clicking the Help button again, a required flag wasn't reset, making the next click on Help try to close a nonexistent window and segfault

* Added closing the config also closing the Help window, and fixed more segfaults due to mismatched flags

* Initial controller support

* clang and debug print cleanup

* Initial axis-to-button logic

* Updated Help text

* Added 'Reset to Default' button in GUI

* Minor text and description updates + fixed an issue with Help text box rendering

* Fix button-to-touchpad logic and l2/r2 handling, as they are both axes and buttons
The touchpad's button state was correctly handled, so games that use that were fine, but the touchDown flag was always set to true, so games that use this flag had problems, like Gravity Rush

* Fix merge conflict

* Clang

* Added back back button to touchpad binding

* Added touchpad button handling

* Added end-of-line comments and fixed some crashes happening with the VS debugger

* Apply recent changes from kbm-only

* Deadzone + initial directional axis-to-button mapping

* Added that one missing space in the README. Are you all happy now?

* Fixups from making everything use SDL

* Revert directional joystick code and fix a memory leak

* Change config directory name again to conform to project standards

* Clang

* Revert the old deeadzone code and properly add the new one

* Clang
2025-01-31 16:36:14 +02:00

407 lines
13 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
// 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<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 = (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<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", 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<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 = {
{"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