diff --git a/src/common/config.cpp b/src/common/config.cpp index 1fb0382fd..43a1cfa83 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -1077,7 +1077,22 @@ void setDefaultValues() { m_language = 1; } -constexpr std::string_view GetDefaultKeyboardConfig() { +constexpr std::string_view GetDefaultGlobalConfig() { + return R"(# Anything put here will be loaded for all games, +# alongside the game's config or default.ini depending on your preference. + +hotkey_renderdoc_capture = f12 +hotkey_fullscreen = f11 +hotkey_show_fps = f10 +hotkey_pause = f9 +hotkey_reload_inputs = f8 +hotkey_toggle_mouse_to_joystick = f7 +hotkey_toggle_mouse_to_gyro = f6 +hotkey_quit = lctrl, lshift, end +)"; +} + +constexpr std::string_view GetDefaultInputConfig() { return R"(#Feeling lost? Check out the Help section! # Keyboard bindings @@ -1151,7 +1166,7 @@ analog_deadzone = rightjoystick, 2, 127 override_controller_color = false, 0, 0, 255 )"; } -std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id) { +std::filesystem::path GetFoolproofInputConfigFile(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. @@ -1168,7 +1183,7 @@ std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id) { // 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(); + const auto default_config = GetDefaultInputConfig(); std::ofstream default_config_stream(default_config_file); if (default_config_stream) { default_config_stream << default_config; @@ -1180,6 +1195,17 @@ std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id) { return default_config_file; } + // Create global config if it doesn't exist yet + if (game_id == "global" && !std::filesystem::exists(config_file)) { + if (!std::filesystem::exists(config_file)) { + const auto global_config = GetDefaultGlobalConfig(); + std::ofstream global_config_stream(config_file); + if (global_config_stream) { + global_config_stream << global_config; + } + } + } + // 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); diff --git a/src/common/config.h b/src/common/config.h index 61a2583e9..4dfeb5a50 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -156,7 +156,7 @@ std::filesystem::path getAddonInstallDir(); void setDefaultValues(); -// todo: name and function location pending -std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id = ""); +constexpr std::string_view GetDefaultGlobalConfig(); +std::filesystem::path GetFoolproofInputConfigFile(const std::string& game_id = ""); }; // namespace Config diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index d811974d8..5b323da7c 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -373,8 +373,6 @@ void L::Draw() { if (IsKeyPressed(ImGuiKey_F10, false)) { if (io.KeyCtrl) { DebugState.IsShowingDebugMenuBar() ^= true; - } else { - show_simple_fps = !show_simple_fps; } visibility_toggled = true; } @@ -384,22 +382,13 @@ void L::Draw() { if (!DebugState.ShouldPauseInSubmit()) { DebugState.RequestFrameDump(dump_frame_count); } - } else { - if (DebugState.IsGuestThreadsPaused()) { - DebugState.ResumeGuestThreads(); - SDL_Log("Game resumed from Keyboard"); - } else { - DebugState.PauseGuestThreads(); - SDL_Log("Game paused from Keyboard"); - } - visibility_toggled = true; } } if (DebugState.IsGuestThreadsPaused()) { ImVec2 pos = ImVec2(10, 10); ImU32 color = IM_COL32(255, 255, 255, 255); - ImGui::GetForegroundDrawList()->AddText(pos, color, "Game Paused Press F9 to Resume"); + ImGui::GetForegroundDrawList()->AddText(pos, color, "Emulation Paused"); } if (show_simple_fps) { diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp index 634e720a4..fbca90efb 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -9,6 +9,7 @@ #include "core/memory.h" #include "imgui_impl_sdl3.h" #include "input/controller.h" +#include "input/input_handler.h" #include "sdl_window.h" // SDL diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index 1c8fa5664..d38b45ddd 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -60,10 +60,6 @@ std::pair leftjoystick_deadzone, rightjoystick_deadzone, lefttrigger_d std::list> pressed_keys; std::list toggled_keys; static std::vector connections; -static std::vector fullscreenHotkeyInputsPad(3, ""); -static std::vector pauseHotkeyInputsPad(3, ""); -static std::vector simpleFpsHotkeyInputsPad(3, ""); -static std::vector quitHotkeyInputsPad(3, ""); auto output_array = std::array{ // Important: these have to be the first, or else they will update in the wrong order @@ -103,6 +99,15 @@ auto output_array = std::array{ ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER), ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER), + ControllerOutput(HOTKEY_FULLSCREEN), + ControllerOutput(HOTKEY_PAUSE), + ControllerOutput(HOTKEY_SIMPLE_FPS), + ControllerOutput(HOTKEY_QUIT), + ControllerOutput(HOTKEY_RELOAD_INPUTS), + ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK), + ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO), + ControllerOutput(HOTKEY_RENDERDOC), + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID), }; @@ -211,8 +216,9 @@ InputBinding GetBindingFromString(std::string& line) { } void ParseInputConfig(const std::string game_id = "") { - std::string config_type = Config::GetUseUnifiedInputConfig() ? "default" : game_id; - const auto config_file = Config::GetFoolproofKbmConfigFile(config_type); + std::string game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id; + const auto config_file = Config::GetFoolproofInputConfigFile(game_id_or_default); + const auto global_config_file = Config::GetFoolproofInputConfigFile("global"); // we reset these here so in case the user fucks up or doesn't include some of these, // we can fall back to default @@ -231,9 +237,10 @@ void ParseInputConfig(const std::string game_id = "") { int lineCount = 0; - std::ifstream file(config_file); + std::ifstream config_stream(config_file); + std::ifstream global_config_stream(global_config_file); std::string line = ""; - while (std::getline(file, line)) { + auto ProcessLine = [&]() -> void { lineCount++; // Strip the ; and whitespace @@ -241,7 +248,7 @@ void ParseInputConfig(const std::string game_id = "") { [](unsigned char c) { return std::isspace(c); }), line.end()); if (line.empty()) { - continue; + return; } // Truncate lines starting at # @@ -250,7 +257,7 @@ void ParseInputConfig(const std::string game_id = "") { line = line.substr(0, comment_pos); } if (line.empty()) { - continue; + return; } // Split the line by '=' @@ -258,7 +265,7 @@ void ParseInputConfig(const std::string game_id = "") { if (equal_pos == std::string::npos) { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); - continue; + return; } std::string output_string = line.substr(0, equal_pos); @@ -287,7 +294,7 @@ void ParseInputConfig(const std::string game_id = "") { LOG_WARNING(Input, "Invalid argument for mouse-to-joystick binding"); SetMouseToJoystick(0); } - continue; + return; } else if (output_string == "key_toggle") { if (comma_pos != std::string::npos) { // handle key-to-key toggling (separate list?) @@ -297,18 +304,18 @@ void ParseInputConfig(const std::string game_id = "") { "Syntax error: Please provide exactly 2 keys: " "first is the toggler, the second is the key to toggle: {}", line); - continue; + return; } ControllerOutput* toggle_out = &*std::ranges::find(output_array, 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; + return; } LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); - continue; + return; } else if (output_string == "mouse_movement_params") { std::stringstream ss(input_string); char comma; // To hold the comma separators between the floats @@ -317,10 +324,10 @@ void ParseInputConfig(const std::string game_id = "") { // 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; + return; } SetMouseParams(mouse_deadzone_offset, mouse_speed, mouse_speed_offset); - continue; + return; } else if (output_string == "analog_deadzone") { std::stringstream ss(input_string); std::string device, inner_deadzone_str, outer_deadzone_str; @@ -328,7 +335,7 @@ void ParseInputConfig(const std::string game_id = "") { 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; + return; } auto inner_deadzone = parseInt(inner_deadzone_str); @@ -336,7 +343,7 @@ void ParseInputConfig(const std::string game_id = "") { if (!inner_deadzone || !outer_deadzone) { LOG_WARNING(Input, "Invalid deadzone values at line {}: \"{}\"", lineCount, line); - continue; + return; } std::pair deadzone = {*inner_deadzone, *outer_deadzone}; @@ -356,7 +363,7 @@ void ParseInputConfig(const std::string game_id = "") { LOG_WARNING(Input, "Invalid axis name at line {}: \"{}\", skipping line.", lineCount, line); } - continue; + return; } else if (output_string == "override_controller_color") { std::stringstream ss(input_string); std::string enable, r_s, g_s, b_s; @@ -365,7 +372,7 @@ void ParseInputConfig(const std::string game_id = "") { !std::getline(ss, g_s, ',') || !std::getline(ss, b_s)) { LOG_WARNING(Input, "Malformed controller color config at line {}: \"{}\"", lineCount, line); - continue; + return; } r = parseInt(r_s); g = parseInt(g_s); @@ -373,13 +380,13 @@ void ParseInputConfig(const std::string game_id = "") { if (!r || !g || !b) { LOG_WARNING(Input, "Invalid RGB values at line {}: \"{}\", skipping line.", lineCount, line); - continue; + return; } Config::SetOverrideControllerColor(enable == "true"); Config::SetControllerCustomColor(*r, *g, *b); LOG_DEBUG(Input, "Parsed color settings: {} {} {} {}", enable == "true" ? "override" : "no override", *r, *b, *g); - continue; + return; } // normal cases @@ -390,7 +397,7 @@ void ParseInputConfig(const std::string game_id = "") { if (binding.IsEmpty()) { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); - continue; + return; } if (button_it != string_to_cbutton_map.end()) { connection = BindingConnection( @@ -409,11 +416,17 @@ void ParseInputConfig(const std::string game_id = "") { } else { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); - continue; + return; } LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount); + }; + while (std::getline(global_config_stream, line)) { + ProcessLine(); } - file.close(); + while (std::getline(config_stream, line)) { + ProcessLine(); + } + config_stream.close(); std::sort(connections.begin(), connections.end()); for (auto& c : connections) { LOG_DEBUG(Input, "Binding: {} : {}", c.output->ToString(), c.binding.ToString()); @@ -489,11 +502,16 @@ void ControllerOutput::ResetUpdate() { *new_param = 0; // bruh } void ControllerOutput::AddUpdate(InputEvent event) { - if (button == KEY_TOGGLE) { + switch (button) { + case KEY_TOGGLE: if (event.active) { ToggleKeyInList(event.input); } - } else if (button != SDL_GAMEPAD_BUTTON_INVALID) { + return; + default: + break; + } + 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; @@ -509,6 +527,14 @@ void ControllerOutput::AddUpdate(InputEvent event) { } } void ControllerOutput::FinalizeUpdate() { + auto PushSDLEvent = [&](u32 event_type) { + if (new_button_state) { + SDL_Event e; + SDL_memset(&e, 0, sizeof(e)); + e.type = event_type; + SDL_PushEvent(&e); + } + }; state_changed = old_button_state != new_button_state || old_param != *new_param; if (!state_changed) { return; @@ -535,10 +561,33 @@ void ControllerOutput::FinalizeUpdate() { 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 + case HOTKEY_FULLSCREEN: + PushSDLEvent(SDL_EVENT_TOGGLE_FULLSCREEN); + break; + case HOTKEY_PAUSE: + PushSDLEvent(SDL_EVENT_TOGGLE_PAUSE); + break; + case HOTKEY_SIMPLE_FPS: + PushSDLEvent(SDL_EVENT_TOGGLE_SIMPLE_FPS); + break; + case HOTKEY_RELOAD_INPUTS: + PushSDLEvent(SDL_EVENT_RELOAD_INPUTS); + break; + case HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK: + PushSDLEvent(SDL_EVENT_MOUSE_TO_JOYSTICK); + break; + case HOTKEY_TOGGLE_MOUSE_TO_GYRO: + PushSDLEvent(SDL_EVENT_MOUSE_TO_GYRO); + break; + case HOTKEY_RENDERDOC: + PushSDLEvent(SDL_EVENT_RDOC_CAPTURE); + break; + case HOTKEY_QUIT: + PushSDLEvent(SDL_EVENT_QUIT_DIALOG); + break; + case KEY_TOGGLE: + // noop + break; case MOUSE_GYRO_ROLL_MODE: SetMouseGyroRollMode(new_button_state); break; @@ -553,8 +602,8 @@ void ControllerOutput::FinalizeUpdate() { *value = 0; } else { *value = (*value >= 0 ? 1 : -1) * - std::clamp((int)((128.0 * (std::abs(*value) - deadzone.first)) / - (float)(deadzone.second - deadzone.first)), + std::clamp(static_cast((128.0 * (std::abs(*value) - deadzone.first)) / + (float)(deadzone.second - deadzone.first)), 0, 128); } }; @@ -735,158 +784,4 @@ void ActivateOutputsFromInputs() { } } -std::vector GetHotkeyInputs(Input::HotkeyPad hotkey) { - switch (hotkey) { - case Input::HotkeyPad::FullscreenPad: - return fullscreenHotkeyInputsPad; - case Input::HotkeyPad::PausePad: - return pauseHotkeyInputsPad; - case Input::HotkeyPad::SimpleFpsPad: - return simpleFpsHotkeyInputsPad; - case Input::HotkeyPad::QuitPad: - return quitHotkeyInputsPad; - default: - return {}; - } -} - -void LoadHotkeyInputs() { - const auto hotkey_file = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "hotkeys.ini"; - if (!std::filesystem::exists(hotkey_file)) { - createHotkeyFile(hotkey_file); - } - - std::string controllerFullscreenString, controllerPauseString, controllerFpsString, - controllerQuitString = ""; - std::ifstream file(hotkey_file); - int lineCount = 0; - std::string line = ""; - - while (std::getline(file, line)) { - lineCount++; - - std::size_t equal_pos = line.find('='); - if (equal_pos == std::string::npos) - continue; - - if (line.contains("controllerFullscreen")) { - controllerFullscreenString = line.substr(equal_pos + 2); - } else if (line.contains("controllerStop")) { - controllerQuitString = line.substr(equal_pos + 2); - } else if (line.contains("controllerFps")) { - controllerFpsString = line.substr(equal_pos + 2); - } else if (line.contains("controllerPause")) { - controllerPauseString = line.substr(equal_pos + 2); - } - } - - file.close(); - - auto getVectorFromString = [&](std::vector& inputVector, - const std::string& inputString) { - std::size_t comma_pos = inputString.find(','); - if (comma_pos == std::string::npos) { - inputVector[0] = inputString; - inputVector[1] = "unused"; - inputVector[2] = "unused"; - } else { - inputVector[0] = inputString.substr(0, comma_pos); - std::string substring = inputString.substr(comma_pos + 1); - std::size_t comma2_pos = substring.find(','); - - if (comma2_pos == std::string::npos) { - inputVector[1] = substring; - inputVector[2] = "unused"; - } else { - inputVector[1] = substring.substr(0, comma2_pos); - inputVector[2] = substring.substr(comma2_pos + 1); - } - } - }; - - getVectorFromString(fullscreenHotkeyInputsPad, controllerFullscreenString); - getVectorFromString(quitHotkeyInputsPad, controllerQuitString); - getVectorFromString(pauseHotkeyInputsPad, controllerPauseString); - getVectorFromString(simpleFpsHotkeyInputsPad, controllerFpsString); -} - -bool HotkeyInputsPressed(std::vector inputs) { - if (inputs[0] == "unmapped") { - return false; - } - - auto controller = Common::Singleton::Instance(); - auto engine = controller->GetEngine(); - SDL_Gamepad* gamepad = engine->m_gamepad; - - if (!gamepad) { - return false; - } - - std::vector isPressed(3, false); - for (int i = 0; i < 3; i++) { - if (inputs[i] == "cross") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH); - } else if (inputs[i] == "circle") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_EAST); - } else if (inputs[i] == "square") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST); - } else if (inputs[i] == "triangle") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_NORTH); - } else if (inputs[i] == "pad_up") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP); - } else if (inputs[i] == "pad_down") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN); - } else if (inputs[i] == "pad_left") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT); - } else if (inputs[i] == "pad_right") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT); - } else if (inputs[i] == "l1") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER); - } else if (inputs[i] == "r1") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER); - } else if (inputs[i] == "l3") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK); - } else if (inputs[i] == "r3") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_STICK); - } else if (inputs[i] == "options") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_START); - } else if (inputs[i] == "back") { - isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_BACK); - } else if (inputs[i] == "l2") { - isPressed[i] = (SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) > 16000); - } else if (inputs[i] == "r2") { - isPressed[i] = (SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) > 16000); - } else if (inputs[i] == "unused") { - isPressed[i] = true; - } else { - isPressed[i] = false; - } - } - - if (isPressed[0] && isPressed[1] && isPressed[2]) { - return true; - } - - return false; -} - -void createHotkeyFile(std::filesystem::path hotkey_file) { - std::string_view default_hotkeys = R"(controllerStop = unmapped -controllerFps = l2,r2,r3 -controllerPause = l2,r2,options -controllerFullscreen = l2,r2,l3 - -keyboardStop = placeholder -keyboardFps = placeholder -keyboardPause = placeholder -keyboardFullscreen = placeholder -)"; - - std::ofstream default_hotkeys_stream(hotkey_file); - if (default_hotkeys_stream) { - default_hotkeys_stream << default_hotkeys; - } -} - } // namespace Input diff --git a/src/input/input_handler.h b/src/input/input_handler.h index 8befb2e16..0d95d1c4a 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -4,11 +4,9 @@ #pragma once #include -#include #include #include #include -#include #include "SDL3/SDL_events.h" #include "SDL3/SDL_timer.h" @@ -29,7 +27,15 @@ #define SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER SDL_GAMEPAD_BUTTON_COUNT + 2 #define SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT SDL_GAMEPAD_BUTTON_COUNT + 3 -// idk who already used what where so I just chose a big number +#define SDL_EVENT_TOGGLE_FULLSCREEN SDL_EVENT_USER + 1 +#define SDL_EVENT_TOGGLE_PAUSE SDL_EVENT_USER + 2 +#define SDL_EVENT_CHANGE_CONTROLLER SDL_EVENT_USER + 3 +#define SDL_EVENT_TOGGLE_SIMPLE_FPS SDL_EVENT_USER + 4 +#define SDL_EVENT_RELOAD_INPUTS SDL_EVENT_USER + 5 +#define SDL_EVENT_MOUSE_TO_JOYSTICK SDL_EVENT_USER + 6 +#define SDL_EVENT_MOUSE_TO_GYRO SDL_EVENT_USER + 7 +#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 8 +#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 9 #define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10 #define LEFTJOYSTICK_HALFMODE 0x00010000 @@ -39,6 +45,15 @@ #define KEY_TOGGLE 0x00200000 #define MOUSE_GYRO_ROLL_MODE 0x00400000 +#define HOTKEY_FULLSCREEN 0xf0000001 +#define HOTKEY_PAUSE 0xf0000002 +#define HOTKEY_SIMPLE_FPS 0xf0000003 +#define HOTKEY_QUIT 0xf0000004 +#define HOTKEY_RELOAD_INPUTS 0xf0000005 +#define HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK 0xf0000006 +#define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007 +#define HOTKEY_RENDERDOC 0xf0000008 + #define SDL_UNMAPPED UINT32_MAX - 1 namespace Input { @@ -113,11 +128,20 @@ const std::map string_to_cbutton_map = { // this is only for input {"back", SDL_GAMEPAD_BUTTON_BACK}, + {"share", SDL_GAMEPAD_BUTTON_BACK}, {"lpaddle_high", SDL_GAMEPAD_BUTTON_LEFT_PADDLE1}, {"lpaddle_low", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2}, {"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1}, {"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2}, {"mouse_gyro_roll_mode", MOUSE_GYRO_ROLL_MODE}, + {"hotkey_pause", HOTKEY_PAUSE}, + {"hotkey_fullscreen", HOTKEY_FULLSCREEN}, + {"hotkey_show_fps", HOTKEY_SIMPLE_FPS}, + {"hotkey_quit", HOTKEY_QUIT}, + {"hotkey_reload_inputs", HOTKEY_RELOAD_INPUTS}, + {"hotkey_toggle_mouse_to_joystick", HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK}, + {"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO}, + {"hotkey_renderdoc_capture", HOTKEY_RENDERDOC}, }; const std::map string_to_axis_map = { @@ -178,6 +202,20 @@ const std::map string_to_keyboard_key_map = { {"8", SDLK_8}, {"9", SDLK_9}, + // F keys + {"f1", SDLK_F1}, + {"f2", SDLK_F2}, + {"f3", SDLK_F3}, + {"f4", SDLK_F4}, + {"f5", SDLK_F5}, + {"f6", SDLK_F6}, + {"f7", SDLK_F7}, + {"f8", SDLK_F8}, + {"f9", SDLK_F9}, + {"f10", SDLK_F10}, + {"f11", SDLK_F11}, + {"f12", SDLK_F12}, + // symbols {"grave", SDLK_GRAVE}, {"tilde", SDLK_TILDE}, @@ -450,16 +488,10 @@ public: InputEvent ProcessBinding(); }; -enum HotkeyPad { FullscreenPad, PausePad, SimpleFpsPad, QuitPad }; - // Updates the list of pressed keys with the given input. // Returns whether the list was updated or not. bool UpdatePressedKeys(InputEvent event); void ActivateOutputsFromInputs(); -void LoadHotkeyInputs(); -bool HotkeyInputsPressed(std::vector inputs); -std::vector GetHotkeyInputs(Input::HotkeyPad hotkey); -void createHotkeyFile(std::filesystem::path hotkey_file); } // namespace Input diff --git a/src/qt_gui/control_settings.cpp b/src/qt_gui/control_settings.cpp index d7a673131..812061a0d 100644 --- a/src/qt_gui/control_settings.cpp +++ b/src/qt_gui/control_settings.cpp @@ -9,7 +9,6 @@ #include "common/path_util.h" #include "control_settings.h" #include "input/input_handler.h" -#include "sdl_window.h" #include "ui_control_settings.h" ControlSettings::ControlSettings(std::shared_ptr game_info_get, bool isGameRunning, @@ -181,7 +180,7 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { config_id = (ui->ProfileComboBox->currentText() == tr("Common Config")) ? "default" : ui->ProfileComboBox->currentText().toStdString(); - const auto config_file = Config::GetFoolproofKbmConfigFile(config_id); + const auto config_file = Config::GetFoolproofInputConfigFile(config_id); int lineCount = 0; std::string line; @@ -208,6 +207,11 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { output_string = line.substr(0, equal_pos - 1); input_string = line.substr(equal_pos + 2); + if (output_string.contains("hotkey")) { + lines.push_back(line); + continue; + } + bool controllerInputdetected = false; for (std::string input : ControllerInputs) { // Needed to avoid detecting backspace while detecting back @@ -411,7 +415,7 @@ void ControlSettings::SetUIValuestoMappings() { ? "default" : ui->ProfileComboBox->currentText().toStdString(); - const auto config_file = Config::GetFoolproofKbmConfigFile(config_id); + const auto config_file = Config::GetFoolproofInputConfigFile(config_id); std::ifstream file(config_file); bool CrossExists = false, CircleExists = false, SquareExists = false, TriangleExists = false, diff --git a/src/qt_gui/hotkeys.cpp b/src/qt_gui/hotkeys.cpp index 9f3ad607a..5fbd1e6d5 100644 --- a/src/qt_gui/hotkeys.cpp +++ b/src/qt_gui/hotkeys.cpp @@ -3,21 +3,23 @@ #include #include -#include +#include +#include #include #include "common/config.h" #include "common/logging/log.h" #include "common/path_util.h" #include "hotkeys.h" +#include "input/controller.h" #include "input/input_handler.h" +#include "sdl_event_wrapper.h" #include "ui_hotkeys.h" -hotkeys::hotkeys(bool isGameRunning, QWidget* parent) - : QDialog(parent), GameRunning(isGameRunning), ui(new Ui::hotkeys) { +Hotkeys::Hotkeys(bool isGameRunning, QWidget* parent) + : QDialog(parent), GameRunning(isGameRunning), ui(new Ui::Hotkeys) { ui->setupUi(this); - installEventFilter(this); if (!GameRunning) { SDL_InitSubSystem(SDL_INIT_GAMEPAD); @@ -28,46 +30,59 @@ hotkeys::hotkeys(bool isGameRunning, QWidget* parent) LoadHotkeys(); CheckGamePad(); + installEventFilter(this); - ButtonsList = { - ui->fpsButtonPad, - ui->quitButtonPad, - ui->fullscreenButtonPad, - ui->pauseButtonPad, - }; + PadButtonsList = {ui->fpsButtonPad, ui->quitButtonPad, ui->fullscreenButtonPad, + ui->pauseButtonPad, ui->reloadButtonPad}; - ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save")); - ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Apply")); - ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + KBButtonsList = {ui->fpsButtonKB, ui->quitButtonKB, ui->fullscreenButtonKB, + ui->pauseButtonKB, ui->reloadButtonKB, ui->renderdocButton, + ui->mouseJoystickButton, ui->mouseGyroButton}; connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button) { if (button == ui->buttonBox->button(QDialogButtonBox::Save)) { SaveHotkeys(true); } else if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) { SaveHotkeys(false); + } else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) { + SetDefault(); } else if (button == ui->buttonBox->button(QDialogButtonBox::Cancel)) { QWidget::close(); } }); - for (auto& button : ButtonsList) { + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); + + ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save")); + ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Apply")); + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(tr("Restore Defaults")); + + for (auto& button : PadButtonsList) { connect(button, &QPushButton::clicked, this, [this, &button]() { StartTimer(button, true); }); } - connect(this, &hotkeys::PushGamepadEvent, this, [this]() { CheckMapping(MappingButton); }); + for (auto& button : KBButtonsList) { + connect(button, &QPushButton::clicked, this, + [this, &button]() { StartTimer(button, false); }); + } SdlEventWrapper::Wrapper::wrapperActive = true; QObject::connect(SdlEventWrapper::Wrapper::GetInstance(), &SdlEventWrapper::Wrapper::SDLEvent, - this, &hotkeys::processSDLEvents); + this, &Hotkeys::processSDLEvents); if (!GameRunning) { - Polling = QtConcurrent::run(&hotkeys::pollSDLEvents, this); + Polling = QtConcurrent::run(&Hotkeys::pollSDLEvents, this); } } -void hotkeys::DisableMappingButtons() { - for (const auto& i : ButtonsList) { +void Hotkeys::DisableMappingButtons() { + for (auto& i : PadButtonsList) { + i->setEnabled(false); + } + + for (auto& i : KBButtonsList) { i->setEnabled(false); } @@ -75,8 +90,12 @@ void hotkeys::DisableMappingButtons() { ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); } -void hotkeys::EnableMappingButtons() { - for (const auto& i : ButtonsList) { +void Hotkeys::EnableMappingButtons() { + for (auto& i : PadButtonsList) { + i->setEnabled(true); + } + + for (auto& i : KBButtonsList) { i->setEnabled(true); } @@ -84,63 +103,154 @@ void hotkeys::EnableMappingButtons() { ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true); } -void hotkeys::SaveHotkeys(bool CloseOnSave) { - const auto hotkey_file = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "hotkeys.ini"; - if (!std::filesystem::exists(hotkey_file)) { - Input::createHotkeyFile(hotkey_file); - } +void Hotkeys::SetDefault() { - QString controllerFullscreenString, controllerPauseString, controllerFpsString, - controllerQuitString = ""; - std::ifstream file(hotkey_file); + PadButtonsList = {ui->fpsButtonPad, ui->quitButtonPad, ui->fullscreenButtonPad, + ui->pauseButtonPad, ui->reloadButtonPad}; + + KBButtonsList = {ui->fpsButtonKB, ui->quitButtonKB, ui->fullscreenButtonKB, + ui->pauseButtonKB, ui->reloadButtonKB, ui->renderdocButton, + ui->mouseJoystickButton, ui->mouseGyroButton}; + + ui->fpsButtonPad->setText("unmapped"); + ui->quitButtonPad->setText("unmapped"); + ui->fullscreenButtonPad->setText("unmapped"); + ui->pauseButtonPad->setText("unmapped"); + ui->reloadButtonPad->setText("unmapped"); + + ui->fpsButtonKB->setText("f10"); + ui->quitButtonKB->setText("lctrl, lshift, end"); + ui->fullscreenButtonKB->setText("f11"); + ui->pauseButtonKB->setText("f9"); + ui->reloadButtonKB->setText("f8"); + + ui->renderdocButton->setText("f12"); + ui->mouseJoystickButton->setText("f7"); + ui->mouseGyroButton->setText("f6"); +} + +void Hotkeys::SaveHotkeys(bool CloseOnSave) { + std::vector lines, inputs; + + auto add_mapping = [&](const QString& buttonText, const std::string& output_name) { + if (buttonText.toStdString() != "unmapped") { + lines.push_back(output_name + " = " + buttonText.toStdString()); + inputs.push_back(buttonText.toStdString()); + } + }; + + lines.push_back("# Anything put here will be loaded for all games,"); + lines.push_back("# alongside the game's config or default.ini depending on your preference."); + lines.push_back(""); + + add_mapping(ui->fullscreenButtonPad->text(), "hotkey_fullscreen"); + add_mapping(ui->fullscreenButtonKB->text(), "hotkey_fullscreen"); + lines.push_back(""); + + add_mapping(ui->pauseButtonPad->text(), "hotkey_pause"); + add_mapping(ui->pauseButtonKB->text(), "hotkey_pause"); + lines.push_back(""); + + add_mapping(ui->fpsButtonPad->text(), "hotkey_show_fps"); + add_mapping(ui->fpsButtonKB->text(), "hotkey_show_fps"); + lines.push_back(""); + + add_mapping(ui->quitButtonPad->text(), "hotkey_quit"); + add_mapping(ui->quitButtonKB->text(), "hotkey_quit"); + lines.push_back(""); + + add_mapping(ui->reloadButtonPad->text(), "hotkey_reload_inputs"); + add_mapping(ui->reloadButtonKB->text(), "hotkey_reload_inputs"); + lines.push_back(""); + + add_mapping(ui->renderdocButton->text(), "hotkey_renderdoc_capture"); + add_mapping(ui->mouseJoystickButton->text(), "hotkey_toggle_mouse_to_joystick"); + add_mapping(ui->mouseGyroButton->text(), "hotkey_toggle_mouse_to_gyro"); + + auto hotkey_file = Config::GetFoolproofInputConfigFile("global"); + std::fstream file(hotkey_file); int lineCount = 0; - std::string line = ""; - std::vector lines; - + std::string line; while (std::getline(file, line)) { lineCount++; + std::size_t comment_pos = line.find('#'); + if (comment_pos != std::string::npos) { + if (!line.contains("Anything put here will be loaded for all games") && + !line.contains("alongside the game's config or default.ini depending on your")) + lines.push_back(line); + continue; + } + std::size_t equal_pos = line.find('='); if (equal_pos == std::string::npos) { lines.push_back(line); continue; } - if (line.contains("controllerFullscreen")) { - line = "controllerFullscreen = " + ui->fullscreenButtonPad->text().toStdString(); - } else if (line.contains("controllerStop")) { - line = "controllerStop = " + ui->quitButtonPad->text().toStdString(); - } else if (line.contains("controllerFps")) { - line = "controllerFps = " + ui->fpsButtonPad->text().toStdString(); - } else if (line.contains("controllerPause")) { - line = "controllerPause = " + ui->pauseButtonPad->text().toStdString(); + if (!line.contains("hotkey")) { + lines.push_back(line); } - - lines.push_back(line); } - file.close(); - std::ofstream output_file(hotkey_file); + // Prevent duplicate inputs that break the input engine + bool duplicateFound = false; + QSet duplicateMappings; + + for (auto it = inputs.begin(); it != inputs.end(); ++it) { + if (std::find(it + 1, inputs.end(), *it) != inputs.end()) { + duplicateFound = true; + duplicateMappings.insert(QString::fromStdString(*it)); + } + } + + if (duplicateFound) { + QStringList duplicatesList; + for (const QString& mapping : duplicateMappings) { + for (auto& button : PadButtonsList) { + if (button->text() == mapping) + duplicatesList.append(button->objectName() + " - " + mapping); + } + + for (auto& button : KBButtonsList) { + if (button->text() == mapping) + duplicatesList.append(button->objectName() + " - " + mapping); + } + } + QMessageBox::information( + this, tr("Unable to Save"), + // clang-format off + QString(tr("Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:\n\n%1").arg(duplicatesList.join("\n")))); + // clang-format on + return; + } + + std::vector save; + bool CurrentLineEmpty = false, LastLineEmpty = false; for (auto const& line : lines) { + LastLineEmpty = CurrentLineEmpty ? true : false; + CurrentLineEmpty = line.empty() ? true : false; + if (!CurrentLineEmpty || !LastLineEmpty) + save.push_back(line); + } + + std::ofstream output_file(hotkey_file); + for (auto const& line : save) { output_file << line << '\n'; } output_file.close(); - Input::LoadHotkeyInputs(); + // this also parses global hotkeys + if (GameRunning) + Input::ParseInputConfig("default"); if (CloseOnSave) QWidget::close(); } -void hotkeys::LoadHotkeys() { - const auto hotkey_file = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "hotkeys.ini"; - if (!std::filesystem::exists(hotkey_file)) { - Input::createHotkeyFile(hotkey_file); - } - - QString controllerFullscreenString, controllerPauseString, controllerFpsString, - controllerQuitString = ""; +void Hotkeys::LoadHotkeys() { + auto hotkey_file = Config::GetFoolproofInputConfigFile("global"); std::ifstream file(hotkey_file); int lineCount = 0; std::string line = ""; @@ -152,26 +262,51 @@ void hotkeys::LoadHotkeys() { if (equal_pos == std::string::npos) continue; - if (line.contains("controllerFullscreen")) { - controllerFullscreenString = QString::fromStdString(line.substr(equal_pos + 2)); - } else if (line.contains("controllerStop")) { - controllerQuitString = QString::fromStdString(line.substr(equal_pos + 2)); - } else if (line.contains("controllerFps")) { - controllerFpsString = QString::fromStdString(line.substr(equal_pos + 2)); - } else if (line.contains("controllerPause")) { - controllerPauseString = QString::fromStdString(line.substr(equal_pos + 2)); + std::string output_string = line.substr(0, equal_pos); + std::string input_string = line.substr(equal_pos + 2); + + bool controllerInputDetected = false; + for (const std::string& input : ControllerInputs) { + // Needed to avoid detecting backspace while detecting back + if (input_string.contains(input) && !input_string.contains("backspace")) { + controllerInputDetected = true; + break; + } + } + + if (output_string.contains("hotkey_fullscreen")) { + controllerInputDetected + ? ui->fullscreenButtonPad->setText(QString::fromStdString(input_string)) + : ui->fullscreenButtonKB->setText(QString::fromStdString(input_string)); + } else if (output_string.contains("hotkey_show_fps")) { + controllerInputDetected + ? ui->fpsButtonPad->setText(QString::fromStdString(input_string)) + : ui->fpsButtonKB->setText(QString::fromStdString(input_string)); + } else if (output_string.contains("hotkey_pause")) { + controllerInputDetected + ? ui->pauseButtonPad->setText(QString::fromStdString(input_string)) + : ui->pauseButtonKB->setText(QString::fromStdString(input_string)); + } else if (output_string.contains("hotkey_quit")) { + controllerInputDetected + ? ui->quitButtonPad->setText(QString::fromStdString(input_string)) + : ui->quitButtonKB->setText(QString::fromStdString(input_string)); + } else if (output_string.contains("hotkey_reload_inputs")) { + controllerInputDetected + ? ui->reloadButtonPad->setText(QString::fromStdString(input_string)) + : ui->reloadButtonKB->setText(QString::fromStdString(input_string)); + } else if (output_string.contains("hotkey_renderdoc_capture")) { + ui->renderdocButton->setText(QString::fromStdString(input_string)); + } else if (output_string.contains("hotkey_toggle_mouse_to_joystick")) { + ui->mouseJoystickButton->setText(QString::fromStdString(input_string)); + } else if (output_string.contains("hotkey_toggle_mouse_to_gyro")) { + ui->mouseGyroButton->setText(QString::fromStdString(input_string)); } } file.close(); - - ui->fpsButtonPad->setText(controllerFpsString); - ui->quitButtonPad->setText(controllerQuitString); - ui->fullscreenButtonPad->setText(controllerFullscreenString); - ui->pauseButtonPad->setText(controllerPauseString); } -void hotkeys::CheckGamePad() { +void Hotkeys::CheckGamePad() { if (h_gamepad) { SDL_CloseGamepad(h_gamepad); h_gamepad = nullptr; @@ -181,6 +316,7 @@ void hotkeys::CheckGamePad() { if (!h_gamepads) { LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError()); + return; } int defaultIndex = GamepadSelect::GetIndexfromGUID(h_gamepads, gamepad_count, @@ -204,16 +340,14 @@ void hotkeys::CheckGamePad() { } } -void hotkeys::StartTimer(QPushButton*& button, bool isButton) { +void Hotkeys::StartTimer(QPushButton*& button, bool isButton) { MappingTimer = 3; - EnableButtonMapping = true; MappingCompleted = false; - L2Pressed = false; - R2Pressed = false; mapping = button->text(); DisableMappingButtons(); - button->setText(tr("Press a button") + " [" + QString::number(MappingTimer) + "]"); + isButton ? EnablePadMapping = true : EnableKBMapping = true; + button->setText(tr("Waiting for inputs") + " [" + QString::number(MappingTimer) + "]"); timer = new QTimer(this); MappingButton = button; @@ -221,18 +355,18 @@ void hotkeys::StartTimer(QPushButton*& button, bool isButton) { connect(timer, &QTimer::timeout, this, [this]() { CheckMapping(MappingButton); }); } -void hotkeys::CheckMapping(QPushButton*& button) { +void Hotkeys::CheckMapping(QPushButton*& button) { MappingTimer -= 1; - button->setText(tr("Press a button") + " [" + QString::number(MappingTimer) + "]"); + button->setText(tr("Waiting for inputs") + " [" + QString::number(MappingTimer) + "]"); if (pressedButtons.size() > 0) { QStringList keyStrings; - for (const QString& buttonAction : pressedButtons) { + for (QString& buttonAction : pressedButtons) { keyStrings << buttonAction; } - QString combo = keyStrings.join(","); + QString combo = keyStrings.join(", "); SetMapping(combo); MappingButton->setText(combo); pressedButtons.clear(); @@ -240,32 +374,427 @@ void hotkeys::CheckMapping(QPushButton*& button) { if (MappingCompleted || MappingTimer <= 0) { button->setText(mapping); - EnableButtonMapping = false; + EnablePadMapping = false; + EnableKBMapping = false; + L2Pressed = false; + R2Pressed = false; EnableMappingButtons(); timer->stop(); } } -void hotkeys::SetMapping(QString input) { +void Hotkeys::SetMapping(QString input) { mapping = input; MappingCompleted = true; } -// use QT events instead of SDL to override default event closing the window with escape -bool hotkeys::eventFilter(QObject* obj, QEvent* event) { - if (event->type() == QEvent::KeyPress && EnableButtonMapping) { +// use QT events instead of SDL for KB since SDL doesn't allow background KB events +bool Hotkeys::eventFilter(QObject* obj, QEvent* event) { + if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Escape) { - SetMapping("unmapped"); - PushGamepadEvent(); + if (EnableKBMapping || EnablePadMapping) { + SetMapping("unmapped"); + CheckMapping(MappingButton); + } return true; + } else { + if (EnableKBMapping) { + + if (pressedButtons.size() >= 3) { + return true; + } + + switch (keyEvent->key()) { + // modifiers + case Qt::Key_Shift: + if (keyEvent->nativeScanCode() == LSHIFT_KEY) { + pressedButtons.insert(1, "lshift"); + } else { + pressedButtons.insert(2, "rshift"); + } + break; + case Qt::Key_Alt: + if (keyEvent->nativeScanCode() == LALT_KEY) { + pressedButtons.insert(3, "lalt"); + } else { + pressedButtons.insert(4, "ralt"); + } + break; + case Qt::Key_Control: + if (keyEvent->nativeScanCode() == LCTRL_KEY) { + pressedButtons.insert(5, "lctrl"); + } else { + pressedButtons.insert(6, "rctrl"); + } + break; + case Qt::Key_Meta: +#ifdef _WIN32 + pressedButtons.insert(7, "lwin"); +#else + pressedButtons.insert(7, "lmeta"); +#endif + break; + + // f keys + + case Qt::Key_F1: + pressedButtons.insert(8, "f1"); + break; + case Qt::Key_F2: + pressedButtons.insert(9, "f2"); + break; + case Qt::Key_F3: + pressedButtons.insert(10, "f3"); + break; + case Qt::Key_F4: + pressedButtons.insert(11, "f4"); + break; + case Qt::Key_F5: + pressedButtons.insert(12, "f5"); + break; + case Qt::Key_F6: + pressedButtons.insert(13, "f6"); + break; + case Qt::Key_F7: + pressedButtons.insert(14, "f7"); + break; + case Qt::Key_F8: + pressedButtons.insert(15, "f8"); + break; + case Qt::Key_F9: + pressedButtons.insert(16, "f9"); + break; + case Qt::Key_F10: + pressedButtons.insert(17, "f10"); + break; + case Qt::Key_F11: + pressedButtons.insert(18, "f11"); + break; + case Qt::Key_F12: + pressedButtons.insert(19, "f12"); + break; + + // alphanumeric + case Qt::Key_A: + pressedButtons.insert(20, "a"); + break; + case Qt::Key_B: + pressedButtons.insert(21, "b"); + break; + case Qt::Key_C: + pressedButtons.insert(22, "c"); + break; + case Qt::Key_D: + pressedButtons.insert(23, "d"); + break; + case Qt::Key_E: + pressedButtons.insert(24, "e"); + break; + case Qt::Key_F: + pressedButtons.insert(25, "f"); + break; + case Qt::Key_G: + pressedButtons.insert(26, "g"); + break; + case Qt::Key_H: + pressedButtons.insert(27, "h"); + break; + case Qt::Key_I: + pressedButtons.insert(28, "i"); + break; + case Qt::Key_J: + pressedButtons.insert(29, "j"); + break; + case Qt::Key_K: + pressedButtons.insert(30, "k"); + break; + case Qt::Key_L: + pressedButtons.insert(31, "l"); + break; + case Qt::Key_M: + pressedButtons.insert(32, "m"); + break; + case Qt::Key_N: + pressedButtons.insert(33, "n"); + break; + case Qt::Key_O: + pressedButtons.insert(34, "o"); + break; + case Qt::Key_P: + pressedButtons.insert(35, "p"); + break; + case Qt::Key_Q: + pressedButtons.insert(36, "q"); + break; + case Qt::Key_R: + pressedButtons.insert(37, "r"); + break; + case Qt::Key_S: + pressedButtons.insert(38, "s"); + break; + case Qt::Key_T: + pressedButtons.insert(39, "t"); + break; + case Qt::Key_U: + pressedButtons.insert(40, "u"); + break; + case Qt::Key_V: + pressedButtons.insert(41, "v"); + break; + case Qt::Key_W: + pressedButtons.insert(42, "w"); + break; + case Qt::Key_X: + pressedButtons.insert(43, "x"); + break; + case Qt::Key_Y: + pressedButtons.insert(44, "y"); + break; + case Qt::Key_Z: + pressedButtons.insert(45, "z"); + break; + case Qt::Key_0: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(46, "kp0") + : pressedButtons.insert(56, "0"); + break; + case Qt::Key_1: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(47, "kp1") + : pressedButtons.insert(57, "1"); + break; + case Qt::Key_2: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(48, "kp2") + : pressedButtons.insert(58, "2"); + break; + case Qt::Key_3: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(49, "kp3") + : pressedButtons.insert(59, "3"); + break; + case Qt::Key_4: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(50, "kp4") + : pressedButtons.insert(60, "4"); + break; + case Qt::Key_5: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(51, "kp5") + : pressedButtons.insert(61, "5"); + break; + case Qt::Key_6: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(52, "kp6") + : pressedButtons.insert(62, "6"); + break; + case Qt::Key_7: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(53, "kp7") + : pressedButtons.insert(63, "7"); + break; + case Qt::Key_8: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(54, "kp8") + : pressedButtons.insert(64, "8"); + break; + case Qt::Key_9: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(55, "kp9") + : pressedButtons.insert(65, "9"); + break; + + // symbols + case Qt::Key_Asterisk: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(66, "kpasterisk") + : pressedButtons.insert(74, "asterisk"); + break; + case Qt::Key_Minus: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(67, "kpminus") + : pressedButtons.insert(75, "minus"); + break; + case Qt::Key_Equal: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(68, "kpequals") + : pressedButtons.insert(76, "equals"); + break; + case Qt::Key_Plus: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(69, "kpplus") + : pressedButtons.insert(77, "plus"); + break; + case Qt::Key_Slash: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(70, "kpslash") + : pressedButtons.insert(78, "slash"); + break; + case Qt::Key_Comma: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(71, "kpcomma") + : pressedButtons.insert(79, "comma"); + break; + case Qt::Key_Period: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(72, "kpperiod") + : pressedButtons.insert(80, "period"); + break; + case Qt::Key_Enter: + QApplication::keyboardModifiers() & Qt::KeypadModifier + ? pressedButtons.insert(73, "kpenter") + : pressedButtons.insert(81, "enter"); + break; + case Qt::Key_QuoteLeft: + pressedButtons.insert(82, "grave"); + break; + case Qt::Key_AsciiTilde: + pressedButtons.insert(83, "tilde"); + break; + case Qt::Key_Exclam: + pressedButtons.insert(84, "exclamation"); + break; + case Qt::Key_At: + pressedButtons.insert(85, "at"); + break; + case Qt::Key_NumberSign: + pressedButtons.insert(86, "hash"); + break; + case Qt::Key_Dollar: + pressedButtons.insert(87, "dollar"); + break; + case Qt::Key_Percent: + pressedButtons.insert(88, "percent"); + break; + case Qt::Key_AsciiCircum: + pressedButtons.insert(89, "caret"); + break; + case Qt::Key_Ampersand: + pressedButtons.insert(90, "ampersand"); + break; + case Qt::Key_ParenLeft: + pressedButtons.insert(91, "lparen"); + break; + case Qt::Key_ParenRight: + pressedButtons.insert(92, "rparen"); + break; + case Qt::Key_BracketLeft: + pressedButtons.insert(93, "lbracket"); + break; + case Qt::Key_BracketRight: + pressedButtons.insert(94, "rbracket"); + break; + case Qt::Key_BraceLeft: + pressedButtons.insert(95, "lbrace"); + break; + case Qt::Key_BraceRight: + pressedButtons.insert(96, "rbrace"); + break; + case Qt::Key_Underscore: + pressedButtons.insert(97, "underscore"); + break; + case Qt::Key_Backslash: + pressedButtons.insert(98, "backslash"); + break; + case Qt::Key_Bar: + pressedButtons.insert(99, "pipe"); + break; + case Qt::Key_Semicolon: + pressedButtons.insert(100, "semicolon"); + break; + case Qt::Key_Colon: + pressedButtons.insert(101, "colon"); + break; + case Qt::Key_Apostrophe: + pressedButtons.insert(102, "apostrophe"); + break; + case Qt::Key_QuoteDbl: + pressedButtons.insert(103, "quote"); + break; + case Qt::Key_Less: + pressedButtons.insert(104, "less"); + break; + case Qt::Key_Greater: + pressedButtons.insert(105, "greater"); + break; + case Qt::Key_Question: + pressedButtons.insert(106, "question"); + break; + + // special keys + case Qt::Key_Print: + pressedButtons.insert(107, "printscreen"); + break; + case Qt::Key_ScrollLock: + pressedButtons.insert(108, "scrolllock"); + break; + case Qt::Key_Pause: + pressedButtons.insert(109, "pausebreak"); + break; + case Qt::Key_Backspace: + pressedButtons.insert(110, "backspace"); + break; + case Qt::Key_Insert: + pressedButtons.insert(111, "insert"); + break; + case Qt::Key_Delete: + pressedButtons.insert(112, "delete"); + break; + case Qt::Key_Home: + pressedButtons.insert(113, "home"); + break; + case Qt::Key_End: + pressedButtons.insert(114, "end"); + break; + case Qt::Key_PageUp: + pressedButtons.insert(115, "pgup"); + break; + case Qt::Key_PageDown: + pressedButtons.insert(116, "pgdown"); + break; + case Qt::Key_Tab: + pressedButtons.insert(117, "tab"); + break; + case Qt::Key_CapsLock: + pressedButtons.insert(118, "capslock"); + break; + case Qt::Key_Return: + pressedButtons.insert(119, "enter"); + break; + case Qt::Key_Space: + pressedButtons.insert(120, "space"); + break; + case Qt::Key_Up: + pressedButtons.insert(121, "up"); + break; + case Qt::Key_Down: + pressedButtons.insert(122, "down"); + break; + case Qt::Key_Left: + pressedButtons.insert(123, "left"); + break; + case Qt::Key_Right: + pressedButtons.insert(124, "right"); + break; + default: + break; + } + return true; + } } } + + if (event->type() == QEvent::KeyPress && EnableKBMapping) { + CheckMapping(MappingButton); + return true; + } + return QDialog::eventFilter(obj, event); } -void hotkeys::processSDLEvents(int Type, int Input, int Value) { - if (EnableButtonMapping) { +void Hotkeys::processSDLEvents(int Type, int Input, int Value) { + if (EnablePadMapping) { if (pressedButtons.size() >= 3) { return; @@ -330,7 +859,7 @@ void hotkeys::processSDLEvents(int Type, int Input, int Value) { L2Pressed = true; } else if (Value < 5000) { if (L2Pressed && !R2Pressed) - emit PushGamepadEvent(); + CheckMapping(MappingButton); } break; case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: @@ -339,7 +868,7 @@ void hotkeys::processSDLEvents(int Type, int Input, int Value) { R2Pressed = true; } else if (Value < 5000) { if (R2Pressed && !L2Pressed) - emit PushGamepadEvent(); + CheckMapping(MappingButton); } break; default: @@ -348,7 +877,7 @@ void hotkeys::processSDLEvents(int Type, int Input, int Value) { } if (Type == SDL_EVENT_GAMEPAD_BUTTON_UP) - emit PushGamepadEvent(); + CheckMapping(MappingButton); } if (Type == SDL_EVENT_GAMEPAD_ADDED || SDL_EVENT_GAMEPAD_REMOVED) { @@ -356,7 +885,7 @@ void hotkeys::processSDLEvents(int Type, int Input, int Value) { } } -void hotkeys::pollSDLEvents() { +void Hotkeys::pollSDLEvents() { SDL_Event event; while (SdlEventWrapper::Wrapper::wrapperActive) { @@ -372,14 +901,15 @@ void hotkeys::pollSDLEvents() { } } -void hotkeys::Cleanup() { +void Hotkeys::Cleanup() { SdlEventWrapper::Wrapper::wrapperActive = false; if (h_gamepad) { SDL_CloseGamepad(h_gamepad); h_gamepad = nullptr; } - SDL_free(h_gamepads); + if (h_gamepads) + SDL_free(h_gamepads); if (!GameRunning) { SDL_Event quitLoop{}; @@ -397,4 +927,4 @@ void hotkeys::Cleanup() { } } -hotkeys::~hotkeys() {} +Hotkeys::~Hotkeys() {} diff --git a/src/qt_gui/hotkeys.h b/src/qt_gui/hotkeys.h index dd34fee27..23f8f0261 100644 --- a/src/qt_gui/hotkeys.h +++ b/src/qt_gui/hotkeys.h @@ -3,40 +3,50 @@ #include #include +#include #include -#include "sdl_event_wrapper.h" +#ifdef _WIN32 +#define LCTRL_KEY 29 +#define LALT_KEY 56 +#define LSHIFT_KEY 42 +#else +#define LCTRL_KEY 37 +#define LALT_KEY 64 +#define LSHIFT_KEY 50 +#endif namespace Ui { -class hotkeys; +class Hotkeys; } -class hotkeys : public QDialog { +class Hotkeys : public QDialog { Q_OBJECT public: - explicit hotkeys(bool GameRunning, QWidget* parent = nullptr); - ~hotkeys(); + explicit Hotkeys(bool GameRunning, QWidget* parent = nullptr); + ~Hotkeys(); -signals: - void PushGamepadEvent(); +private Q_SLOTS: + void processSDLEvents(int Type, int Input, int Value); + void StartTimer(QPushButton*& button, bool isPad); + void SaveHotkeys(bool CloseOnSave); + void SetDefault(); private: bool eventFilter(QObject* obj, QEvent* event) override; void CheckMapping(QPushButton*& button); - void StartTimer(QPushButton*& button, bool isButton); void DisableMappingButtons(); void EnableMappingButtons(); - void SaveHotkeys(bool CloseOnSave); void LoadHotkeys(); - void processSDLEvents(int Type, int Input, int Value); void pollSDLEvents(); void CheckGamePad(); void SetMapping(QString input); void Cleanup(); bool GameRunning; - bool EnableButtonMapping = false; + bool EnablePadMapping = false; + bool EnableKBMapping = false; bool MappingCompleted = false; bool L2Pressed = false; bool R2Pressed = false; @@ -50,13 +60,29 @@ private: // use QMap instead of QSet to maintain order of inserted strings QMap pressedButtons; - QList ButtonsList; + QList PadButtonsList; + QList KBButtonsList; QFuture Polling; - Ui::hotkeys* ui; + Ui::Hotkeys* ui; + + const std::vector ControllerInputs = { + "cross", "circle", "square", "triangle", "l1", + "r1", "l2", "r2", "l3", + + "r3", "options", "pad_up", + + "pad_down", + + "pad_left", "pad_right", "axis_left_x", "axis_left_y", "axis_right_x", + "axis_right_y", "back"}; protected: void closeEvent(QCloseEvent* event) override { Cleanup(); } + + void accept() override { + // Blank override to prevent quitting when save button pressed + } }; diff --git a/src/qt_gui/hotkeys.ui b/src/qt_gui/hotkeys.ui index 29dd638fd..7b4d01be4 100644 --- a/src/qt_gui/hotkeys.ui +++ b/src/qt_gui/hotkeys.ui @@ -2,217 +2,315 @@ - hotkeys - + Hotkeys + + + Qt::WindowModality::WindowModal + 0 0 - 849 - 496 + 987 + 549 Customize Hotkeys - + - 750 - 200 - 81 - 81 + 3 + 2 + 981 + 541 - - Qt::Orientation::Vertical + + true - - QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save - - - - - - 30 - 10 - 681 - 231 - - - - - - - - 0 - 0 - - - - - 19 - true - - - - Controller Hotkeys - - - Qt::AlignmentFlag::AlignCenter - - - - - + + + + 0 + 0 + 979 + 539 + + + + + + 3 + 7 + 971 + 532 + + + - - - Show FPS Counter + + + + 0 + 0 + + + + + 19 + true + + + + Controller Hotkeys + + + Qt::AlignmentFlag::AlignCenter - - - - - Qt::FocusPolicy::NoFocus - - - unmapped - - - - - - - Stop Emulator - - - - - - Qt::FocusPolicy::NoFocus - - - unmapped - - - - - - - - - - - - - - Toggle Fullscreen - - - - - - Qt::FocusPolicy::NoFocus - - - unmapped - - - - - - - - - - Toggle Pause - - - - - - Qt::FocusPolicy::NoFocus - - - unmapped - - - - - - - - - - - - - - 30 - 250 - 681 - 191 - - - - - - - - 0 - 0 - - - - - 19 - true - - - - Keyboard Hotkeys - - - Qt::AlignmentFlag::AlignCenter - - - - - - - + - - - - 11 - + + + + 0 + 0 + - - QFrame::Shape::Box - - - Show Fps Counter: F10 + + Show FPS Counter + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + - - - - 11 - + + + + 0 + 0 + - - QFrame::Shape::Box + + Quit Emulation - - Stop Emulator: n/a + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + 0 + 0 + + + Reload Button Mappings + + + + + + unmapped + + + + + + + + + + + + + + + 0 + 0 + + + + Toggle Fullscreen + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + 0 + 0 + + + + Toggle Pause + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + + 19 + true + + + + Keyboard Hotkeys + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + + + 0 + 0 + + + + Show FPS Counter + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + 0 + 0 + + + + Quit Emulation + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + 0 + 0 + + + + Reload Button Mappings + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + @@ -220,59 +318,197 @@ - - - - 11 - + + + + 0 + 0 + - - QFrame::Shape::Box - - - Toggle Fullscreen: F11 + + Toggle Fullscreen + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + - - - - 11 - + + + + 0 + 0 + - - QFrame::Shape::Box + + Toggle Pause - - Toggle Pause: F9 + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + 0 + 0 + + + Renderdoc Capture (for debugging only) + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + 0 + 0 + + + + Toggle Mouse to Joystick Emulation + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + + 0 + 0 + + + + Toggle Mouse to Gyro Emulation + + + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + + 12 + true + + + + Tip: Up to three simultaneous inputs can be assigned for each hotkey + + + Qt::AlignmentFlag::AlignCenter + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::RestoreDefaults|QDialogButtonBox::StandardButton::Save + + + - - - - - - - 50 - 450 - 631 - 31 - - - - - 12 - true - - - - Tip: Up to three inputs can be assigned for each function - + + @@ -280,7 +516,7 @@ buttonBox accepted() - hotkeys + Hotkeys accept() @@ -296,7 +532,7 @@ buttonBox rejected() - hotkeys + Hotkeys reject() diff --git a/src/qt_gui/kbm_config_dialog.cpp b/src/qt_gui/kbm_config_dialog.cpp index 1851c591d..89c551128 100644 --- a/src/qt_gui/kbm_config_dialog.cpp +++ b/src/qt_gui/kbm_config_dialog.cpp @@ -85,7 +85,7 @@ EditorDialog::EditorDialog(QWidget* parent) : QDialog(parent) { void EditorDialog::loadFile(QString game) { - const auto config_file = Config::GetFoolproofKbmConfigFile(game.toStdString()); + const auto config_file = Config::GetFoolproofInputConfigFile(game.toStdString()); QFile file(config_file); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -100,7 +100,7 @@ void EditorDialog::loadFile(QString game) { void EditorDialog::saveFile(QString game) { - const auto config_file = Config::GetFoolproofKbmConfigFile(game.toStdString()); + const auto config_file = Config::GetFoolproofInputConfigFile(game.toStdString()); QFile file(config_file); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { @@ -195,10 +195,10 @@ void EditorDialog::onResetToDefaultClicked() { if (reply == QMessageBox::Yes) { if (default_default) { - const auto default_file = Config::GetFoolproofKbmConfigFile("default"); + const auto default_file = Config::GetFoolproofInputConfigFile("default"); std::filesystem::remove(default_file); } - const auto config_file = Config::GetFoolproofKbmConfigFile("default"); + const auto config_file = Config::GetFoolproofInputConfigFile("default"); QFile file(config_file); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index 1e56c61c0..ce57b7c7f 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -254,7 +254,7 @@ void KBMSettings::SaveKBMConfig(bool close_on_save) { lines.push_back(output_string + " = " + input_string); lines.push_back(""); - const auto config_file = Config::GetFoolproofKbmConfigFile(config_id); + const auto config_file = Config::GetFoolproofInputConfigFile(config_id); std::fstream file(config_file); int lineCount = 0; std::string line; @@ -293,7 +293,7 @@ void KBMSettings::SaveKBMConfig(bool close_on_save) { } if (controllerInputdetected || output_string == "analog_deadzone" || - output_string == "override_controller_color") { + output_string == "override_controller_color" || output_string.contains("hotkey")) { lines.push_back(line); } } @@ -396,7 +396,7 @@ void KBMSettings::SetDefault() { } void KBMSettings::SetUIValuestoMappings(std::string config_id) { - const auto config_file = Config::GetFoolproofKbmConfigFile(config_id); + const auto config_file = Config::GetFoolproofInputConfigFile(config_id); std::ifstream file(config_file); int lineCount = 0; diff --git a/src/qt_gui/kbm_help_dialog.cpp b/src/qt_gui/kbm_help_dialog.cpp index b9055222c..1c28f6895 100644 --- a/src/qt_gui/kbm_help_dialog.cpp +++ b/src/qt_gui/kbm_help_dialog.cpp @@ -114,11 +114,9 @@ HelpDialog::HelpDialog(bool* open_flag, QWidget* parent) : QDialog(parent) { // Helper functions that store the text contents for each tab inb the HelpDialog menu QString HelpDialog::quickstart() { return R"( -The keyboard and controller remapping backend, GUI, and documentation have been written by kalaposfos. - In this section, you will find information about the project and its features, as well as help setting up your ideal setup. To view the config file's syntax, check out the Syntax tab, for keybind names, visit Normal Keybinds and Special Bindings, and if you are here to view emulator-wide keybinds, you can find it in the FAQ section. -This project began because I disliked the original, unchangeable keybinds. Rather than waiting for someone else to do it, I implemented this myself. From the default keybinds, you can clearly tell this was a project built for Bloodborne, but obviously, you can make adjustments however you like.)"; +)"; } QString HelpDialog::syntax() { @@ -157,6 +155,8 @@ Keyboard: 'a', 'b', ..., 'z' Numbers: '0', '1', ..., '9' + F keys: + 'f1', 'f2', ..., 'f12' Keypad: 'kp0', 'kp1', ..., 'kp9', 'kpperiod', 'kpcomma', 'kpslash', 'kpasterisk', 'kpminus', 'kpplus', 'kpequals', 'kpenter' @@ -186,7 +186,7 @@ Controller: 'triangle', 'circle', 'cross', 'square', 'l1', 'l3', 'options', touchpad', 'up', 'down', 'left', 'right' Input-only: - 'lpaddle_low', 'lpaddle_high' + 'lpaddle_low', 'lpaddle_high', 'back' = 'share' (Xbox and PS4 names) Output-only: 'touchpad_left', 'touchpad_center', 'touchpad_right' Axes if you bind them to a button input: @@ -196,9 +196,6 @@ Controller: Axes if you bind them to another axis input: 'axis_left_x', 'axis_left_y', 'axis_right_x', 'axis_right_y', 'l2' - -Invalid Inputs: - 'F1-F12' are reserved for emulator-wide keybinds, and cannot be bound to controller inputs. Symbols (expanded): ` 'grave' @@ -235,7 +232,13 @@ Symbols (expanded): QString HelpDialog::special() { return R"( There are some extra bindings you can put in the config file that don't correspond to a controller input but something else. -You can find these here, with detailed comments, examples, and suggestions for most of them. +You can find these here, with comments, examples, and suggestions for most of them. + +Emulator hotkeys: + These are regarded as normal bindings, but they are put in a special config named global.ini by default, which is a config that is always loaded alongside the main config (If you want to, you can use this to set some common bindings that'll be in effect for every game, then put only the game specific bindings in their respective files). This doesn't mean you can't add them to the normal configs, you can absolutely make game specific emulator hokeys as well. + 'hotkey_pause', 'hotkey_fullscreen', 'hotkey_show_fps', + 'hotkey_quit', 'hotkey_reload_inputs', 'hotkey_toggle_mouse_to_joystick', + 'hotkey_toggle_mouse_to_gyro', 'hotkey_renderdoc_capture' 'leftjoystick_halfmode' and 'rightjoystick_halfmode' = ; These are a pair of input modifiers that change the way keyboard button-bound axes work. By default, those push the joystick to the max in their respective direction, but if their respective 'joystick_halfmode' modifier value is true, they only push it... halfway. With this, you can change from run to walk in games like Bloodborne. @@ -272,7 +275,7 @@ You can find these here, with detailed comments, examples, and suggestions for m QString HelpDialog::faq() { return R"( Q: What are the emulator-wide keybinds? -A: +A: By default, they're the following: -F12: Triggers Renderdoc capture -F11: Toggles fullscreen -F10: Toggles FPS counter @@ -281,6 +284,7 @@ A: -F8: Reparses the config file while in-game -F7: Toggles mouse capture and mouse input -F6: Toggles mouse-to-gyro emulation +More info on these can be found in the Special keybinds tab. Q: How do I switch between mouse and controller joystick input? Why is it even required? A: Pressing F7 toggles between mouse and controller joystick input. It is required because the program polls the mouse input, which means it checks mouse movement every frame. If it didn't move, the code would manually set the emulator's virtual controller to 0 (to the center), even if other input devices would update it. diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index afa5030e9..ad3ab688e 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -21,6 +21,7 @@ #include "control_settings.h" #include "game_install_dialog.h" #include "hotkeys.h" +#include "input/input_handler.h" #include "kbm_gui.h" #include "main_window.h" #include "settings_dialog.h" @@ -497,7 +498,7 @@ void MainWindow::CreateConnects() { }); connect(ui->configureHotkeys, &QAction::triggered, this, [this]() { - auto hotkeyDialog = new hotkeys(isGameRunning, this); + auto hotkeyDialog = new Hotkeys(isGameRunning, this); hotkeyDialog->exec(); }); diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 4760ce4a9..bc97c6414 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -352,7 +352,6 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ Input::ControllerOutput::SetControllerOutputController(controller); Input::ControllerOutput::LinkJoystickAxes(); Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial())); - Input::LoadHotkeyInputs(); if (Config::getBackgroundControllerInput()) { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); @@ -432,6 +431,9 @@ void WindowSDL::WaitEvent() { case SDL_EVENT_QUIT: is_open = false; break; + case SDL_EVENT_QUIT_DIALOG: + Overlay::ToggleQuitWindow(); + break; case SDL_EVENT_TOGGLE_FULLSCREEN: { if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) { SDL_SetWindowFullscreen(window, 0); @@ -441,19 +443,34 @@ void WindowSDL::WaitEvent() { break; } case SDL_EVENT_TOGGLE_PAUSE: - SDL_Log("Received SDL_EVENT_TOGGLE_PAUSE"); - if (DebugState.IsGuestThreadsPaused()) { - SDL_Log("Game Resumed"); + LOG_INFO(Frontend, "Game Resumed"); DebugState.ResumeGuestThreads(); } else { - SDL_Log("Game Paused"); + LOG_INFO(Frontend, "Game Paused"); DebugState.PauseGuestThreads(); } break; case SDL_EVENT_CHANGE_CONTROLLER: controller->GetEngine()->Init(); break; + case SDL_EVENT_TOGGLE_SIMPLE_FPS: + Overlay::ToggleSimpleFps(); + break; + case SDL_EVENT_RELOAD_INPUTS: + Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial())); + break; + case SDL_EVENT_MOUSE_TO_JOYSTICK: + SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), + Input::ToggleMouseModeTo(Input::MouseMode::Joystick)); + break; + case SDL_EVENT_MOUSE_TO_GYRO: + SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), + Input::ToggleMouseModeTo(Input::MouseMode::Gyro)); + break; + case SDL_EVENT_RDOC_CAPTURE: + VideoCore::TriggerCapture(); + break; default: break; } @@ -505,40 +522,6 @@ void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) { 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())); - return; - } - // Toggle mouse capture and joystick input emulation - else if (input_id == SDLK_F7) { - SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), - Input::ToggleMouseModeTo(Input::MouseMode::Joystick)); - return; - } - // Toggle mouse capture and gyro input emulation - else if (input_id == SDLK_F6) { - SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), - Input::ToggleMouseModeTo(Input::MouseMode::Gyro)); - return; - } - // Toggle fullscreen - 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 (input_id == SDLK_F12) { - VideoCore::TriggerCapture(); - return; - } - } - // 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); @@ -571,54 +554,9 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) { bool inputs_changed = Input::UpdatePressedKeys(input_event); if (inputs_changed) { - // process hotkeys - if (event->type == SDL_EVENT_GAMEPAD_BUTTON_UP) { - process_hotkeys = true; - } else if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { - if (event->gbutton.timestamp) - CheckHotkeys(); - } else if (event->type == SDL_EVENT_GAMEPAD_AXIS_MOTION) { - if (event->gaxis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || - event->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { - if (event->gaxis.value < 5000) { - process_hotkeys = true; - } else if (event->gaxis.value > 16000) { - CheckHotkeys(); - } - } - } - // update bindings Input::ActivateOutputsFromInputs(); } } -void WindowSDL::CheckHotkeys() { - if (Input::HotkeyInputsPressed(Input::GetHotkeyInputs(Input::HotkeyPad::FullscreenPad))) { - SDL_Event event; - SDL_memset(&event, 0, sizeof(event)); - event.type = SDL_EVENT_TOGGLE_FULLSCREEN; - SDL_PushEvent(&event); - process_hotkeys = false; - } - - if (Input::HotkeyInputsPressed(Input::GetHotkeyInputs(Input::HotkeyPad::PausePad))) { - SDL_Event event; - SDL_memset(&event, 0, sizeof(event)); - event.type = SDL_EVENT_TOGGLE_PAUSE; - SDL_PushEvent(&event); - process_hotkeys = false; - } - - if (Input::HotkeyInputsPressed(Input::GetHotkeyInputs(Input::HotkeyPad::SimpleFpsPad))) { - Overlay::ToggleSimpleFps(); - process_hotkeys = false; - } - - if (Input::HotkeyInputsPressed(Input::GetHotkeyInputs(Input::HotkeyPad::QuitPad))) { - Overlay::ToggleQuitWindow(); - process_hotkeys = false; - } -} - } // namespace Frontend diff --git a/src/sdl_window.h b/src/sdl_window.h index c05860b4a..3a4341de5 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -9,10 +9,6 @@ #include "core/libraries/pad/pad.h" #include "input/controller.h" -#define SDL_EVENT_TOGGLE_FULLSCREEN (SDL_EVENT_USER + 1) -#define SDL_EVENT_TOGGLE_PAUSE (SDL_EVENT_USER + 2) -#define SDL_EVENT_CHANGE_CONTROLLER (SDL_EVENT_USER + 3) - struct SDL_Window; struct SDL_Gamepad; union SDL_Event; @@ -100,7 +96,6 @@ private: void OnResize(); void OnKeyboardMouseInput(const SDL_Event* event); void OnGamepadEvent(const SDL_Event* event); - void CheckHotkeys(); private: s32 width; @@ -110,7 +105,6 @@ private: SDL_Window* window{}; bool is_shown{}; bool is_open{true}; - bool process_hotkeys{true}; }; } // namespace Frontend