mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-06-16 04:07:51 +00:00
Common/Keyboard: Add keyboard layout support and partial translation
This commit is contained in:
parent
b5fc88c495
commit
106845a15d
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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{
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user