mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-06-12 13:43:15 +00:00
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
This commit is contained in:
parent
f3810cebea
commit
c4bfaa6031
17 changed files with 1996 additions and 286 deletions
676
src/input/input_handler.cpp
Normal file
676
src/input/input_handler.cpp
Normal file
|
@ -0,0 +1,676 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "input_handler.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <typeinfo>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "SDL3/SDL_events.h"
|
||||
#include "SDL3/SDL_timer.h"
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/version.h"
|
||||
#include "input/controller.h"
|
||||
#include "input/input_mouse.h"
|
||||
|
||||
namespace Input {
|
||||
/*
|
||||
Project structure:
|
||||
n to m connection between inputs and outputs
|
||||
Keyup and keydown events update a dynamic list* of u32 'flags' (what is currently in the list is
|
||||
'pressed') On every event, after flag updates, we check for every input binding -> controller output
|
||||
pair if all their flags are 'on' If not, disable; if so, enable them. For axes, we gather their data
|
||||
into a struct cumulatively from all inputs, then after we checked all of those, we update them all
|
||||
at once. Wheel inputs generate a timer that doesn't turn off their outputs automatically, but push a
|
||||
userevent to do so.
|
||||
|
||||
What structs are needed?
|
||||
InputBinding(key1, key2, key3)
|
||||
ControllerOutput(button, axis) - we only need a const array of these, and one of the attr-s is
|
||||
always 0 BindingConnection(inputBinding (member), controllerOutput (ref to the array element))
|
||||
|
||||
Things to always test before pushing like a dumbass:
|
||||
Button outputs
|
||||
Axis outputs
|
||||
Input hierarchy
|
||||
Multi key inputs
|
||||
Mouse to joystick
|
||||
Key toggle
|
||||
Joystick halfmode
|
||||
|
||||
Don't be an idiot and test only the changed part expecting everything else to not be broken
|
||||
*/
|
||||
|
||||
bool leftjoystick_halfmode = false, rightjoystick_halfmode = false;
|
||||
int leftjoystick_deadzone, rightjoystick_deadzone, lefttrigger_deadzone, righttrigger_deadzone;
|
||||
|
||||
std::list<std::pair<InputEvent, bool>> pressed_keys;
|
||||
std::list<InputID> toggled_keys;
|
||||
static std::vector<BindingConnection> connections;
|
||||
|
||||
auto output_array = std::array{
|
||||
// Important: these have to be the first, or else they will update in the wrong order
|
||||
ControllerOutput(LEFTJOYSTICK_HALFMODE),
|
||||
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
|
||||
ControllerOutput(KEY_TOGGLE),
|
||||
|
||||
// Button mappings
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
|
||||
|
||||
// Axis mappings
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
|
||||
};
|
||||
|
||||
void ControllerOutput::LinkJoystickAxes() {
|
||||
// for (int i = 17; i < 23; i += 2) {
|
||||
// delete output_array[i].new_param;
|
||||
// output_array[i].new_param = output_array[i + 1].new_param;
|
||||
// }
|
||||
}
|
||||
|
||||
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
|
||||
using OPBDO = OrbisPadButtonDataOffset;
|
||||
|
||||
switch (button) {
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
|
||||
return OPBDO::Down;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_UP:
|
||||
return OPBDO::Up;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
|
||||
return OPBDO::Left;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
|
||||
return OPBDO::Right;
|
||||
case SDL_GAMEPAD_BUTTON_SOUTH:
|
||||
return OPBDO::Cross;
|
||||
case SDL_GAMEPAD_BUTTON_NORTH:
|
||||
return OPBDO::Triangle;
|
||||
case SDL_GAMEPAD_BUTTON_WEST:
|
||||
return OPBDO::Square;
|
||||
case SDL_GAMEPAD_BUTTON_EAST:
|
||||
return OPBDO::Circle;
|
||||
case SDL_GAMEPAD_BUTTON_START:
|
||||
return OPBDO::Options;
|
||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
|
||||
return OPBDO::TouchPad;
|
||||
case SDL_GAMEPAD_BUTTON_BACK:
|
||||
return OPBDO::TouchPad;
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
||||
return OPBDO::L1;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
||||
return OPBDO::R1;
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
|
||||
return OPBDO::L3;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
|
||||
return OPBDO::R3;
|
||||
default:
|
||||
return OPBDO::None;
|
||||
}
|
||||
}
|
||||
|
||||
Axis GetAxisFromSDLAxis(u8 sdl_axis) {
|
||||
switch (sdl_axis) {
|
||||
case SDL_GAMEPAD_AXIS_LEFTX:
|
||||
return Axis::LeftX;
|
||||
case SDL_GAMEPAD_AXIS_LEFTY:
|
||||
return Axis::LeftY;
|
||||
case SDL_GAMEPAD_AXIS_RIGHTX:
|
||||
return Axis::RightX;
|
||||
case SDL_GAMEPAD_AXIS_RIGHTY:
|
||||
return Axis::RightY;
|
||||
case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
|
||||
return Axis::TriggerLeft;
|
||||
case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
|
||||
return Axis::TriggerRight;
|
||||
default:
|
||||
return Axis::AxisMax;
|
||||
}
|
||||
}
|
||||
|
||||
// syntax: 'name, name,name' or 'name,name' or 'name'
|
||||
InputBinding GetBindingFromString(std::string& line) {
|
||||
std::array<InputID, 3> keys = {InputID(), InputID(), InputID()};
|
||||
|
||||
// Check and process tokens
|
||||
for (const auto token : std::views::split(line, ',')) { // Split by comma
|
||||
const std::string t(token.begin(), token.end());
|
||||
InputID input;
|
||||
|
||||
if (string_to_keyboard_key_map.find(t) != string_to_keyboard_key_map.end()) {
|
||||
input = InputID(InputType::KeyboardMouse, string_to_keyboard_key_map.at(t));
|
||||
} else if (string_to_axis_map.find(t) != string_to_axis_map.end()) {
|
||||
input = InputID(InputType::Axis, (u32)string_to_axis_map.at(t).axis);
|
||||
} else if (string_to_cbutton_map.find(t) != string_to_cbutton_map.end()) {
|
||||
input = InputID(InputType::Controller, string_to_cbutton_map.at(t));
|
||||
} else {
|
||||
// Invalid token found; return default binding
|
||||
LOG_DEBUG(Input, "Invalid token found: {}", t);
|
||||
return InputBinding();
|
||||
}
|
||||
|
||||
// Assign to the first available slot
|
||||
for (auto& key : keys) {
|
||||
if (!key.IsValid()) {
|
||||
key = input;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(Input, "Parsed line: {}", InputBinding(keys[0], keys[1], keys[2]).ToString());
|
||||
return InputBinding(keys[0], keys[1], keys[2]);
|
||||
}
|
||||
|
||||
void ParseInputConfig(const std::string game_id = "") {
|
||||
const auto config_file = Config::GetFoolproofKbmConfigFile(game_id);
|
||||
|
||||
if (game_id == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
// we reset these here so in case the user fucks up or doesn't include some of these,
|
||||
// we can fall back to default
|
||||
connections.clear();
|
||||
float mouse_deadzone_offset = 0.5;
|
||||
float mouse_speed = 1;
|
||||
float mouse_speed_offset = 0.125;
|
||||
|
||||
leftjoystick_deadzone = 1;
|
||||
rightjoystick_deadzone = 1;
|
||||
lefttrigger_deadzone = 1;
|
||||
righttrigger_deadzone = 1;
|
||||
|
||||
int lineCount = 0;
|
||||
|
||||
std::ifstream file(config_file);
|
||||
std::string line = "";
|
||||
while (std::getline(file, line)) {
|
||||
lineCount++;
|
||||
|
||||
// Strip the ; and whitespace
|
||||
line.erase(std::remove_if(line.begin(), line.end(),
|
||||
[](unsigned char c) { return std::isspace(c); }),
|
||||
line.end());
|
||||
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
// Truncate lines starting at #
|
||||
std::size_t comment_pos = line.find('#');
|
||||
if (comment_pos != std::string::npos) {
|
||||
line = line.substr(0, comment_pos);
|
||||
}
|
||||
// Remove trailing semicolon
|
||||
if (!line.empty() && line[line.length() - 1] == ';') {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
}
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split the line by '='
|
||||
std::size_t equal_pos = line.find('=');
|
||||
if (equal_pos == std::string::npos) {
|
||||
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
|
||||
lineCount, line);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string output_string = line.substr(0, equal_pos);
|
||||
std::string input_string = line.substr(equal_pos + 1);
|
||||
std::size_t comma_pos = input_string.find(',');
|
||||
|
||||
if (output_string == "mouse_to_joystick") {
|
||||
if (input_string == "left") {
|
||||
SetMouseToJoystick(1);
|
||||
} else if (input_string == "right") {
|
||||
SetMouseToJoystick(2);
|
||||
} else {
|
||||
LOG_WARNING(Input, "Invalid argument for mouse-to-joystick binding");
|
||||
SetMouseToJoystick(0);
|
||||
}
|
||||
continue;
|
||||
} else if (output_string == "key_toggle") {
|
||||
if (comma_pos != std::string::npos) {
|
||||
// handle key-to-key toggling (separate list?)
|
||||
InputBinding toggle_keys = GetBindingFromString(input_string);
|
||||
if (toggle_keys.KeyCount() != 2) {
|
||||
LOG_WARNING(Input,
|
||||
"Syntax error: Please provide exactly 2 keys: "
|
||||
"first is the toggler, the second is the key to toggle: {}",
|
||||
line);
|
||||
continue;
|
||||
}
|
||||
ControllerOutput* toggle_out =
|
||||
&*std::ranges::find(output_array, ControllerOutput(KEY_TOGGLE));
|
||||
BindingConnection toggle_connection = BindingConnection(
|
||||
InputBinding(toggle_keys.keys[0]), toggle_out, 0, toggle_keys.keys[1]);
|
||||
connections.insert(connections.end(), toggle_connection);
|
||||
continue;
|
||||
}
|
||||
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
|
||||
lineCount, line);
|
||||
continue;
|
||||
} else if (output_string == "mouse_movement_params") {
|
||||
std::stringstream ss(input_string);
|
||||
char comma; // To hold the comma separators between the floats
|
||||
ss >> mouse_deadzone_offset >> comma >> mouse_speed >> comma >> mouse_speed_offset;
|
||||
|
||||
// Check for invalid input (in case there's an unexpected format)
|
||||
if (ss.fail()) {
|
||||
LOG_WARNING(Input, "Failed to parse mouse movement parameters from line: {}", line);
|
||||
continue;
|
||||
}
|
||||
SetMouseParams(mouse_deadzone_offset, mouse_speed, mouse_speed_offset);
|
||||
continue;
|
||||
} else if (output_string == "analog_deadzone") {
|
||||
std::stringstream ss(input_string);
|
||||
std::string device;
|
||||
int deadzone;
|
||||
std::getline(ss, device, ',');
|
||||
ss >> deadzone;
|
||||
if (ss.fail()) {
|
||||
LOG_WARNING(Input, "Failed to parse deadzone config from line: {}", line);
|
||||
continue;
|
||||
} else {
|
||||
LOG_DEBUG(Input, "Parsed deadzone: {} {}", device, deadzone);
|
||||
}
|
||||
if (device == "leftjoystick") {
|
||||
leftjoystick_deadzone = deadzone;
|
||||
} else if (device == "rightjoystick") {
|
||||
rightjoystick_deadzone = deadzone;
|
||||
} else if (device == "l2") {
|
||||
lefttrigger_deadzone = deadzone;
|
||||
} else if (device == "r2") {
|
||||
righttrigger_deadzone = deadzone;
|
||||
} else {
|
||||
LOG_WARNING(Input, "Invalid axis name at line: {}, data: \"{}\", skipping line.",
|
||||
lineCount, line);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// normal cases
|
||||
InputBinding binding = GetBindingFromString(input_string);
|
||||
BindingConnection connection(InputID(), nullptr);
|
||||
auto button_it = string_to_cbutton_map.find(output_string);
|
||||
auto axis_it = string_to_axis_map.find(output_string);
|
||||
|
||||
if (binding.IsEmpty()) {
|
||||
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
|
||||
lineCount, line);
|
||||
continue;
|
||||
}
|
||||
if (button_it != string_to_cbutton_map.end()) {
|
||||
connection = BindingConnection(
|
||||
binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second)));
|
||||
connections.insert(connections.end(), connection);
|
||||
|
||||
} else if (axis_it != string_to_axis_map.end()) {
|
||||
int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value;
|
||||
connection = BindingConnection(
|
||||
binding,
|
||||
&*std::ranges::find(output_array, ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID,
|
||||
axis_it->second.axis,
|
||||
axis_it->second.value >= 0)),
|
||||
value_to_set);
|
||||
connections.insert(connections.end(), connection);
|
||||
} else {
|
||||
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
|
||||
lineCount, line);
|
||||
continue;
|
||||
}
|
||||
LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount);
|
||||
}
|
||||
file.close();
|
||||
std::sort(connections.begin(), connections.end());
|
||||
for (auto& c : connections) {
|
||||
LOG_DEBUG(Input, "Binding: {} : {}", c.output->ToString(), c.binding.ToString());
|
||||
}
|
||||
LOG_DEBUG(Input, "Done parsing the input config!");
|
||||
}
|
||||
|
||||
u32 GetMouseWheelEvent(const SDL_Event& event) {
|
||||
if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) {
|
||||
LOG_WARNING(Input, "Something went wrong with wheel input parsing!");
|
||||
return (u32)-1;
|
||||
}
|
||||
if (event.wheel.y > 0) {
|
||||
return SDL_MOUSE_WHEEL_UP;
|
||||
} else if (event.wheel.y < 0) {
|
||||
return SDL_MOUSE_WHEEL_DOWN;
|
||||
} else if (event.wheel.x > 0) {
|
||||
return SDL_MOUSE_WHEEL_RIGHT;
|
||||
} else if (event.wheel.x < 0) {
|
||||
return SDL_MOUSE_WHEEL_LEFT;
|
||||
}
|
||||
return (u32)-1;
|
||||
}
|
||||
|
||||
InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
|
||||
switch (e.type) {
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
return InputEvent(InputType::KeyboardMouse, e.key.key, e.key.down, 0);
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
return InputEvent(InputType::KeyboardMouse, (u32)e.button.button, e.button.down, 0);
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
case SDL_EVENT_MOUSE_WHEEL_OFF:
|
||||
return InputEvent(InputType::KeyboardMouse, GetMouseWheelEvent(e),
|
||||
e.type == SDL_EVENT_MOUSE_WHEEL, 0);
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
return InputEvent(InputType::Controller, (u32)e.gbutton.button, e.gbutton.down, 0);
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
return InputEvent(InputType::Axis, (u32)e.gaxis.axis, true, e.gaxis.value / 256);
|
||||
default:
|
||||
return InputEvent();
|
||||
}
|
||||
}
|
||||
|
||||
GameController* ControllerOutput::controller = nullptr;
|
||||
void ControllerOutput::SetControllerOutputController(GameController* c) {
|
||||
ControllerOutput::controller = c;
|
||||
}
|
||||
|
||||
void ToggleKeyInList(InputID input) {
|
||||
if (input.type == InputType::Axis) {
|
||||
LOG_ERROR(Input, "Toggling analog inputs is not supported!");
|
||||
return;
|
||||
}
|
||||
auto it = std::find(toggled_keys.begin(), toggled_keys.end(), input);
|
||||
if (it == toggled_keys.end()) {
|
||||
toggled_keys.insert(toggled_keys.end(), input);
|
||||
LOG_DEBUG(Input, "Added {} to toggled keys", input.ToString());
|
||||
} else {
|
||||
toggled_keys.erase(it);
|
||||
LOG_DEBUG(Input, "Removed {} from toggled keys", input.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerOutput::ResetUpdate() {
|
||||
state_changed = false;
|
||||
new_button_state = false;
|
||||
*new_param = 0; // bruh
|
||||
}
|
||||
void ControllerOutput::AddUpdate(InputEvent event) {
|
||||
state_changed = true;
|
||||
if (button == KEY_TOGGLE) {
|
||||
if (event.active) {
|
||||
ToggleKeyInList(event.input);
|
||||
}
|
||||
} else if (button != SDL_GAMEPAD_BUTTON_INVALID) {
|
||||
if (event.input.type == InputType::Axis) {
|
||||
bool temp = event.axis_value * (positive_axis ? 1 : -1) > 0x40;
|
||||
new_button_state |= event.active && event.axis_value * (positive_axis ? 1 : -1) > 0x40;
|
||||
if (temp) {
|
||||
LOG_DEBUG(Input, "Toggled a button from an axis");
|
||||
}
|
||||
} else {
|
||||
new_button_state |= event.active;
|
||||
}
|
||||
|
||||
} else if (axis != SDL_GAMEPAD_AXIS_INVALID) {
|
||||
switch (axis) {
|
||||
case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
|
||||
case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
|
||||
// if it's a button input, then we know the value to set, so the param is 0.
|
||||
// if it's an analog input, then the param isn't 0
|
||||
*new_param = (event.active ? event.axis_value : 0) + *new_param;
|
||||
break;
|
||||
default:
|
||||
*new_param = (event.active ? event.axis_value : 0) + *new_param;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void ControllerOutput::FinalizeUpdate() {
|
||||
if (!state_changed) {
|
||||
// return;
|
||||
}
|
||||
|
||||
old_button_state = new_button_state;
|
||||
old_param = *new_param;
|
||||
float touchpad_x = 0;
|
||||
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
|
||||
switch (button) {
|
||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
|
||||
touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f
|
||||
: Config::getBackButtonBehavior() == "right" ? 0.75f
|
||||
: 0.5f;
|
||||
controller->SetTouchpadState(0, new_button_state, touchpad_x, 0.5f);
|
||||
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
|
||||
break;
|
||||
case LEFTJOYSTICK_HALFMODE:
|
||||
leftjoystick_halfmode = new_button_state;
|
||||
break;
|
||||
case RIGHTJOYSTICK_HALFMODE:
|
||||
rightjoystick_halfmode = new_button_state;
|
||||
break;
|
||||
// KEY_TOGGLE isn't handled here anymore, as this function doesn't have the necessary data
|
||||
// to do it, and it would be inconvenient to force it here, when AddUpdate does the job just
|
||||
// fine, and a toggle doesn't have to checked against every input that's bound to it, it's
|
||||
// enough that one is pressed
|
||||
default: // is a normal key (hopefully)
|
||||
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
|
||||
break;
|
||||
}
|
||||
} else if (axis != SDL_GAMEPAD_AXIS_INVALID && positive_axis) {
|
||||
// avoid double-updating axes, but don't skip directional button bindings
|
||||
float multiplier = 1.0;
|
||||
int deadzone = 0;
|
||||
auto ApplyDeadzone = [](s16* value, int deadzone) {
|
||||
if (std::abs(*value) <= deadzone) {
|
||||
*value = 0;
|
||||
}
|
||||
};
|
||||
Axis c_axis = GetAxisFromSDLAxis(axis);
|
||||
switch (c_axis) {
|
||||
case Axis::LeftX:
|
||||
case Axis::LeftY:
|
||||
ApplyDeadzone(new_param, leftjoystick_deadzone);
|
||||
multiplier = leftjoystick_halfmode ? 0.5 : 1.0;
|
||||
break;
|
||||
case Axis::RightX:
|
||||
case Axis::RightY:
|
||||
ApplyDeadzone(new_param, rightjoystick_deadzone);
|
||||
multiplier = rightjoystick_halfmode ? 0.5 : 1.0;
|
||||
break;
|
||||
case Axis::TriggerLeft:
|
||||
ApplyDeadzone(new_param, lefttrigger_deadzone);
|
||||
controller->Axis(0, c_axis, GetAxis(0x0, 0x80, *new_param));
|
||||
controller->CheckButton(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20);
|
||||
return;
|
||||
case Axis::TriggerRight:
|
||||
ApplyDeadzone(new_param, righttrigger_deadzone);
|
||||
controller->Axis(0, c_axis, GetAxis(0x0, 0x80, *new_param));
|
||||
controller->CheckButton(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
controller->Axis(0, c_axis, GetAxis(-0x80, 0x80, *new_param * multiplier));
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the list of pressed keys with the given input.
|
||||
// Returns whether the list was updated or not.
|
||||
bool UpdatePressedKeys(InputEvent event) {
|
||||
// Skip invalid inputs
|
||||
InputID input = event.input;
|
||||
if (input.sdl_id == (u32)-1) {
|
||||
return false;
|
||||
}
|
||||
if (input.type == InputType::Axis) {
|
||||
// analog input, it gets added when it first sends an event,
|
||||
// and from there, it only changes the parameter
|
||||
auto it = std::lower_bound(pressed_keys.begin(), pressed_keys.end(), input,
|
||||
[](const std::pair<InputEvent, bool>& e, InputID i) {
|
||||
return std::tie(e.first.input.type, e.first.input.sdl_id) <
|
||||
std::tie(i.type, i.sdl_id);
|
||||
});
|
||||
if (it == pressed_keys.end() || it->first.input != input) {
|
||||
pressed_keys.insert(it, {event, false});
|
||||
LOG_DEBUG(Input, "Added axis {} to the input list", event.input.sdl_id);
|
||||
} else {
|
||||
it->first.axis_value = event.axis_value;
|
||||
}
|
||||
return true;
|
||||
} else if (event.active) {
|
||||
// Find the correct position for insertion to maintain order
|
||||
auto it = std::lower_bound(pressed_keys.begin(), pressed_keys.end(), input,
|
||||
[](const std::pair<InputEvent, bool>& e, InputID i) {
|
||||
return std::tie(e.first.input.type, e.first.input.sdl_id) <
|
||||
std::tie(i.type, i.sdl_id);
|
||||
});
|
||||
|
||||
// Insert only if 'value' is not already in the list
|
||||
if (it == pressed_keys.end() || it->first.input != input) {
|
||||
pressed_keys.insert(it, {event, false});
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Remove 'value' from the list if it's not pressed
|
||||
auto it = std::find_if(
|
||||
pressed_keys.begin(), pressed_keys.end(),
|
||||
[input](const std::pair<InputEvent, bool>& e) { return e.first.input == input; });
|
||||
if (it != pressed_keys.end()) {
|
||||
pressed_keys.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(Input, "No change was made!");
|
||||
return false;
|
||||
}
|
||||
// Check if the binding's all keys are currently active.
|
||||
// It also extracts the analog inputs' parameters, and updates the input hierarchy flags.
|
||||
InputEvent BindingConnection::ProcessBinding() {
|
||||
// the last key is always set (if the connection isn't empty),
|
||||
// and the analog inputs are always the last one due to how they are sorted,
|
||||
// so this signifies whether or not the input is analog
|
||||
InputEvent event = InputEvent(binding.keys[0]);
|
||||
if (pressed_keys.empty()) {
|
||||
return event;
|
||||
}
|
||||
if (event.input.type != InputType::Axis) {
|
||||
// for button inputs
|
||||
event.axis_value = axis_param;
|
||||
}
|
||||
// it's a bit scuffed, but if the output is a toggle, then we put the key here
|
||||
if (output->button == KEY_TOGGLE) {
|
||||
event.input = toggle;
|
||||
}
|
||||
|
||||
// Extract keys from InputBinding and ignore unused or toggled keys
|
||||
std::list<InputID> input_keys = {binding.keys[0], binding.keys[1], binding.keys[2]};
|
||||
input_keys.remove(InputID());
|
||||
for (auto key = input_keys.begin(); key != input_keys.end();) {
|
||||
if (std::find(toggled_keys.begin(), toggled_keys.end(), *key) != toggled_keys.end()) {
|
||||
key = input_keys.erase(key); // Use the returned iterator
|
||||
} else {
|
||||
++key; // Increment only if no erase happened
|
||||
}
|
||||
}
|
||||
if (input_keys.empty()) {
|
||||
LOG_DEBUG(Input, "No actual inputs to check, returning true");
|
||||
event.active = true;
|
||||
return event;
|
||||
}
|
||||
|
||||
// Iterator for pressed_keys, starting from the beginning
|
||||
auto pressed_it = pressed_keys.begin();
|
||||
|
||||
// Store pointers to flags in pressed_keys that need to be set if all keys are active
|
||||
std::list<bool*> flags_to_set;
|
||||
|
||||
// Check if all keys in input_keys are active
|
||||
for (InputID key : input_keys) {
|
||||
bool key_found = false;
|
||||
|
||||
while (pressed_it != pressed_keys.end()) {
|
||||
if (pressed_it->first.input == key && (pressed_it->second == false)) {
|
||||
key_found = true;
|
||||
if (output->positive_axis) {
|
||||
flags_to_set.push_back(&pressed_it->second);
|
||||
}
|
||||
if (pressed_it->first.input.type == InputType::Axis) {
|
||||
event.axis_value = pressed_it->first.axis_value;
|
||||
}
|
||||
++pressed_it;
|
||||
break;
|
||||
}
|
||||
++pressed_it;
|
||||
}
|
||||
if (!key_found) {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
for (bool* flag : flags_to_set) {
|
||||
*flag = true;
|
||||
}
|
||||
if (binding.keys[0].type != InputType::Axis) { // the axes spam inputs, making this unreadable
|
||||
LOG_DEBUG(Input, "Input found: {}", binding.ToString());
|
||||
}
|
||||
event.active = true;
|
||||
return event; // All keys are active
|
||||
}
|
||||
|
||||
void ActivateOutputsFromInputs() {
|
||||
// Reset values and flags
|
||||
for (auto& it : pressed_keys) {
|
||||
it.second = false;
|
||||
}
|
||||
for (auto& it : output_array) {
|
||||
it.ResetUpdate();
|
||||
}
|
||||
|
||||
// Iterate over all inputs, and update their respecive outputs accordingly
|
||||
for (auto& it : connections) {
|
||||
it.output->AddUpdate(it.ProcessBinding());
|
||||
}
|
||||
|
||||
// Update all outputs
|
||||
for (auto& it : output_array) {
|
||||
it.FinalizeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Input
|
407
src/input/input_handler.h
Normal file
407
src/input/input_handler.h
Normal file
|
@ -0,0 +1,407 @@
|
|||
// 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
|
74
src/input/input_mouse.cpp
Normal file
74
src/input/input_mouse.cpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "common/types.h"
|
||||
#include "input/controller.h"
|
||||
#include "input_mouse.h"
|
||||
|
||||
#include "SDL3/SDL.h"
|
||||
|
||||
namespace Input {
|
||||
|
||||
int mouse_joystick_binding = 0;
|
||||
float mouse_deadzone_offset = 0.5, mouse_speed = 1, mouse_speed_offset = 0.1250;
|
||||
Uint32 mouse_polling_id = 0;
|
||||
bool mouse_enabled = false;
|
||||
|
||||
// We had to go through 3 files of indirection just to update a flag
|
||||
void ToggleMouseEnabled() {
|
||||
mouse_enabled = !mouse_enabled;
|
||||
}
|
||||
|
||||
void SetMouseToJoystick(int joystick) {
|
||||
mouse_joystick_binding = joystick;
|
||||
}
|
||||
|
||||
void SetMouseParams(float mdo, float ms, float mso) {
|
||||
mouse_deadzone_offset = mdo;
|
||||
mouse_speed = ms;
|
||||
mouse_speed_offset = mso;
|
||||
}
|
||||
|
||||
Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) {
|
||||
auto* controller = (GameController*)param;
|
||||
if (!mouse_enabled)
|
||||
return interval;
|
||||
|
||||
Axis axis_x, axis_y;
|
||||
switch (mouse_joystick_binding) {
|
||||
case 1:
|
||||
axis_x = Axis::LeftX;
|
||||
axis_y = Axis::LeftY;
|
||||
break;
|
||||
case 2:
|
||||
axis_x = Axis::RightX;
|
||||
axis_y = Axis::RightY;
|
||||
break;
|
||||
default:
|
||||
return interval; // no update needed
|
||||
}
|
||||
|
||||
float d_x = 0, d_y = 0;
|
||||
SDL_GetRelativeMouseState(&d_x, &d_y);
|
||||
|
||||
float output_speed =
|
||||
SDL_clamp((sqrt(d_x * d_x + d_y * d_y) + mouse_speed_offset * 128) * mouse_speed,
|
||||
mouse_deadzone_offset * 128, 128.0);
|
||||
|
||||
float angle = atan2(d_y, d_x);
|
||||
float a_x = cos(angle) * output_speed, a_y = sin(angle) * output_speed;
|
||||
|
||||
if (d_x != 0 && d_y != 0) {
|
||||
controller->Axis(0, axis_x, GetAxis(-0x80, 0x80, a_x));
|
||||
controller->Axis(0, axis_y, GetAxis(-0x80, 0x80, a_y));
|
||||
} else {
|
||||
controller->Axis(0, axis_x, GetAxis(-0x80, 0x80, 0));
|
||||
controller->Axis(0, axis_y, GetAxis(-0x80, 0x80, 0));
|
||||
}
|
||||
|
||||
return interval;
|
||||
}
|
||||
|
||||
} // namespace Input
|
18
src/input/input_mouse.h
Normal file
18
src/input/input_mouse.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SDL3/SDL.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Input {
|
||||
|
||||
void ToggleMouseEnabled();
|
||||
void SetMouseToJoystick(int joystick);
|
||||
void SetMouseParams(float mouse_deadzone_offset, float mouse_speed, float mouse_speed_offset);
|
||||
|
||||
// Polls the mouse for changes, and simulates joystick movement from it.
|
||||
Uint32 MousePolling(void* param, Uint32 id, Uint32 interval);
|
||||
|
||||
} // namespace Input
|
Loading…
Add table
Add a link
Reference in a new issue