diff --git a/src/device_controller/keyboard/mac/keyboard_capturer.cpp b/src/device_controller/keyboard/mac/keyboard_capturer.cpp index f570a9c..99df50a 100644 --- a/src/device_controller/keyboard/mac/keyboard_capturer.cpp +++ b/src/device_controller/keyboard/mac/keyboard_capturer.cpp @@ -1,5 +1,7 @@ #include "keyboard_capturer.h" +#include + #include "keyboard_converter.h" #include "rd_log.h" @@ -7,6 +9,96 @@ namespace crossdesk { static OnKeyAction g_on_key_action = nullptr; static void* g_user_ptr = nullptr; +static std::unordered_map g_unmapped_keycode_to_vk; + +static int VkCodeFromUnicode(UniChar ch) { + if (ch >= 'a' && ch <= 'z') { + return static_cast(ch - 'a' + 'A'); + } + if (ch >= 'A' && ch <= 'Z') { + return static_cast(ch); + } + if (ch >= '0' && ch <= '9') { + return static_cast(ch); + } + + switch (ch) { + case ' ': + return 0x20; // VK_SPACE + case '-': + case '_': + return 0xBD; // VK_OEM_MINUS + case '=': + case '+': + return 0xBB; // VK_OEM_PLUS + case '[': + case '{': + return 0xDB; // VK_OEM_4 + case ']': + case '}': + return 0xDD; // VK_OEM_6 + case '\\': + case '|': + return 0xDC; // VK_OEM_5 + case ';': + case ':': + return 0xBA; // VK_OEM_1 + case '\'': + case '"': + return 0xDE; // VK_OEM_7 + case ',': + case '<': + return 0xBC; // VK_OEM_COMMA + case '.': + case '>': + return 0xBE; // VK_OEM_PERIOD + case '/': + case '?': + return 0xBF; // VK_OEM_2 + case '`': + case '~': + return 0xC0; // VK_OEM_3 + default: + return -1; + } +} + +static int ResolveVkCodeFromMacEvent(CGEventRef event, CGKeyCode key_code, + bool is_key_down) { + auto key_it = CGKeyCodeToVkCode.find(key_code); + if (key_it != CGKeyCodeToVkCode.end()) { + if (is_key_down) { + g_unmapped_keycode_to_vk.erase(static_cast(key_code)); + } + return key_it->second; + } + + int vk_code = -1; + UniChar chars[4] = {0}; + UniCharCount char_count = 0; + CGEventKeyboardGetUnicodeString(event, 4, &char_count, chars); + if (char_count > 0) { + vk_code = VkCodeFromUnicode(chars[0]); + } + + if (vk_code < 0) { + auto fallback_it = + g_unmapped_keycode_to_vk.find(static_cast(key_code)); + if (fallback_it != g_unmapped_keycode_to_vk.end()) { + vk_code = fallback_it->second; + } + } + + if (vk_code >= 0) { + if (is_key_down) { + g_unmapped_keycode_to_vk[static_cast(key_code)] = vk_code; + } else { + g_unmapped_keycode_to_vk.erase(static_cast(key_code)); + } + } + + return vk_code; +} CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* userInfo) { @@ -22,11 +114,12 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, } if (type == kCGEventKeyDown || type == kCGEventKeyUp) { + const bool is_key_down = (type == kCGEventKeyDown); CGKeyCode key_code = static_cast( CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode)); - auto key_it = CGKeyCodeToVkCode.find(key_code); - if (key_it != CGKeyCodeToVkCode.end()) { - g_on_key_action(key_it->second, type == kCGEventKeyDown, g_user_ptr); + int vk_code = ResolveVkCodeFromMacEvent(event, key_code, is_key_down); + if (vk_code >= 0) { + g_on_key_action(vk_code, is_key_down, g_user_ptr); } } else if (type == kCGEventFlagsChanged) { CGEventFlags current_flags = CGEventGetFlags(event); @@ -87,6 +180,7 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { return 0; } + g_unmapped_keycode_to_vk.clear(); g_on_key_action = on_key_action; g_user_ptr = user_ptr; @@ -127,6 +221,7 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { } int KeyboardCapturer::Unhook() { + g_unmapped_keycode_to_vk.clear(); g_on_key_action = nullptr; g_user_ptr = nullptr; diff --git a/src/device_controller/keyboard/windows/keyboard_capturer.cpp b/src/device_controller/keyboard/windows/keyboard_capturer.cpp index 30ddad4..f6338e0 100644 --- a/src/device_controller/keyboard/windows/keyboard_capturer.cpp +++ b/src/device_controller/keyboard/windows/keyboard_capturer.cpp @@ -54,11 +54,28 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) { input.type = INPUT_KEYBOARD; input.ki.wVk = (WORD)key_code; - if (!is_down) { - input.ki.dwFlags = KEYEVENTF_KEYUP; + const UINT scan_code = + MapVirtualKeyW(static_cast(key_code), MAPVK_VK_TO_VSC_EX); + if (scan_code != 0) { + input.ki.wVk = 0; + input.ki.wScan = static_cast(scan_code & 0xFF); + input.ki.dwFlags |= KEYEVENTF_SCANCODE; + if ((scan_code & 0xFF00) != 0) { + input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + } + } + + if (!is_down) { + input.ki.dwFlags |= KEYEVENTF_KEYUP; + } + + UINT sent = SendInput(1, &input, sizeof(INPUT)); + if (sent != 1) { + LOG_WARN("SendInput failed for key_code={}, is_down={}, err={}", key_code, + is_down, GetLastError()); + return -1; } - SendInput(1, &input, sizeof(INPUT)); return 0; } -} // namespace crossdesk \ No newline at end of file +} // namespace crossdesk