From 156a360d8634fe1cc820b1dd5f06bf8a2b1a7359 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 11 Apr 2020 00:00:44 +1000 Subject: [PATCH] SDL: Migrate to common host interface It now supports controllers again. But you have to bind them in Qt. --- README.md | 24 +- src/duckstation-sdl/CMakeLists.txt | 1 + src/duckstation-sdl/duckstation-sdl.vcxproj | 3 +- .../duckstation-sdl.vcxproj.filters | 1 + src/duckstation-sdl/sdl_host_interface.cpp | 348 +++++------------ src/duckstation-sdl/sdl_host_interface.h | 55 +-- src/duckstation-sdl/sdl_key_names.h | 353 ++++++++++++++++++ 7 files changed, 474 insertions(+), 311 deletions(-) create mode 100644 src/duckstation-sdl/sdl_key_names.h diff --git a/README.md b/README.md index d7c19ae67..f4d620d8c 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ Other features include: - Direct booting of homebrew executables - Digital and analog controllers for input (rumble is forwarded to host) - Qt and SDL frontends for desktop - - Qt frontend has graphical configuration, and controller binding - Automatic content scanning - game titles/regions are provided by redump.org ## System Requirements @@ -142,26 +141,25 @@ click the button next to button name, and press the key/button you want to use w **Currently, it is only possible to bind one input to each controller button/axis. Multiple bindings per button are planned for the future.** -## Default keyboard bindings for SDL frontend -Keyboard bindings in the SDL frontend are currently not customizable. For reference: - - **D-Pad:** W/A/S/D or Up/Left/Down/Right - - **Triangle/Square/Circle/Cross:** I/J/L/K or Numpad8/Numpad4/Numpad6/Numpad2 +## Bindings for SDL frontend +Keyboard bindings in the SDL frontend are currently not customizable in the frontend itself. You should use the Qt frontend to set up your key/controller bindings first. + +## Default bindings +Controller 1: + - **D-Pad:** W/A/S/D + - **Triangle/Square/Circle/Cross:** Numpad8/Numpad4/Numpad6/Numpad2 - **L1/R1:** Q/E - **L2/L2:** 1/3 - **Start:** Enter - **Select:** Backspace -Gamepads are automatically detected and supported. Tested with an Xbox One controller. -To access the menus with the controller, press the right stick down and use the D-Pad to navigate, A to select. - -## Useful hotkeys for SDL frontend - - **F1-F8:** Quick load/save (hold shift to save) - - **F11:** Toggle fullscreen +Hotkeys: + - **Escape:** Power off console + - **ALT+ENTER:** Toggle fullscreen - **Tab:** Temporarily disable speed limiter - **Pause/Break:** Pause/resume emulation - - **Space:** Frame step - - **End:** Toggle software renderer - **Page Up/Down:** Increase/decrease resolution scale in hardware renderers + - **End:** Toggle software renderer ## Tests - Passes amidog's CPU and GTE tests in both interpreter and recompiler modes, partial passing of CPX tests diff --git a/src/duckstation-sdl/CMakeLists.txt b/src/duckstation-sdl/CMakeLists.txt index cc101b8d9..b25b4a4b6 100644 --- a/src/duckstation-sdl/CMakeLists.txt +++ b/src/duckstation-sdl/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(duckstation-sdl opengl_host_display.h sdl_host_interface.cpp sdl_host_interface.h + sdl_key_names.h ) target_include_directories(duckstation-sdl PRIVATE ${SDL2_INCLUDE_DIRS}) diff --git a/src/duckstation-sdl/duckstation-sdl.vcxproj b/src/duckstation-sdl/duckstation-sdl.vcxproj index 02c82b077..300527d76 100644 --- a/src/duckstation-sdl/duckstation-sdl.vcxproj +++ b/src/duckstation-sdl/duckstation-sdl.vcxproj @@ -63,6 +63,7 @@ + @@ -394,4 +395,4 @@ - \ No newline at end of file + diff --git a/src/duckstation-sdl/duckstation-sdl.vcxproj.filters b/src/duckstation-sdl/duckstation-sdl.vcxproj.filters index 7f3f2db71..8ff6182dd 100644 --- a/src/duckstation-sdl/duckstation-sdl.vcxproj.filters +++ b/src/duckstation-sdl/duckstation-sdl.vcxproj.filters @@ -12,6 +12,7 @@ + diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index d028c4579..9c1afdd1c 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -15,6 +15,7 @@ #include "frontend-common/sdl_controller_interface.h" #include "imgui_impl_sdl.h" #include "opengl_host_display.h" +#include "sdl_key_names.h" #include #include #include @@ -190,11 +191,18 @@ bool SDLHostInterface::AcquireHostDisplay() } #endif + // Switch to fullscreen if requested. + if (m_settings.start_fullscreen) + SetFullscreen(true); + return true; } void SDLHostInterface::ReleaseHostDisplay() { + if (m_fullscreen) + SetFullscreen(false); + // restore vsync, since we don't want to burn cycles at the menu m_display->SetVSync(true); } @@ -217,17 +225,35 @@ std::unique_ptr SDLHostInterface::CreateAudioStream(AudioBackend ba } } +std::unique_ptr SDLHostInterface::CreateControllerInterface() +{ + return std::make_unique(); +} + +std::optional SDLHostInterface::GetHostKeyCode(const std::string_view key_code) const +{ + const std::optional code = SDLKeyNames::ParseKeyString(key_code); + if (!code) + return std::nullopt; + + return static_cast(*code); +} + +void SDLHostInterface::UpdateInputMap() +{ + CommonHostInterface::UpdateInputMap(*m_settings_interface.get()); +} + void SDLHostInterface::OnSystemCreated() { - HostInterface::OnSystemCreated(); + CommonHostInterface::OnSystemCreated(); - UpdateKeyboardControllerMapping(); ClearImGuiFocus(); } void SDLHostInterface::OnSystemPaused(bool paused) { - HostInterface::OnSystemPaused(paused); + CommonHostInterface::OnSystemPaused(paused); if (!paused) ClearImGuiFocus(); @@ -235,14 +261,17 @@ void SDLHostInterface::OnSystemPaused(bool paused) void SDLHostInterface::OnSystemDestroyed() { - HostInterface::OnSystemDestroyed(); + CommonHostInterface::OnSystemDestroyed(); } -void SDLHostInterface::OnControllerTypeChanged(u32 slot) +void SDLHostInterface::OnRunningGameChanged() { - HostInterface::OnControllerTypeChanged(slot); + CommonHostInterface::OnRunningGameChanged(); - UpdateKeyboardControllerMapping(); + if (m_system && !m_system->GetRunningTitle().empty()) + SDL_SetWindowTitle(m_window, m_system->GetRunningTitle().c_str()); + else + SDL_SetWindowTitle(m_window, "DuckStation"); } void SDLHostInterface::RunLater(std::function callback) @@ -256,29 +285,35 @@ void SDLHostInterface::RunLater(std::function callback) void SDLHostInterface::SaveSettings() { - INISettingsInterface si(GetSettingsFileName()); - m_settings_copy.Save(si); + m_settings_copy.Save(*m_settings_interface.get()); + m_settings_interface->Save(); } void SDLHostInterface::UpdateSettings() { - HostInterface::UpdateSettings([this]() { m_settings = m_settings_copy; }); + CommonHostInterface::UpdateSettings([this]() { m_settings = m_settings_copy; }); } -void SDLHostInterface::SetFullscreen(bool enabled) +bool SDLHostInterface::IsFullscreen() const +{ + return m_fullscreen; +} + +bool SDLHostInterface::SetFullscreen(bool enabled) { if (m_fullscreen == enabled) - return; + return true; SDL_SetWindowFullscreen(m_window, enabled ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); - // We set the margin only in windowed mode, the menu bar is drawn on top in fullscreen. + // We set the margin only in windowed mode, the menu bar is not drawn fullscreen. m_display->SetDisplayTopMargin(enabled ? 0 : static_cast(20.0f * ImGui::GetIO().DisplayFramebufferScale.x)); int window_width, window_height; SDL_GetWindowSize(m_window, &window_width, &window_height); m_display->WindowResized(window_width, window_height); m_fullscreen = enabled; + return true; } std::unique_ptr SDLHostInterface::Create() @@ -288,7 +323,7 @@ std::unique_ptr SDLHostInterface::Create() bool SDLHostInterface::Initialize() { - if (!HostInterface::Initialize()) + if (!CommonHostInterface::Initialize()) return false; // Change to the user directory so that all default/relative paths in the config are after this. @@ -309,6 +344,10 @@ bool SDLHostInterface::Initialize() } ImGui::NewFrame(); + + // process events to pick up controllers before updating input map + ProcessEvents(); + UpdateInputMap(); return true; } @@ -325,21 +364,27 @@ void SDLHostInterface::Shutdown() if (m_window) DestroySDLWindow(); - HostInterface::Shutdown(); + CommonHostInterface::Shutdown(); } void SDLHostInterface::LoadSettings() { // Settings need to be loaded prior to creating the window for OpenGL bits. - INISettingsInterface si(GetSettingsFileName()); - m_settings_copy.Load(si); + m_settings_interface = std::make_unique(GetSettingsFileName()); + m_settings_copy.Load(*m_settings_interface.get()); m_settings = m_settings_copy; - m_fullscreen = m_settings_copy.start_fullscreen; } void SDLHostInterface::ReportError(const char* message) { + const bool was_fullscreen = IsFullscreen(); + if (was_fullscreen) + SetFullscreen(false); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "DuckStation", message, m_window); + + if (was_fullscreen) + SetFullscreen(true); } void SDLHostInterface::ReportMessage(const char* message) @@ -349,6 +394,10 @@ void SDLHostInterface::ReportMessage(const char* message) bool SDLHostInterface::ConfirmMessage(const char* message) { + const bool was_fullscreen = IsFullscreen(); + if (was_fullscreen) + SetFullscreen(false); + SDL_MessageBoxData mbd = {}; mbd.flags = SDL_MESSAGEBOX_INFORMATION; mbd.window = m_window; @@ -369,13 +418,23 @@ bool SDLHostInterface::ConfirmMessage(const char* message) int button_id = 0; SDL_ShowMessageBox(&mbd, &button_id); - return (button_id == 0); + const bool result = (button_id == 0); + + if (was_fullscreen) + SetFullscreen(true); + + return result; } void SDLHostInterface::HandleSDLEvent(const SDL_Event* event) { ImGui_ImplSDL2_ProcessEvent(event); - // g_sdl_controller_interface.ProcessSDLEvent(event); + + if (m_controller_interface && + static_cast(m_controller_interface.get())->ProcessSDLEvent(event)) + { + return; + } switch (event->type) { @@ -400,23 +459,12 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event) case SDL_KEYDOWN: case SDL_KEYUP: { - if (!ImGui::GetIO().WantCaptureKeyboard) - HandleSDLKeyEvent(event); - } - break; - - case SDL_CONTROLLERDEVICEADDED: - case SDL_CONTROLLERDEVICEREMOVED: - // g_sdl_controller_interface.SetDefaultBindings(); - break; - - case SDL_CONTROLLERBUTTONDOWN: - case SDL_CONTROLLERBUTTONUP: - { - if (event->type == SDL_CONTROLLERBUTTONDOWN && event->cbutton.button == SDL_CONTROLLER_BUTTON_RIGHTSTICK) + if (!ImGui::GetIO().WantCaptureKeyboard && event->key.repeat == 0) { - // focus the menu bar - m_focus_main_menu_bar = true; + const HostKeyCode code = static_cast(static_cast(event->key.keysym.sym) | + static_cast(event->key.keysym.mod) << 16); + const bool pressed = (event->type == SDL_KEYDOWN); + HandleHostKeyEvent(code, pressed); } } break; @@ -435,208 +483,24 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event) } } -void SDLHostInterface::HandleSDLKeyEvent(const SDL_Event* event) +void SDLHostInterface::ProcessEvents() { - const bool repeat = event->key.repeat != 0; - if (!repeat && HandleSDLKeyEventForController(event)) - return; - - const bool pressed = (event->type == SDL_KEYDOWN); - switch (event->key.keysym.scancode) + for (;;) { - case SDL_SCANCODE_F1: - case SDL_SCANCODE_F2: - case SDL_SCANCODE_F3: - case SDL_SCANCODE_F4: - case SDL_SCANCODE_F5: - case SDL_SCANCODE_F6: - case SDL_SCANCODE_F7: - case SDL_SCANCODE_F8: - { - if (!pressed) - { - const u32 index = event->key.keysym.scancode - SDL_SCANCODE_F1 + 1; - if (event->key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) - SaveState(true, index); - else - LoadState(true, index); - } - } - break; - - case SDL_SCANCODE_RETURN: - case SDL_SCANCODE_KP_ENTER: - { - if ((event->key.keysym.mod & (KMOD_LALT | KMOD_RALT)) && !pressed) - SetFullscreen(!m_fullscreen); - } - break; - - case SDL_SCANCODE_TAB: - { - if (!repeat) - { - m_speed_limiter_temp_disabled = pressed; - UpdateSpeedLimiterState(); - } - } - break; - - case SDL_SCANCODE_PAUSE: - { - if (pressed) - PauseSystem(!m_paused); - } - break; - - case SDL_SCANCODE_SPACE: - { - if (pressed) - DoFrameStep(); - } - break; - - case SDL_SCANCODE_HOME: - { - if (pressed && !repeat && m_system) - { - m_settings.speed_limiter_enabled = !m_settings.speed_limiter_enabled; - m_settings_copy.speed_limiter_enabled = m_settings.speed_limiter_enabled; - UpdateSpeedLimiterState(); - AddOSDMessage(m_settings.speed_limiter_enabled ? "Speed limiter enabled." : "Speed limiter disabled."); - } - } - break; - - case SDL_SCANCODE_END: - { - if (pressed) - ToggleSoftwareRendering(); - } - break; - - case SDL_SCANCODE_PAGEUP: - case SDL_SCANCODE_PAGEDOWN: - { - if (pressed) - ModifyResolutionScale(event->key.keysym.scancode == SDL_SCANCODE_PAGEUP ? 1 : -1); - } - break; - } -} - -void SDLHostInterface::UpdateKeyboardControllerMapping() -{ - m_keyboard_button_mapping.fill(-1); - - const Controller* controller = m_system ? m_system->GetController(0) : nullptr; - if (controller) - { -#define SET_BUTTON_MAP(action, name) \ - m_keyboard_button_mapping[static_cast(action)] = controller->GetButtonCodeByName(name).value_or(-1) - - SET_BUTTON_MAP(KeyboardControllerAction::Up, "Up"); - SET_BUTTON_MAP(KeyboardControllerAction::Down, "Down"); - SET_BUTTON_MAP(KeyboardControllerAction::Left, "Left"); - SET_BUTTON_MAP(KeyboardControllerAction::Right, "Right"); - SET_BUTTON_MAP(KeyboardControllerAction::Triangle, "Triangle"); - SET_BUTTON_MAP(KeyboardControllerAction::Cross, "Cross"); - SET_BUTTON_MAP(KeyboardControllerAction::Square, "Square"); - SET_BUTTON_MAP(KeyboardControllerAction::Circle, "Circle"); - SET_BUTTON_MAP(KeyboardControllerAction::L1, "L1"); - SET_BUTTON_MAP(KeyboardControllerAction::R1, "R1"); - SET_BUTTON_MAP(KeyboardControllerAction::L2, "L2"); - SET_BUTTON_MAP(KeyboardControllerAction::R2, "R2"); - SET_BUTTON_MAP(KeyboardControllerAction::Start, "Start"); - SET_BUTTON_MAP(KeyboardControllerAction::Select, "Select"); - -#undef SET_BUTTON_MAP - } -} - -bool SDLHostInterface::HandleSDLKeyEventForController(const SDL_Event* event) -{ - const bool pressed = (event->type == SDL_KEYDOWN); - Controller* controller; - -#define DO_ACTION(action) \ - if ((controller = m_system ? m_system->GetController(0) : nullptr) != nullptr && \ - m_keyboard_button_mapping[static_cast(action)]) \ - { \ - controller->SetButtonState(m_keyboard_button_mapping[static_cast(action)], pressed); \ - } - - switch (event->key.keysym.scancode) - { - case SDL_SCANCODE_KP_8: - case SDL_SCANCODE_I: - DO_ACTION(KeyboardControllerAction::Triangle); - return true; - case SDL_SCANCODE_KP_2: - case SDL_SCANCODE_K: - DO_ACTION(KeyboardControllerAction::Cross); - return true; - case SDL_SCANCODE_KP_4: - case SDL_SCANCODE_J: - DO_ACTION(KeyboardControllerAction::Square); - return true; - case SDL_SCANCODE_KP_6: - case SDL_SCANCODE_L: - DO_ACTION(KeyboardControllerAction::Circle); - return true; - - case SDL_SCANCODE_W: - case SDL_SCANCODE_UP: - DO_ACTION(KeyboardControllerAction::Up); - return true; - case SDL_SCANCODE_S: - case SDL_SCANCODE_DOWN: - DO_ACTION(KeyboardControllerAction::Down); - return true; - case SDL_SCANCODE_A: - case SDL_SCANCODE_LEFT: - DO_ACTION(KeyboardControllerAction::Left); - return true; - case SDL_SCANCODE_D: - case SDL_SCANCODE_RIGHT: - DO_ACTION(KeyboardControllerAction::Right); - return true; - - case SDL_SCANCODE_Q: - DO_ACTION(KeyboardControllerAction::L1); - return true; - case SDL_SCANCODE_E: - DO_ACTION(KeyboardControllerAction::R1); - return true; - - case SDL_SCANCODE_1: - DO_ACTION(KeyboardControllerAction::L2); - return true; - case SDL_SCANCODE_3: - DO_ACTION(KeyboardControllerAction::R2); - return true; - - case SDL_SCANCODE_RETURN: - DO_ACTION(KeyboardControllerAction::Start); - return true; - case SDL_SCANCODE_BACKSPACE: - DO_ACTION(KeyboardControllerAction::Select); - return true; - - default: + SDL_Event ev; + if (SDL_PollEvent(&ev)) + HandleSDLEvent(&ev); + else break; } - -#undef DO_ACTION - - return false; } void SDLHostInterface::DrawImGuiWindows() { - DrawMainMenuBar(); + if (!m_fullscreen) + DrawMainMenuBar(); - HostInterface::DrawImGuiWindows(); + CommonHostInterface::DrawImGuiWindows(); if (!m_system) DrawPoweredOffWindow(); @@ -652,26 +516,11 @@ void SDLHostInterface::DrawImGuiWindows() void SDLHostInterface::DrawMainMenuBar() { - // We skip drawing the menu bar if we're in fullscreen and the mouse pointer isn't in range. - const float SHOW_THRESHOLD = 20.0f; - if (m_fullscreen && !m_system && - ImGui::GetIO().MousePos.y >= (SHOW_THRESHOLD * ImGui::GetIO().DisplayFramebufferScale.x) && - !ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)) - { - return; - } - if (!ImGui::BeginMainMenuBar()) return; const bool system_enabled = static_cast(m_system); - if (m_focus_main_menu_bar) - { - ImGui::OpenPopup("System"); - m_focus_main_menu_bar = false; - } - if (ImGui::BeginMenu("System")) { if (ImGui::MenuItem("Start Disc", nullptr, false, !system_enabled)) @@ -1441,14 +1290,7 @@ void SDLHostInterface::Run() { while (!m_quit_request) { - for (;;) - { - SDL_Event ev; - if (SDL_PollEvent(&ev)) - HandleSDLEvent(&ev); - else - break; - } + ProcessEvents(); if (m_system && !m_paused) { @@ -1460,8 +1302,6 @@ void SDLHostInterface::Run() } } - // g_sdl_controller_interface.UpdateControllerRumble(); - // rendering { DrawImGuiWindows(); diff --git a/src/duckstation-sdl/sdl_host_interface.h b/src/duckstation-sdl/sdl_host_interface.h index 5cc635082..59c3634fe 100644 --- a/src/duckstation-sdl/sdl_host_interface.h +++ b/src/duckstation-sdl/sdl_host_interface.h @@ -3,7 +3,7 @@ #include "common/gl/texture.h" #include "core/host_display.h" #include "core/host_interface.h" -#include "frontend-common/sdl_controller_interface.h" +#include "frontend-common/common_host_interface.h" #include #include #include @@ -15,9 +15,9 @@ class System; class AudioStream; -class Controller; +class INISettingsInterface; -class SDLHostInterface final : public HostInterface +class SDLHostInterface final : public CommonHostInterface { public: SDLHostInterface(); @@ -40,42 +40,17 @@ protected: bool AcquireHostDisplay() override; void ReleaseHostDisplay() override; std::unique_ptr CreateAudioStream(AudioBackend backend) override; + std::unique_ptr CreateControllerInterface() override; void OnSystemCreated() override; void OnSystemPaused(bool paused) override; void OnSystemDestroyed() override; - void OnControllerTypeChanged(u32 slot) override; + void OnRunningGameChanged() override; + + std::optional GetHostKeyCode(const std::string_view key_code) const override; + void UpdateInputMap() override; private: - enum class KeyboardControllerAction - { - Up, - Down, - Left, - Right, - Triangle, - Cross, - Square, - Circle, - L1, - R1, - L2, - R2, - Start, - Select, - Count - }; - - using KeyboardControllerActionMap = std::array(KeyboardControllerAction::Count)>; - - struct ControllerData - { - SDL_GameController* controller; - SDL_Haptic* haptic; - u32 controller_index; - float last_rumble_strength; - }; - bool HasSystem() const { return static_cast(m_system); } #ifdef WIN32 @@ -99,8 +74,8 @@ private: void SaveSettings(); void UpdateSettings(); - bool IsFullscreen() const { return m_fullscreen; } - void SetFullscreen(bool enabled); + bool IsFullscreen() const override; + bool SetFullscreen(bool enabled) override; // We only pass mouse input through if it's grabbed void DrawImGuiWindows() override; @@ -109,10 +84,7 @@ private: void DoFrameStep(); void HandleSDLEvent(const SDL_Event* event); - void HandleSDLKeyEvent(const SDL_Event* event); - - void UpdateKeyboardControllerMapping(); - bool HandleSDLKeyEventForController(const SDL_Event* event); + void ProcessEvents(); void DrawMainMenuBar(); void DrawQuickSettingsMenu(); @@ -125,15 +97,12 @@ private: SDL_Window* m_window = nullptr; std::unique_ptr m_app_icon_texture; - - KeyboardControllerActionMap m_keyboard_button_mapping; - + std::unique_ptr m_settings_interface; u32 m_run_later_event_id = 0; bool m_fullscreen = false; bool m_quit_request = false; bool m_frame_step_request = false; - bool m_focus_main_menu_bar = false; bool m_settings_window_open = false; bool m_about_window_open = false; diff --git a/src/duckstation-sdl/sdl_key_names.h b/src/duckstation-sdl/sdl_key_names.h new file mode 100644 index 000000000..de0e2ffcf --- /dev/null +++ b/src/duckstation-sdl/sdl_key_names.h @@ -0,0 +1,353 @@ +#pragma once +#include "common/string.h" +#include "common/types.h" +#include +#include +#include +#include +#include +#include + +namespace SDLKeyNames { + +static const std::map s_sdl_key_names = {{SDLK_RETURN, "Return"}, + {SDLK_ESCAPE, "Escape"}, + {SDLK_BACKSPACE, "Backspace"}, + {SDLK_TAB, "Tab"}, + {SDLK_SPACE, "Space"}, + {SDLK_EXCLAIM, "Exclam"}, + {SDLK_QUOTEDBL, "QuoteDbl"}, + {SDLK_HASH, "Hash"}, + {SDLK_PERCENT, "Percent"}, + {SDLK_DOLLAR, "Dollar"}, + {SDLK_AMPERSAND, "Ampersand"}, + {SDLK_QUOTE, "Apostrophe"}, + {SDLK_LEFTPAREN, "ParenLeft"}, + {SDLK_RIGHTPAREN, "ParenRight"}, + {SDLK_ASTERISK, "Asterisk"}, + {SDLK_PLUS, "PLus"}, + {SDLK_COMMA, "Comma"}, + {SDLK_MINUS, "Minus"}, + {SDLK_PERIOD, "Period"}, + {SDLK_SLASH, "Slash"}, + {SDLK_0, "0"}, + {SDLK_1, "1"}, + {SDLK_2, "2"}, + {SDLK_3, "3"}, + {SDLK_4, "4"}, + {SDLK_5, "5"}, + {SDLK_6, "6"}, + {SDLK_7, "7"}, + {SDLK_8, "8"}, + {SDLK_9, "9"}, + {SDLK_COLON, "Colon"}, + {SDLK_SEMICOLON, "Semcolon"}, + {SDLK_LESS, "Less"}, + {SDLK_EQUALS, "Equal"}, + {SDLK_GREATER, "Greater"}, + {SDLK_QUESTION, "Question"}, + {SDLK_AT, "AT"}, + {SDLK_LEFTBRACKET, "BracketLeft"}, + {SDLK_BACKSLASH, "Backslash"}, + {SDLK_RIGHTBRACKET, "BracketRight"}, + {SDLK_CARET, "Caret"}, + {SDLK_UNDERSCORE, "Underscore"}, + {SDLK_BACKQUOTE, "Backquote"}, + {SDLK_a, "A"}, + {SDLK_b, "B"}, + {SDLK_c, "C"}, + {SDLK_d, "D"}, + {SDLK_e, "E"}, + {SDLK_f, "F"}, + {SDLK_g, "G"}, + {SDLK_h, "H"}, + {SDLK_i, "I"}, + {SDLK_j, "J"}, + {SDLK_k, "K"}, + {SDLK_l, "L"}, + {SDLK_m, "M"}, + {SDLK_n, "N"}, + {SDLK_o, "O"}, + {SDLK_p, "P"}, + {SDLK_q, "Q"}, + {SDLK_r, "R"}, + {SDLK_s, "S"}, + {SDLK_t, "T"}, + {SDLK_u, "U"}, + {SDLK_v, "V"}, + {SDLK_w, "W"}, + {SDLK_x, "X"}, + {SDLK_y, "Y"}, + {SDLK_z, "Z"}, + {SDLK_CAPSLOCK, "CapsLock"}, + {SDLK_F1, "F1"}, + {SDLK_F2, "F2"}, + {SDLK_F3, "F3"}, + {SDLK_F4, "F4"}, + {SDLK_F5, "F5"}, + {SDLK_F6, "F6"}, + {SDLK_F7, "F7"}, + {SDLK_F8, "F8"}, + {SDLK_F9, "F9"}, + {SDLK_F10, "F10"}, + {SDLK_F11, "F11"}, + {SDLK_F12, "F12"}, + {SDLK_PRINTSCREEN, "Print"}, + {SDLK_SCROLLLOCK, "ScrollLock"}, + {SDLK_PAUSE, "Pause"}, + {SDLK_INSERT, "Insert"}, + {SDLK_HOME, "Home"}, + {SDLK_PAGEUP, "PageUp"}, + {SDLK_DELETE, "Delete"}, + {SDLK_END, "End"}, + {SDLK_PAGEDOWN, "PageDown"}, + {SDLK_RIGHT, "Right"}, + {SDLK_LEFT, "Left"}, + {SDLK_DOWN, "Down"}, + {SDLK_UP, "Up"}, + {SDLK_NUMLOCKCLEAR, "NumLock"}, + {SDLK_KP_DIVIDE, "Keypad+Divide"}, + {SDLK_KP_MULTIPLY, "Keypad+Multiply"}, + {SDLK_KP_MINUS, "Keypad+Minus"}, + {SDLK_KP_PLUS, "Keypad+Plus"}, + {SDLK_KP_ENTER, "Keypad+Return"}, + {SDLK_KP_1, "Keypad+1"}, + {SDLK_KP_2, "Keypad+2"}, + {SDLK_KP_3, "Keypad+3"}, + {SDLK_KP_4, "Keypad+4"}, + {SDLK_KP_5, "Keypad+5"}, + {SDLK_KP_6, "Keypad+6"}, + {SDLK_KP_7, "Keypad+7"}, + {SDLK_KP_8, "Keypad+8"}, + {SDLK_KP_9, "Keypad+9"}, + {SDLK_KP_0, "Keypad+0"}, + {SDLK_KP_PERIOD, "Keypad+Period"}, + {SDLK_APPLICATION, "Application"}, + {SDLK_POWER, "Power"}, + {SDLK_KP_EQUALS, "Keypad+Equal"}, + {SDLK_F13, "F13"}, + {SDLK_F14, "F14"}, + {SDLK_F15, "F15"}, + {SDLK_F16, "F16"}, + {SDLK_F17, "F17"}, + {SDLK_F18, "F18"}, + {SDLK_F19, "F19"}, + {SDLK_F20, "F20"}, + {SDLK_F21, "F21"}, + {SDLK_F22, "F22"}, + {SDLK_F23, "F23"}, + {SDLK_F24, "F24"}, + {SDLK_EXECUTE, "Execute"}, + {SDLK_HELP, "Help"}, + {SDLK_MENU, "Menu"}, + {SDLK_SELECT, "Select"}, + {SDLK_STOP, "Stop"}, + {SDLK_AGAIN, "Again"}, + {SDLK_UNDO, "Undo"}, + {SDLK_CUT, "Cut"}, + {SDLK_COPY, "Copy"}, + {SDLK_PASTE, "Paste"}, + {SDLK_FIND, "Find"}, + {SDLK_MUTE, "Mute"}, + {SDLK_VOLUMEUP, "VolumeUp"}, + {SDLK_VOLUMEDOWN, "VolumeDown"}, + {SDLK_KP_COMMA, "Keypad+Comma"}, + {SDLK_KP_EQUALSAS400, "Keypad+EqualAS400"}, + {SDLK_ALTERASE, "AltErase"}, + {SDLK_SYSREQ, "SysReq"}, + {SDLK_CANCEL, "Cancel"}, + {SDLK_CLEAR, "Clear"}, + {SDLK_PRIOR, "Prior"}, + {SDLK_RETURN2, "Return2"}, + {SDLK_SEPARATOR, "Separator"}, + {SDLK_OUT, "Out"}, + {SDLK_OPER, "Oper"}, + {SDLK_CLEARAGAIN, "ClearAgain"}, + {SDLK_CRSEL, "CrSel"}, + {SDLK_EXSEL, "ExSel"}, + {SDLK_KP_00, "Keypad+00"}, + {SDLK_KP_000, "Keypad+000"}, + {SDLK_THOUSANDSSEPARATOR, "ThousandsSeparator"}, + {SDLK_DECIMALSEPARATOR, "DecimalSeparator"}, + {SDLK_CURRENCYUNIT, "CurrencyUnit"}, + {SDLK_CURRENCYSUBUNIT, "CurrencySubunit"}, + {SDLK_KP_LEFTPAREN, "Keypad+ParenLeft"}, + {SDLK_KP_RIGHTPAREN, "Keypad+ParenRight"}, + {SDLK_KP_LEFTBRACE, "Keypad+LeftBrace"}, + {SDLK_KP_RIGHTBRACE, "Keypad+RightBrace"}, + {SDLK_KP_TAB, "Keypad+Tab"}, + {SDLK_KP_BACKSPACE, "Keypad+Backspace"}, + {SDLK_KP_A, "Keypad+A"}, + {SDLK_KP_B, "Keypad+B"}, + {SDLK_KP_C, "Keypad+C"}, + {SDLK_KP_D, "Keypad+D"}, + {SDLK_KP_E, "Keypad+E"}, + {SDLK_KP_F, "Keypad+F"}, + {SDLK_KP_XOR, "Keypad+XOR"}, + {SDLK_KP_POWER, "Keypad+Power"}, + {SDLK_KP_PERCENT, "Keypad+Percent"}, + {SDLK_KP_LESS, "Keypad+Less"}, + {SDLK_KP_GREATER, "Keypad+Greater"}, + {SDLK_KP_AMPERSAND, "Keypad+Ampersand"}, + {SDLK_KP_DBLAMPERSAND, "Keypad+AmpersandDbl"}, + {SDLK_KP_VERTICALBAR, "Keypad+Bar"}, + {SDLK_KP_DBLVERTICALBAR, "Keypad+BarDbl"}, + {SDLK_KP_COLON, "Keypad+Colon"}, + {SDLK_KP_HASH, "Keypad+Hash"}, + {SDLK_KP_SPACE, "Keypad+Space"}, + {SDLK_KP_AT, "Keypad+At"}, + {SDLK_KP_EXCLAM, "Keypad+Exclam"}, + {SDLK_KP_MEMSTORE, "Keypad+MemStore"}, + {SDLK_KP_MEMRECALL, "Keypad+MemRecall"}, + {SDLK_KP_MEMCLEAR, "Keypad+MemClear"}, + {SDLK_KP_MEMADD, "Keypad+MemAdd"}, + {SDLK_KP_MEMSUBTRACT, "Keypad+MemSubtract"}, + {SDLK_KP_MEMMULTIPLY, "Keypad+MemMultiply"}, + {SDLK_KP_MEMDIVIDE, "Keypad+MemDivide"}, + {SDLK_KP_PLUSMINUS, "Keypad+PlusMinus"}, + {SDLK_KP_CLEAR, "Keypad+Clear"}, + {SDLK_KP_CLEARENTRY, "Keypad+ClearEntry"}, + {SDLK_KP_BINARY, "Keypad+Binary"}, + {SDLK_KP_OCTAL, "Keypad+Octal"}, + {SDLK_KP_DECIMAL, "Keypad+Decimal"}, + {SDLK_KP_HEXADECIMAL, "Keypad+Hexadecimal"}, + {SDLK_LCTRL, "LeftControl"}, + {SDLK_LSHIFT, "LeftShift"}, + {SDLK_LALT, "LeftAlt"}, + {SDLK_LGUI, "Super_L"}, + {SDLK_RCTRL, "RightCtrl"}, + {SDLK_RSHIFT, "RightShift"}, + {SDLK_RALT, "RightAlt"}, + {SDLK_RGUI, "RightSuper"}, + {SDLK_MODE, "Mode"}, + {SDLK_AUDIONEXT, "MediaNext"}, + {SDLK_AUDIOPREV, "MediaPrevious"}, + {SDLK_AUDIOSTOP, "MediaStop"}, + {SDLK_AUDIOPLAY, "MediaPlay"}, + {SDLK_AUDIOMUTE, "VolumeMute"}, + {SDLK_MEDIASELECT, "MediaSelect"}, + {SDLK_WWW, "WWW"}, + {SDLK_MAIL, "Mail"}, + {SDLK_CALCULATOR, "Calculator"}, + {SDLK_COMPUTER, "Computer"}, + {SDLK_AC_SEARCH, "Search"}, + {SDLK_AC_HOME, "Home"}, + {SDLK_AC_BACK, "Back"}, + {SDLK_AC_FORWARD, "Forward"}, + {SDLK_AC_STOP, "Stop"}, + {SDLK_AC_REFRESH, "Refresh"}, + {SDLK_AC_BOOKMARKS, "Bookmarks"}, + {SDLK_BRIGHTNESSDOWN, "BrightnessDown"}, + {SDLK_BRIGHTNESSUP, "BrightnessUp"}, + {SDLK_DISPLAYSWITCH, "DisplaySwitch"}, + {SDLK_KBDILLUMTOGGLE, "IllumToggle"}, + {SDLK_KBDILLUMDOWN, "IllumDown"}, + {SDLK_KBDILLUMUP, "IllumUp"}, + {SDLK_EJECT, "Eject"}, + {SDLK_SLEEP, "Sleep"}, + {SDLK_APP1, "App1"}, + {SDLK_APP2, "App2"}, + {SDLK_AUDIOREWIND, "MediaRewind"}, + {SDLK_AUDIOFASTFORWARD, "MediaFastForward"}}; + +struct SDLKeyModifierEntry +{ + SDL_Keymod mod; + SDL_Keymod mod_mask; + SDL_Keycode key_left; + SDL_Keycode key_right; + const char* name; +}; + +static const std::array s_sdl_key_modifiers = { + {{KMOD_LSHIFT, static_cast(KMOD_LSHIFT | KMOD_RSHIFT), SDLK_LSHIFT, SDLK_RSHIFT, "Shift"}, + {KMOD_LCTRL, static_cast(KMOD_LCTRL | KMOD_LCTRL), SDLK_LCTRL, SDLK_RCTRL, "Control"}, + {KMOD_LALT, static_cast(KMOD_LALT | KMOD_RALT), SDLK_LALT, SDLK_RALT, "Alt"}, + {KMOD_LGUI, static_cast(KMOD_LGUI | KMOD_RGUI), SDLK_LGUI, SDLK_RGUI, "Meta"}}}; + +const char* GetKeyName(SDL_Keycode key) +{ + const auto it = s_sdl_key_names.find(key); + return it == s_sdl_key_names.end() ? nullptr : it->second; +} + +std::optional GetKeyCodeForName(const std::string_view key_name) +{ + for (const auto& it : s_sdl_key_names) + { + if (key_name == it.second) + return it.first; + } + + return std::nullopt; +} + +u32 KeyEventToInt(const SDL_Event* event) +{ + u32 code = static_cast(event->key.keysym.sym); + + const SDL_Keymod mods = static_cast(event->key.keysym.mod); + if (mods & (KMOD_LSHIFT | KMOD_RSHIFT)) + code |= static_cast(KMOD_LSHIFT) << 16; + if (mods & (KMOD_LCTRL | KMOD_RCTRL)) + code |= static_cast(KMOD_LCTRL) << 16; + if (mods & (KMOD_LALT | KMOD_RALT)) + code |= static_cast(KMOD_LALT) << 16; + if (mods & (KMOD_LGUI | KMOD_RGUI)) + code |= static_cast(KMOD_LGUI) << 16; + + return code; +} + +bool KeyEventToString(const SDL_Event* event, String& out_string) +{ + const SDL_Keycode key = event->key.keysym.sym; + const SDL_Keymod mods = static_cast(event->key.keysym.mod); + const char* key_name = GetKeyName(event->key.keysym.sym); + if (!key_name) + return false; + + out_string.Clear(); + + for (const SDLKeyModifierEntry& mod : s_sdl_key_modifiers) + { + if (mods & mod.mod_mask && key != mod.key_left && key != mod.key_right) + { + out_string.AppendString(mod.name); + out_string.AppendCharacter('+'); + } + } + + out_string.AppendString(key_name); + return true; +} + +std::optional ParseKeyString(const std::string_view key_str) +{ + u32 modifiers = 0; + std::string_view::size_type pos = 0; + for (;;) + { + std::string_view::size_type plus_pos = key_str.find('+', pos); + if (plus_pos == std::string_view::npos) + break; + + const std::string_view mod_part = key_str.substr(pos, plus_pos - pos); + for (const SDLKeyModifierEntry& mod : s_sdl_key_modifiers) + { + if (mod_part == mod.name) + { + modifiers |= static_cast(mod.mod); + break; + } + } + pos = plus_pos + 1; + } + + std::optional key_code = GetKeyCodeForName(key_str.substr(pos)); + if (!key_code) + return std::nullopt; + + return static_cast(key_code.value()) | (modifiers << 16); +} +} // namespace SDLKeyNames \ No newline at end of file