[fix] fix left/right modifier key injection while preserving scan code metadata

This commit is contained in:
dijunkun
2026-05-06 17:52:31 +08:00
parent b7a031bb7f
commit b1d956af2c
14 changed files with 354 additions and 75 deletions
@@ -37,7 +37,7 @@ static int KeyboardEventHandler(Display* display, XEvent* event) {
bool is_key_down = (event->xkey.type == KeyPress);
if (g_on_key_action) {
g_on_key_action(key_code, is_key_down, g_user_ptr);
g_on_key_action(key_code, is_key_down, 0, false, g_user_ptr);
}
}
return 0;
@@ -146,7 +146,10 @@ int KeyboardCapturer::Unhook() {
return 0;
}
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down,
uint32_t scan_code, bool extended) {
(void)scan_code;
(void)extended;
if (IsWaylandSession()) {
if (!use_wayland_portal_ && !wayland_init_attempted_) {
wayland_init_attempted_ = true;
@@ -159,7 +162,7 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
}
if (use_wayland_portal_) {
return SendWaylandKeyboardCommand(key_code, is_down);
return SendWaylandKeyboardCommand(key_code, is_down, scan_code, extended);
}
}
@@ -32,12 +32,15 @@ class KeyboardCapturer : public DeviceController {
public:
virtual int Hook(OnKeyAction on_key_action, void* user_ptr);
virtual int Unhook();
virtual int SendKeyboardCommand(int key_code, bool is_down);
virtual int SendKeyboardCommand(int key_code, bool is_down,
uint32_t scan_code = 0,
bool extended = false);
private:
bool InitWaylandPortal();
void CleanupWaylandPortal();
int SendWaylandKeyboardCommand(int key_code, bool is_down);
int SendWaylandKeyboardCommand(int key_code, bool is_down, uint32_t scan_code,
bool extended);
bool NotifyWaylandKeyboardKeysym(int keysym, uint32_t state);
bool NotifyWaylandKeyboardKeycode(int keycode, uint32_t state);
bool SendWaylandPortalVoidCall(const char* method_name,
@@ -575,8 +575,12 @@ void KeyboardCapturer::CleanupWaylandPortal() {
wayland_session_handle_.clear();
}
int KeyboardCapturer::SendWaylandKeyboardCommand(int key_code, bool is_down) {
int KeyboardCapturer::SendWaylandKeyboardCommand(int key_code, bool is_down,
uint32_t scan_code,
bool extended) {
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
(void)scan_code;
(void)extended;
if (!dbus_connection_ || wayland_session_handle_.empty()) {
return -1;
}
@@ -613,6 +617,8 @@ int KeyboardCapturer::SendWaylandKeyboardCommand(int key_code, bool is_down) {
#else
(void)key_code;
(void)is_down;
(void)scan_code;
(void)extended;
return -1;
#endif
}
@@ -119,7 +119,7 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type,
CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode));
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);
g_on_key_action(vk_code, is_key_down, 0, false, g_user_ptr);
}
} else if (type == kCGEventFlagsChanged) {
CGEventFlags current_flags = CGEventGetFlags(event);
@@ -135,35 +135,40 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type,
bool caps_lock_state = (current_flags & kCGEventFlagMaskAlphaShift) != 0;
if (caps_lock_state != keyboard_capturer->caps_lock_flag_) {
keyboard_capturer->caps_lock_flag_ = caps_lock_state;
g_on_key_action(vk_code, keyboard_capturer->caps_lock_flag_, g_user_ptr);
g_on_key_action(vk_code, keyboard_capturer->caps_lock_flag_, 0, false,
g_user_ptr);
}
// shift
bool shift_state = (current_flags & kCGEventFlagMaskShift) != 0;
if (shift_state != keyboard_capturer->shift_flag_) {
keyboard_capturer->shift_flag_ = shift_state;
g_on_key_action(vk_code, keyboard_capturer->shift_flag_, g_user_ptr);
g_on_key_action(vk_code, keyboard_capturer->shift_flag_, 0, false,
g_user_ptr);
}
// control
bool control_state = (current_flags & kCGEventFlagMaskControl) != 0;
if (control_state != keyboard_capturer->control_flag_) {
keyboard_capturer->control_flag_ = control_state;
g_on_key_action(vk_code, keyboard_capturer->control_flag_, g_user_ptr);
g_on_key_action(vk_code, keyboard_capturer->control_flag_, 0, false,
g_user_ptr);
}
// option
bool option_state = (current_flags & kCGEventFlagMaskAlternate) != 0;
if (option_state != keyboard_capturer->option_flag_) {
keyboard_capturer->option_flag_ = option_state;
g_on_key_action(vk_code, keyboard_capturer->option_flag_, g_user_ptr);
g_on_key_action(vk_code, keyboard_capturer->option_flag_, 0, false,
g_user_ptr);
}
// command
bool command_state = (current_flags & kCGEventFlagMaskCommand) != 0;
if (command_state != keyboard_capturer->command_flag_) {
keyboard_capturer->command_flag_ = command_state;
g_on_key_action(vk_code, keyboard_capturer->command_flag_, g_user_ptr);
g_on_key_action(vk_code, keyboard_capturer->command_flag_, 0, false,
g_user_ptr);
}
}
@@ -264,7 +269,10 @@ inline bool IsFunctionKey(int key_code) {
}
}
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down,
uint32_t scan_code, bool extended) {
(void)scan_code;
(void)extended;
if (vkCodeToCGKeyCode.find(key_code) != vkCodeToCGKeyCode.end()) {
CGKeyCode cg_key_code = vkCodeToCGKeyCode[key_code];
CGEventRef event = CGEventCreateKeyboardEvent(NULL, cg_key_code, is_down);
@@ -21,7 +21,9 @@ class KeyboardCapturer : public DeviceController {
public:
virtual int Hook(OnKeyAction on_key_action, void* user_ptr);
virtual int Unhook();
virtual int SendKeyboardCommand(int key_code, bool is_down);
virtual int SendKeyboardCommand(int key_code, bool is_down,
uint32_t scan_code = 0,
bool extended = false);
private:
CFMachPortRef event_tap_ = nullptr;
@@ -7,14 +7,56 @@ namespace crossdesk {
static OnKeyAction g_on_key_action = nullptr;
static void* g_user_ptr = nullptr;
static int NormalizeModifierVkCode(const KBDLLHOOKSTRUCT* kb_data) {
if (kb_data == nullptr) {
return -1;
}
if (kb_data->vkCode != VK_SHIFT && kb_data->vkCode != VK_CONTROL &&
kb_data->vkCode != VK_MENU) {
return static_cast<int>(kb_data->vkCode);
}
UINT scan_code = static_cast<UINT>(kb_data->scanCode & 0xFF);
if ((kb_data->flags & LLKHF_EXTENDED) != 0) {
scan_code |= 0xE000;
}
const UINT normalized_vk = MapVirtualKeyW(scan_code, MAPVK_VSC_TO_VK_EX);
if (normalized_vk != 0) {
return static_cast<int>(normalized_vk);
}
return static_cast<int>(kb_data->vkCode);
}
static bool PreferSideSpecificVkInjection(int key_code) {
switch (key_code) {
case VK_LSHIFT:
case VK_RSHIFT:
case VK_LCONTROL:
case VK_RCONTROL:
case VK_LMENU:
case VK_RMENU:
case VK_LWIN:
case VK_RWIN:
return true;
default:
return false;
}
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode == HC_ACTION && g_on_key_action) {
KBDLLHOOKSTRUCT* kbData = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
const int key_code = NormalizeModifierVkCode(kbData);
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
g_on_key_action(kbData->vkCode, true, g_user_ptr);
g_on_key_action(key_code, true, kbData->scanCode,
(kbData->flags & LLKHF_EXTENDED) != 0, g_user_ptr);
} else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
g_on_key_action(kbData->vkCode, false, g_user_ptr);
g_on_key_action(key_code, false, kbData->scanCode,
(kbData->flags & LLKHF_EXTENDED) != 0, g_user_ptr);
}
return 1;
}
@@ -49,20 +91,40 @@ int KeyboardCapturer::Unhook() {
}
// apply remote keyboard commands to the local machine
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down,
uint32_t scan_code, bool extended) {
INPUT input = {0};
input.type = INPUT_KEYBOARD;
input.ki.wVk = (WORD)key_code;
const UINT scan_code =
MapVirtualKeyW(static_cast<UINT>(key_code), MAPVK_VK_TO_VSC_EX);
if (scan_code != 0) {
const bool prefer_vk = PreferSideSpecificVkInjection(key_code);
const UINT resolved_scan_code =
scan_code != 0
? static_cast<UINT>(scan_code & 0xFF) | (extended ? 0xE000u : 0u)
: MapVirtualKeyW(static_cast<UINT>(key_code), MAPVK_VK_TO_VSC_EX);
if (scan_code != 0 && !prefer_vk) {
input.ki.wVk = 0;
input.ki.wScan = static_cast<WORD>(scan_code & 0xFF);
input.ki.dwFlags |= KEYEVENTF_SCANCODE;
if ((scan_code & 0xFF00) != 0) {
if (extended) {
input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
}
} else {
input.ki.wVk = (WORD)key_code;
if (prefer_vk && resolved_scan_code != 0) {
input.ki.wScan = static_cast<WORD>(resolved_scan_code & 0xFF);
if ((resolved_scan_code & 0xFF00) != 0) {
input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
}
} else if (resolved_scan_code != 0) {
input.ki.wVk = 0;
input.ki.wScan = static_cast<WORD>(resolved_scan_code & 0xFF);
input.ki.dwFlags |= KEYEVENTF_SCANCODE;
if ((resolved_scan_code & 0xFF00) != 0) {
input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
}
}
}
if (!is_down) {
@@ -21,7 +21,9 @@ class KeyboardCapturer : public DeviceController {
public:
virtual int Hook(OnKeyAction on_key_action, void* user_ptr);
virtual int Unhook();
virtual int SendKeyboardCommand(int key_code, bool is_down);
virtual int SendKeyboardCommand(int key_code, bool is_down,
uint32_t scan_code = 0,
bool extended = false);
private:
HHOOK keyboard_hook_ = nullptr;