Apply recent changes from kbm-only

This commit is contained in:
kalaposfos13 2025-01-17 20:21:32 +01:00
parent c46edf1c39
commit 20cdbf5988
7 changed files with 367 additions and 324 deletions

View File

@ -808,6 +808,8 @@ set(INPUT src/input/controller.cpp
src/input/controller.h
src/input/input_handler.cpp
src/input/input_handler.h
src/input/input_mouse.cpp
src/input/input_mouse.h
)
set(EMULATOR src/emulator.cpp

View File

@ -148,7 +148,7 @@ bool ProcessEvent(SDL_Event* event) {
case SDL_EVENT_MOUSE_BUTTON_DOWN: {
const auto& io = GetIO();
return io.WantCaptureMouse && io.Ctx->NavWindow != nullptr &&
io.Ctx->NavWindow->ID != dock_id;
(io.Ctx->NavWindow->Flags & ImGuiWindowFlags_NoNav) == 0;
}
case SDL_EVENT_TEXT_INPUT:
case SDL_EVENT_KEY_DOWN: {

View File

@ -3,14 +3,17 @@
#include "input_handler.h"
#include "fstream"
#include "iostream"
#include "list"
#include "map"
#include "sstream"
#include "string"
#include "unordered_map"
#include "vector"
#include <fstream>
#include <iostream>
#include <list>
#include <map>
#include <ranges>
#include <sstream>
#include <string>
#include <string_view>
#include <typeinfo>
#include <unordered_map>
#include <vector>
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_timer.h"
@ -21,6 +24,7 @@
#include "common/path_util.h"
#include "common/version.h"
#include "input/controller.h"
#include "input/input_mouse.h"
namespace Input {
/*
@ -50,18 +54,13 @@ Joystick halfmode
Don't be an idiot and test only the changed part expecting everything else to not be broken
*/
// 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;
bool 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>();
std::list<std::pair<InputEvent, bool>> pressed_keys;
std::list<InputID> toggled_keys;
static std::vector<BindingConnection> connections;
ControllerOutput output_array[] = {
auto output_array = std::array{
// Important: these have to be the first, or else they will update in the wrong order
ControllerOutput((OrbisPadButtonDataOffset)LEFTJOYSTICK_HALFMODE),
ControllerOutput((OrbisPadButtonDataOffset)RIGHTJOYSTICK_HALFMODE),
@ -91,15 +90,9 @@ ControllerOutput output_array[] = {
ControllerOutput(OrbisPadButtonDataOffset::None, Axis::TriggerLeft),
ControllerOutput(OrbisPadButtonDataOffset::None, Axis::TriggerRight),
// the "sorry, you gave an incorrect value, now we bind it to nothing" output
ControllerOutput(OrbisPadButtonDataOffset::None, Axis::AxisMax),
};
// We had to go through 3 files of indirection just to update a flag
void ToggleMouseEnabled() {
mouse_enabled ^= true;
}
// parsing related functions
u32 GetAxisInputId(AxisMapping a) {
// LOG_INFO(Input, "Parsing an axis...");
@ -156,76 +149,39 @@ u32 GetControllerButtonInputId(u32 cbutton) {
// 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);
}
std::array<InputID, 3> keys = {InputID(), InputID(), InputID()};
// Check and process tokens
for (const auto& t : tokens) {
for (const auto token : std::views::split(line, ',')) { // Split by comma
const std::string t(token.begin(), token.end());
InputID input;
if (string_to_keyboard_key_map.find(t) != string_to_keyboard_key_map.end()) {
// Map to keyboard key
u32 key_id = string_to_keyboard_key_map.at(t);
if (!key1)
key1 = key_id;
else if (!key2)
key2 = key_id;
else if (!key3)
key3 = key_id;
input = InputID(InputType::KeyboardMouse, string_to_keyboard_key_map.at(t));
} else if (string_to_axis_map.find(t) != string_to_axis_map.end()) {
// Map to axis input ID
u32 axis_id = GetAxisInputId(string_to_axis_map.at(t));
if (axis_id == (u32)-1) {
return InputBinding(0, 0, 0);
}
if (!key1)
key1 = axis_id;
else if (!key2)
key2 = axis_id;
else if (!key3)
key3 = axis_id;
input = InputID(InputType::Axis, (u32)string_to_axis_map.at(t).axis);
} else if (string_to_cbutton_map.find(t) != string_to_cbutton_map.end()) {
// Map to controller button input ID
u32 cbutton_id = GetControllerButtonInputId((u32)string_to_cbutton_map.at(t));
if (cbutton_id == (u32)-1) {
return InputBinding(0, 0, 0);
}
if (!key1)
key1 = cbutton_id;
else if (!key2)
key2 = cbutton_id;
else if (!key3)
key3 = cbutton_id;
input = InputID(InputType::Controller, (u32)string_to_cbutton_map.at(t));
} else {
// Invalid token found; return default binding
return InputBinding(0, 0, 0);
LOG_DEBUG(Input, "Invalid token found: {}", t);
return InputBinding();
}
// Assign to the first available slot
for (auto& key : keys) {
if (!key.IsValid()) {
key = input;
break;
}
}
}
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
int i = 0;
for (; i[output_array] != ControllerOutput(OrbisPadButtonDataOffset::None, Axis::AxisMax);
i++) {
if (i[output_array] == parsed) {
return &output_array[i];
}
}
return &output_array[i];
LOG_DEBUG(Input, "Parsed line: {} {} {}", keys[0].ToString(), keys[1].ToString(),
keys[2].ToString());
return InputBinding(keys[0], keys[1], keys[2]);
}
void ParseInputConfig(const std::string game_id = "") {
const auto config_file = Config::GetFoolproofKbmConfigFile(game_id);
// todo: change usages of this to GetFoolproofKbmConfigFile (in the gui)
@ -236,17 +192,21 @@ void ParseInputConfig(const std::string game_id = "") {
// 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;
float mouse_deadzone_offset = 0.5;
float mouse_speed = 1;
float 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());
line.erase(std::remove_if(line.begin(), line.end(),
[](unsigned char c) { return std::isspace(c); }),
line.end());
if (line.empty()) {
continue;
}
@ -255,15 +215,14 @@ void ParseInputConfig(const std::string game_id = "") {
if (comment_pos != std::string::npos) {
line = line.substr(0, comment_pos);
}
// Remove trailing semicolon
if (!line.empty() && line[line.length() - 1] == ';') {
line = line.substr(0, line.length() - 1);
}
if (line.empty()) {
continue;
}
// Split the line by '='
std::size_t equal_pos = line.find('=');
if (equal_pos == std::string::npos) {
@ -278,11 +237,12 @@ void ParseInputConfig(const std::string game_id = "") {
if (output_string == "mouse_to_joystick") {
if (input_string == "left") {
mouse_joystick_binding = 1;
SetMouseToJoystick(1);
} else if (input_string == "right") {
mouse_joystick_binding = 2;
SetMouseToJoystick(2);
} else {
mouse_joystick_binding = 0; // default to 'none' or invalid
LOG_WARNING(Input, "Invalid argument for mouse-to-joystick binding");
SetMouseToJoystick(0);
}
continue;
}
@ -297,10 +257,10 @@ void ParseInputConfig(const std::string game_id = "") {
line);
continue;
}
ControllerOutput* toggle_out =
GetOutputPointer(ControllerOutput((OrbisPadButtonDataOffset)KEY_TOGGLE));
BindingConnection toggle_connection =
BindingConnection(InputBinding(toggle_keys.key2), toggle_out, toggle_keys.key3);
ControllerOutput* toggle_out = &*std::ranges::find(
output_array, ControllerOutput((OrbisPadButtonDataOffset)KEY_TOGGLE));
BindingConnection toggle_connection = BindingConnection(
InputBinding(toggle_keys.keys[0]), toggle_out, 0, toggle_keys.keys[1]);
connections.insert(connections.end(), toggle_connection);
continue;
}
@ -322,7 +282,7 @@ void ParseInputConfig(const std::string game_id = "") {
// normal cases
InputBinding binding = GetBindingFromString(input_string);
BindingConnection connection(0, nullptr);
BindingConnection connection(InputID(), nullptr);
auto button_it = string_to_cbutton_map.find(output_string);
auto axis_it = string_to_axis_map.find(output_string);
@ -332,37 +292,40 @@ void ParseInputConfig(const std::string game_id = "") {
continue;
}
if (button_it != string_to_cbutton_map.end()) {
connection =
BindingConnection(binding, GetOutputPointer(ControllerOutput(button_it->second)));
connection = BindingConnection(
binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second)));
connections.insert(connections.end(), connection);
} else if (axis_it != string_to_axis_map.end()) {
int value_to_set = (binding.key3 & 0x80000000) != 0 ? 0
int value_to_set = binding.keys[2].type == InputType::Axis ? 0
: (axis_it->second.axis == Axis::TriggerLeft ||
axis_it->second.axis == Axis::TriggerRight)
? 127
: axis_it->second.value;
connection =
BindingConnection(binding,
GetOutputPointer(ControllerOutput(OrbisPadButtonDataOffset::None,
axis_it->second.axis)),
value_to_set);
connection = BindingConnection(
binding,
&*std::ranges::find(output_array, ControllerOutput(OrbisPadButtonDataOffset::None,
axis_it->second.axis)),
value_to_set);
connections.insert(connections.end(), connection);
} else {
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
lineCount, line);
continue;
}
// LOG_INFO(Input, "Succesfully parsed line {}", lineCount);
LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount);
}
file.close();
connections.sort();
std::sort(connections.begin(), connections.end());
for (auto& c : connections) {
LOG_DEBUG(Input, "Binding: {}", c.binding.ToString());
}
LOG_DEBUG(Input, "Done parsing the input config!");
}
u32 GetMouseWheelEvent(const SDL_Event& event) {
if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) {
LOG_DEBUG(Input, "Something went wrong with wheel input parsing!");
LOG_WARNING(Input, "Something went wrong with wheel input parsing!");
return (u32)-1;
}
if (event.wheel.y > 0) {
@ -377,31 +340,28 @@ u32 GetMouseWheelEvent(const SDL_Event& event) {
return (u32)-1;
}
u32 InputBinding::GetInputIDFromEvent(const SDL_Event& e) {
InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
int value_mask;
switch (e.type) {
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
return e.key.key;
return InputEvent(InputType::KeyboardMouse, e.key.key, e.type == SDL_EVENT_KEY_DOWN, 0);
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
return (u32)e.button.button;
return InputEvent(InputType::KeyboardMouse, e.button.button,
e.type == SDL_EVENT_MOUSE_BUTTON_DOWN, 0);
case SDL_EVENT_MOUSE_WHEEL:
case SDL_EVENT_MOUSE_WHEEL_OFF:
return GetMouseWheelEvent(e);
return InputEvent(InputType::KeyboardMouse, GetMouseWheelEvent(e),
e.type == SDL_EVENT_MOUSE_WHEEL, 0);
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
return (u32)e.gbutton.button + 0x10000000; // I believe this range is unused
return InputEvent(InputType::Controller, e.gbutton.button,
e.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN, 0);
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
// todo: somehow put this value into the correct connection
// solution 1: add it to the keycode as a 0x0FF00000 (a bit hacky but works I guess?)
// I guess in software developement, there really is nothing more permanent than a temporary
// solution
value_mask = (u32)((e.gaxis.value / 256 + 128) << 20); // +-32000 to +-128 to 0-255
return (u32)e.gaxis.axis + 0x80000000 +
value_mask; // they are pushed to the end of the sorted array
return InputEvent(InputType::Controller, e.gaxis.axis, true, (s8)(e.gaxis.value / 256));
default:
return (u32)-1;
return InputEvent(InputType::Count, (u32)-1, false, 0);
}
}
@ -410,18 +370,18 @@ void ControllerOutput::SetControllerOutputController(GameController* c) {
ControllerOutput::controller = c;
}
void ToggleKeyInList(u32 key) {
if ((key & 0x80000000) != 0) {
void ToggleKeyInList(InputID input) {
if (input.type == InputType::Axis) {
LOG_ERROR(Input, "Toggling analog inputs is not supported!");
return;
}
auto it = std::find(toggled_keys.begin(), toggled_keys.end(), key);
auto it = std::find(toggled_keys.begin(), toggled_keys.end(), input);
if (it == toggled_keys.end()) {
toggled_keys.insert(toggled_keys.end(), key);
LOG_DEBUG(Input, "Added {} to toggled keys", key);
toggled_keys.insert(toggled_keys.end(), input);
LOG_DEBUG(Input, "Added {} to toggled keys", input.ToString());
} else {
toggled_keys.erase(it);
LOG_DEBUG(Input, "Removed {} from toggled keys", key);
LOG_DEBUG(Input, "Removed {} from toggled keys", input.ToString());
}
}
@ -430,14 +390,19 @@ void ControllerOutput::ResetUpdate() {
new_button_state = false;
new_param = 0;
}
void ControllerOutput::AddUpdate(bool pressed, bool analog, u32 param) {
void ControllerOutput::AddUpdate(InputEvent event) {
state_changed = true;
if (button != OrbisPadButtonDataOffset::None) {
if (analog) {
new_button_state |= abs((s32)param) > 0x40;
if ((u32)button == KEY_TOGGLE) {
if (event.active) {
LOG_DEBUG(Input, "Toggling a button...");
ToggleKeyInList(event.input); // todo: fix key toggle
}
} else if (button != OrbisPadButtonDataOffset::None) {
if (event.input.type == InputType::Axis) {
new_button_state |= abs((s32)event.axis_value) > 0x40;
} else {
new_button_state |= pressed;
new_param = param;
new_button_state |= event.active;
new_param = event.axis_value;
}
} else if (axis != Axis::AxisMax) {
@ -446,17 +411,18 @@ void ControllerOutput::AddUpdate(bool pressed, bool analog, u32 param) {
case Axis::TriggerRight:
// if it's a button input, then we know the value to set, so the param is 0.
// if it's an analog input, then the param isn't 0
// warning: doesn't work yet
new_param = SDL_clamp((pressed ? (s32)param : 0) + new_param, 0, 127);
new_param = (event.active ? (s32)event.axis_value : 0) + new_param;
break;
default:
// todo: do the same as above
new_param = SDL_clamp((pressed ? (s32)param : 0) + new_param, -127, 127);
new_param = (event.active ? (s32)event.axis_value : 0) + new_param;
break;
}
}
}
void ControllerOutput::FinalizeUpdate() {
if (!state_changed) {
// return;
}
old_button_state = new_button_state;
old_param = new_param;
float touchpad_x = 0;
@ -475,12 +441,10 @@ void ControllerOutput::FinalizeUpdate() {
case RIGHTJOYSTICK_HALFMODE:
rightjoystick_halfmode = new_button_state;
break;
case KEY_TOGGLE:
if (new_button_state) {
// LOG_DEBUG(Input, "Toggling a button...");
ToggleKeyInList(new_param);
}
break;
// KEY_TOGGLE isn't handled here anymore, as this function doesn't have the necessary data
// to do it, and it would be inconvenient to force it here, when AddUpdate does the job just
// fine, and a toggle doesn't have to checked against every input that's bound to it, it's
// enough that one is pressed
default: // is a normal key (hopefully)
controller->CheckButton(0, (OrbisPadButtonDataOffset)button, new_button_state);
break;
@ -514,44 +478,43 @@ void ControllerOutput::FinalizeUpdate() {
// Updates the list of pressed keys with the given input.
// Returns whether the list was updated or not.
bool UpdatePressedKeys(u32 value, bool is_pressed) {
bool UpdatePressedKeys(InputEvent event) {
// Skip invalid inputs
if (value == (u32)-1) {
InputID input = event.input;
if (input.sdl_id == (u32)-1) {
return false;
}
if ((value & 0x80000000) != 0) {
if (input.type == InputType::Axis) {
// analog input, it gets added when it first sends an event,
// and from there, it only changes the parameter
// reverse iterate until we get out of the 0x8000000 range, if found,
// update the parameter, if not, add it to the end
u32 value_to_search = value & 0xF00FFFFF;
for (auto& it = --pressed_keys.end(); (it->first & 0x80000000) != 0; it--) {
if ((it->first & 0xF00FFFFF) == value_to_search) {
it->first = value;
// LOG_DEBUG(Input, "New value for {:X}: {:x}", value, value);
return true;
}
const auto& it = std::find_if(
pressed_keys.begin(), pressed_keys.end(),
[input](const std::pair<InputEvent, bool>& e) { return e.first.input == input; });
if (it == pressed_keys.end()) {
pressed_keys.push_back({event, false});
} else {
it->first.axis_value = event.axis_value;
}
// LOG_DEBUG(Input, "Input activated for the first time, adding it to the list");
pressed_keys.insert(pressed_keys.end(), {value, false});
return true;
}
if (is_pressed) {
if (event.active) {
// 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; });
auto it = std::lower_bound(pressed_keys.begin(), pressed_keys.end(), input,
[](const std::pair<InputEvent, bool>& e, InputID i) {
return e.first.input.type <= i.type &&
e.first.input.sdl_id < i.sdl_id;
});
// Insert only if 'value' is not already in the list
if (it == pressed_keys.end() || it->first != value) {
pressed_keys.insert(it, {value, false});
if (it == pressed_keys.end() || it->first.input != input) {
pressed_keys.insert(it, {event, false});
return true;
}
} else {
// Remove 'value' from the list if it's not pressed
auto it =
std::find_if(pressed_keys.begin(), pressed_keys.end(),
[value](const std::pair<u32, bool>& pk) { return pk.first == value; });
auto it = std::find_if(
pressed_keys.begin(), pressed_keys.end(),
[input](const std::pair<InputEvent, bool>& e) { return e.first.input == input; });
if (it != pressed_keys.end()) {
pressed_keys.erase(it);
return true;
@ -559,17 +522,28 @@ bool UpdatePressedKeys(u32 value, bool is_pressed) {
}
return false;
}
// Check if a given binding's all keys are currently active.
// For now it also extracts the analog inputs' parameters.
void IsInputActive(BindingConnection& connection, bool& active, bool& analog) {
// Check if the binding's all keys are currently active.
// It also extracts the analog inputs' parameters, and updates the input hierarchy flags.
InputEvent BindingConnection::ProcessBinding() {
// the last key is always set (if the connection isn't empty),
// and the analog inputs are always the last one due to how they are sorted,
// so this signifies whether or not the input is analog
InputEvent event = InputEvent(binding.keys[3]);
if (pressed_keys.empty()) {
active = false;
return;
return event;
}
InputBinding i = connection.binding;
// Extract keys from InputBinding and ignore unused (0) or toggled keys
std::list<u32> input_keys = {i.key1, i.key2, i.key3};
input_keys.remove(0);
if (event.input.type != InputType::Axis) {
// for button inputs
event.axis_value = axis_param;
}
// it's a bit scuffed, but if the output is a toggle, then we put the key here
if ((u32)output->button == KEY_TOGGLE) {
event.input = toggle;
}
// Extract keys from InputBinding and ignore unused or toggled keys
std::list<InputID> input_keys = {binding.keys[0], binding.keys[1], binding.keys[2]};
input_keys.remove(InputID());
for (auto key = input_keys.begin(); key != input_keys.end();) {
if (std::find(toggled_keys.begin(), toggled_keys.end(), *key) != toggled_keys.end()) {
key = input_keys.erase(key); // Use the returned iterator
@ -579,8 +553,8 @@ void IsInputActive(BindingConnection& connection, bool& active, bool& analog) {
}
if (input_keys.empty()) {
LOG_DEBUG(Input, "No actual inputs to check, returning true");
active = true;
return;
event.active = true;
return event;
}
// Iterator for pressed_keys, starting from the beginning
@ -590,119 +564,53 @@ void IsInputActive(BindingConnection& connection, bool& active, bool& analog) {
std::list<bool*> flags_to_set;
// Check if all keys in input_keys are active
for (u32 key : input_keys) {
for (InputID key : input_keys) {
bool key_found = false;
// Search for the current key in pressed_keys starting from the last checked position
while (pressed_it != pressed_keys.end() && (pressed_it->first & 0x80000000) == 0) {
if (pressed_it->first == key) {
while (pressed_it != pressed_keys.end()) {
if (pressed_it->first.input == key && pressed_it->second == false) {
key_found = true;
flags_to_set.push_back(&pressed_it->second);
++pressed_it; // Move to the next key in pressed_keys
if (pressed_it->first.input.type == InputType::Axis) {
event.axis_value = pressed_it->first.axis_value;
}
++pressed_it;
break;
}
++pressed_it;
}
if (!key_found && (key & 0x80000000) != 0) {
// reverse iterate over the analog inputs, as they can't be sorted
auto& rev_it = --pressed_keys.end();
for (auto rev_it = --pressed_keys.end(); (rev_it->first & 0x80000000) != 0; rev_it--) {
if ((rev_it->first & 0xF00FFFFF) == (key & 0xF00FFFFF)) {
connection.parameter = (u32)((s32)((rev_it->first & 0x0FF00000) >> 20) - 128);
// LOG_DEBUG(Input, "Extracted the following param: {:X} from {:X}",
// (s32)connection.parameter, rev_it->first);
key_found = true;
analog = true;
flags_to_set.push_back(&rev_it->second);
break;
}
}
}
if (!key_found) {
return;
return event;
}
}
bool is_fully_blocked = true;
for (bool* flag : flags_to_set) {
is_fully_blocked &= *flag;
}
if (is_fully_blocked) {
return;
}
for (bool* flag : flags_to_set) {
*flag = true;
}
LOG_DEBUG(Input, "Input found: {}", i.ToString());
active = true;
return; // All keys are active
LOG_DEBUG(Input, "Input found: {}", binding.ToString());
event.active = true;
return event; // All keys are active
}
void ActivateOutputsFromInputs() {
// LOG_DEBUG(Input, "Start of an input frame...");
// Reset values and flags
for (auto& it : pressed_keys) {
it.second = false;
}
// this is the cleanest looking code I've ever written, too bad it is not working
// (i left the last part in by accident, then it turnd out to still be true even after I thought
// everything is good) (but now it really should be fine)
for (auto& it : output_array) {
it.ResetUpdate();
}
// Iterate over all inputs, and update their respecive outputs accordingly
for (auto& it : connections) {
bool active = false, analog_input_detected = false;
IsInputActive(it, active, analog_input_detected);
it.output->AddUpdate(active, analog_input_detected, it.parameter);
it.output->AddUpdate(it.ProcessBinding());
}
// Update all outputs
for (auto& it : output_array) {
it.FinalizeUpdate();
}
}
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
} // namespace Input

View File

@ -3,18 +3,19 @@
#pragma once
#include "array"
#include <array>
#include <map>
#include <string>
#include <unordered_set>
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_timer.h"
#include "common/logging/log.h"
#include "common/types.h"
#include "core/libraries/pad/pad.h"
#include "fmt/format.h"
#include "input/controller.h"
#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
@ -40,6 +41,42 @@ struct AxisMapping {
int value; // Value to set for key press (+127 or -127 for movement)
};
enum class InputType { KeyboardMouse, Controller, Axis, Count };
class InputID {
public:
InputType type;
u32 sdl_id;
InputID(InputType d = InputType::Count, u32 i = (u32)-1) : type(d), sdl_id(i) {}
bool operator==(const InputID& o) const {
return type == o.type && sdl_id == o.sdl_id;
}
bool operator!=(const InputID& o) const {
return type != o.type || sdl_id != o.sdl_id;
}
bool operator<=(const InputID& o) const {
return type <= o.type && sdl_id <= o.sdl_id;
}
bool IsValid() const {
return *this != InputID();
}
std::string ToString() {
return fmt::format("({}: {:x})", (u8)type, sdl_id);
}
};
class InputEvent {
public:
InputID input;
bool active;
s8 axis_value;
InputEvent(InputID i = InputID(), bool a = false, s8 v = 0)
: input(i), active(a), axis_value(v) {}
InputEvent(InputType d, u32 i, bool a = false, s8 v = 0)
: input(d, i), active(a), axis_value(v) {}
};
// i strongly suggest you collapse these maps
const std::map<std::string, OrbisPadButtonDataOffset> string_to_cbutton_map = {
{"triangle", OrbisPadButtonDataOffset::Triangle},
@ -187,86 +224,88 @@ const std::map<std::string, u32> string_to_keyboard_key_map = {
{"capslock", SDLK_CAPSLOCK},
};
// literally the only flag that needs external access
void ToggleMouseEnabled();
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) {
InputID keys[3];
InputBinding(InputID k1 = InputID(), InputID k2 = InputID(), InputID k3 = InputID()) {
// we format the keys so comparing them will be very fast, because we will only have to
// compare 3 sorted elements, where the only possible duplicate item is 0
// duplicate entries get changed to one original, one null
if (k1 == k2 && k1 != SDLK_UNKNOWN) {
k2 = 0;
if (k1 == k2 && k1 != InputID()) {
k2 = InputID();
}
if (k1 == k3 && k1 != SDLK_UNKNOWN) {
k3 = 0;
if (k1 == k3 && k1 != InputID()) {
k3 = InputID();
}
if (k3 == k2 && k2 != SDLK_UNKNOWN) {
k2 = 0;
if (k3 == k2 && k2 != InputID()) {
k2 = InputID();
}
// this sorts them
if (k1 <= k2 && k1 <= k3) {
key1 = k1;
keys[0] = k1;
if (k2 <= k3) {
key2 = k2;
key3 = k3;
keys[1] = k2;
keys[2] = k3;
} else {
key2 = k3;
key3 = k2;
keys[1] = k3;
keys[2] = k2;
}
} else if (k2 <= k1 && k2 <= k3) {
key1 = k2;
keys[0] = k2;
if (k1 <= k3) {
key2 = k1;
key3 = k3;
keys[1] = k1;
keys[2] = k3;
} else {
key2 = k3;
key3 = k1;
keys[1] = k3;
keys[2] = k1;
}
} else {
key1 = k3;
keys[0] = k3;
if (k1 <= k2) {
key2 = k1;
key3 = k2;
keys[1] = k1;
keys[2] = k2;
} else {
key2 = k2;
key3 = k1;
keys[1] = k2;
keys[3] = k1;
}
}
}
// copy ctor
InputBinding(const InputBinding& o) : key1(o.key1), key2(o.key2), key3(o.key3) {}
InputBinding(const InputBinding& o) {
keys[0] = o.keys[0];
keys[1] = o.keys[1];
keys[2] = o.keys[2];
}
inline bool operator==(const InputBinding& o) {
// 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);
// InputID() signifies an unused slot
return (keys[2] == o.keys[2] || keys[2] == InputID() || o.keys[2] == InputID()) &&
(keys[1] == o.keys[1] || keys[1] == InputID() || o.keys[1] == InputID()) &&
(keys[0] == o.keys[0] || keys[0] == InputID() || o.keys[0] == InputID());
// it is already very fast,
// but reverse order makes it check the actual keys first instead of possible 0-s,
// potenially skipping the later expressions of the three-way AND
}
inline int KeyCount() const {
return (key1 ? 1 : 0) + (key2 ? 1 : 0) + (key3 ? 1 : 0);
return (keys[0].IsValid() ? 1 : 0) + (keys[1].IsValid() ? 1 : 0) +
(keys[2].IsValid() ? 1 : 0);
}
// Sorts by the amount of non zero keys - left side is 'bigger' here
bool operator<(const InputBinding& other) const {
return KeyCount() > other.KeyCount();
}
inline bool IsEmpty() {
return key1 == 0 && key2 == 0 && key3 == 0;
return !(keys[0].IsValid() || keys[1].IsValid() || keys[2].IsValid());
}
std::string ToString() {
return fmt::format("({:X}, {:X}, {:X})", key1, key2, key3);
std::string ToString() { // todo add device type
return fmt::format("({:X}, {:X}, {:X})", keys[0].sdl_id, keys[1].sdl_id, keys[2].sdl_id);
}
// returns a u32 based on the event type (keyboard, mouse buttons, or wheel)
static u32 GetInputIDFromEvent(const SDL_Event& e);
// returns an InputEvent based on the event type (keyboard, mouse buttons/wheel, or controller)
static InputEvent GetInputEventFromSDLEvent(const SDL_Event& e);
};
class ControllerOutput {
static GameController* controller;
@ -302,19 +341,21 @@ public:
}
void ResetUpdate();
void AddUpdate(bool pressed, bool analog, u32 param = 0);
void AddUpdate(InputEvent event);
void FinalizeUpdate();
};
class BindingConnection {
public:
InputBinding binding;
ControllerOutput* output;
u32 parameter;
BindingConnection(InputBinding b, ControllerOutput* out, u32 param = 0) {
binding = b;
parameter = param;
u32 axis_param;
InputID toggle;
BindingConnection(InputBinding b, ControllerOutput* out, u32 param = 0, InputID t = InputID()) {
binding = b;
axis_param = param;
output = out;
toggle = t;
}
bool operator<(const BindingConnection& other) const {
// a button is a higher priority than an axis, as buttons can influence axes
@ -329,17 +370,13 @@ public:
}
return false;
}
InputEvent ProcessBinding();
};
// Updates the list of pressed keys with the given input.
// Returns whether the list was updated or not.
bool UpdatePressedKeys(u32 button, bool is_pressed);
bool UpdatePressedKeys(InputEvent event);
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
} // namespace Input

74
src/input/input_mouse.cpp Normal file
View File

@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cmath>
#include "common/types.h"
#include "input/controller.h"
#include "input_mouse.h"
#include "SDL3/SDL.h"
namespace Input {
int mouse_joystick_binding = 0;
float mouse_deadzone_offset = 0.5, mouse_speed = 1, mouse_speed_offset = 0.1250;
Uint32 mouse_polling_id = 0;
bool mouse_enabled = false;
// We had to go through 3 files of indirection just to update a flag
void ToggleMouseEnabled() {
mouse_enabled = !mouse_enabled;
}
void SetMouseToJoystick(int joystick) {
mouse_joystick_binding = joystick;
}
void SetMouseParams(float mdo, float ms, float mso) {
mouse_deadzone_offset = mdo;
mouse_speed = ms;
mouse_speed_offset = mso;
}
Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) {
auto* controller = (GameController*)param;
if (!mouse_enabled)
return interval;
Axis axis_x, axis_y;
switch (mouse_joystick_binding) {
case 1:
axis_x = Axis::LeftX;
axis_y = Axis::LeftY;
break;
case 2:
axis_x = Axis::RightX;
axis_y = Axis::RightY;
break;
default:
return interval; // no update needed
}
float d_x = 0, d_y = 0;
SDL_GetRelativeMouseState(&d_x, &d_y);
float output_speed =
SDL_clamp((sqrt(d_x * d_x + d_y * d_y) + mouse_speed_offset * 128) * mouse_speed,
mouse_deadzone_offset * 128, 128.0);
float angle = atan2(d_y, d_x);
float a_x = cos(angle) * output_speed, a_y = sin(angle) * output_speed;
if (d_x != 0 && d_y != 0) {
controller->Axis(0, axis_x, GetAxis(-0x80, 0x80, a_x));
controller->Axis(0, axis_y, GetAxis(-0x80, 0x80, a_y));
} else {
controller->Axis(0, axis_x, GetAxis(-0x80, 0x80, 0));
controller->Axis(0, axis_y, GetAxis(-0x80, 0x80, 0));
}
return interval;
}
} // namespace Input

18
src/input/input_mouse.h Normal file
View File

@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "SDL3/SDL.h"
#include "common/types.h"
namespace Input {
void ToggleMouseEnabled();
void SetMouseToJoystick(int joystick);
void SetMouseParams(float mouse_deadzone_offset, float mouse_speed, float mouse_speed_offset);
// Polls the mouse for changes, and simulates joystick movement from it.
Uint32 MousePolling(void* param, Uint32 id, Uint32 interval);
} // namespace Input

View File

@ -15,6 +15,7 @@
#include "imgui/renderer/imgui_core.h"
#include "input/controller.h"
#include "input/input_handler.h"
#include "input/input_mouse.h"
#include "sdl_window.h"
#include "video_core/renderdoc.h"
@ -218,13 +219,14 @@ 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);
const bool input_down = event->type == SDL_EVENT_KEY_DOWN ||
event->type == SDL_EVENT_MOUSE_BUTTON_DOWN ||
event->type == SDL_EVENT_MOUSE_WHEEL;
Input::InputEvent input_event = Input::InputBinding::GetInputEventFromSDLEvent(*event);
// Handle window controls outside of the input maps
if (event->type == SDL_EVENT_KEY_DOWN) {
u32 input_id = input_event.input.sdl_id;
// Reparse kbm inputs
if (input_id == SDLK_F8) {
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
@ -258,7 +260,7 @@ void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) {
}
// add/remove it from the list
bool inputs_changed = Input::UpdatePressedKeys(input_id, input_down);
bool inputs_changed = Input::UpdatePressedKeys(input_event);
// update bindings
if (inputs_changed) {
@ -270,7 +272,7 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) {
bool input_down = event->type == SDL_EVENT_GAMEPAD_AXIS_MOTION ||
event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN;
u32 input_id = Input::InputBinding::GetInputIDFromEvent(*event);
Input::InputEvent input_event = Input::InputBinding::GetInputEventFromSDLEvent(*event);
// the touchpad button shouldn't be rebound to anything else,
// as it would break the entire touchpad handling
@ -280,8 +282,10 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) {
return;
}
bool inputs_changed = Input::UpdatePressedKeys(input_id, input_down);
// add/remove it from the list
bool inputs_changed = Input::UpdatePressedKeys(input_event);
// update bindings
if (inputs_changed) {
Input::ActivateOutputsFromInputs();
}