mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-20 09:19:48 +00:00
Common/Keyboard: Add keyboard layout support and partial translation
This commit is contained in:
parent
f3c73a7572
commit
7dbff8fddb
7 changed files with 202 additions and 16 deletions
|
@ -4,6 +4,7 @@
|
||||||
#include "Common/Keyboard.h"
|
#include "Common/Keyboard.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
@ -18,13 +19,128 @@ u32 Common::KeyboardContext::s_sdl_quit_event_type(-1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "Core/Config/MainSettings.h"
|
#include "Core/Config/MainSettings.h"
|
||||||
|
#include "Core/Config/SYSCONFSettings.h"
|
||||||
|
#include "DiscIO/Enums.h"
|
||||||
|
|
||||||
namespace
|
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_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<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)
|
||||||
{
|
{
|
||||||
// SDL3 keyboard state uses scan codes already based on HID usage id
|
// 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<Common::KeyboardContext> s_keyboard_context;
|
std::weak_ptr<Common::KeyboardContext> s_keyboard_context;
|
||||||
|
@ -57,6 +173,7 @@ void KeyboardContext::Init()
|
||||||
SDL_PushEvent(&event);
|
SDL_PushEvent(&event);
|
||||||
m_keyboard_state = SDL_GetKeyboardState(nullptr);
|
m_keyboard_state = SDL_GetKeyboardState(nullptr);
|
||||||
#endif
|
#endif
|
||||||
|
UpdateLayout();
|
||||||
m_is_ready = true;
|
m_is_ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +218,15 @@ void KeyboardContext::NotifyQuit()
|
||||||
self->Quit();
|
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()
|
void* KeyboardContext::GetWindowHandle()
|
||||||
{
|
{
|
||||||
return s_handler_state.GetHandle();
|
return s_handler_state.GetHandle();
|
||||||
|
@ -118,10 +244,10 @@ std::shared_ptr<KeyboardContext> KeyboardContext::GetInstance()
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
HIDPressedState KeyboardContext::GetPressedState(int keyboard_layout) const
|
HIDPressedState KeyboardContext::GetPressedState() const
|
||||||
{
|
{
|
||||||
return m_is_ready ? HIDPressedState{.modifiers = PollHIDModifiers(),
|
return m_is_ready ? HIDPressedState{.modifiers = PollHIDModifiers(),
|
||||||
.pressed_keys = PollHIDPressedKeys(keyboard_layout)} :
|
.pressed_keys = PollHIDPressedKeys()} :
|
||||||
HIDPressedState{};
|
HIDPressedState{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +301,7 @@ u8 KeyboardContext::PollHIDModifiers() const
|
||||||
return modifiers;
|
return modifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
HIDPressedKeys KeyboardContext::PollHIDPressedKeys(int keyboard_layout) const
|
HIDPressedKeys KeyboardContext::PollHIDPressedKeys() const
|
||||||
{
|
{
|
||||||
HIDPressedKeys pressed_keys{};
|
HIDPressedKeys pressed_keys{};
|
||||||
auto it = pressed_keys.begin();
|
auto it = pressed_keys.begin();
|
||||||
|
@ -193,7 +319,7 @@ HIDPressedKeys KeyboardContext::PollHIDPressedKeys(int keyboard_layout) const
|
||||||
if (!IsVirtualKeyPressed(static_cast<int>(virtual_key)))
|
if (!IsVirtualKeyPressed(static_cast<int>(virtual_key)))
|
||||||
continue;
|
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())
|
if (++it == pressed_keys.end())
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,63 @@
|
||||||
|
|
||||||
namespace Common
|
namespace Common
|
||||||
{
|
{
|
||||||
|
namespace HIDUsageID
|
||||||
|
{
|
||||||
|
// See HID Usage Tables - Keyboard (0x07):
|
||||||
|
// https://usb.org/sites/default/files/hut1_21.pdf
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
KBD_LAYOUT_QWERTY = 0,
|
A = 0x04,
|
||||||
KBD_LAYOUT_AZERTY = 1
|
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>;
|
using HIDPressedKeys = std::array<u8, 6>;
|
||||||
|
|
||||||
struct HIDPressedState
|
struct HIDPressedState
|
||||||
|
@ -44,10 +95,11 @@ public:
|
||||||
static void NotifyInit();
|
static void NotifyInit();
|
||||||
static void NotifyHandlerChanged(const HandlerState& state);
|
static void NotifyHandlerChanged(const HandlerState& state);
|
||||||
static void NotifyQuit();
|
static void NotifyQuit();
|
||||||
|
static void UpdateLayout();
|
||||||
static void* GetWindowHandle();
|
static void* GetWindowHandle();
|
||||||
static std::shared_ptr<KeyboardContext> GetInstance();
|
static std::shared_ptr<KeyboardContext> GetInstance();
|
||||||
|
|
||||||
HIDPressedState GetPressedState(int keyboard_layout) const;
|
HIDPressedState GetPressedState() const;
|
||||||
|
|
||||||
#ifdef HAVE_SDL3
|
#ifdef HAVE_SDL3
|
||||||
static u32 s_sdl_init_event_type;
|
static u32 s_sdl_init_event_type;
|
||||||
|
@ -62,9 +114,11 @@ private:
|
||||||
void Quit();
|
void Quit();
|
||||||
bool IsVirtualKeyPressed(int virtual_key) const;
|
bool IsVirtualKeyPressed(int virtual_key) const;
|
||||||
u8 PollHIDModifiers() const;
|
u8 PollHIDModifiers() const;
|
||||||
HIDPressedKeys PollHIDPressedKeys(int keyboard_layout) const;
|
HIDPressedKeys PollHIDPressedKeys() const;
|
||||||
|
|
||||||
bool m_is_ready = false;
|
bool m_is_ready = false;
|
||||||
|
int m_host_layout = KeyboardLayout::AUTO;
|
||||||
|
int m_game_layout = KeyboardLayout::AUTO;
|
||||||
#ifdef HAVE_SDL3
|
#ifdef HAVE_SDL3
|
||||||
const bool* m_keyboard_state = nullptr;
|
const bool* m_keyboard_state = nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -196,6 +196,8 @@ const Info<bool> MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC{
|
||||||
{System::Main, "Core", "WiiSDCardEnableFolderSync"}, false};
|
{System::Main, "Core", "WiiSDCardEnableFolderSync"}, false};
|
||||||
const Info<u64> MAIN_WII_SD_CARD_FILESIZE{{System::Main, "Core", "WiiSDCardFilesize"}, 0};
|
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<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{
|
const Info<bool> MAIN_WIIMOTE_CONTINUOUS_SCANNING{
|
||||||
{System::Main, "Core", "WiimoteContinuousScanning"}, false};
|
{System::Main, "Core", "WiimoteContinuousScanning"}, false};
|
||||||
const Info<std::string> MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES{
|
const Info<std::string> MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES{
|
||||||
|
|
|
@ -108,6 +108,8 @@ extern const Info<bool> MAIN_WII_SD_CARD;
|
||||||
extern const Info<bool> MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC;
|
extern const Info<bool> MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC;
|
||||||
extern const Info<u64> MAIN_WII_SD_CARD_FILESIZE;
|
extern const Info<u64> MAIN_WII_SD_CARD_FILESIZE;
|
||||||
extern const Info<bool> MAIN_WII_KEYBOARD;
|
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<bool> MAIN_WIIMOTE_CONTINUOUS_SCANNING;
|
||||||
extern const Info<std::string> MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES;
|
extern const Info<std::string> MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES;
|
||||||
extern const Info<bool> MAIN_WIIMOTE_ENABLE_SPEAKER;
|
extern const Info<bool> MAIN_WIIMOTE_ENABLE_SPEAKER;
|
||||||
|
|
|
@ -31,10 +31,6 @@ USB_KBD::USB_KBD(EmulationKernel& ios, const std::string& device_name)
|
||||||
std::optional<IPCReply> USB_KBD::Open(const OpenRequest& request)
|
std::optional<IPCReply> USB_KBD::Open(const OpenRequest& request)
|
||||||
{
|
{
|
||||||
INFO_LOG_FMT(IOS, "USB_KBD: Open");
|
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_message_queue = {};
|
||||||
m_previous_state = {};
|
m_previous_state = {};
|
||||||
|
@ -80,7 +76,7 @@ void USB_KBD::Update()
|
||||||
return;
|
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)
|
if (current_state == m_previous_state)
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -48,7 +48,6 @@ private:
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
std::queue<MessageData> m_message_queue;
|
std::queue<MessageData> m_message_queue;
|
||||||
Common::HIDPressedState m_previous_state;
|
Common::HIDPressedState m_previous_state;
|
||||||
int m_keyboard_layout = Common::KBD_LAYOUT_QWERTY;
|
|
||||||
std::shared_ptr<Common::KeyboardContext> m_keyboard_context;
|
std::shared_ptr<Common::KeyboardContext> m_keyboard_context;
|
||||||
};
|
};
|
||||||
} // namespace IOS::HLE
|
} // namespace IOS::HLE
|
||||||
|
|
|
@ -91,6 +91,9 @@ std::optional<const char*> UpdateKeyboardHandle(UniqueSDLWindow* unique_window)
|
||||||
SDL_SetWindowFullscreen(keyboard_window, 0);
|
SDL_SetWindowFullscreen(keyboard_window, 0);
|
||||||
SDL_SetWindowBordered(keyboard_window, true);
|
SDL_SetWindowBordered(keyboard_window, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Common::KeyboardContext::UpdateLayout();
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -398,6 +401,10 @@ bool InputBackend::HandleEventAndContinue(const SDL_Event& e)
|
||||||
m_keyboard_window.reset();
|
m_keyboard_window.reset();
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
}
|
}
|
||||||
|
else if (e.type == SDL_EVENT_KEYMAP_CHANGED)
|
||||||
|
{
|
||||||
|
Common::KeyboardContext::UpdateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue