Merge branch 'new-kbm'

This commit is contained in:
kalaposfos13 2024-11-14 15:12:17 +01:00
commit e38e75b27b
7 changed files with 1049 additions and 762 deletions

View File

@ -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

View File

@ -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

View File

@ -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
View 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
View 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

View File

@ -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) {

View File

@ -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;