From 7dbff8fddb00600e6a24d413e7a67a0b1c3ab56d Mon Sep 17 00:00:00 2001 From: Sepalani Date: Fri, 2 May 2025 15:20:03 +0400 Subject: [PATCH] Common/Keyboard: Add keyboard layout support and partial translation --- Source/Core/Common/Keyboard.cpp | 138 +++++++++++++++++- Source/Core/Common/Keyboard.h | 62 +++++++- Source/Core/Core/Config/MainSettings.cpp | 2 + Source/Core/Core/Config/MainSettings.h | 2 + Source/Core/Core/IOS/USB/USB_KBD.cpp | 6 +- Source/Core/Core/IOS/USB/USB_KBD.h | 1 - .../ControllerInterface/SDL/SDL.cpp | 7 + 7 files changed, 202 insertions(+), 16 deletions(-) diff --git a/Source/Core/Common/Keyboard.cpp b/Source/Core/Common/Keyboard.cpp index 24a7919980..c93c97bd2a 100644 --- a/Source/Core/Common/Keyboard.cpp +++ b/Source/Core/Common/Keyboard.cpp @@ -4,6 +4,7 @@ #include "Common/Keyboard.h" #include +#include #include #include @@ -18,13 +19,128 @@ u32 Common::KeyboardContext::s_sdl_quit_event_type(-1); #endif #include "Core/Config/MainSettings.h" +#include "Core/Config/SYSCONFSettings.h" +#include "DiscIO/Enums.h" namespace { -u8 MapVirtualKeyToHID(u8 virtual_key, int keyboard_layout) +// Translate HID usage ID based on the host and the game keyboard layout: +// - we need to take into account the host layout as we receive raw scan codes +// - we need to consider the game layout as it might be different from the host one +u8 TranslateUsageID(u8 usage_id, int host_layout, int game_layout) +{ + if (host_layout == game_layout) + return usage_id; + + // Currently, the translation is partial (i.e. alpha only) + if (usage_id != Common::HIDUsageID::M_AZERTY && + (usage_id < Common::HIDUsageID::A || usage_id > Common::HIDUsageID::Z)) + { + return usage_id; + } + + switch (host_layout | game_layout) + { + case Common::KeyboardLayout::AZERTY_QWERTZ: + { + static const std::map TO_QWERTZ{ + {Common::HIDUsageID::A_AZERTY, Common::HIDUsageID::A}, + {Common::HIDUsageID::Z_AZERTY, Common::HIDUsageID::Z_QWERTZ}, + {Common::HIDUsageID::Y, Common::HIDUsageID::Y_QWERTZ}, + {Common::HIDUsageID::Q_AZERTY, Common::HIDUsageID::Q}, + {Common::HIDUsageID::M_AZERTY, Common::HIDUsageID::M}, + {Common::HIDUsageID::W_AZERTY, Common::HIDUsageID::W}, + {Common::HIDUsageID::M, Common::HIDUsageID::M_AZERTY}, + }; + static const std::map TO_AZERTY{ + {Common::HIDUsageID::Q, Common::HIDUsageID::Q_AZERTY}, + {Common::HIDUsageID::W, Common::HIDUsageID::W_AZERTY}, + {Common::HIDUsageID::Z_QWERTZ, Common::HIDUsageID::Z_AZERTY}, + {Common::HIDUsageID::A, Common::HIDUsageID::A_AZERTY}, + {Common::HIDUsageID::M_AZERTY, Common::HIDUsageID::M}, + {Common::HIDUsageID::Y_QWERTZ, Common::HIDUsageID::Y}, + {Common::HIDUsageID::M, Common::HIDUsageID::M_AZERTY}, + }; + const auto& map = game_layout == Common::KeyboardLayout::QWERTZ ? TO_QWERTZ : TO_AZERTY; + if (const auto it{map.find(usage_id)}; it != map.end()) + return it->second; + break; + } + case Common::KeyboardLayout::QWERTY_AZERTY: + { + static constexpr std::array, 3> BI_MAP{ + {{Common::HIDUsageID::Q, Common::HIDUsageID::A}, + {Common::HIDUsageID::W, Common::HIDUsageID::Z}, + {Common::HIDUsageID::M, Common::HIDUsageID::M_AZERTY}}}; + for (const auto& [a, b] : BI_MAP) + { + if (usage_id == a) + return b; + else if (usage_id == b) + return a; + } + break; + } + case Common::KeyboardLayout::QWERTY_QWERTZ: + { + if (usage_id == Common::HIDUsageID::Y) + return Common::HIDUsageID::Z; + else if (usage_id == Common::HIDUsageID::Z) + return Common::HIDUsageID::Y; + break; + } + default: + // Shouldn't happen + break; + } + return usage_id; +} + +int GetHostLayout() +{ + const int layout = Config::Get(Config::MAIN_WII_KEYBOARD_HOST_LAYOUT); + if (layout != Common::KeyboardLayout::AUTO) + return layout; + +#ifdef HAVE_SDL3 + if (const SDL_Keycode key_code = SDL_GetKeyFromScancode(SDL_SCANCODE_Y, SDL_KMOD_NONE, false); + key_code == SDLK_Z) + { + return Common::KeyboardLayout::QWERTZ; + } + if (const SDL_Keycode key_code = SDL_GetKeyFromScancode(SDL_SCANCODE_Q, SDL_KMOD_NONE, false); + key_code == SDLK_A) + { + return Common::KeyboardLayout::AZERTY; + } +#endif + + return Common::KeyboardLayout::QWERTY; +} + +int GetGameLayout() +{ + const int layout = Config::Get(Config::MAIN_WII_KEYBOARD_GAME_LAYOUT); + if (layout != Common::KeyboardLayout::AUTO) + return layout; + + const DiscIO::Language language = + static_cast(Config::Get(Config::SYSCONF_LANGUAGE)); + switch (language) + { + case DiscIO::Language::French: + return Common::KeyboardLayout::AZERTY; + case DiscIO::Language::German: + return Common::KeyboardLayout::QWERTZ; + default: + return Common::KeyboardLayout::QWERTY; + } +} + +u8 MapVirtualKeyToHID(u8 virtual_key, int host_layout, int game_layout) { // SDL3 keyboard state uses scan codes already based on HID usage id - return virtual_key; + return TranslateUsageID(virtual_key, host_layout, game_layout); } std::weak_ptr s_keyboard_context; @@ -57,6 +173,7 @@ void KeyboardContext::Init() SDL_PushEvent(&event); m_keyboard_state = SDL_GetKeyboardState(nullptr); #endif + UpdateLayout(); m_is_ready = true; } @@ -101,6 +218,15 @@ void KeyboardContext::NotifyQuit() self->Quit(); } +void KeyboardContext::UpdateLayout() +{ + if (auto self = s_keyboard_context.lock()) + { + self->m_host_layout = GetHostLayout(); + self->m_game_layout = GetGameLayout(); + } +} + void* KeyboardContext::GetWindowHandle() { return s_handler_state.GetHandle(); @@ -118,10 +244,10 @@ std::shared_ptr KeyboardContext::GetInstance() return ptr; } -HIDPressedState KeyboardContext::GetPressedState(int keyboard_layout) const +HIDPressedState KeyboardContext::GetPressedState() const { return m_is_ready ? HIDPressedState{.modifiers = PollHIDModifiers(), - .pressed_keys = PollHIDPressedKeys(keyboard_layout)} : + .pressed_keys = PollHIDPressedKeys()} : HIDPressedState{}; } @@ -175,7 +301,7 @@ u8 KeyboardContext::PollHIDModifiers() const return modifiers; } -HIDPressedKeys KeyboardContext::PollHIDPressedKeys(int keyboard_layout) const +HIDPressedKeys KeyboardContext::PollHIDPressedKeys() const { HIDPressedKeys pressed_keys{}; auto it = pressed_keys.begin(); @@ -193,7 +319,7 @@ HIDPressedKeys KeyboardContext::PollHIDPressedKeys(int keyboard_layout) const if (!IsVirtualKeyPressed(static_cast(virtual_key))) continue; - *it = MapVirtualKeyToHID(static_cast(virtual_key), keyboard_layout); + *it = MapVirtualKeyToHID(static_cast(virtual_key), m_host_layout, m_game_layout); if (++it == pressed_keys.end()) break; } diff --git a/Source/Core/Common/Keyboard.h b/Source/Core/Common/Keyboard.h index adf5591c86..36d6d7212b 100644 --- a/Source/Core/Common/Keyboard.h +++ b/Source/Core/Common/Keyboard.h @@ -10,12 +10,63 @@ namespace Common { +namespace HIDUsageID +{ +// See HID Usage Tables - Keyboard (0x07): +// https://usb.org/sites/default/files/hut1_21.pdf enum { - KBD_LAYOUT_QWERTY = 0, - KBD_LAYOUT_AZERTY = 1 + A = 0x04, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + COLON = 0x33, + A_AZERTY = Q, + M_AZERTY = COLON, + Q_AZERTY = A, + W_AZERTY = Z, + Z_AZERTY = W, + Y_QWERTZ = Z, + Z_QWERTZ = Y, }; +} // namespace HIDUsageID +namespace KeyboardLayout +{ +enum +{ + AUTO = 0, + QWERTY = 1, + AZERTY = 2, + QWERTZ = 4, + // Translation + QWERTY_AZERTY = QWERTY | AZERTY, + QWERTY_QWERTZ = QWERTY | QWERTZ, + AZERTY_QWERTZ = AZERTY | QWERTZ +}; +} using HIDPressedKeys = std::array; struct HIDPressedState @@ -44,10 +95,11 @@ public: static void NotifyInit(); static void NotifyHandlerChanged(const HandlerState& state); static void NotifyQuit(); + static void UpdateLayout(); static void* GetWindowHandle(); static std::shared_ptr GetInstance(); - HIDPressedState GetPressedState(int keyboard_layout) const; + HIDPressedState GetPressedState() const; #ifdef HAVE_SDL3 static u32 s_sdl_init_event_type; @@ -62,9 +114,11 @@ private: void Quit(); bool IsVirtualKeyPressed(int virtual_key) const; u8 PollHIDModifiers() const; - HIDPressedKeys PollHIDPressedKeys(int keyboard_layout) const; + HIDPressedKeys PollHIDPressedKeys() const; bool m_is_ready = false; + int m_host_layout = KeyboardLayout::AUTO; + int m_game_layout = KeyboardLayout::AUTO; #ifdef HAVE_SDL3 const bool* m_keyboard_state = nullptr; #endif diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 699e0d1cd7..a89565bb6a 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -196,6 +196,8 @@ const Info MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC{ {System::Main, "Core", "WiiSDCardEnableFolderSync"}, false}; const Info MAIN_WII_SD_CARD_FILESIZE{{System::Main, "Core", "WiiSDCardFilesize"}, 0}; const Info MAIN_WII_KEYBOARD{{System::Main, "Core", "WiiKeyboard"}, false}; +const Info MAIN_WII_KEYBOARD_HOST_LAYOUT{{System::Main, "Core", "WiiKeyboardHostLayout"}, 0}; +const Info MAIN_WII_KEYBOARD_GAME_LAYOUT{{System::Main, "Core", "WiiKeyboardGameLayout"}, 0}; const Info MAIN_WIIMOTE_CONTINUOUS_SCANNING{ {System::Main, "Core", "WiimoteContinuousScanning"}, false}; const Info MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES{ diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 580ccfdabf..6fe42bb279 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -108,6 +108,8 @@ extern const Info MAIN_WII_SD_CARD; extern const Info MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC; extern const Info MAIN_WII_SD_CARD_FILESIZE; extern const Info MAIN_WII_KEYBOARD; +extern const Info MAIN_WII_KEYBOARD_HOST_LAYOUT; +extern const Info MAIN_WII_KEYBOARD_GAME_LAYOUT; extern const Info MAIN_WIIMOTE_CONTINUOUS_SCANNING; extern const Info MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES; extern const Info MAIN_WIIMOTE_ENABLE_SPEAKER; diff --git a/Source/Core/Core/IOS/USB/USB_KBD.cpp b/Source/Core/Core/IOS/USB/USB_KBD.cpp index 9200b185c5..f5a4e673de 100644 --- a/Source/Core/Core/IOS/USB/USB_KBD.cpp +++ b/Source/Core/Core/IOS/USB/USB_KBD.cpp @@ -31,10 +31,6 @@ USB_KBD::USB_KBD(EmulationKernel& ios, const std::string& device_name) std::optional USB_KBD::Open(const OpenRequest& request) { INFO_LOG_FMT(IOS, "USB_KBD: Open"); - Common::IniFile ini; - ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); - ini.GetOrCreateSection("USB Keyboard") - ->Get("Layout", &m_keyboard_layout, Common::KBD_LAYOUT_QWERTY); m_message_queue = {}; m_previous_state = {}; @@ -80,7 +76,7 @@ void USB_KBD::Update() return; } - const auto current_state = m_keyboard_context->GetPressedState(m_keyboard_layout); + const auto current_state = m_keyboard_context->GetPressedState(); if (current_state == m_previous_state) return; diff --git a/Source/Core/Core/IOS/USB/USB_KBD.h b/Source/Core/Core/IOS/USB/USB_KBD.h index 31ac3ee77a..41788b828b 100644 --- a/Source/Core/Core/IOS/USB/USB_KBD.h +++ b/Source/Core/Core/IOS/USB/USB_KBD.h @@ -48,7 +48,6 @@ private: #pragma pack(pop) std::queue m_message_queue; Common::HIDPressedState m_previous_state; - int m_keyboard_layout = Common::KBD_LAYOUT_QWERTY; std::shared_ptr m_keyboard_context; }; } // namespace IOS::HLE diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp index 515d944624..8633853b3a 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp @@ -91,6 +91,9 @@ std::optional UpdateKeyboardHandle(UniqueSDLWindow* unique_window) SDL_SetWindowFullscreen(keyboard_window, 0); SDL_SetWindowBordered(keyboard_window, true); } + + Common::KeyboardContext::UpdateLayout(); + return error; } } // namespace @@ -398,6 +401,10 @@ bool InputBackend::HandleEventAndContinue(const SDL_Event& e) m_keyboard_window.reset(); SDL_QuitSubSystem(SDL_INIT_VIDEO); } + else if (e.type == SDL_EVENT_KEYMAP_CHANGED) + { + Common::KeyboardContext::UpdateLayout(); + } return true; }