mirror of
https://github.com/morgan9e/FreeRDP
synced 2026-04-14 00:14:11 +09:00
[client,x11] add keysym-based fallback for keyboard mapping
When using X11 forwarding from non-standard X servers (e.g. XQuartz, Xephyr), the XKB key names reported by the remote server do not match the expected evdev names, causing all keys to map to RDP_SCANCODE_UNKNOWN. Add a keysym-to-scancode lookup table as fallback. Unlike XKB key names, keysyms are standardized across X11 implementations. The fallback only fills keycodes that remain unmapped after the XKB pass, so standard setups are unaffected. Tested with X11 forwarding from Exegol (Docker) via XQuartz on macOS, and natively on Linux and macOS. Related to #4215 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -325,6 +325,181 @@ static const struct x11_key_scancode_t XKB_KEY_NAME_SCANCODE_TABLE[] = {
|
||||
{ "I255", RDP_SCANCODE_UNKNOWN } /* 255: I255 [XF86RFKill] */
|
||||
};
|
||||
|
||||
struct x11_keysym_scancode_t
|
||||
{
|
||||
KeySym keysym;
|
||||
DWORD sc;
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
static const struct x11_keysym_scancode_t KEYSYM_SCANCODE_TABLE[] =
|
||||
{
|
||||
{ XK_a, RDP_SCANCODE_KEY_A },
|
||||
{ XK_b, RDP_SCANCODE_KEY_B },
|
||||
{ XK_c, RDP_SCANCODE_KEY_C },
|
||||
{ XK_d, RDP_SCANCODE_KEY_D },
|
||||
{ XK_e, RDP_SCANCODE_KEY_E },
|
||||
{ XK_f, RDP_SCANCODE_KEY_F },
|
||||
{ XK_g, RDP_SCANCODE_KEY_G },
|
||||
{ XK_h, RDP_SCANCODE_KEY_H },
|
||||
{ XK_i, RDP_SCANCODE_KEY_I },
|
||||
{ XK_j, RDP_SCANCODE_KEY_J },
|
||||
{ XK_k, RDP_SCANCODE_KEY_K },
|
||||
{ XK_l, RDP_SCANCODE_KEY_L },
|
||||
{ XK_m, RDP_SCANCODE_KEY_M },
|
||||
{ XK_n, RDP_SCANCODE_KEY_N },
|
||||
{ XK_o, RDP_SCANCODE_KEY_O },
|
||||
{ XK_p, RDP_SCANCODE_KEY_P },
|
||||
{ XK_q, RDP_SCANCODE_KEY_Q },
|
||||
{ XK_r, RDP_SCANCODE_KEY_R },
|
||||
{ XK_s, RDP_SCANCODE_KEY_S },
|
||||
{ XK_t, RDP_SCANCODE_KEY_T },
|
||||
{ XK_u, RDP_SCANCODE_KEY_U },
|
||||
{ XK_v, RDP_SCANCODE_KEY_V },
|
||||
{ XK_w, RDP_SCANCODE_KEY_W },
|
||||
{ XK_x, RDP_SCANCODE_KEY_X },
|
||||
{ XK_y, RDP_SCANCODE_KEY_Y },
|
||||
{ XK_z, RDP_SCANCODE_KEY_Z },
|
||||
{ XK_A, RDP_SCANCODE_KEY_A },
|
||||
{ XK_B, RDP_SCANCODE_KEY_B },
|
||||
{ XK_C, RDP_SCANCODE_KEY_C },
|
||||
{ XK_D, RDP_SCANCODE_KEY_D },
|
||||
{ XK_E, RDP_SCANCODE_KEY_E },
|
||||
{ XK_F, RDP_SCANCODE_KEY_F },
|
||||
{ XK_G, RDP_SCANCODE_KEY_G },
|
||||
{ XK_H, RDP_SCANCODE_KEY_H },
|
||||
{ XK_I, RDP_SCANCODE_KEY_I },
|
||||
{ XK_J, RDP_SCANCODE_KEY_J },
|
||||
{ XK_K, RDP_SCANCODE_KEY_K },
|
||||
{ XK_L, RDP_SCANCODE_KEY_L },
|
||||
{ XK_M, RDP_SCANCODE_KEY_M },
|
||||
{ XK_N, RDP_SCANCODE_KEY_N },
|
||||
{ XK_O, RDP_SCANCODE_KEY_O },
|
||||
{ XK_P, RDP_SCANCODE_KEY_P },
|
||||
{ XK_Q, RDP_SCANCODE_KEY_Q },
|
||||
{ XK_R, RDP_SCANCODE_KEY_R },
|
||||
{ XK_S, RDP_SCANCODE_KEY_S },
|
||||
{ XK_T, RDP_SCANCODE_KEY_T },
|
||||
{ XK_U, RDP_SCANCODE_KEY_U },
|
||||
{ XK_V, RDP_SCANCODE_KEY_V },
|
||||
{ XK_W, RDP_SCANCODE_KEY_W },
|
||||
{ XK_X, RDP_SCANCODE_KEY_X },
|
||||
{ XK_Y, RDP_SCANCODE_KEY_Y },
|
||||
{ XK_Z, RDP_SCANCODE_KEY_Z },
|
||||
{ XK_0, RDP_SCANCODE_KEY_0 },
|
||||
{ XK_1, RDP_SCANCODE_KEY_1 },
|
||||
{ XK_2, RDP_SCANCODE_KEY_2 },
|
||||
{ XK_3, RDP_SCANCODE_KEY_3 },
|
||||
{ XK_4, RDP_SCANCODE_KEY_4 },
|
||||
{ XK_5, RDP_SCANCODE_KEY_5 },
|
||||
{ XK_6, RDP_SCANCODE_KEY_6 },
|
||||
{ XK_7, RDP_SCANCODE_KEY_7 },
|
||||
{ XK_8, RDP_SCANCODE_KEY_8 },
|
||||
{ XK_9, RDP_SCANCODE_KEY_9 },
|
||||
{ XK_F1, RDP_SCANCODE_F1 },
|
||||
{ XK_F2, RDP_SCANCODE_F2 },
|
||||
{ XK_F3, RDP_SCANCODE_F3 },
|
||||
{ XK_F4, RDP_SCANCODE_F4 },
|
||||
{ XK_F5, RDP_SCANCODE_F5 },
|
||||
{ XK_F6, RDP_SCANCODE_F6 },
|
||||
{ XK_F7, RDP_SCANCODE_F7 },
|
||||
{ XK_F8, RDP_SCANCODE_F8 },
|
||||
{ XK_F9, RDP_SCANCODE_F9 },
|
||||
{ XK_F10, RDP_SCANCODE_F10 },
|
||||
{ XK_F11, RDP_SCANCODE_F11 },
|
||||
{ XK_F12, RDP_SCANCODE_F12 },
|
||||
{ XK_F13, RDP_SCANCODE_F13 },
|
||||
{ XK_F14, RDP_SCANCODE_F14 },
|
||||
{ XK_F15, RDP_SCANCODE_F15 },
|
||||
{ XK_F16, RDP_SCANCODE_F16 },
|
||||
{ XK_F17, RDP_SCANCODE_F17 },
|
||||
{ XK_F18, RDP_SCANCODE_F18 },
|
||||
{ XK_F19, RDP_SCANCODE_F19 },
|
||||
{ XK_F20, RDP_SCANCODE_F20 },
|
||||
{ XK_F21, RDP_SCANCODE_F21 },
|
||||
{ XK_F22, RDP_SCANCODE_F22 },
|
||||
{ XK_F23, RDP_SCANCODE_F23 },
|
||||
{ XK_F24, RDP_SCANCODE_F24 },
|
||||
{ XK_Shift_L, RDP_SCANCODE_LSHIFT },
|
||||
{ XK_Shift_R, RDP_SCANCODE_RSHIFT },
|
||||
{ XK_Control_L, RDP_SCANCODE_LCONTROL },
|
||||
{ XK_Control_R, RDP_SCANCODE_RCONTROL },
|
||||
{ XK_Alt_L, RDP_SCANCODE_LMENU },
|
||||
{ XK_Alt_R, RDP_SCANCODE_RMENU },
|
||||
{ XK_Super_L, RDP_SCANCODE_LWIN },
|
||||
{ XK_Super_R, RDP_SCANCODE_RWIN },
|
||||
{ XK_Meta_L, RDP_SCANCODE_LWIN },
|
||||
{ XK_Meta_R, RDP_SCANCODE_RWIN },
|
||||
{ XK_Hyper_L, RDP_SCANCODE_LWIN },
|
||||
{ XK_Hyper_R, RDP_SCANCODE_RWIN },
|
||||
{ XK_Menu, RDP_SCANCODE_APPS },
|
||||
{ XK_ISO_Level3_Shift, RDP_SCANCODE_RMENU },
|
||||
{ XK_Mode_switch, RDP_SCANCODE_RMENU },
|
||||
{ XK_Caps_Lock, RDP_SCANCODE_CAPSLOCK },
|
||||
{ XK_Num_Lock, RDP_SCANCODE_NUMLOCK },
|
||||
{ XK_Scroll_Lock, RDP_SCANCODE_SCROLLLOCK },
|
||||
{ XK_Return, RDP_SCANCODE_RETURN },
|
||||
{ XK_Escape, RDP_SCANCODE_ESCAPE },
|
||||
{ XK_BackSpace, RDP_SCANCODE_BACKSPACE },
|
||||
{ XK_Tab, RDP_SCANCODE_TAB },
|
||||
{ XK_ISO_Left_Tab, RDP_SCANCODE_TAB },
|
||||
{ XK_space, RDP_SCANCODE_SPACE },
|
||||
{ XK_Insert, RDP_SCANCODE_INSERT },
|
||||
{ XK_Delete, RDP_SCANCODE_DELETE },
|
||||
{ XK_Pause, RDP_SCANCODE_PAUSE },
|
||||
{ XK_Print, RDP_SCANCODE_PRINTSCREEN },
|
||||
{ XK_Help, RDP_SCANCODE_HELP },
|
||||
{ XK_Home, RDP_SCANCODE_HOME },
|
||||
{ XK_End, RDP_SCANCODE_END },
|
||||
{ XK_Prior, RDP_SCANCODE_PRIOR },
|
||||
{ XK_Next, RDP_SCANCODE_NEXT },
|
||||
{ XK_Up, RDP_SCANCODE_UP },
|
||||
{ XK_Down, RDP_SCANCODE_DOWN },
|
||||
{ XK_Left, RDP_SCANCODE_LEFT },
|
||||
{ XK_Right, RDP_SCANCODE_RIGHT },
|
||||
{ XK_minus, RDP_SCANCODE_OEM_MINUS },
|
||||
{ XK_equal, RDP_SCANCODE_OEM_PLUS },
|
||||
{ XK_bracketleft, RDP_SCANCODE_OEM_4 },
|
||||
{ XK_bracketright, RDP_SCANCODE_OEM_6 },
|
||||
{ XK_backslash, RDP_SCANCODE_OEM_5 },
|
||||
{ XK_semicolon, RDP_SCANCODE_OEM_1 },
|
||||
{ XK_apostrophe, RDP_SCANCODE_OEM_7 },
|
||||
{ XK_grave, RDP_SCANCODE_OEM_3 },
|
||||
{ XK_comma, RDP_SCANCODE_OEM_COMMA },
|
||||
{ XK_period, RDP_SCANCODE_OEM_PERIOD },
|
||||
{ XK_slash, RDP_SCANCODE_OEM_2 },
|
||||
{ XK_less, RDP_SCANCODE_OEM_102 },
|
||||
{ XK_KP_0, RDP_SCANCODE_NUMPAD0 },
|
||||
{ XK_KP_Insert, RDP_SCANCODE_NUMPAD0 },
|
||||
{ XK_KP_1, RDP_SCANCODE_NUMPAD1 },
|
||||
{ XK_KP_End, RDP_SCANCODE_NUMPAD1 },
|
||||
{ XK_KP_2, RDP_SCANCODE_NUMPAD2 },
|
||||
{ XK_KP_Down, RDP_SCANCODE_NUMPAD2 },
|
||||
{ XK_KP_3, RDP_SCANCODE_NUMPAD3 },
|
||||
{ XK_KP_Next, RDP_SCANCODE_NUMPAD3 },
|
||||
{ XK_KP_4, RDP_SCANCODE_NUMPAD4 },
|
||||
{ XK_KP_Left, RDP_SCANCODE_NUMPAD4 },
|
||||
{ XK_KP_5, RDP_SCANCODE_NUMPAD5 },
|
||||
{ XK_KP_Begin, RDP_SCANCODE_NUMPAD5 },
|
||||
{ XK_KP_6, RDP_SCANCODE_NUMPAD6 },
|
||||
{ XK_KP_Right, RDP_SCANCODE_NUMPAD6 },
|
||||
{ XK_KP_7, RDP_SCANCODE_NUMPAD7 },
|
||||
{ XK_KP_Home, RDP_SCANCODE_NUMPAD7 },
|
||||
{ XK_KP_8, RDP_SCANCODE_NUMPAD8 },
|
||||
{ XK_KP_Up, RDP_SCANCODE_NUMPAD8 },
|
||||
{ XK_KP_9, RDP_SCANCODE_NUMPAD9 },
|
||||
{ XK_KP_Prior, RDP_SCANCODE_NUMPAD9 },
|
||||
{ XK_KP_Enter, RDP_SCANCODE_RETURN_KP },
|
||||
{ XK_KP_Multiply, RDP_SCANCODE_MULTIPLY },
|
||||
{ XK_KP_Add, RDP_SCANCODE_ADD },
|
||||
{ XK_KP_Subtract, RDP_SCANCODE_SUBTRACT },
|
||||
{ XK_KP_Decimal, RDP_SCANCODE_DECIMAL },
|
||||
{ XK_KP_Delete, RDP_SCANCODE_DECIMAL },
|
||||
{ XK_KP_Separator, RDP_SCANCODE_DECIMAL },
|
||||
{ XK_KP_Divide, RDP_SCANCODE_DIVIDE }
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
static UINT32 xf_keyboard_get_toggle_keys_state(xfContext* xfc);
|
||||
static BOOL xf_keyboard_handle_special_keys(xfContext* xfc, KeySym keysym);
|
||||
static void xf_keyboard_handle_special_keys_release(xfContext* xfc, KeySym keysym);
|
||||
@@ -422,6 +597,24 @@ static int xkb_cmp(const void* pva, const void* pvb)
|
||||
return strcmp(a->name, b->name);
|
||||
}
|
||||
|
||||
static int keysym_cmp(const void* pva, const void* pvb)
|
||||
{
|
||||
const struct x11_keysym_scancode_t* a = pva;
|
||||
const struct x11_keysym_scancode_t* b = pvb;
|
||||
|
||||
if (!a && !b)
|
||||
return 0;
|
||||
if (!a)
|
||||
return 1;
|
||||
if (!b)
|
||||
return -1;
|
||||
if (a->keysym < b->keysym)
|
||||
return -1;
|
||||
if (a->keysym > b->keysym)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static BOOL try_add(xfContext* xfc, size_t offset, const char* xkb_keyname)
|
||||
{
|
||||
WINPR_ASSERT(xfc);
|
||||
@@ -454,6 +647,27 @@ static BOOL try_add(xfContext* xfc, size_t offset, const char* xkb_keyname)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Keysym fallback for X11 forwarding where XKB key names don't match evdev */
|
||||
static DWORD xf_keysym_to_rdp_scancode(KeySym keysym)
|
||||
{
|
||||
static BOOL initialized = FALSE;
|
||||
static struct x11_keysym_scancode_t copy[ARRAYSIZE(KEYSYM_SCANCODE_TABLE)] = { 0 };
|
||||
if (!initialized)
|
||||
{
|
||||
memcpy(copy, KEYSYM_SCANCODE_TABLE, sizeof(copy));
|
||||
qsort(copy, ARRAYSIZE(copy), sizeof(struct x11_keysym_scancode_t), keysym_cmp);
|
||||
initialized = TRUE;
|
||||
}
|
||||
|
||||
struct x11_keysym_scancode_t key = { .keysym = keysym, .sc = 0 };
|
||||
struct x11_keysym_scancode_t* found =
|
||||
bsearch(&key, copy, ARRAYSIZE(copy), sizeof(struct x11_keysym_scancode_t), keysym_cmp);
|
||||
if (found)
|
||||
return found->sc;
|
||||
|
||||
return RDP_SCANCODE_UNKNOWN;
|
||||
}
|
||||
|
||||
static int load_map_from_xkbfile(xfContext* xfc)
|
||||
{
|
||||
WINPR_ASSERT(xfc);
|
||||
@@ -518,6 +732,46 @@ static int load_map_from_xkbfile(xfContext* xfc)
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Keysym-based fallback for unmapped keycodes (e.g. X11 forwarding) */
|
||||
static BOOL load_map_from_keysym(xfContext* xfc)
|
||||
{
|
||||
WINPR_ASSERT(xfc);
|
||||
|
||||
if (!xfc->display)
|
||||
return FALSE;
|
||||
|
||||
BOOL mapped = FALSE;
|
||||
int min_kc = 0;
|
||||
int max_kc = 0;
|
||||
XDisplayKeycodes(xfc->display, &min_kc, &max_kc);
|
||||
|
||||
for (int i = min_kc; i <= max_kc; i++)
|
||||
{
|
||||
if (i < 0 || (size_t)i >= ARRAYSIZE(xfc->X11_KEYCODE_TO_VIRTUAL_SCANCODE))
|
||||
continue;
|
||||
|
||||
if (xfc->X11_KEYCODE_TO_VIRTUAL_SCANCODE[i] != RDP_SCANCODE_UNKNOWN)
|
||||
continue;
|
||||
|
||||
const KeySym ks = XkbKeycodeToKeysym(xfc->display, (KeyCode)i, 0, 0);
|
||||
if (ks == NoSymbol)
|
||||
continue;
|
||||
|
||||
const DWORD sc = xf_keysym_to_rdp_scancode(ks);
|
||||
if (sc != RDP_SCANCODE_UNKNOWN)
|
||||
{
|
||||
xfc->X11_KEYCODE_TO_VIRTUAL_SCANCODE[i] = sc;
|
||||
mapped = TRUE;
|
||||
WLog_Print(xfc->log, WLOG_DEBUG,
|
||||
"keycode: 0x%02x -> keysym: 0x%04lx -> rdp scancode: 0x%08" PRIx32
|
||||
" (keysym fallback)",
|
||||
i, (unsigned long)ks, sc);
|
||||
}
|
||||
}
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
BOOL xf_keyboard_init(xfContext* xfc)
|
||||
{
|
||||
rdpSettings* settings = NULL;
|
||||
@@ -549,7 +803,10 @@ BOOL xf_keyboard_init(xfContext* xfc)
|
||||
}
|
||||
|
||||
const int rc = load_map_from_xkbfile(xfc);
|
||||
if (rc != 0)
|
||||
|
||||
const BOOL keysym_mapped = load_map_from_keysym(xfc);
|
||||
|
||||
if (rc != 0 && !keysym_mapped)
|
||||
return FALSE;
|
||||
|
||||
return xf_keyboard_update_modifier_map(xfc);
|
||||
|
||||
Reference in New Issue
Block a user