Common/Keyboard: Add OSX support

This commit is contained in:
Sepalani 2025-07-17 23:46:33 +04:00
parent e6e0553af3
commit 59abb648f1
2 changed files with 192 additions and 14 deletions

View File

@ -6,8 +6,15 @@
#include <array>
#include <map>
#include <mutex>
#include <ranges>
#include <utility>
#ifdef __APPLE__
#include <Carbon/Carbon.h>
#include "Common/ScopeGuard.h"
#endif
#ifdef HAVE_SDL3
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_keyboard.h>
@ -102,7 +109,32 @@ int GetHostLayout()
if (layout != Common::KeyboardLayout::AUTO)
return layout;
#ifdef HAVE_SDL3
#ifdef __APPLE__
auto current_layout = TISCopyCurrentKeyboardLayoutInputSource();
Common::ScopeGuard guard([&current_layout] { CFRelease(current_layout); });
auto layout_data = static_cast<CFDataRef>(
TISGetInputSourceProperty(current_layout, kTISPropertyUnicodeKeyLayoutData));
if (!layout_data)
return Common::KeyboardLayout::QWERTY;
auto unicode_layout = reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(layout_data));
using VkToLayout = std::tuple<CGKeyCode, char, Common::KeyboardLayout::LayoutEnum>;
for (auto& [key_code, c, l] :
std::array{VkToLayout(kVK_ANSI_Y, 'z', Common::KeyboardLayout::QWERTZ),
VkToLayout(kVK_ANSI_Q, 'a', Common::KeyboardLayout::AZERTY)})
{
UInt32 dead_key_state = 0;
std::array<UniChar, 4> str{};
UniCharCount real_length = 0;
UCKeyTranslate(unicode_layout, key_code, kUCKeyActionDown, 0, LMGetKbdType(),
kUCKeyTranslateNoDeadKeysMask, &dead_key_state, str.size(), &real_length,
str.data());
if (real_length && (str[0] == c || str[0] == (c - 0x20)))
return l;
}
#elif defined(HAVE_SDL3)
if (const SDL_Keycode key_code = SDL_GetKeyFromScancode(SDL_SCANCODE_Y, SDL_KMOD_NONE, false);
key_code == SDLK_Z)
{
@ -139,8 +171,117 @@ int GetGameLayout()
u8 MapVirtualKeyToHID(u8 virtual_key, int host_layout, int game_layout)
{
#ifdef __APPLE__
static const std::map<u8, u8> VK_TO_HID{{kVK_ANSI_A, 0x04},
{kVK_ANSI_B, 0x05},
{kVK_ANSI_C, 0x06},
{kVK_ANSI_D, 0x07},
{kVK_ANSI_E, 0x08},
{kVK_ANSI_F, 0x09},
{kVK_ANSI_G, 0x0a},
{kVK_ANSI_H, 0x0b},
{kVK_ANSI_I, 0x0c},
{kVK_ANSI_J, 0x0d},
{kVK_ANSI_K, 0x0e},
{kVK_ANSI_L, 0x0f},
{kVK_ANSI_M, 0x10},
{kVK_ANSI_N, 0x11},
{kVK_ANSI_O, 0x12},
{kVK_ANSI_P, 0x13},
{kVK_ANSI_Q, 0x14},
{kVK_ANSI_R, 0x15},
{kVK_ANSI_S, 0x16},
{kVK_ANSI_T, 0x17},
{kVK_ANSI_U, 0x18},
{kVK_ANSI_V, 0x19},
{kVK_ANSI_W, 0x1a},
{kVK_ANSI_X, 0x1b},
{kVK_ANSI_Y, 0x1c},
{kVK_ANSI_Z, 0x1d},
{kVK_ANSI_1, 0x1e},
{kVK_ANSI_2, 0x1f},
{kVK_ANSI_3, 0x20},
{kVK_ANSI_4, 0x21},
{kVK_ANSI_5, 0x22},
{kVK_ANSI_6, 0x23},
{kVK_ANSI_7, 0x24},
{kVK_ANSI_8, 0x25},
{kVK_ANSI_9, 0x26},
{kVK_ANSI_0, 0x27},
{kVK_Return, 0x28},
{kVK_Escape, 0x29},
{kVK_Delete, 0x2a},
{kVK_Tab, 0x2b},
{kVK_Space, 0x2c},
{kVK_ANSI_Minus, 0x2d},
{kVK_ANSI_Equal, 0x2e},
{kVK_ANSI_LeftBracket, 0x2f},
{kVK_ANSI_RightBracket, 0x30},
{kVK_ANSI_Backslash, 0x31},
// Missing: Non-US #
{kVK_ANSI_Semicolon, 0x33},
{kVK_ANSI_Quote, 0x34},
{kVK_ANSI_Grave, 0x35},
{kVK_ANSI_Comma, 0x36},
{kVK_ANSI_Period, 0x37},
{kVK_ANSI_Slash, 0x38},
{kVK_CapsLock, 0x39},
{kVK_F1, 0x3a},
{kVK_F2, 0x3b},
{kVK_F3, 0x3c},
{kVK_F4, 0x3d},
{kVK_F5, 0x3e},
{kVK_F6, 0x3f},
{kVK_F7, 0x40},
{kVK_F8, 0x41},
{kVK_F9, 0x42},
{kVK_F10, 0x43},
{kVK_F11, 0x44},
{kVK_F12, 0x45},
// Missing: PrintScreen, Scroll Lock, Pause
{kVK_Help, 0x49},
{kVK_Home, 0x4a},
{kVK_PageUp, 0x4b},
{kVK_ForwardDelete, 0x4c},
{kVK_End, 0x4d},
{kVK_PageDown, 0x4e},
{kVK_RightArrow, 0x4f},
{kVK_LeftArrow, 0x50},
{kVK_DownArrow, 0x51},
{kVK_UpArrow, 0x52},
{kVK_ANSI_KeypadClear, 0x53},
{kVK_ANSI_KeypadDivide, 0x54},
{kVK_ANSI_KeypadMultiply, 0x55},
{kVK_ANSI_KeypadMinus, 0x56},
{kVK_ANSI_KeypadPlus, 0x57},
{kVK_ANSI_KeypadEnter, 0x58},
{kVK_ANSI_Keypad1, 0x59},
{kVK_ANSI_Keypad2, 0x5a},
{kVK_ANSI_Keypad3, 0x5b},
{kVK_ANSI_Keypad4, 0x5c},
{kVK_ANSI_Keypad5, 0x5d},
{kVK_ANSI_Keypad6, 0x5e},
{kVK_ANSI_Keypad7, 0x5f},
{kVK_ANSI_Keypad8, 0x60},
{kVK_ANSI_Keypad9, 0x61},
{kVK_ANSI_Keypad0, 0x62},
{kVK_ANSI_KeypadDecimal, 0x63},
// Missing: Non-US |, Application, Power
{kVK_ANSI_KeypadEquals, 0x67},
{kVK_Control, 0xe0},
{kVK_Shift, 0xe1},
{kVK_Option, 0xe2},
{kVK_Command, 0xe3},
{kVK_RightControl, 0xe4},
{kVK_RightShift, 0xe5},
{kVK_RightOption, 0xe6},
{kVK_RightCommand, 0xe7}};
const auto it{VK_TO_HID.find(virtual_key)};
u8 usage_id = (it == VK_TO_HID.end()) ? 0 : it->second;
#else
// SDL3 keyboard state uses scan codes already based on HID usage id
u8 usage_id = virtual_key;
#endif
if (Config::Get(Config::MAIN_WII_KEYBOARD_TRANSLATION))
usage_id = TranslateUsageID(usage_id, host_layout, game_layout);
return usage_id;
@ -171,7 +312,15 @@ KeyboardContext::~KeyboardContext()
void KeyboardContext::Init()
{
#ifdef HAVE_SDL3
#ifdef __APPLE__
CFNotificationCenterAddObserver(
CFNotificationCenterGetDistributedCenter(), this,
[](CFNotificationCenterRef /* center */, void* /* observer */, CFStringRef /* name */,
const void* /* object */,
CFDictionaryRef /* userInfo */) { KeyboardContext::UpdateLayout(); },
kTISNotifySelectedKeyboardInputSourceChanged, nullptr,
CFNotificationSuspensionBehaviorDeliverImmediately);
#elif defined(HAVE_SDL3)
SDL_Event event{s_sdl_init_event_type};
SDL_PushEvent(&event);
m_keyboard_state = SDL_GetKeyboardState(nullptr);
@ -183,7 +332,9 @@ void KeyboardContext::Init()
void KeyboardContext::Quit()
{
m_is_ready = false;
#ifdef HAVE_SDL3
#ifdef __APPLE__
CFNotificationCenterRemoveEveryObserver(CFNotificationCenterGetDistributedCenter(), this);
#elif defined(HAVE_SDL3)
SDL_Event event{s_sdl_quit_event_type};
SDL_PushEvent(&event);
#endif
@ -209,7 +360,9 @@ void KeyboardContext::NotifyHandlerChanged(const KeyboardContext::HandlerState&
s_handler_state = state;
if (s_keyboard_context.expired())
return;
#ifdef HAVE_SDL3
#ifdef __APPLE__
// Quartz doesn't seem impacted
#elif defined(HAVE_SDL3)
SDL_Event event{s_sdl_update_event_type};
SDL_PushEvent(&event);
#endif
@ -256,7 +409,9 @@ HIDPressedState KeyboardContext::GetPressedState() const
bool KeyboardContext::IsVirtualKeyPressed(int virtual_key) const
{
#ifdef HAVE_SDL3
#ifdef __APPLE__
return CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, CGKeyCode(virtual_key));
#elif defined(HAVE_SDL3)
if (virtual_key >= SDL_SCANCODE_COUNT)
return false;
return m_keyboard_state[virtual_key];
@ -273,6 +428,7 @@ u8 KeyboardContext::PollHIDModifiers() const
using VkHidPair = std::pair<int, u8>;
// References:
// https://stackoverflow.com/a/16125341
// https://wiki.libsdl.org/SDL3/SDL_Scancode
// https://www.usb.org/document-library/device-class-definition-hid-111
//
@ -286,7 +442,11 @@ u8 KeyboardContext::PollHIDModifiers() const
// Bit 6 - RIGHT ALT
// Bit 7 - RIGHT GUI
static const std::vector<VkHidPair> MODIFIERS_MAP{
#ifdef HAVE_SDL3
#ifdef __APPLE__
{kVK_Control, 0x01}, {kVK_Shift, 0x02}, {kVK_Option, 0x04},
{kVK_Command, 0x08}, {kVK_RightControl, 0x10}, {kVK_RightShift, 0x20},
{kVK_RightOption, 0x40}, {kVK_RightCommand, 0x80}
#elif defined(HAVE_SDL3)
{SDL_SCANCODE_LCTRL, 0x01}, {SDL_SCANCODE_LSHIFT, 0x02}, {SDL_SCANCODE_LALT, 0x04},
{SDL_SCANCODE_LGUI, 0x08}, {SDL_SCANCODE_RCTRL, 0x10}, {SDL_SCANCODE_RSHIFT, 0x20},
{SDL_SCANCODE_RALT, 0x40}, {SDL_SCANCODE_RGUI, 0x80}
@ -309,17 +469,35 @@ HIDPressedKeys KeyboardContext::PollHIDPressedKeys() const
HIDPressedKeys pressed_keys{};
auto it = pressed_keys.begin();
#ifdef HAVE_SDL3
const std::size_t begin = SDL_SCANCODE_A;
const std::size_t end = SDL_SCANCODE_LCTRL;
#ifdef __APPLE__
// See comment about <HIToolbox/Events.h> keycodes in QuartzKeyboardAndMouse.mm
static auto KEYCODES = std::ranges::filter_view(std::ranges::iota_view{0, 0x7f}, [](int keycode) {
switch (keycode)
{
case kVK_Command:
case kVK_RightCommand:
case kVK_Shift:
case kVK_RightShift:
case kVK_Option:
case kVK_RightOption:
case kVK_Control:
case kVK_RightControl:
return false;
default:
return true;
}
});
static const std::vector<int> VIRTUAL_KEYS{KEYCODES.begin(), KEYCODES.end()};
#elif defined(HAVE_SDL3)
static constexpr std::ranges::iota_view SCANCODES{int(SDL_SCANCODE_A), int(SDL_SCANCODE_LCTRL)};
static const std::vector<int> VIRTUAL_KEYS{SCANCODES.begin(), SCANCODES.end()};
#else
const std::size_t begin = 0;
const std::size_t end = 0;
static constexpr std::vector<int> VIRTUAL_KEYS{};
#endif
for (std::size_t virtual_key = begin; virtual_key < end; ++virtual_key)
for (int virtual_key : VIRTUAL_KEYS)
{
if (!IsVirtualKeyPressed(static_cast<int>(virtual_key)))
if (!IsVirtualKeyPressed(virtual_key))
continue;
*it = MapVirtualKeyToHID(static_cast<u8>(virtual_key), m_host_layout, m_game_layout);

View File

@ -55,7 +55,7 @@ enum
namespace KeyboardLayout
{
enum
enum LayoutEnum
{
AUTO = 0,
QWERTY = 1,