mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-07-23 10:35:03 +00:00
KBM Input Bug Fixes / Added Binds
Fixed input issues where some inputs would not bind when pressing (side mouse buttons, some symbols, etc). Also, fixed up code formatting in altered files (removed C-style casts and replaced with C++ <static_casts>, added a few macros and one member functions). Update: Fixed file locations.
This commit is contained in:
parent
226058d2e9
commit
be6b25dbe9
718
input_handler.cpp
Normal file
718
input_handler.cpp
Normal file
@ -0,0 +1,718 @@
|
|||||||
|
// 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 "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;
|
||||||
|
std::pair<int, 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, 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 = "") {
|
||||||
|
std::string config_type = Config::GetUseUnifiedInputConfig() ? "default" : game_id;
|
||||||
|
const auto config_file = Config::GetFoolproofKbmConfigFile(config_type);
|
||||||
|
|
||||||
|
// 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, 127};
|
||||||
|
rightjoystick_deadzone = {1, 127};
|
||||||
|
lefttrigger_deadzone = {1, 127};
|
||||||
|
righttrigger_deadzone = {1, 127};
|
||||||
|
|
||||||
|
Config::SetOverrideControllerColor(false);
|
||||||
|
Config::SetControllerCustomColor(0, 0, 255);
|
||||||
|
|
||||||
|
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(',');
|
||||||
|
|
||||||
|
auto parseInt = [](const std::string& s) -> std::optional<int> {
|
||||||
|
try {
|
||||||
|
return std::stoi(s);
|
||||||
|
} catch (...) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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, inner_deadzone_str, outer_deadzone_str;
|
||||||
|
|
||||||
|
if (!std::getline(ss, device, ',') || !std::getline(ss, inner_deadzone_str, ',') ||
|
||||||
|
!std::getline(ss, outer_deadzone_str)) {
|
||||||
|
LOG_WARNING(Input, "Malformed deadzone config at line {}: \"{}\"", lineCount, line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto inner_deadzone = parseInt(inner_deadzone_str);
|
||||||
|
auto outer_deadzone = parseInt(outer_deadzone_str);
|
||||||
|
|
||||||
|
if (!inner_deadzone || !outer_deadzone) {
|
||||||
|
LOG_WARNING(Input, "Invalid deadzone values at line {}: \"{}\"", lineCount, line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> deadzone = {*inner_deadzone, *outer_deadzone};
|
||||||
|
|
||||||
|
static std::unordered_map<std::string, std::pair<int, int>&> deadzone_map = {
|
||||||
|
{"leftjoystick", leftjoystick_deadzone},
|
||||||
|
{"rightjoystick", rightjoystick_deadzone},
|
||||||
|
{"l2", lefttrigger_deadzone},
|
||||||
|
{"r2", righttrigger_deadzone},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (auto it = deadzone_map.find(device); it != deadzone_map.end()) {
|
||||||
|
it->second = deadzone;
|
||||||
|
LOG_DEBUG(Input, "Parsed deadzone: {} {} {}", device, inner_deadzone_str,
|
||||||
|
outer_deadzone_str);
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Input, "Invalid axis name at line {}: \"{}\", skipping line.",
|
||||||
|
lineCount, line);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else if (output_string == "override_controller_color") {
|
||||||
|
std::stringstream ss(input_string);
|
||||||
|
std::string enable, r_s, g_s, b_s;
|
||||||
|
std::optional<int> r, g, b;
|
||||||
|
if (!std::getline(ss, enable, ',') || !std::getline(ss, r_s, ',') ||
|
||||||
|
!std::getline(ss, g_s, ',') || !std::getline(ss, b_s)) {
|
||||||
|
LOG_WARNING(Input, "Malformed controller color config at line {}: \"{}\"",
|
||||||
|
lineCount, line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
r = parseInt(r_s);
|
||||||
|
g = parseInt(g_s);
|
||||||
|
b = parseInt(b_s);
|
||||||
|
if (!r || !g || !b) {
|
||||||
|
LOG_WARNING(Input, "Invalid RGB values at line {}: \"{}\", skipping line.",
|
||||||
|
lineCount, line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Config::SetOverrideControllerColor(enable == "true");
|
||||||
|
Config::SetControllerCustomColor(*r, *g, *b);
|
||||||
|
LOG_DEBUG(Input, "Parsed color settings: {} {} {} {}",
|
||||||
|
enable == "true" ? "override" : "no override", *r, *b, *g);
|
||||||
|
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 UINT32_MAX;
|
||||||
|
}
|
||||||
|
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 UINT32_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, static_cast<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, static_cast<u32>(e.gbutton.button),
|
||||||
|
e.gbutton.down, 0);
|
||||||
|
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||||
|
return InputEvent(InputType::Axis, static_cast<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) {
|
||||||
|
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) {
|
||||||
|
*new_param = (event.active ? event.axis_value : 0) + *new_param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void ControllerOutput::FinalizeUpdate() {
|
||||||
|
state_changed = old_button_state != new_button_state || old_param != *new_param;
|
||||||
|
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
|
||||||
|
auto ApplyDeadzone = [](s16* value, std::pair<int, int> deadzone) {
|
||||||
|
if (std::abs(*value) <= deadzone.first || deadzone.first == deadzone.second) {
|
||||||
|
*value = 0;
|
||||||
|
} else {
|
||||||
|
*value = (*value >= 0 ? 1 : -1) *
|
||||||
|
std::clamp((int)((128.0 * (std::abs(*value) - deadzone.first)) /
|
||||||
|
(float)(deadzone.second - deadzone.first)),
|
||||||
|
0, 128);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
float multiplier = 1.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, 0x7f, *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, 0x7f, *new_param));
|
||||||
|
controller->CheckButton(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
controller->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *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 == UINT32_MAX) {
|
||||||
|
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 {
|
||||||
|
// noise filter
|
||||||
|
if (std::abs(it->first.axis_value - event.axis_value) <= 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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
|
439
input_handler.h
Normal file
439
input_handler.h
Normal file
@ -0,0 +1,439 @@
|
|||||||
|
// 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 = UINT32_MAX) : 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", 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 = {
|
||||||
|
// 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
|
||||||
|
{"`", SDLK_GRAVE},
|
||||||
|
{"~", SDLK_TILDE},
|
||||||
|
{"!", SDLK_EXCLAIM},
|
||||||
|
{"@", SDLK_AT},
|
||||||
|
{"#", SDLK_HASH},
|
||||||
|
{"$", SDLK_DOLLAR},
|
||||||
|
{"%", SDLK_PERCENT},
|
||||||
|
{"^", SDLK_CARET},
|
||||||
|
{"&", SDLK_AMPERSAND},
|
||||||
|
{"*", SDLK_ASTERISK},
|
||||||
|
{"(", SDLK_LEFTPAREN},
|
||||||
|
{")", SDLK_RIGHTPAREN},
|
||||||
|
{"-", SDLK_MINUS},
|
||||||
|
{"_", SDLK_UNDERSCORE},
|
||||||
|
{"=", SDLK_EQUALS},
|
||||||
|
{"+", SDLK_PLUS},
|
||||||
|
{"[", SDLK_LEFTBRACKET},
|
||||||
|
{"]", SDLK_RIGHTBRACKET},
|
||||||
|
{"{", SDLK_LEFTBRACE},
|
||||||
|
{"}", SDLK_RIGHTBRACE},
|
||||||
|
{"\\", SDLK_BACKSLASH},
|
||||||
|
{"|", SDLK_PIPE},
|
||||||
|
{";", SDLK_SEMICOLON},
|
||||||
|
{":", SDLK_COLON},
|
||||||
|
{"'", SDLK_APOSTROPHE},
|
||||||
|
{"\"", SDLK_DBLAPOSTROPHE},
|
||||||
|
{",", SDLK_COMMA},
|
||||||
|
{"<", SDLK_LESS},
|
||||||
|
{".", SDLK_PERIOD},
|
||||||
|
{">", SDLK_GREATER},
|
||||||
|
{"/", SDLK_SLASH},
|
||||||
|
{"?", SDLK_QUESTION},
|
||||||
|
|
||||||
|
// special keys
|
||||||
|
{"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},
|
||||||
|
{"kp .", SDLK_KP_PERIOD},
|
||||||
|
{"kp ,", SDLK_KP_COMMA},
|
||||||
|
{"kp /", SDLK_KP_DIVIDE},
|
||||||
|
{"kp *", SDLK_KP_MULTIPLY},
|
||||||
|
{"kp -", SDLK_KP_MINUS},
|
||||||
|
{"kp +", SDLK_KP_PLUS},
|
||||||
|
{"kp =", SDLK_KP_EQUALS},
|
||||||
|
{"kp enter", 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},
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
1067
kbm_gui.cpp
Normal file
1067
kbm_gui.cpp
Normal file
File diff suppressed because it is too large
Load Diff
71
kbm_gui.h
Normal file
71
kbm_gui.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include "game_info.h"
|
||||||
|
|
||||||
|
// macros > declaring constants
|
||||||
|
// also, we were only using one counterpart
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define LCTRL_KEY 29
|
||||||
|
#define LALT_KEY 56
|
||||||
|
#define LSHIFT_KEY 42
|
||||||
|
#else
|
||||||
|
#define LCTRL_KEY 37
|
||||||
|
#define LALT_KEY 64
|
||||||
|
#define LSHIFT_KEY 50
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class KBMSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
class KBMSettings : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit KBMSettings(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent = nullptr);
|
||||||
|
~KBMSettings();
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void SaveKBMConfig(bool CloseOnSave);
|
||||||
|
void SetDefault();
|
||||||
|
void CheckMapping(QPushButton*& button);
|
||||||
|
void StartTimer(QPushButton*& button);
|
||||||
|
void onHelpClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Ui::KBMSettings> ui;
|
||||||
|
std::shared_ptr<GameInfoClass> m_game_info;
|
||||||
|
|
||||||
|
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||||
|
QString GetModifiedButton(Qt::KeyboardModifiers modifier,
|
||||||
|
std::string m_button, std::string n_button);
|
||||||
|
void ButtonConnects();
|
||||||
|
void SetUIValuestoMappings(std::string config_id);
|
||||||
|
void GetGameTitle();
|
||||||
|
void DisableMappingButtons();
|
||||||
|
void EnableMappingButtons();
|
||||||
|
void SetMapping(QString input);
|
||||||
|
|
||||||
|
QSet<QString> pressedKeys;
|
||||||
|
bool EnableMapping = false;
|
||||||
|
bool MappingCompleted = false;
|
||||||
|
bool HelpWindowOpen = false;
|
||||||
|
QString mapping;
|
||||||
|
QString modifier;
|
||||||
|
int MappingTimer;
|
||||||
|
QTimer* timer;
|
||||||
|
QPushButton* MappingButton;
|
||||||
|
QList<QPushButton*> ButtonsList;
|
||||||
|
std::string config_id;
|
||||||
|
const std::vector<std::string> ControllerInputs = {
|
||||||
|
"cross", "circle", "square", "triangle", "l1",
|
||||||
|
"r1", "l2", "r2", "l3",
|
||||||
|
|
||||||
|
"r3", "options", "pad_up",
|
||||||
|
|
||||||
|
"pad_down",
|
||||||
|
|
||||||
|
"pad_left", "pad_right", "axis_left_x", "axis_left_y", "axis_right_x",
|
||||||
|
"axis_right_y", "back"};
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user