mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-08-04 16:32:39 +00:00
Merge branch 'new-kbm'
This commit is contained in:
commit
e38e75b27b
@ -724,6 +724,8 @@ set(IMGUI src/imgui/imgui_config.h
|
||||
|
||||
set(INPUT src/input/controller.cpp
|
||||
src/input/controller.h
|
||||
src/input/input_handler.cpp
|
||||
src/input/input_handler.h
|
||||
)
|
||||
|
||||
set(EMULATOR src/emulator.cpp
|
||||
|
@ -701,4 +701,115 @@ void setDefaultValues() {
|
||||
gpuId = -1;
|
||||
}
|
||||
|
||||
std::string_view getDefaultKeyboardConfig() {
|
||||
static std::string_view default_config =
|
||||
R"(#This is the default keybinding config
|
||||
#To change per-game configs, modify the CUSAXXXXX.ini files
|
||||
#To change the default config that applies to new games without already existing configs, modify default.ini
|
||||
#If you don't like certain mappings, delete, change or comment them out.
|
||||
#You can add any amount of KBM keybinds to a single controller input,
|
||||
#but you can use each KBM keybind for one controller input.
|
||||
|
||||
#Keybinds used by the emulator (these are unchangeable):
|
||||
#F11 : fullscreen
|
||||
#F10 : FPS counter
|
||||
#F9 : toggle mouse-to-joystick input
|
||||
# (it overwrites everything else to that joystick, so this is required)
|
||||
#F8 : reparse keyboard input(this)
|
||||
|
||||
#This is a mapping for Bloodborne, inspired by other Souls titles on PC.
|
||||
|
||||
#Specifies which joystick the mouse movement controls.
|
||||
mouse_to_joystick = right;
|
||||
|
||||
#Use healing item, change status in inventory
|
||||
triangle = f;
|
||||
#Dodge, back in inventory
|
||||
circle = space;
|
||||
#Interact, select item in inventory
|
||||
cross = e;
|
||||
#Use quick item, remove item in inventory
|
||||
square = r;
|
||||
|
||||
#Emergency extra bullets
|
||||
up = w, lalt;
|
||||
up = mousewheelup;
|
||||
#Change quick item
|
||||
down = s, lalt;
|
||||
down = mousewheeldown;
|
||||
#Change weapon in left hand
|
||||
left = a, lalt;
|
||||
left = mousewheelleft;
|
||||
#Change weapon in right hand
|
||||
right = d, lalt;
|
||||
right = mousewheelright;
|
||||
#Change into 'inventory mode', so you don't have to hold lalt every time you go into menus
|
||||
modkey_toggle = i, lalt;
|
||||
|
||||
#Menu
|
||||
options = escape;
|
||||
#Gestures
|
||||
touchpad = g;
|
||||
|
||||
#Transform
|
||||
l1 = rightbutton, lshift;
|
||||
#Shoot
|
||||
r1 = leftbutton;
|
||||
#Light attack
|
||||
l2 = rightbutton;
|
||||
#Heavy attack
|
||||
r2 = leftbutton, lshift;
|
||||
#Does nothing
|
||||
l3 = x;
|
||||
#Center cam, lock on
|
||||
r3 = q;
|
||||
r3 = middlebutton;
|
||||
|
||||
#Axis mappings
|
||||
#Move
|
||||
axis_left_x_minus = a;
|
||||
axis_left_x_plus = d;
|
||||
axis_left_y_minus = w;
|
||||
axis_left_y_plus = s;
|
||||
#Change to 'walk mode' by holding the following key:
|
||||
leftjoystick_halfmode = lctrl;
|
||||
)";
|
||||
return default_config;
|
||||
}
|
||||
std::filesystem::path getFoolproofKbmConfigFile(const std::string& game_id) {
|
||||
// Read configuration file of the game, and if it doesn't exist, generate it from default
|
||||
// If that doesn't exist either, generate that from getDefaultConfig() and try again
|
||||
// If even the folder is missing, we start with that.
|
||||
|
||||
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "kbmConfig";
|
||||
const auto config_file = config_dir / (game_id + ".ini");
|
||||
const auto default_config_file = config_dir / "default.ini";
|
||||
|
||||
// Ensure the config directory exists
|
||||
if (!std::filesystem::exists(config_dir)) {
|
||||
std::filesystem::create_directories(config_dir);
|
||||
}
|
||||
|
||||
// Check if the default config exists
|
||||
if (!std::filesystem::exists(default_config_file)) {
|
||||
// If the default config is also missing, create it from getDefaultConfig()
|
||||
const auto default_config = getDefaultKeyboardConfig();
|
||||
std::ofstream default_config_stream(default_config_file);
|
||||
if (default_config_stream) {
|
||||
default_config_stream << default_config;
|
||||
}
|
||||
}
|
||||
|
||||
// if empty, we only need to execute the function up until this point
|
||||
if(game_id.empty()) {
|
||||
return default_config_file;
|
||||
}
|
||||
|
||||
// If game-specific config doesn't exist, create it from the default config
|
||||
if (!std::filesystem::exists(config_file)) {
|
||||
std::filesystem::copy(default_config_file, config_file);
|
||||
}
|
||||
return config_file;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
|
@ -123,6 +123,9 @@ std::string getEmulatorLanguage();
|
||||
|
||||
void setDefaultValues();
|
||||
|
||||
// todo: name and function location pending
|
||||
std::filesystem::path getFoolproofKbmConfigFile(const std::string& game_id = "");
|
||||
|
||||
// settings
|
||||
u32 GetLanguage();
|
||||
}; // namespace Config
|
||||
|
564
src/input/input_handler.cpp
Normal file
564
src/input/input_handler.cpp
Normal file
@ -0,0 +1,564 @@
|
||||
// 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 "sstream"
|
||||
#include "string"
|
||||
#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"
|
||||
|
||||
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))
|
||||
*/
|
||||
|
||||
// Flags and values for varying purposes
|
||||
// todo: can we change these?
|
||||
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, leftjoystick_halfmode = false, rightjoystick_halfmode = false;
|
||||
|
||||
std::list<std::pair<u32, bool>> pressed_keys;
|
||||
std::list<u32> toggled_keys;
|
||||
std::list<BindingConnection> connections = std::list<BindingConnection>();
|
||||
|
||||
ControllerOutput output_array[] = {
|
||||
// Button mappings
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TRIANGLE),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CIRCLE),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CROSS),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_SQUARE),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L1),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L2),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R1),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R2),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L3),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R3),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_OPTIONS),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_DOWN),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_LEFT),
|
||||
ControllerOutput(OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_RIGHT),
|
||||
|
||||
// Axis mappings
|
||||
ControllerOutput(0, Input::Axis::LeftX), ControllerOutput(0, Input::Axis::LeftY),
|
||||
ControllerOutput(0, Input::Axis::RightX), ControllerOutput(0, Input::Axis::RightY),
|
||||
ControllerOutput(0, Input::Axis::TriggerLeft), ControllerOutput(0, Input::Axis::TriggerRight),
|
||||
|
||||
ControllerOutput(LEFTJOYSTICK_HALFMODE), ControllerOutput(RIGHTJOYSTICK_HALFMODE),
|
||||
ControllerOutput(KEY_TOGGLE),
|
||||
|
||||
// End marker to signify the end of the array
|
||||
ControllerOutput(0, Input::Axis::AxisMax)};
|
||||
|
||||
// We had to go through 3 files of indirection just to update a flag
|
||||
void toggleMouseEnabled() {
|
||||
mouse_enabled ^= true;
|
||||
}
|
||||
// parsing related functions
|
||||
|
||||
// syntax: 'name, name,name' or 'name,name' or 'name'
|
||||
InputBinding getBindingFromString(std::string& line) {
|
||||
u32 key1 = 0, key2 = 0, key3 = 0;
|
||||
|
||||
// Split the string by commas
|
||||
std::vector<std::string> tokens;
|
||||
std::stringstream ss(line);
|
||||
std::string token;
|
||||
|
||||
while (std::getline(ss, token, ',')) {
|
||||
tokens.push_back(token);
|
||||
}
|
||||
|
||||
// Check for invalid tokens and map valid ones to keys
|
||||
for (const auto& t : tokens) {
|
||||
if (string_to_keyboard_key_map.find(t) == string_to_keyboard_key_map.end()) {
|
||||
return InputBinding(0, 0, 0); // Skip by setting all keys to 0
|
||||
}
|
||||
}
|
||||
|
||||
// Assign values to keys if all tokens were valid
|
||||
if (tokens.size() > 0)
|
||||
key1 = string_to_keyboard_key_map.at(tokens[0]);
|
||||
if (tokens.size() > 1)
|
||||
key2 = string_to_keyboard_key_map.at(tokens[1]);
|
||||
if (tokens.size() > 2)
|
||||
key3 = string_to_keyboard_key_map.at(tokens[2]);
|
||||
|
||||
return InputBinding(key1, key2, key3);
|
||||
}
|
||||
|
||||
// function that takes a controlleroutput, and returns the array's corresponding element's pointer
|
||||
ControllerOutput* getOutputPointer(const ControllerOutput& parsed) {
|
||||
// i wonder how long until someone notices this or I get rid of it
|
||||
for (int i = 0; i[output_array] != ControllerOutput(0, Axis::AxisMax); i++) {
|
||||
if (i[output_array] == parsed) {
|
||||
return &output_array[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void parseInputConfig(const std::string game_id = "") {
|
||||
|
||||
const auto config_file = Config::getFoolproofKbmConfigFile(game_id);
|
||||
|
||||
// todo: change usages of this to getFoolproofKbmConfigFile (in the gui)
|
||||
if (game_id == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
// todo
|
||||
// we reset these here so in case the user fucks up or doesn't include this we can fall back to
|
||||
// default
|
||||
connections.clear();
|
||||
mouse_deadzone_offset = 0.5;
|
||||
mouse_speed = 1;
|
||||
mouse_speed_offset = 0.125;
|
||||
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(line.begin(), line.end(), ' '), line.end());
|
||||
if (line[line.length() - 1] == ';') {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
}
|
||||
// Ignore comment lines
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
// Split the line by '='
|
||||
std::size_t equal_pos = line.find('=');
|
||||
if (equal_pos == std::string::npos) {
|
||||
LOG_ERROR(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(',');
|
||||
|
||||
// special check for mouse to joystick input
|
||||
if (output_string == "mouse_to_joystick") {
|
||||
if (input_string == "left") {
|
||||
mouse_joystick_binding = 1;
|
||||
} else if (input_string == "right") {
|
||||
mouse_joystick_binding = 2;
|
||||
} else {
|
||||
mouse_joystick_binding = 0; // default to 'none' or invalid
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// key toggle
|
||||
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_ERROR(Input,
|
||||
"Syntax error: Please provide exactly 2 keys: "
|
||||
"first is the toggler, the second is the key to toggle: {}",
|
||||
line);
|
||||
continue;
|
||||
}
|
||||
ControllerOutput* toggle_out = getOutputPointer(ControllerOutput(KEY_TOGGLE));
|
||||
BindingConnection toggle_connection =
|
||||
BindingConnection(InputBinding(toggle_keys.key2), toggle_out, toggle_keys.key3);
|
||||
connections.insert(connections.end(), toggle_connection);
|
||||
continue;
|
||||
}
|
||||
LOG_ERROR(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount,
|
||||
line);
|
||||
continue;
|
||||
}
|
||||
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_ERROR(Input, "Failed to parse mouse movement parameters from line: {}", line);
|
||||
} else {
|
||||
// LOG_DEBUG(Input, "Mouse movement parameters parsed: {} {} {}",
|
||||
// mouse_deadzone_offset, mouse_speed, mouse_speed_offset);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// normal cases
|
||||
InputBinding binding = getBindingFromString(input_string);
|
||||
BindingConnection connection(0, 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_DEBUG(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount,
|
||||
line);
|
||||
continue;
|
||||
}
|
||||
if (button_it != string_to_cbutton_map.end()) {
|
||||
connection =
|
||||
BindingConnection(binding, getOutputPointer(ControllerOutput(button_it->second)));
|
||||
connections.insert(connections.end(), connection);
|
||||
|
||||
} else if (axis_it != string_to_axis_map.end()) {
|
||||
connection = BindingConnection(
|
||||
binding, getOutputPointer(ControllerOutput(0, axis_it->second.axis)),
|
||||
axis_it->second.value);
|
||||
connections.insert(connections.end(), connection);
|
||||
} else {
|
||||
LOG_DEBUG(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount,
|
||||
line);
|
||||
continue;
|
||||
}
|
||||
// LOG_INFO(Input, "Succesfully parsed line {}", lineCount);
|
||||
}
|
||||
file.close();
|
||||
connections.sort();
|
||||
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_DEBUG(Input, "Something went wrong with wheel input parsing!");
|
||||
return 0;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
u32 InputBinding::getInputIDFromEvent(const SDL_Event& e) {
|
||||
switch (e.type) {
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
return e.key.key;
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
return (u32)e.button.button;
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
case SDL_EVENT_MOUSE_WHEEL_OFF:
|
||||
return getMouseWheelEvent(e);
|
||||
default:
|
||||
return (u32)-1;
|
||||
}
|
||||
}
|
||||
|
||||
GameController* ControllerOutput::controller = nullptr;
|
||||
void ControllerOutput::setControllerOutputController(GameController* c) {
|
||||
ControllerOutput::controller = c;
|
||||
}
|
||||
|
||||
void toggleKeyInList(u32 key) {
|
||||
auto it = std::find(toggled_keys.begin(), toggled_keys.end(), key);
|
||||
if (it == toggled_keys.end()) {
|
||||
toggled_keys.insert(toggled_keys.end(), key);
|
||||
LOG_DEBUG(Input, "Added {} to toggled keys", key);
|
||||
} else {
|
||||
toggled_keys.erase(it);
|
||||
LOG_DEBUG(Input, "Removed {} from toggled keys", key);
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerOutput::update(bool pressed, u32 param) {
|
||||
float touchpad_x = 0;
|
||||
if (button != 0) {
|
||||
switch (button) {
|
||||
case OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD:
|
||||
touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f
|
||||
: Config::getBackButtonBehavior() == "right" ? 0.75f
|
||||
: 0.5f;
|
||||
controller->SetTouchpadState(0, true, touchpad_x, 0.5f);
|
||||
controller->CheckButton(0, button, pressed);
|
||||
break;
|
||||
case LEFTJOYSTICK_HALFMODE:
|
||||
leftjoystick_halfmode = pressed;
|
||||
break;
|
||||
case RIGHTJOYSTICK_HALFMODE:
|
||||
rightjoystick_halfmode = pressed;
|
||||
break;
|
||||
case KEY_TOGGLE:
|
||||
if (pressed) {
|
||||
toggleKeyInList(param);
|
||||
}
|
||||
break;
|
||||
default: // is a normal key (hopefully)
|
||||
controller->CheckButton(0, button, pressed);
|
||||
break;
|
||||
}
|
||||
} else if (axis != Axis::AxisMax) {
|
||||
float multiplier = 1.0;
|
||||
switch (axis) {
|
||||
case Axis::LeftX:
|
||||
case Axis::LeftY:
|
||||
multiplier = leftjoystick_halfmode ? 0.5 : 1.0;
|
||||
break;
|
||||
case Axis::RightX:
|
||||
case Axis::RightY:
|
||||
multiplier = rightjoystick_halfmode ? 0.5 : 1.0;
|
||||
break;
|
||||
case Axis::TriggerLeft:
|
||||
case Axis::TriggerRight:
|
||||
// todo: verify this works (This probably works from testing,
|
||||
// but needs extra info (multiple input to the same trigger?))
|
||||
axis_value = SDL_clamp((pressed ? (int)param : 0) * multiplier, 0, 127);
|
||||
controller->Axis(0, axis, GetAxis(0, 0x80, axis_value));
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
axis_value = SDL_clamp((pressed ? (int)param : 0) * multiplier, -127, 127);
|
||||
int ax = GetAxis(-0x80, 0x80, axis_value);
|
||||
controller->Axis(0, axis, ax);
|
||||
} else {
|
||||
LOG_DEBUG(Input, "Controller output with no values detected!");
|
||||
}
|
||||
}
|
||||
void ControllerOutput::addUpdate(bool pressed, u32 param) {
|
||||
|
||||
float touchpad_x = 0;
|
||||
if (button != 0) {
|
||||
if (!pressed) {
|
||||
return;
|
||||
}
|
||||
switch (button) {
|
||||
case OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD:
|
||||
touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f
|
||||
: Config::getBackButtonBehavior() == "right" ? 0.75f
|
||||
: 0.5f;
|
||||
controller->SetTouchpadState(0, true, touchpad_x, 0.5f);
|
||||
controller->CheckButton(0, button, pressed);
|
||||
break;
|
||||
case LEFTJOYSTICK_HALFMODE:
|
||||
leftjoystick_halfmode = pressed;
|
||||
break;
|
||||
case RIGHTJOYSTICK_HALFMODE:
|
||||
rightjoystick_halfmode = pressed;
|
||||
break;
|
||||
case KEY_TOGGLE:
|
||||
if (pressed) {
|
||||
toggleKeyInList(param);
|
||||
}
|
||||
break;
|
||||
default: // is a normal key (hopefully)
|
||||
controller->CheckButton(0, button, pressed);
|
||||
break;
|
||||
}
|
||||
} else if (axis != Axis::AxisMax) {
|
||||
float multiplier = 1.0;
|
||||
switch (axis) {
|
||||
case Axis::LeftX:
|
||||
case Axis::LeftY:
|
||||
multiplier = leftjoystick_halfmode ? 0.5 : 1.0;
|
||||
break;
|
||||
case Axis::RightX:
|
||||
case Axis::RightY:
|
||||
multiplier = rightjoystick_halfmode ? 0.5 : 1.0;
|
||||
break;
|
||||
case Axis::TriggerLeft:
|
||||
case Axis::TriggerRight:
|
||||
// todo: verify this works
|
||||
axis_value = SDL_clamp((pressed ? (int)param : 0) * multiplier + axis_value, 0, 127);
|
||||
controller->Axis(0, axis, GetAxis(0, 0x80, axis_value));
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
axis_value = SDL_clamp((pressed ? (int)param : 0) * multiplier + axis_value, -127, 127);
|
||||
controller->Axis(0, axis, GetAxis(-0x80, 0x80, axis_value));
|
||||
// LOG_INFO(Input, "Axis value delta: {} final value: {}", (pressed ? a_value : 0),
|
||||
// axis_value);
|
||||
} else {
|
||||
LOG_DEBUG(Input, "Controller output with no values detected!");
|
||||
}
|
||||
}
|
||||
|
||||
void updatePressedKeys(u32 value, bool is_pressed) {
|
||||
if (is_pressed) {
|
||||
// Find the correct position for insertion to maintain order
|
||||
auto it =
|
||||
std::lower_bound(pressed_keys.begin(), pressed_keys.end(), value,
|
||||
[](const std::pair<u32, bool>& pk, u32 v) { return pk.first < v; });
|
||||
|
||||
// Insert only if 'value' is not already in the list
|
||||
if (it == pressed_keys.end() || it->first != value) {
|
||||
pressed_keys.insert(it, {value, false});
|
||||
}
|
||||
} else {
|
||||
// Remove 'value' from the list if it's not pressed
|
||||
auto it =
|
||||
std::find_if(pressed_keys.begin(), pressed_keys.end(),
|
||||
[value](const std::pair<u32, bool>& pk) { return pk.first == value; });
|
||||
if (it != pressed_keys.end()) {
|
||||
pressed_keys.erase(it); // Remove the key entirely from the list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a given binding's all keys are currently active.
|
||||
bool isInputActive(const InputBinding& i) {
|
||||
bool* flag1 = nullptr;
|
||||
bool* flag2 = nullptr;
|
||||
bool* flag3 = nullptr;
|
||||
|
||||
bool key1_pressed =
|
||||
std::find(toggled_keys.begin(), toggled_keys.end(), i.key1) != toggled_keys.end();
|
||||
bool key2_pressed =
|
||||
std::find(toggled_keys.begin(), toggled_keys.end(), i.key2) != toggled_keys.end();
|
||||
bool key3_pressed =
|
||||
std::find(toggled_keys.begin(), toggled_keys.end(), i.key3) != toggled_keys.end();
|
||||
|
||||
// First pass: locate each key and save pointers to their flags if found
|
||||
for (auto& entry : pressed_keys) {
|
||||
u32 key = entry.first;
|
||||
bool& is_active = entry.second;
|
||||
|
||||
if (key1_pressed || (i.key1 != 0 && key == i.key1 && !flag1)) {
|
||||
flag1 = &is_active;
|
||||
} else if (key2_pressed || (i.key2 != 0 && key == i.key2 && !flag2)) {
|
||||
flag2 = &is_active;
|
||||
} else if (key3_pressed || (i.key3 != 0 && key == i.key3 && !flag3)) {
|
||||
flag3 = &is_active;
|
||||
break;
|
||||
} else {
|
||||
return false; // an all 0 input never gets activated
|
||||
}
|
||||
}
|
||||
|
||||
// If any required key was not found, return false without updating flags
|
||||
if ((i.key1 != 0 && !flag1) || (i.key2 != 0 && !flag2) || (i.key3 != 0 && !flag3)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if all flags are already true, which indicates this input is overridden (only if the
|
||||
// key is not 0)
|
||||
if ((i.key1 == 0 || (flag1 && *flag1)) && (i.key2 == 0 || (flag2 && *flag2)) &&
|
||||
(i.key3 == 0 || (flag3 && *flag3))) {
|
||||
return false; // This input is overridden by another input
|
||||
}
|
||||
|
||||
// Set flags to true only after confirming all keys are present and not overridden
|
||||
if (flag1 && !key1_pressed)
|
||||
*flag1 = true;
|
||||
if (flag2 && !key2_pressed)
|
||||
*flag2 = true;
|
||||
if (flag3 && !key3_pressed)
|
||||
*flag3 = true;
|
||||
|
||||
LOG_DEBUG(Input, "A valid held input is found: {}, flag ptrs: {} {} {}", i.toString(),
|
||||
fmt::ptr(flag1), fmt::ptr(flag2), fmt::ptr(flag3));
|
||||
return true;
|
||||
}
|
||||
|
||||
void activateOutputsFromInputs() {
|
||||
LOG_DEBUG(Input, "Starting input scan...");
|
||||
// reset everything
|
||||
for (auto it = connections.begin(); it != connections.end(); it++) {
|
||||
if (it->output) {
|
||||
it->output->update(false, 0);
|
||||
} else {
|
||||
LOG_DEBUG(Input, "Null output in BindingConnection at position {}",
|
||||
std::distance(connections.begin(), it));
|
||||
}
|
||||
}
|
||||
for (auto it : pressed_keys) {
|
||||
it.second = false;
|
||||
}
|
||||
// iterates over the connections, and updates them depending on whether the corresponding input
|
||||
// trio is found
|
||||
for (auto it = connections.begin(); it != connections.end(); it++) {
|
||||
if (it->output) {
|
||||
it->output->addUpdate(isInputActive(it->binding), it->parameter);
|
||||
} else {
|
||||
// LOG_DEBUG(Input, "Null output in BindingConnection at position {}",
|
||||
// std::distance(connections.begin(), it));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateMouse(GameController* controller) {
|
||||
if (!mouse_enabled)
|
||||
return;
|
||||
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;
|
||||
case 0:
|
||||
default:
|
||||
return; // 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));
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 mousePolling(void* param, Uint32 id, Uint32 interval) {
|
||||
auto* data = (GameController*)param;
|
||||
updateMouse(data);
|
||||
return interval;
|
||||
}
|
||||
|
||||
} // namespace Input
|
320
src/input/input_handler.h
Normal file
320
src/input/input_handler.h
Normal file
@ -0,0 +1,320 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "array"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
#include "fmt/format.h"
|
||||
#include "input/controller.h"
|
||||
#include "map"
|
||||
#include "string"
|
||||
#include "unordered_set"
|
||||
|
||||
#include "SDL3/SDL_events.h"
|
||||
#include "SDL3/SDL_timer.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 KEY_TOGGLE 0x00200000
|
||||
|
||||
namespace Input {
|
||||
using Input::Axis;
|
||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
||||
|
||||
struct AxisMapping {
|
||||
Axis axis;
|
||||
int value; // Value to set for key press (+127 or -127 for movement)
|
||||
};
|
||||
|
||||
// i strongly suggest you collapse these maps
|
||||
const std::map<std::string, u32> string_to_cbutton_map = {
|
||||
{"triangle", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TRIANGLE},
|
||||
{"circle", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CIRCLE},
|
||||
{"cross", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CROSS},
|
||||
{"square", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_SQUARE},
|
||||
{"l1", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L1},
|
||||
{"r1", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R1},
|
||||
{"l3", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L3},
|
||||
{"r3", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R3},
|
||||
{"options", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_OPTIONS},
|
||||
{"touchpad", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD},
|
||||
{"up", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP},
|
||||
{"down", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_DOWN},
|
||||
{"left", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_LEFT},
|
||||
{"right", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_RIGHT},
|
||||
{"leftjoystick_halfmode", LEFTJOYSTICK_HALFMODE},
|
||||
{"rightjoystick_halfmode", RIGHTJOYSTICK_HALFMODE},
|
||||
};
|
||||
const std::map<std::string, AxisMapping> string_to_axis_map = {
|
||||
{"axis_left_x_plus", {Input::Axis::LeftX, 127}},
|
||||
{"axis_left_x_minus", {Input::Axis::LeftX, -127}},
|
||||
{"axis_left_y_plus", {Input::Axis::LeftY, 127}},
|
||||
{"axis_left_y_minus", {Input::Axis::LeftY, -127}},
|
||||
{"axis_right_x_plus", {Input::Axis::RightX, 127}},
|
||||
{"axis_right_x_minus", {Input::Axis::RightX, -127}},
|
||||
{"axis_right_y_plus", {Input::Axis::RightY, 127}},
|
||||
{"axis_right_y_minus", {Input::Axis::RightY, -127}},
|
||||
{"l2", {Axis::TriggerLeft, 127}},
|
||||
{"r2", {Axis::TriggerRight, 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},
|
||||
};
|
||||
|
||||
// literally the only flag that needs external access
|
||||
void toggleMouseEnabled();
|
||||
|
||||
// i wrapped it in a function so I can collapse it
|
||||
std::string_view getDefaultKeyboardConfig();
|
||||
|
||||
void parseInputConfig(const std::string game_id);
|
||||
|
||||
class InputBinding {
|
||||
public:
|
||||
u32 key1, key2, key3;
|
||||
InputBinding(u32 k1 = SDLK_UNKNOWN, u32 k2 = SDLK_UNKNOWN, u32 k3 = SDLK_UNKNOWN) {
|
||||
// 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 != SDLK_UNKNOWN) {
|
||||
k2 = 0;
|
||||
}
|
||||
if (k1 == k3 && k1 != SDLK_UNKNOWN) {
|
||||
k3 = 0;
|
||||
}
|
||||
if (k3 == k2 && k2 != SDLK_UNKNOWN) {
|
||||
k2 = 0;
|
||||
}
|
||||
// this sorts them
|
||||
if (k1 <= k2 && k1 <= k3) {
|
||||
key1 = k1;
|
||||
if (k2 <= k3) {
|
||||
key2 = k2;
|
||||
key3 = k3;
|
||||
} else {
|
||||
key2 = k3;
|
||||
key3 = k2;
|
||||
}
|
||||
} else if (k2 <= k1 && k2 <= k3) {
|
||||
key1 = k2;
|
||||
if (k1 <= k3) {
|
||||
key2 = k1;
|
||||
key3 = k3;
|
||||
} else {
|
||||
key2 = k3;
|
||||
key3 = k1;
|
||||
}
|
||||
} else {
|
||||
key1 = k3;
|
||||
if (k1 <= k2) {
|
||||
key2 = k1;
|
||||
key3 = k2;
|
||||
} else {
|
||||
key2 = k2;
|
||||
key3 = k1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// copy ctor
|
||||
InputBinding(const InputBinding& o) : key1(o.key1), key2(o.key2), key3(o.key3) {}
|
||||
|
||||
inline bool operator==(const InputBinding& o) {
|
||||
// 0 = SDLK_UNKNOWN aka unused slot
|
||||
return (key3 == o.key3 || key3 == 0 || o.key3 == 0) &&
|
||||
(key2 == o.key2 || key2 == 0 || o.key2 == 0) &&
|
||||
(key1 == o.key1 || key1 == 0 || o.key1 == 0);
|
||||
// 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 (key1 ? 1 : 0) + (key2 ? 1 : 0) + (key3 ? 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 key1 == 0 && key2 == 0 && key3 == 0;
|
||||
}
|
||||
std::string toString() const {
|
||||
return fmt::format("({}, {}, {})", key1, key2, key3);
|
||||
}
|
||||
|
||||
// returns a u32 based on the event type (keyboard, mouse buttons, or wheel)
|
||||
static u32 getInputIDFromEvent(const SDL_Event& e);
|
||||
};
|
||||
class ControllerOutput {
|
||||
static GameController* controller;
|
||||
|
||||
public:
|
||||
static void setControllerOutputController(GameController* c);
|
||||
|
||||
u32 button;
|
||||
Axis axis;
|
||||
int axis_value;
|
||||
|
||||
ControllerOutput(const u32 b, Axis a = Axis::AxisMax) {
|
||||
button = b;
|
||||
axis = a;
|
||||
axis_value = 0;
|
||||
}
|
||||
ControllerOutput(const ControllerOutput& o) : button(o.button), axis(o.axis) {}
|
||||
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("({}, {}, {})", button, (int)axis, axis_value);
|
||||
}
|
||||
void update(bool pressed, u32 param = 0);
|
||||
// Off events are not counted
|
||||
void addUpdate(bool pressed, u32 param = 0);
|
||||
};
|
||||
class BindingConnection {
|
||||
public:
|
||||
InputBinding binding;
|
||||
ControllerOutput* output;
|
||||
u32 parameter;
|
||||
BindingConnection(InputBinding b, ControllerOutput* out, u32 param = 0) {
|
||||
binding = b;
|
||||
parameter = param; // bruh this accidentally set to be 0 no wonder it didn't do anything
|
||||
|
||||
// todo: check if out is in the allowed array
|
||||
output = out;
|
||||
}
|
||||
bool operator<(const BindingConnection& other) {
|
||||
return binding < other.binding;
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the 3 key input is currently active.
|
||||
bool checkForInputDown(InputBinding i);
|
||||
|
||||
// Add/remove the input that generated the event to/from the held keys container.
|
||||
void updatePressedKeys(u32 button, bool is_pressed);
|
||||
|
||||
void activateOutputsFromInputs();
|
||||
|
||||
void updateMouse(GameController* controller);
|
||||
|
||||
// Polls the mouse for changes, and simulates joystick movement from it.
|
||||
Uint32 mousePolling(void* param, Uint32 id, Uint32 interval);
|
||||
|
||||
} // namespace Input
|
@ -1,442 +1,27 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <SDL3/SDL_init.h>
|
||||
#include <SDL3/SDL_properties.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <SDL3/SDL_video.h>
|
||||
#include "SDL3/SDL_events.h"
|
||||
#include "SDL3/SDL_init.h"
|
||||
#include "SDL3/SDL_properties.h"
|
||||
#include "SDL3/SDL_timer.h"
|
||||
#include "SDL3/SDL_video.h"
|
||||
#include "common/assert.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 "core/libraries/pad/pad.h"
|
||||
#include "imgui/renderer/imgui_core.h"
|
||||
#include "input/controller.h"
|
||||
#include "input/input_handler.h"
|
||||
#include "sdl_window.h"
|
||||
#include "video_core/renderdoc.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <SDL3/SDL_metal.h>
|
||||
#include "SDL3/SDL_metal.h"
|
||||
#endif
|
||||
|
||||
Uint32 getMouseWheelEvent(const SDL_Event* event) {
|
||||
if (event->type != SDL_EVENT_MOUSE_WHEEL)
|
||||
return 0;
|
||||
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 0;
|
||||
}
|
||||
|
||||
namespace KBMConfig {
|
||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
||||
|
||||
// i wrapped it in a function so I can collapse it
|
||||
std::string getDefaultKeyboardConfig() {
|
||||
std::string default_config =
|
||||
R"(## SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
## SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#This is the default keybinding config
|
||||
#To change per-game configs, modify the CUSAXXXXX.ini files
|
||||
#To change the default config that applies to new games without already existing configs, modify default.ini
|
||||
#If you don't like certain mappings, delete, change or comment them out.
|
||||
#You can add any amount of KBM keybinds to a single controller input,
|
||||
#but you can use each KBM keybind for one controller input.
|
||||
|
||||
#Keybinds used by the emulator (these are unchangeable):
|
||||
#F11 : fullscreen
|
||||
#F10 : FPS counter
|
||||
#F9 : toggle mouse-to-joystick input
|
||||
# (it overwrites everything else to that joystick, so this is required)
|
||||
#F8 : reparse keyboard input(this)
|
||||
|
||||
#This is a mapping for Bloodborne, inspired by other Souls titles on PC.
|
||||
|
||||
#Specifies which joystick the mouse movement controls.
|
||||
mouse_to_joystick = right;
|
||||
|
||||
#Use healing item, change status in inventory
|
||||
triangle = f;
|
||||
#Dodge, back in inventory
|
||||
circle = space;
|
||||
#Interact, select item in inventory
|
||||
cross = e;
|
||||
#Use quick item, remove item in inventory
|
||||
square = r;
|
||||
|
||||
#Emergency extra bullets
|
||||
up = w, lalt;
|
||||
up = mousewheelup;
|
||||
#Change quick item
|
||||
down = s, lalt;
|
||||
down = mousewheeldown;
|
||||
#Change weapon in left hand
|
||||
left = a, lalt;
|
||||
left = mousewheelleft;
|
||||
#Change weapon in right hand
|
||||
right = d, lalt;
|
||||
right = mousewheelright;
|
||||
#Change into 'inventory mode', so you don't have to hold lalt every time you go into menus
|
||||
modkey_toggle = i, lalt;
|
||||
|
||||
#Menu
|
||||
options = escape;
|
||||
#Gestures
|
||||
touchpad = g;
|
||||
|
||||
#Transform
|
||||
l1 = rightbutton, lshift;
|
||||
#Shoot
|
||||
r1 = leftbutton;
|
||||
#Light attack
|
||||
l2 = rightbutton;
|
||||
#Heavy attack
|
||||
r2 = leftbutton, lshift;
|
||||
#Does nothing
|
||||
l3 = x;
|
||||
#Center cam, lock on
|
||||
r3 = q;
|
||||
r3 = middlebutton;
|
||||
|
||||
#Axis mappings
|
||||
#Move
|
||||
axis_left_x_minus = a;
|
||||
axis_left_x_plus = d;
|
||||
axis_left_y_minus = w;
|
||||
axis_left_y_plus = s;
|
||||
#Change to 'walk mode' by holding the following key:
|
||||
leftjoystick_halfmode = lctrl;
|
||||
)";
|
||||
return default_config;
|
||||
}
|
||||
|
||||
// Button map: maps key+modifier to controller button
|
||||
std::map<KeyBinding, u32> button_map = {};
|
||||
std::map<KeyBinding, AxisMapping> axis_map = {};
|
||||
std::map<SDL_Keycode, std::pair<SDL_Keymod, bool>> key_to_modkey_toggle_map = {};
|
||||
|
||||
// Flags and values for varying purposes
|
||||
int mouse_joystick_binding = 0;
|
||||
float mouse_deadzone_offset = 0.5, mouse_speed = 1, mouse_speed_offset = 0.125;
|
||||
Uint32 mouse_polling_id = 0;
|
||||
bool mouse_enabled = false, leftjoystick_halfmode = false, rightjoystick_halfmode = false;
|
||||
|
||||
// A vector to store delayed actions by event ID
|
||||
std::vector<DelayedAction> delayedActions;
|
||||
|
||||
KeyBinding::KeyBinding(const SDL_Event* event) {
|
||||
modifier = getCustomModState();
|
||||
key = 0;
|
||||
if (event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) {
|
||||
key = event->key.key;
|
||||
} else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN ||
|
||||
event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
|
||||
key = event->button.button;
|
||||
} else if (event->type == SDL_EVENT_MOUSE_WHEEL) {
|
||||
key = getMouseWheelEvent(event);
|
||||
} else {
|
||||
std::cout << "We don't support this event type!\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool KeyBinding::operator<(const KeyBinding& other) const {
|
||||
return std::tie(key, modifier) < std::tie(other.key, other.modifier);
|
||||
}
|
||||
|
||||
SDL_Keymod KeyBinding::getCustomModState() {
|
||||
SDL_Keymod state = SDL_GetModState();
|
||||
for (auto mod_flag : KBMConfig::key_to_modkey_toggle_map) {
|
||||
if (mod_flag.second.second) {
|
||||
state |= mod_flag.second.first;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
void parseInputConfig(const std::string game_id = "") {
|
||||
// Read configuration file of the game, and if it doesn't exist, generate it from default
|
||||
// If that doesn't exist either, generate that from getDefaultConfig() and try again
|
||||
// If even the folder is missing, we start with that.
|
||||
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "kbmConfig";
|
||||
const auto config_file = config_dir / (game_id + ".ini");
|
||||
const auto default_config_file = config_dir / "default.ini";
|
||||
|
||||
// Ensure the config directory exists
|
||||
if (!std::filesystem::exists(config_dir)) {
|
||||
std::filesystem::create_directories(config_dir);
|
||||
}
|
||||
|
||||
// Try loading the game-specific config file
|
||||
if (!std::filesystem::exists(config_file)) {
|
||||
// If game-specific config doesn't exist, check for the default config
|
||||
if (!std::filesystem::exists(default_config_file)) {
|
||||
// If the default config is also missing, create it from getDefaultConfig()
|
||||
const auto default_config = getDefaultKeyboardConfig();
|
||||
std::ofstream default_config_stream(default_config_file);
|
||||
if (default_config_stream) {
|
||||
default_config_stream << default_config;
|
||||
}
|
||||
}
|
||||
|
||||
// If default config now exists, copy it to the game-specific config file
|
||||
if (std::filesystem::exists(default_config_file) && !game_id.empty()) {
|
||||
std::filesystem::copy(default_config_file, config_file);
|
||||
}
|
||||
}
|
||||
// if we just called the function to generate the directory and the default .ini
|
||||
if (game_id.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we reset these here so in case the user fucks up or doesn't include this we can fall back to
|
||||
// default
|
||||
mouse_deadzone_offset = 0.5;
|
||||
mouse_speed = 1;
|
||||
mouse_speed_offset = 0.125;
|
||||
button_map.clear();
|
||||
axis_map.clear();
|
||||
key_to_modkey_toggle_map.clear();
|
||||
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(line.begin(), line.end(), ' '), line.end());
|
||||
if (line[line.length() - 1] == ';') {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
}
|
||||
// Ignore comment lines
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
// Split the line by '='
|
||||
std::size_t equal_pos = line.find('=');
|
||||
if (equal_pos == std::string::npos) {
|
||||
std::cerr << "Invalid line format at line: " << lineCount << " data: " << line
|
||||
<< std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string before_equals = line.substr(0, equal_pos);
|
||||
std::string after_equals = line.substr(equal_pos + 1);
|
||||
std::size_t comma_pos = after_equals.find(',');
|
||||
KeyBinding binding = {0, SDL_KMOD_NONE};
|
||||
|
||||
// special check for mouse to joystick input
|
||||
if (before_equals == "mouse_to_joystick") {
|
||||
if (after_equals == "left") {
|
||||
mouse_joystick_binding = 1;
|
||||
} else if (after_equals == "right") {
|
||||
mouse_joystick_binding = 2;
|
||||
} else {
|
||||
mouse_joystick_binding = 0; // default to 'none' or invalid
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// mod key toggle
|
||||
if (before_equals == "modkey_toggle") {
|
||||
if (comma_pos != std::string::npos) {
|
||||
auto k = string_to_keyboard_key_map.find(after_equals.substr(0, comma_pos));
|
||||
auto m = string_to_keyboard_mod_key_map.find(after_equals.substr(comma_pos + 1));
|
||||
if (k != string_to_keyboard_key_map.end() &&
|
||||
m != string_to_keyboard_mod_key_map.end()) {
|
||||
key_to_modkey_toggle_map[k->second] = {m->second, false};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
std::cerr << "Invalid line format at line: " << lineCount << " data: " << line
|
||||
<< std::endl;
|
||||
continue;
|
||||
}
|
||||
// first we parse the binding, and if its wrong, we skip to the next line
|
||||
if (comma_pos != std::string::npos) {
|
||||
// Handle key + modifier
|
||||
std::string key = after_equals.substr(0, comma_pos);
|
||||
std::string mod = after_equals.substr(comma_pos + 1);
|
||||
|
||||
auto key_it = string_to_keyboard_key_map.find(key);
|
||||
auto mod_it = string_to_keyboard_mod_key_map.find(mod);
|
||||
|
||||
if (key_it != string_to_keyboard_key_map.end() &&
|
||||
mod_it != string_to_keyboard_mod_key_map.end()) {
|
||||
binding.key = key_it->second;
|
||||
binding.modifier = mod_it->second;
|
||||
} else if (before_equals == "mouse_movement_params") {
|
||||
// handle mouse movement params
|
||||
float p1 = 0.5, p2 = 1, p3 = 0.125;
|
||||
std::size_t second_comma_pos = after_equals.find(',');
|
||||
try {
|
||||
p1 = std::stof(key);
|
||||
p2 = std::stof(mod.substr(0, second_comma_pos));
|
||||
p3 = std::stof(mod.substr(second_comma_pos + 1));
|
||||
mouse_deadzone_offset = p1;
|
||||
mouse_speed = p2;
|
||||
mouse_speed_offset = p3;
|
||||
} catch (...) {
|
||||
// fallback to default values
|
||||
mouse_deadzone_offset = 0.5;
|
||||
mouse_speed = 1;
|
||||
mouse_speed_offset = 0.125;
|
||||
std::cerr << "Parsing error while parsing kbm inputs at line " << lineCount
|
||||
<< " line data: " << line << "\n";
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
std::cerr << "Syntax error while parsing kbm inputs at line " << lineCount
|
||||
<< " line data: " << line << "\n";
|
||||
continue; // skip
|
||||
}
|
||||
} else {
|
||||
// Just a key without modifier
|
||||
auto key_it = string_to_keyboard_key_map.find(after_equals);
|
||||
if (key_it != string_to_keyboard_key_map.end()) {
|
||||
binding.key = key_it->second;
|
||||
} else {
|
||||
std::cerr << "Syntax error while parsing kbm inputs at line " << lineCount
|
||||
<< " line data: " << line << "\n";
|
||||
continue; // skip
|
||||
}
|
||||
}
|
||||
|
||||
// Check for axis mapping (example: axis_left_x_plus)
|
||||
auto axis_it = string_to_axis_map.find(before_equals);
|
||||
auto button_it = string_to_cbutton_map.find(before_equals);
|
||||
if (axis_it != string_to_axis_map.end()) {
|
||||
axis_map[binding] = axis_it->second;
|
||||
} else if (button_it != string_to_cbutton_map.end()) {
|
||||
button_map[binding] = button_it->second;
|
||||
} else {
|
||||
std::cerr << "Syntax error while parsing kbm inputs at line " << lineCount
|
||||
<< " line data: " << line << "\n";
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
} // namespace KBMConfig
|
||||
|
||||
namespace Frontend {
|
||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
||||
|
||||
using namespace KBMConfig;
|
||||
using KBMConfig::AxisMapping;
|
||||
using KBMConfig::KeyBinding;
|
||||
|
||||
// modifiers are bitwise or-d together, so we need to check if ours is in that
|
||||
template <typename T>
|
||||
typename std::map<KeyBinding, T>::const_iterator FindKeyAllowingPartialModifiers(
|
||||
const std::map<KeyBinding, T>& map, KeyBinding binding) {
|
||||
for (typename std::map<KeyBinding, T>::const_iterator it = map.cbegin(); it != map.cend();
|
||||
it++) {
|
||||
if ((it->first.key == binding.key) && (it->first.modifier & binding.modifier) != 0) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return map.end(); // Return end if no match is found
|
||||
}
|
||||
template <typename T>
|
||||
typename std::map<KeyBinding, T>::const_iterator FindKeyAllowingOnlyNoModifiers(
|
||||
const std::map<KeyBinding, T>& map, KeyBinding binding) {
|
||||
for (typename std::map<KeyBinding, T>::const_iterator it = map.cbegin(); it != map.cend();
|
||||
it++) {
|
||||
if (it->first.key == binding.key && it->first.modifier == SDL_KMOD_NONE) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return map.end(); // Return end if no match is found
|
||||
}
|
||||
|
||||
void WindowSDL::handleDelayedActions() {
|
||||
// Uncomment at your own terminal's risk
|
||||
// std::cout << "I fear the amount of spam this line will generate\n";
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
Uint32 currentTime = SDL_GetTicks();
|
||||
for (auto it = delayedActions.begin(); it != delayedActions.end();) {
|
||||
if (currentTime >= it->triggerTime) {
|
||||
if (it->event.type == SDL_EVENT_MOUSE_WHEEL) {
|
||||
SDL_Event* mouseEvent = &(it->event);
|
||||
KeyBinding binding(mouseEvent);
|
||||
|
||||
auto button_it = button_map.find(binding);
|
||||
auto axis_it = axis_map.find(binding);
|
||||
|
||||
if (button_it != button_map.end()) {
|
||||
updateButton(binding, button_it->second, false);
|
||||
} else if (axis_it != axis_map.end()) {
|
||||
controller->Axis(0, axis_it->second.axis, Input::GetAxis(-0x80, 0x80, 0));
|
||||
}
|
||||
} else {
|
||||
KeyBinding b(&(it->event));
|
||||
updateModKeyedInputsManually(b);
|
||||
}
|
||||
it = delayedActions.erase(it); // Erase returns the next iterator
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 WindowSDL::mousePolling(void* param, Uint32 id, Uint32 interval) {
|
||||
auto* data = (WindowSDL*)param;
|
||||
data->updateMouse();
|
||||
return 33;
|
||||
}
|
||||
|
||||
void WindowSDL::updateMouse() {
|
||||
if (!mouse_enabled)
|
||||
return;
|
||||
Input::Axis axis_x, axis_y;
|
||||
switch (mouse_joystick_binding) {
|
||||
case 1:
|
||||
axis_x = Input::Axis::LeftX;
|
||||
axis_y = Input::Axis::LeftY;
|
||||
break;
|
||||
case 2:
|
||||
axis_x = Input::Axis::RightX;
|
||||
axis_y = Input::Axis::RightY;
|
||||
break;
|
||||
case 0:
|
||||
default:
|
||||
return; // 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, Input::GetAxis(-0x80, 0x80, a_x));
|
||||
controller->Axis(0, axis_y, Input::GetAxis(-0x80, 0x80, a_y));
|
||||
} else {
|
||||
controller->Axis(0, axis_x, Input::GetAxis(-0x80, 0x80, 0));
|
||||
controller->Axis(0, axis_y, Input::GetAxis(-0x80, 0x80, 0));
|
||||
}
|
||||
}
|
||||
|
||||
static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
|
||||
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
|
||||
@ -493,31 +78,25 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
|
||||
window_info.type = WindowSystemType::Metal;
|
||||
window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(window));
|
||||
#endif
|
||||
// initialize kbm controls
|
||||
parseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
|
||||
// input handler init-s
|
||||
Input::ControllerOutput::setControllerOutputController(controller);
|
||||
Input::parseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
|
||||
}
|
||||
|
||||
WindowSDL::~WindowSDL() = default;
|
||||
|
||||
void WindowSDL::waitEvent() {
|
||||
// Called on main thread
|
||||
SDL_Event event;
|
||||
|
||||
handleDelayedActions();
|
||||
|
||||
SDL_Event event{};
|
||||
|
||||
// waitEvent locks the execution here until the next event,
|
||||
// but we want to poll handleDelayedActions too
|
||||
if (!SDL_PollEvent(&event)) {
|
||||
if (!SDL_WaitEvent(&event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::Core::ProcessEvent(&event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set execution time to 33 ms later than 'now'
|
||||
DelayedAction d = {SDL_GetTicks() + 33, event};
|
||||
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_WINDOW_RESIZED:
|
||||
case SDL_EVENT_WINDOW_MAXIMIZED:
|
||||
@ -529,15 +108,13 @@ void WindowSDL::waitEvent() {
|
||||
is_shown = event.type == SDL_EVENT_WINDOW_EXPOSED;
|
||||
onResize();
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
// native mouse update function goes here
|
||||
// as seen in pr #633
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
case SDL_EVENT_MOUSE_WHEEL_OFF:
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
delayedActions.push_back(d);
|
||||
onKeyboardMouseEvent(&event);
|
||||
onKeyboardMouseInput(&event);
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
@ -559,7 +136,7 @@ void WindowSDL::waitEvent() {
|
||||
|
||||
void WindowSDL::initTimers() {
|
||||
SDL_AddTimer(100, &PollController, controller);
|
||||
SDL_AddTimer(33, mousePolling, (void*)this);
|
||||
SDL_AddTimer(33, Input::mousePolling, (void*)controller);
|
||||
}
|
||||
|
||||
void WindowSDL::onResize() {
|
||||
@ -567,152 +144,62 @@ void WindowSDL::onResize() {
|
||||
ImGui::Core::OnResize();
|
||||
}
|
||||
|
||||
// for L2/R2, touchpad and normal buttons
|
||||
void WindowSDL::updateButton(KeyBinding& binding, u32 button, bool is_pressed) {
|
||||
float touchpad_x = 0;
|
||||
Input::Axis axis = Input::Axis::AxisMax;
|
||||
switch (button) {
|
||||
case OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L2:
|
||||
case OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R2:
|
||||
axis = (button == OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R2) ? Input::Axis::TriggerRight
|
||||
: Input::Axis::TriggerLeft;
|
||||
controller->Axis(0, axis, Input::GetAxis(0, 0x80, is_pressed ? 255 : 0));
|
||||
break;
|
||||
case OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD:
|
||||
touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f
|
||||
: Config::getBackButtonBehavior() == "right" ? 0.75f
|
||||
: 0.5f;
|
||||
controller->SetTouchpadState(0, true, touchpad_x, 0.5f);
|
||||
controller->CheckButton(0, button, is_pressed);
|
||||
break;
|
||||
default: // is a normal key
|
||||
controller->CheckButton(0, button, is_pressed);
|
||||
break;
|
||||
}
|
||||
Uint32 wheelOffCallback(void* og_event, Uint32 timer_id, Uint32 interval) {
|
||||
SDL_Event off_event = *(SDL_Event*)og_event;
|
||||
off_event.type = SDL_EVENT_MOUSE_WHEEL_OFF;
|
||||
SDL_PushEvent(&off_event);
|
||||
delete (SDL_Event*)og_event;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// previously onKeyPress
|
||||
void WindowSDL::onKeyboardMouseEvent(const SDL_Event* event) {
|
||||
// Extract key and modifier
|
||||
KeyBinding binding(event);
|
||||
void WindowSDL::onKeyboardMouseInput(const SDL_Event* event) {
|
||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
||||
|
||||
// get the event's id, if it's keyup or keydown
|
||||
bool input_down = event->type == SDL_EVENT_KEY_DOWN ||
|
||||
event->type == SDL_EVENT_MOUSE_BUTTON_DOWN ||
|
||||
event->type == SDL_EVENT_MOUSE_WHEEL;
|
||||
u32 input_id = Input::InputBinding::getInputIDFromEvent(*event);
|
||||
|
||||
// Handle window controls outside of the input maps
|
||||
if (event->type == SDL_EVENT_KEY_DOWN) {
|
||||
// Reparse kbm inputs
|
||||
if (binding.key == SDLK_F8) {
|
||||
parseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
|
||||
if (input_id == SDLK_F8) {
|
||||
Input::parseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
|
||||
return;
|
||||
}
|
||||
// Toggle mouse capture and movement input
|
||||
else if (binding.key == SDLK_F9) {
|
||||
mouse_enabled = !mouse_enabled;
|
||||
else if (input_id == SDLK_F7) {
|
||||
Input::toggleMouseEnabled();
|
||||
SDL_SetWindowRelativeMouseMode(this->GetSdlWindow(),
|
||||
!SDL_GetWindowRelativeMouseMode(this->GetSdlWindow()));
|
||||
return;
|
||||
}
|
||||
// Toggle fullscreen
|
||||
else if (binding.key == SDLK_F11) {
|
||||
else if (input_id == SDLK_F11) {
|
||||
SDL_WindowFlags flag = SDL_GetWindowFlags(window);
|
||||
bool is_fullscreen = flag & SDL_WINDOW_FULLSCREEN;
|
||||
SDL_SetWindowFullscreen(window, !is_fullscreen);
|
||||
return;
|
||||
}
|
||||
// Trigger rdoc capture
|
||||
else if (binding.key == SDLK_F12) {
|
||||
else if (input_id == SDLK_F12) {
|
||||
VideoCore::TriggerCapture();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for modifier toggle
|
||||
auto modkey_toggle_it = key_to_modkey_toggle_map.find(binding.key);
|
||||
modkey_toggle_it->second.second ^=
|
||||
(modkey_toggle_it != key_to_modkey_toggle_map.end() &&
|
||||
(binding.modifier & (~modkey_toggle_it->second.first)) == SDL_KMOD_NONE && input_down);
|
||||
|
||||
// Check if the current key+modifier is a button or axis mapping
|
||||
// first only exact matches
|
||||
auto button_it = FindKeyAllowingPartialModifiers(button_map, binding);
|
||||
auto axis_it = FindKeyAllowingPartialModifiers(axis_map, binding);
|
||||
// then no mod key matches if we didn't find it in the previous pass
|
||||
if (button_it == button_map.end() && axis_it == axis_map.end()) {
|
||||
button_it = FindKeyAllowingOnlyNoModifiers(button_map, binding);
|
||||
}
|
||||
if (axis_it == axis_map.end() && button_it == button_map.end()) {
|
||||
axis_it = FindKeyAllowingOnlyNoModifiers(axis_map, binding);
|
||||
// if it's a wheel event, make a timer that turns it off after a set time
|
||||
if (event->type == SDL_EVENT_MOUSE_WHEEL) {
|
||||
const SDL_Event* copy = new SDL_Event(*event);
|
||||
SDL_AddTimer(33, wheelOffCallback, (void*)copy);
|
||||
}
|
||||
|
||||
if (button_it != button_map.end()) {
|
||||
// joystick_halfmode is not a button update so we handle it differently
|
||||
if (button_it->second == LEFTJOYSTICK_HALFMODE) {
|
||||
leftjoystick_halfmode = input_down;
|
||||
} else if (button_it->second == RIGHTJOYSTICK_HALFMODE) {
|
||||
rightjoystick_halfmode = input_down;
|
||||
} else {
|
||||
WindowSDL::updateButton(binding, button_it->second, input_down);
|
||||
}
|
||||
}
|
||||
if (axis_it != axis_map.end()) {
|
||||
Input::Axis axis = axis_it->second.axis;
|
||||
float multiplier = 1.0;
|
||||
switch (axis) {
|
||||
case Input::Axis::LeftX:
|
||||
case Input::Axis::LeftY:
|
||||
multiplier = leftjoystick_halfmode ? 0.5 : 1.0;
|
||||
break;
|
||||
case Input::Axis::RightX:
|
||||
case Input::Axis::RightY:
|
||||
multiplier = rightjoystick_halfmode ? 0.5 : 1.0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
int axis_value = (input_down ? axis_it->second.value : 0) * multiplier;
|
||||
int ax = Input::GetAxis(-0x80, 0x80, axis_value);
|
||||
controller->Axis(0, axis, ax);
|
||||
}
|
||||
}
|
||||
// add/remove it from the list
|
||||
Input::updatePressedKeys(input_id, input_down);
|
||||
|
||||
// if we don't do this, then if we activate a mod keyed input and let go of the mod key first,
|
||||
// the button will be stuck on the "on" state becuse the "turn off" signal would only come from
|
||||
// the other key being unpressed
|
||||
void WindowSDL::updateModKeyedInputsManually(Frontend::KeyBinding& binding) {
|
||||
bool mod_keyed_input_found = false;
|
||||
for (auto input : button_map) {
|
||||
if (input.first.modifier != SDL_KMOD_NONE) {
|
||||
if ((input.first.modifier & binding.modifier) == 0) {
|
||||
WindowSDL::updateButton(binding, input.second, false);
|
||||
} else if (input.first.key == binding.key) {
|
||||
mod_keyed_input_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto input : axis_map) {
|
||||
if (input.first.modifier != SDL_KMOD_NONE) {
|
||||
if ((input.first.modifier & binding.modifier) == 0) {
|
||||
controller->Axis(0, input.second.axis, Input::GetAxis(-0x80, 0x80, 0));
|
||||
} else if (input.first.key == binding.key) {
|
||||
mod_keyed_input_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if both non mod keyed and mod keyed inputs are used and you press the key and then the mod
|
||||
// key in a single frame, both will activate but the simple one will not deactivate, unless i
|
||||
// use this stupid looking workaround
|
||||
if (!mod_keyed_input_found)
|
||||
return; // in this case the fix for the fix for the wrong update order is not needed
|
||||
for (auto input : button_map) {
|
||||
if (input.first.modifier == SDL_KMOD_NONE) {
|
||||
WindowSDL::updateButton(binding, input.second, false);
|
||||
}
|
||||
}
|
||||
for (auto input : axis_map) {
|
||||
if (input.first.modifier == SDL_KMOD_NONE) {
|
||||
controller->Axis(0, input.second.axis, Input::GetAxis(-0x80, 0x80, 0));
|
||||
}
|
||||
}
|
||||
// also this sometimes leads to janky inputs but whoever decides to intentionally create a state
|
||||
// where this is needed should not deserve a smooth experience anyway
|
||||
// update bindings
|
||||
Input::activateOutputsFromInputs();
|
||||
}
|
||||
|
||||
void WindowSDL::onGamepadEvent(const SDL_Event* event) {
|
||||
|
206
src/sdl_window.h
206
src/sdl_window.h
@ -3,22 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
#include "input/controller.h"
|
||||
|
||||
#include <SDL3/SDL_events.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 + 6
|
||||
|
||||
#define LEFTJOYSTICK_HALFMODE 0x00010000
|
||||
#define RIGHTJOYSTICK_HALFMODE 0x00020000
|
||||
#include "string"
|
||||
|
||||
struct SDL_Window;
|
||||
struct SDL_Gamepad;
|
||||
@ -28,187 +14,6 @@ namespace Input {
|
||||
class GameController;
|
||||
}
|
||||
|
||||
namespace KBMConfig {
|
||||
|
||||
class KeyBinding {
|
||||
public:
|
||||
Uint32 key;
|
||||
SDL_Keymod modifier;
|
||||
KeyBinding(SDL_Keycode k, SDL_Keymod m) : key(k), modifier(m){};
|
||||
KeyBinding(const SDL_Event* event);
|
||||
bool operator<(const KeyBinding& other) const;
|
||||
~KeyBinding(){};
|
||||
static SDL_Keymod getCustomModState();
|
||||
};
|
||||
|
||||
struct AxisMapping {
|
||||
Input::Axis axis;
|
||||
int value; // Value to set for key press (+127 or -127 for movement)
|
||||
};
|
||||
|
||||
// Define a struct to hold any necessary timing information for delayed actions
|
||||
struct DelayedAction {
|
||||
Uint64 triggerTime; // When the action should be triggered
|
||||
SDL_Event event; // Event data
|
||||
};
|
||||
|
||||
std::string getDefaultKeyboardConfig();
|
||||
void parseInputConfig(const std::string game_id);
|
||||
|
||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
||||
// i strongly suggest you collapse these maps
|
||||
const std::map<std::string, u32> string_to_cbutton_map = {
|
||||
{"triangle", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TRIANGLE},
|
||||
{"circle", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CIRCLE},
|
||||
{"cross", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CROSS},
|
||||
{"square", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_SQUARE},
|
||||
{"l1", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L1},
|
||||
{"l2", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L2},
|
||||
{"r1", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R1},
|
||||
{"r2", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R2},
|
||||
{"l3", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L3},
|
||||
{"r3", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R3},
|
||||
{"options", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_OPTIONS},
|
||||
{"touchpad", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD},
|
||||
{"up", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP},
|
||||
{"down", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_DOWN},
|
||||
{"left", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_LEFT},
|
||||
{"right", OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_RIGHT},
|
||||
{"leftjoystick_halfmode", LEFTJOYSTICK_HALFMODE},
|
||||
{"rightjoystick_halfmode", RIGHTJOYSTICK_HALFMODE},
|
||||
};
|
||||
const std::map<std::string, AxisMapping> string_to_axis_map = {
|
||||
{"axis_left_x_plus", {Input::Axis::LeftX, 127}},
|
||||
{"axis_left_x_minus", {Input::Axis::LeftX, -127}},
|
||||
{"axis_left_y_plus", {Input::Axis::LeftY, 127}},
|
||||
{"axis_left_y_minus", {Input::Axis::LeftY, -127}},
|
||||
{"axis_right_x_plus", {Input::Axis::RightX, 127}},
|
||||
{"axis_right_x_minus", {Input::Axis::RightX, -127}},
|
||||
{"axis_right_y_plus", {Input::Axis::RightY, 127}},
|
||||
{"axis_right_y_minus", {Input::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},
|
||||
};
|
||||
const std::map<std::string, u32> string_to_keyboard_mod_key_map = {
|
||||
{"lshift", SDL_KMOD_LSHIFT}, {"rshift", SDL_KMOD_RSHIFT},
|
||||
{"lctrl", SDL_KMOD_LCTRL}, {"rctrl", SDL_KMOD_RCTRL},
|
||||
{"lalt", SDL_KMOD_LALT}, {"ralt", SDL_KMOD_RALT},
|
||||
{"shift", SDL_KMOD_SHIFT}, {"ctrl", SDL_KMOD_CTRL},
|
||||
{"alt", SDL_KMOD_ALT}, {"l_meta", SDL_KMOD_LGUI},
|
||||
{"r_meta", SDL_KMOD_RGUI}, {"meta", SDL_KMOD_GUI},
|
||||
{"lwin", SDL_KMOD_LGUI}, {"rwin", SDL_KMOD_RGUI},
|
||||
{"win", SDL_KMOD_GUI}, {"capslock", SDL_KMOD_CAPS},
|
||||
{"numlock", SDL_KMOD_NUM}, {"none", SDL_KMOD_NONE}, // if you want to be fancy
|
||||
};
|
||||
|
||||
// Button map: maps key+modifier to controller button
|
||||
extern std::map<KeyBinding, u32> button_map;
|
||||
extern std::map<KeyBinding, AxisMapping> axis_map;
|
||||
extern std::map<SDL_Keycode, std::pair<SDL_Keymod, bool>> key_to_modkey_toggle_map;
|
||||
|
||||
} // namespace KBMConfig
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
enum class WindowSystemType : u8 {
|
||||
@ -262,20 +67,15 @@ public:
|
||||
}
|
||||
|
||||
void waitEvent();
|
||||
void updateMouse();
|
||||
|
||||
void initTimers();
|
||||
|
||||
private:
|
||||
void onResize();
|
||||
void onKeyboardMouseEvent(const SDL_Event* event);
|
||||
void onKeyboardMouseInput(const SDL_Event* event);
|
||||
void onGamepadEvent(const SDL_Event* event);
|
||||
int sdlGamepadToOrbisButton(u8 button);
|
||||
|
||||
void updateModKeyedInputsManually(KBMConfig::KeyBinding& binding);
|
||||
void updateButton(KBMConfig::KeyBinding& binding, u32 button, bool isPressed);
|
||||
static Uint32 mousePolling(void* param, Uint32 id, Uint32 interval);
|
||||
void handleDelayedActions();
|
||||
int sdlGamepadToOrbisButton(u8 button);
|
||||
|
||||
private:
|
||||
s32 width;
|
||||
|
Loading…
Reference in New Issue
Block a user