diff --git a/CMakeLists.txt b/CMakeLists.txt index d320d49e33..de884db28b 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/common/config.cpp b/src/common/config.cpp index e97a460053..8764bed39c 100644 --- a/src/common/config.cpp +++ b/src/common/config.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 diff --git a/src/common/config.h b/src/common/config.h index 9c71c96a8e..3dfafade7b 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -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 diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp new file mode 100644 index 0000000000..556ce3e97c --- /dev/null +++ b/src/input/input_handler.cpp @@ -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> pressed_keys; +std::list toggled_keys; +std::list connections = std::list(); + +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 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& 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& 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 \ No newline at end of file diff --git a/src/input/input_handler.h b/src/input/input_handler.h new file mode 100644 index 0000000000..cd752e9675 --- /dev/null +++ b/src/input/input_handler.h @@ -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 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 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 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 \ No newline at end of file diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 1c682e82ae..acd34bc2d1 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -1,442 +1,27 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include +#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 +#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 button_map = {}; -std::map axis_map = {}; -std::map> 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 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 std::map::const_iterator FindKeyAllowingPartialModifiers( - const std::map& map, KeyBinding binding) { - for (typename std::map::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 std::map::const_iterator FindKeyAllowingOnlyNoModifiers( - const std::map& map, KeyBinding binding) { - for (typename std::map::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(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) { diff --git a/src/sdl_window.h b/src/sdl_window.h index 4f4e9b8eba..4266165c17 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -3,22 +3,8 @@ #pragma once -#include -#include #include "common/types.h" -#include "core/libraries/pad/pad.h" -#include "input/controller.h" - -#include - -// +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 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 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 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 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 button_map; -extern std::map axis_map; -extern std::map> key_to_modkey_toggle_map; - -} // namespace KBMConfig - namespace Frontend { enum class WindowSystemType : u8 { @@ -262,20 +67,15 @@ class WindowSDL { } 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;