Common/Keyboard: Add keyboard layout support and partial translation

This commit is contained in:
Sepalani 2025-05-02 15:20:03 +04:00
parent b5fc88c495
commit 106845a15d
7 changed files with 200 additions and 16 deletions

View File

@ -4,6 +4,7 @@
#include "Common/Keyboard.h"
#include <array>
#include <map>
#include <mutex>
#include <utility>
@ -18,13 +19,126 @@ 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<u8, u8> 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<u8, u8> 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<std::pair<u8, u8>, 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_SDL2
if (const SDL_Keycode key_code = SDL_GetKeyFromScancode(SDL_SCANCODE_Y); key_code == SDLK_z)
{
return Common::KeyboardLayout::QWERTZ;
}
if (const SDL_Keycode key_code = SDL_GetKeyFromScancode(SDL_SCANCODE_Q); 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<DiscIO::Language>(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)
{
// SDL2 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<Common::KeyboardContext> s_keyboard_context;
@ -57,6 +171,7 @@ void KeyboardContext::Init()
SDL_PushEvent(&event);
m_keyboard_state = SDL_GetKeyboardState(nullptr);
#endif
UpdateLayout();
m_is_ready = true;
}
@ -99,6 +214,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();
}
}
const void* KeyboardContext::GetWindowHandle()
{
return s_handler_state.GetHandle();
@ -116,10 +240,10 @@ std::shared_ptr<KeyboardContext> 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{};
}
@ -172,7 +296,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();
@ -190,7 +314,7 @@ HIDPressedKeys KeyboardContext::PollHIDPressedKeys(int keyboard_layout) const
if (!IsVirtualKeyPressed(static_cast<int>(virtual_key)))
continue;
*it = MapVirtualKeyToHID(static_cast<u8>(virtual_key), keyboard_layout);
*it = MapVirtualKeyToHID(static_cast<u8>(virtual_key), m_host_layout, m_game_layout);
if (++it == pressed_keys.end())
break;
}

View File

@ -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<u8, 6>;
struct HIDPressedState
@ -44,10 +95,11 @@ public:
static void NotifyInit();
static void NotifyHandlerChanged(const HandlerState& state);
static void NotifyQuit();
static void UpdateLayout();
static const void* GetWindowHandle();
static std::shared_ptr<KeyboardContext> GetInstance();
HIDPressedState GetPressedState(int keyboard_layout) const;
HIDPressedState GetPressedState() const;
#ifdef HAVE_SDL2
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_SDL2
const u8* m_keyboard_state = nullptr;
#endif

View File

@ -188,6 +188,8 @@ const Info<bool> MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC{
{System::Main, "Core", "WiiSDCardEnableFolderSync"}, false};
const Info<u64> MAIN_WII_SD_CARD_FILESIZE{{System::Main, "Core", "WiiSDCardFilesize"}, 0};
const Info<bool> MAIN_WII_KEYBOARD{{System::Main, "Core", "WiiKeyboard"}, false};
const Info<int> MAIN_WII_KEYBOARD_HOST_LAYOUT{{System::Main, "Core", "WiiKeyboardHostLayout"}, 0};
const Info<int> MAIN_WII_KEYBOARD_GAME_LAYOUT{{System::Main, "Core", "WiiKeyboardGameLayout"}, 0};
const Info<bool> MAIN_WIIMOTE_CONTINUOUS_SCANNING{
{System::Main, "Core", "WiimoteContinuousScanning"}, false};
const Info<std::string> MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES{

View File

@ -106,6 +106,8 @@ extern const Info<bool> MAIN_WII_SD_CARD;
extern const Info<bool> MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC;
extern const Info<u64> MAIN_WII_SD_CARD_FILESIZE;
extern const Info<bool> MAIN_WII_KEYBOARD;
extern const Info<int> MAIN_WII_KEYBOARD_HOST_LAYOUT;
extern const Info<int> MAIN_WII_KEYBOARD_GAME_LAYOUT;
extern const Info<bool> MAIN_WIIMOTE_CONTINUOUS_SCANNING;
extern const Info<std::string> MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES;
extern const Info<bool> MAIN_WIIMOTE_ENABLE_SPEAKER;

View File

@ -31,10 +31,6 @@ USB_KBD::USB_KBD(EmulationKernel& ios, const std::string& device_name)
std::optional<IPCReply> 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;

View File

@ -48,7 +48,6 @@ private:
#pragma pack(pop)
std::queue<MessageData> m_message_queue;
Common::HIDPressedState m_previous_state;
int m_keyboard_layout = Common::KBD_LAYOUT_QWERTY;
std::shared_ptr<Common::KeyboardContext> m_keyboard_context;
};
} // namespace IOS::HLE

View File

@ -47,6 +47,9 @@ std::optional<const char*> UpdateKeyboardHandle(UniqueSDLWindow* unique_window)
SDL_SetWindowFullscreen(keyboard_window, 0);
SDL_SetWindowBordered(keyboard_window, SDL_TRUE);
}
Common::KeyboardContext::UpdateLayout();
return error;
}
} // namespace
@ -346,6 +349,10 @@ bool InputBackend::HandleEventAndContinue(const SDL_Event& e)
m_keyboard_window.reset();
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
else if (e.type == SDL_KEYMAPCHANGED)
{
Common::KeyboardContext::UpdateLayout();
}
return true;
}