// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "input_handler.h" #include #include #include #include #include #include #include #include #include #include #include #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/singleton.h" #include "input/controller.h" #include "input/input_mouse.h" namespace Input { /* Project structure: n to m connection between inputs and outputs Keyup and keydown events update a dynamic list* of u32 'flags' (what is currently in the list is 'pressed') On every event, after flag updates, we check for every input binding -> controller output pair if all their flags are 'on' If not, disable; if so, enable them. For axes, we gather their data into a struct cumulatively from all inputs, then after we checked all of those, we update them all at once. Wheel inputs generate a timer that doesn't turn off their outputs automatically, but push a userevent to do so. What structs are needed? InputBinding(key1, key2, key3) ControllerOutput(button, axis) - we only need a const array of these, and one of the attr-s is always 0 BindingConnection(inputBinding (member), controllerOutput (ref to the array element)) Things to always test before pushing like a dumbass: Button outputs Axis outputs Input hierarchy Multi key inputs Mouse to joystick Key toggle Joystick halfmode Don't be an idiot and test only the changed part expecting everything else to not be broken */ bool leftjoystick_halfmode = false, rightjoystick_halfmode = false; std::pair leftjoystick_deadzone, rightjoystick_deadzone, lefttrigger_deadzone, righttrigger_deadzone; std::list> pressed_keys; std::list toggled_keys; static std::vector connections; std::array output_arrays = { ControllerAllOutputs(0), ControllerAllOutputs(1), ControllerAllOutputs(2), ControllerAllOutputs(3), }; void ControllerOutput::LinkJoystickAxes() { // for (int i = 17; i < 23; i += 2) { // delete output_array[i].new_param; // output_array[i].new_param = output_array[i + 1].new_param; // } } static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) { using OPBDO = OrbisPadButtonDataOffset; switch (button) { case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return OPBDO::Down; case SDL_GAMEPAD_BUTTON_DPAD_UP: return OPBDO::Up; case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return OPBDO::Left; case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return OPBDO::Right; case SDL_GAMEPAD_BUTTON_SOUTH: return OPBDO::Cross; case SDL_GAMEPAD_BUTTON_NORTH: return OPBDO::Triangle; case SDL_GAMEPAD_BUTTON_WEST: return OPBDO::Square; case SDL_GAMEPAD_BUTTON_EAST: return OPBDO::Circle; case SDL_GAMEPAD_BUTTON_START: return OPBDO::Options; case SDL_GAMEPAD_BUTTON_TOUCHPAD: return OPBDO::TouchPad; case SDL_GAMEPAD_BUTTON_BACK: return OPBDO::TouchPad; case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: return OPBDO::L1; case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: return OPBDO::R1; case SDL_GAMEPAD_BUTTON_LEFT_STICK: return OPBDO::L3; case SDL_GAMEPAD_BUTTON_RIGHT_STICK: return OPBDO::R3; default: return OPBDO::None; } } Axis GetAxisFromSDLAxis(u8 sdl_axis) { switch (sdl_axis) { case SDL_GAMEPAD_AXIS_LEFTX: return Axis::LeftX; case SDL_GAMEPAD_AXIS_LEFTY: return Axis::LeftY; case SDL_GAMEPAD_AXIS_RIGHTX: return Axis::RightX; case SDL_GAMEPAD_AXIS_RIGHTY: return Axis::RightY; case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: return Axis::TriggerLeft; case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: return Axis::TriggerRight; default: return Axis::AxisMax; } } // syntax: 'name, name,name' or 'name,name' or 'name' InputBinding GetBindingFromString(std::string& line) { std::array keys = {InputID(), InputID(), InputID()}; // Check and process tokens for (const auto token : std::views::split(line, ',')) { // Split by comma const std::string t(token.begin(), token.end()); InputID input; if (string_to_keyboard_key_map.find(t) != string_to_keyboard_key_map.end()) { input = InputID(InputType::KeyboardMouse, string_to_keyboard_key_map.at(t)); } else if (string_to_axis_map.find(t) != string_to_axis_map.end()) { input = InputID(InputType::Axis, (u32)string_to_axis_map.at(t).axis); } else if (string_to_cbutton_map.find(t) != string_to_cbutton_map.end()) { input = InputID(InputType::Controller, string_to_cbutton_map.at(t)); } else { // Invalid token found; return default binding LOG_DEBUG(Input, "Invalid token found: {}", t); return InputBinding(); } // Assign to the first available slot for (auto& key : keys) { if (!key.IsValid()) { key = input; break; } } } LOG_DEBUG(Input, "Parsed line: {}", InputBinding(keys[0], keys[1], keys[2]).ToString()); return InputBinding(keys[0], keys[1], keys[2]); } std::optional parseInt(const std::string& s) { try { return std::stoi(s); } catch (...) { return std::nullopt; } }; void ParseInputConfig(const std::string game_id = "") { std::string config_type = Config::GetUseUnifiedInputConfig() ? "default" : game_id; const auto config_file = Config::GetFoolproofKbmConfigFile(config_type); // we reset these here so in case the user fucks up or doesn't include some of these, // we can fall back to default connections.clear(); float mouse_deadzone_offset = 0.5; float mouse_speed = 1; float mouse_speed_offset = 0.125; leftjoystick_deadzone = {1, 127}; rightjoystick_deadzone = {1, 127}; lefttrigger_deadzone = {1, 127}; righttrigger_deadzone = {1, 127}; Config::SetOverrideControllerColor(false); Config::SetControllerCustomColor(0, 0, 255); int lineCount = 0; std::ifstream file(config_file); std::string line = ""; while (std::getline(file, line)) { lineCount++; // Strip the ; and whitespace line.erase(std::remove_if(line.begin(), line.end(), [](unsigned char c) { return std::isspace(c); }), line.end()); if (line.empty()) { continue; } // Truncate lines starting at # std::size_t comment_pos = line.find('#'); if (comment_pos != std::string::npos) { line = line.substr(0, comment_pos); } // Remove trailing semicolon if (!line.empty() && line[line.length() - 1] == ';') { line = line.substr(0, line.length() - 1); } if (line.empty()) { continue; } // Split the line by '=' std::size_t equal_pos = line.find('='); if (equal_pos == std::string::npos) { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); continue; } std::string output_string = line.substr(0, equal_pos); std::string input_string = line.substr(equal_pos + 1); s8 input_gamepad_id = -1, output_gamepad_id = -1; // -1 means it's not specified // todo: here the inputs and outputs are formatted and split, we need to extract the // controller ID now // input gamepad id is only for controllers, it's discarded otherwise std::size_t input_colon_pos = input_string.find(':'); if (input_colon_pos != std::string::npos) { auto temp = parseInt(input_string.substr(input_colon_pos + 1)); if (!temp) { LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line); } else { input_gamepad_id = *temp; } input_string = input_string.substr(0, input_colon_pos); } // if not provided, assume it's for all gamepads, if the input is a controller and that also // doesn't have an ID, and for the first otherwise std::size_t output_colon_pos = output_string.find(':'); if (output_colon_pos != std::string::npos) { auto temp = parseInt(output_string.substr(output_colon_pos + 1)); if (!temp) { LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line); } else { output_gamepad_id = *temp; } output_string = output_string.substr(0, output_colon_pos); } std::size_t comma_pos = input_string.find(','); // todo: make remapping work for special bindings for gamepads that are not the first if (output_string == "mouse_to_joystick") { if (input_string == "left") { SetMouseToJoystick(1); } else if (input_string == "right") { SetMouseToJoystick(2); } else { LOG_WARNING(Input, "Invalid argument for mouse-to-joystick binding"); SetMouseToJoystick(0); } continue; } else if (output_string == "key_toggle") { if (comma_pos != std::string::npos) { // handle key-to-key toggling (separate list?) InputBinding toggle_keys = GetBindingFromString(input_string); if (toggle_keys.KeyCount() != 2) { LOG_WARNING(Input, "Syntax error: Please provide exactly 2 keys: " "first is the toggler, the second is the key to toggle: {}", line); continue; } ControllerOutput* toggle_out = &*std::ranges::find(output_arrays[0].data, ControllerOutput(KEY_TOGGLE)); BindingConnection toggle_connection = BindingConnection( InputBinding(toggle_keys.keys[0]), toggle_out, 0, toggle_keys.keys[1]); connections.insert(connections.end(), toggle_connection); continue; } LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); continue; } else if (output_string == "mouse_movement_params") { std::stringstream ss(input_string); char comma; // To hold the comma separators between the floats ss >> mouse_deadzone_offset >> comma >> mouse_speed >> comma >> mouse_speed_offset; // Check for invalid input (in case there's an unexpected format) if (ss.fail()) { LOG_WARNING(Input, "Failed to parse mouse movement parameters from line: {}", line); continue; } SetMouseParams(mouse_deadzone_offset, mouse_speed, mouse_speed_offset); continue; } else if (output_string == "analog_deadzone") { std::stringstream ss(input_string); std::string device, inner_deadzone_str, outer_deadzone_str; if (!std::getline(ss, device, ',') || !std::getline(ss, inner_deadzone_str, ',') || !std::getline(ss, outer_deadzone_str)) { LOG_WARNING(Input, "Malformed deadzone config at line {}: \"{}\"", lineCount, line); continue; } auto inner_deadzone = parseInt(inner_deadzone_str); auto outer_deadzone = parseInt(outer_deadzone_str); if (!inner_deadzone || !outer_deadzone) { LOG_WARNING(Input, "Invalid deadzone values at line {}: \"{}\"", lineCount, line); continue; } std::pair deadzone = {*inner_deadzone, *outer_deadzone}; static std::unordered_map&> deadzone_map = { {"leftjoystick", leftjoystick_deadzone}, {"rightjoystick", rightjoystick_deadzone}, {"l2", lefttrigger_deadzone}, {"r2", righttrigger_deadzone}, }; if (auto it = deadzone_map.find(device); it != deadzone_map.end()) { it->second = deadzone; LOG_DEBUG(Input, "Parsed deadzone: {} {} {}", device, inner_deadzone_str, outer_deadzone_str); } else { LOG_WARNING(Input, "Invalid axis name at line {}: \"{}\", skipping line.", lineCount, line); } continue; } else if (output_string == "override_controller_color") { std::stringstream ss(input_string); std::string enable, r_s, g_s, b_s; std::optional r, g, b; if (!std::getline(ss, enable, ',') || !std::getline(ss, r_s, ',') || !std::getline(ss, g_s, ',') || !std::getline(ss, b_s)) { LOG_WARNING(Input, "Malformed controller color config at line {}: \"{}\"", lineCount, line); continue; } r = parseInt(r_s); g = parseInt(g_s); b = parseInt(b_s); if (!r || !g || !b) { LOG_WARNING(Input, "Invalid RGB values at line {}: \"{}\", skipping line.", lineCount, line); continue; } Config::SetOverrideControllerColor(enable == "true"); Config::SetControllerCustomColor(*r, *g, *b); LOG_DEBUG(Input, "Parsed color settings: {} {} {} {}", enable == "true" ? "override" : "no override", *r, *b, *g); continue; } // normal cases InputBinding binding = GetBindingFromString(input_string); BindingConnection connection(InputID(), nullptr); auto button_it = string_to_cbutton_map.find(output_string); auto axis_it = string_to_axis_map.find(output_string); if (binding.IsEmpty()) { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); continue; } if (button_it != string_to_cbutton_map.end()) { // todo add new shit here connection = BindingConnection( binding, &*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data, ControllerOutput(button_it->second))); } else if (axis_it != string_to_axis_map.end()) { // todo add new shit here int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value; connection = BindingConnection( binding, &*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data, ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, axis_it->second.axis, axis_it->second.value >= 0)), value_to_set); } else { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); continue; } // todo make the following: if the input binding contains a controller input, and gamepad ID // isn't specified for either inputs or output (both are -1), then multiply the binding and // add it to all 4 controllers if (connection.HasGamepadInput() && input_gamepad_id == -1 && output_gamepad_id == -1) { for (int i = 0; i < 4; i++) { BindingConnection copy = connection.CopyWithChangedGamepadId(i + 1); copy.output = &*std::ranges::find(output_arrays[i].data, *connection.output); connections.push_back(copy); } } else { connections.push_back(connection); } LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount); } file.close(); std::sort(connections.begin(), connections.end()); for (auto& c : connections) { // todo add new shit here LOG_DEBUG(Input, "Binding: {} : {}", c.output->ToString(), c.binding.ToString()); } LOG_DEBUG(Input, "Done parsing the input config!"); } BindingConnection BindingConnection::CopyWithChangedGamepadId(u8 gamepad) { BindingConnection copy = *this; for (auto& key : copy.binding.keys) { if (key.type == InputType::Controller || key.type == InputType::Axis) { key.gamepad_id = gamepad; } } return copy; } u32 GetMouseWheelEvent(const SDL_Event& event) { if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) { LOG_WARNING(Input, "Something went wrong with wheel input parsing!"); return (u32)-1; } 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; } InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) { // todo add new shit here u8 gamepad = 1; switch (e.type) { case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_UP: return InputEvent(InputType::KeyboardMouse, e.key.key, e.key.down, 0); case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: return InputEvent(InputType::KeyboardMouse, (u32)e.button.button, e.button.down, 0); case SDL_EVENT_MOUSE_WHEEL: case SDL_EVENT_MOUSE_WHEEL_OFF: return InputEvent(InputType::KeyboardMouse, GetMouseWheelEvent(e), e.type == SDL_EVENT_MOUSE_WHEEL, 0); case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_UP: gamepad = GetGamepadIndexFromJoystickId(e.gbutton.which); return InputEvent({InputType::Controller, (u32)e.gbutton.button, gamepad}, e.gbutton.down, 0); case SDL_EVENT_GAMEPAD_AXIS_MOTION: gamepad = GetGamepadIndexFromJoystickId(e.gaxis.which); return InputEvent({InputType::Axis, (u32)e.gaxis.axis, gamepad}, true, e.gaxis.value / 256); default: return InputEvent(); } } GameControllers ControllerOutput::controllers = *Common::Singleton::Instance(); void ToggleKeyInList(InputID input) { if (input.type == InputType::Axis) { LOG_ERROR(Input, "Toggling analog inputs is not supported!"); return; } auto it = std::find(toggled_keys.begin(), toggled_keys.end(), input); if (it == toggled_keys.end()) { toggled_keys.insert(toggled_keys.end(), input); LOG_DEBUG(Input, "Added {} to toggled keys", input.ToString()); } else { toggled_keys.erase(it); LOG_DEBUG(Input, "Removed {} from toggled keys", input.ToString()); } } void ControllerOutput::ResetUpdate() { state_changed = false; new_button_state = false; *new_param = 0; // bruh } void ControllerOutput::AddUpdate(InputEvent event) { if (button == KEY_TOGGLE) { if (event.active) { ToggleKeyInList(event.input); } } else if (button != SDL_GAMEPAD_BUTTON_INVALID) { if (event.input.type == InputType::Axis) { bool temp = event.axis_value * (positive_axis ? 1 : -1) > 0x40; new_button_state |= event.active && event.axis_value * (positive_axis ? 1 : -1) > 0x40; if (temp) { LOG_DEBUG(Input, "Toggled a button from an axis"); } } else { new_button_state |= event.active; } } else if (axis != SDL_GAMEPAD_AXIS_INVALID) { *new_param = (event.active ? event.axis_value : 0) + *new_param; } } void ControllerOutput::FinalizeUpdate(u8 gamepad_index) { state_changed = old_button_state != new_button_state || old_param != *new_param; if (!state_changed) { return; } old_button_state = new_button_state; old_param = *new_param; float touchpad_x = 0; if (button != SDL_GAMEPAD_BUTTON_INVALID) { switch (button) { case SDL_GAMEPAD_BUTTON_TOUCHPAD: touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f : Config::getBackButtonBehavior() == "right" ? 0.75f : 0.5f; controllers[0]->SetTouchpadState(0, new_button_state, touchpad_x, 0.5f); controllers[0]->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); break; case LEFTJOYSTICK_HALFMODE: leftjoystick_halfmode = new_button_state; break; case RIGHTJOYSTICK_HALFMODE: rightjoystick_halfmode = new_button_state; break; // KEY_TOGGLE isn't handled here anymore, as this function doesn't have the necessary data // to do it, and it would be inconvenient to force it here, when AddUpdate does the job just // fine, and a toggle doesn't have to checked against every input that's bound to it, it's // enough that one is pressed default: // is a normal key (hopefully) controllers[gamepad_index]->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); break; } } else if (axis != SDL_GAMEPAD_AXIS_INVALID && positive_axis) { // avoid double-updating axes, but don't skip directional button bindings auto ApplyDeadzone = [](s16* value, std::pair deadzone) { if (std::abs(*value) <= deadzone.first || deadzone.first == deadzone.second) { *value = 0; } else { *value = (*value >= 0 ? 1 : -1) * std::clamp((int)((128.0 * (std::abs(*value) - deadzone.first)) / (float)(deadzone.second - deadzone.first)), 0, 128); } }; float multiplier = 1.0; Axis c_axis = GetAxisFromSDLAxis(axis); switch (c_axis) { case Axis::LeftX: case Axis::LeftY: ApplyDeadzone(new_param, leftjoystick_deadzone); multiplier = leftjoystick_halfmode ? 0.5 : 1.0; break; case Axis::RightX: case Axis::RightY: ApplyDeadzone(new_param, rightjoystick_deadzone); multiplier = rightjoystick_halfmode ? 0.5 : 1.0; break; case Axis::TriggerLeft: ApplyDeadzone(new_param, lefttrigger_deadzone); controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20); return; case Axis::TriggerRight: ApplyDeadzone(new_param, righttrigger_deadzone); controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20); return; default: break; } controllers[gamepad_index]->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier)); } } // Updates the list of pressed keys with the given input. // Returns whether the list was updated or not. bool UpdatePressedKeys(InputEvent event) { // Skip invalid inputs InputID input = event.input; if (input.sdl_id == (u32)-1) { return false; } if (input.type == InputType::Axis) { // analog input, it gets added when it first sends an event, // and from there, it only changes the parameter auto it = std::lower_bound(pressed_keys.begin(), pressed_keys.end(), input, [](const std::pair& e, InputID i) { return std::tie(e.first.input.type, e.first.input.sdl_id) < std::tie(i.type, i.sdl_id); }); if (it == pressed_keys.end() || it->first.input != input) { pressed_keys.insert(it, {event, false}); LOG_DEBUG(Input, "Added axis {} to the input list", event.input.sdl_id); } else { // noise filter if (std::abs(it->first.axis_value - event.axis_value) <= 1) { return false; } it->first.axis_value = event.axis_value; } return true; } else if (event.active) { // Find the correct position for insertion to maintain order auto it = std::lower_bound(pressed_keys.begin(), pressed_keys.end(), input, [](const std::pair& e, InputID i) { return std::tie(e.first.input.type, e.first.input.sdl_id) < std::tie(i.type, i.sdl_id); }); // Insert only if 'value' is not already in the list if (it == pressed_keys.end() || it->first.input != input) { pressed_keys.insert(it, {event, false}); return true; } } else { // Remove 'value' from the list if it's not pressed auto it = std::find_if( pressed_keys.begin(), pressed_keys.end(), [input](const std::pair& e) { return e.first.input == input; }); if (it != pressed_keys.end()) { pressed_keys.erase(it); return true; } } LOG_DEBUG(Input, "No change was made!"); return false; } // Check if the binding's all keys are currently active. // It also extracts the analog inputs' parameters, and updates the input hierarchy flags. InputEvent BindingConnection::ProcessBinding() { // the last key is always set (if the connection isn't empty), // and the analog inputs are always the last one due to how they are sorted, // so this signifies whether or not the input is analog InputEvent event = InputEvent(binding.keys[0]); if (pressed_keys.empty()) { return event; } if (event.input.type != InputType::Axis) { // for button inputs event.axis_value = axis_param; } // it's a bit scuffed, but if the output is a toggle, then we put the key here if (output->button == KEY_TOGGLE) { event.input = toggle; } // Extract keys from InputBinding and ignore unused or toggled keys std::list input_keys = {binding.keys[0], binding.keys[1], binding.keys[2]}; input_keys.remove(InputID()); for (auto key = input_keys.begin(); key != input_keys.end();) { if (std::find(toggled_keys.begin(), toggled_keys.end(), *key) != toggled_keys.end()) { key = input_keys.erase(key); // Use the returned iterator } else { ++key; // Increment only if no erase happened } } if (input_keys.empty()) { LOG_DEBUG(Input, "No actual inputs to check, returning true"); event.active = true; return event; } // Iterator for pressed_keys, starting from the beginning auto pressed_it = pressed_keys.begin(); // Store pointers to flags in pressed_keys that need to be set if all keys are active std::list flags_to_set; // Check if all keys in input_keys are active for (InputID key : input_keys) { bool key_found = false; while (pressed_it != pressed_keys.end()) { if (pressed_it->first.input == key && (pressed_it->second == false)) { key_found = true; if (output->positive_axis) { flags_to_set.push_back(&pressed_it->second); } if (pressed_it->first.input.type == InputType::Axis) { event.axis_value = pressed_it->first.axis_value; } ++pressed_it; break; } ++pressed_it; } if (!key_found) { return event; } } for (bool* flag : flags_to_set) { *flag = true; } if (binding.keys[0].type != InputType::Axis) { // the axes spam inputs, making this unreadable LOG_DEBUG(Input, "Input found: {}", binding.ToString()); } event.active = true; return event; // All keys are active } void ActivateOutputsFromInputs() { // todo find a better solution for (int i = 0; i < 4; i++) { // Reset values and flags for (auto& it : pressed_keys) { it.second = false; } for (auto& it : output_arrays[i].data) { it.ResetUpdate(); } // Iterate over all inputs, and update their respecive outputs accordingly for (auto& it : connections) { // only update this when it's the correct pass if (it.output->gamepad_id == i) { it.output->AddUpdate(it.ProcessBinding()); } } // Update all outputs for (auto& it : output_arrays[i].data) { it.FinalizeUpdate(i); } } } u8 GetGamepadIndexFromJoystickId(SDL_JoystickID id) { return SDL_GetGamepadPlayerIndex(SDL_GetGamepadFromID(id)) + 1; } } // namespace Input