20 #include <freerdp/config.h>
26 #include <winpr/assert.h>
27 #include <winpr/crt.h>
29 #include <freerdp/utils/string.h>
30 #include <freerdp/types.h>
31 #include <freerdp/locale/keyboard.h>
32 #include <freerdp/locale/locale.h>
34 #include <freerdp/log.h>
36 #include "liblocale.h"
38 #if defined(__MACOSX__)
39 #include "keyboard_apple.h"
42 #define TAG FREERDP_TAG("locale.keyboard")
45 #include "keyboard_x11.h"
48 #include "keyboard_xkbfile.h"
53 static WINPR_KEYCODE_TYPE maptype = WINPR_KEYCODE_TYPE_NONE;
54 static DWORD VIRTUAL_SCANCODE_TO_X11_KEYCODE[256][2] = { 0 };
55 static DWORD X11_KEYCODE_TO_VIRTUAL_SCANCODE[256] = { 0 };
56 static DWORD REMAPPING_TABLE[0x10000] = { 0 };
58 struct rdp_remap_table
63 struct scancode_map_entry
69 static const struct scancode_map_entry RDP_SCANCODE_MAP[] = {
70 { RDP_SCANCODE_ESCAPE,
"VK_ESCAPE" },
71 { RDP_SCANCODE_KEY_1,
"VK_KEY_1" },
72 { RDP_SCANCODE_KEY_2,
"VK_KEY_2" },
73 { RDP_SCANCODE_KEY_3,
"VK_KEY_3" },
74 { RDP_SCANCODE_KEY_4,
"VK_KEY_4" },
75 { RDP_SCANCODE_KEY_5,
"VK_KEY_5" },
76 { RDP_SCANCODE_KEY_6,
"VK_KEY_6" },
77 { RDP_SCANCODE_KEY_7,
"VK_KEY_7" },
78 { RDP_SCANCODE_KEY_8,
"VK_KEY_8" },
79 { RDP_SCANCODE_KEY_9,
"VK_KEY_9" },
80 { RDP_SCANCODE_KEY_0,
"VK_KEY_0" },
81 { RDP_SCANCODE_OEM_MINUS,
"VK_OEM_MINUS" },
82 { RDP_SCANCODE_OEM_PLUS,
"VK_OEM_PLUS" },
83 { RDP_SCANCODE_BACKSPACE,
"VK_BACK Backspace" },
84 { RDP_SCANCODE_TAB,
"VK_TAB" },
85 { RDP_SCANCODE_KEY_Q,
"VK_KEY_Q" },
86 { RDP_SCANCODE_KEY_W,
"VK_KEY_W" },
87 { RDP_SCANCODE_KEY_E,
"VK_KEY_E" },
88 { RDP_SCANCODE_KEY_R,
"VK_KEY_R" },
89 { RDP_SCANCODE_KEY_T,
"VK_KEY_T" },
90 { RDP_SCANCODE_KEY_Y,
"VK_KEY_Y" },
91 { RDP_SCANCODE_KEY_U,
"VK_KEY_U" },
92 { RDP_SCANCODE_KEY_I,
"VK_KEY_I" },
93 { RDP_SCANCODE_KEY_O,
"VK_KEY_O" },
94 { RDP_SCANCODE_KEY_P,
"VK_KEY_P" },
95 { RDP_SCANCODE_OEM_4,
"VK_OEM_4 '[' on US" },
96 { RDP_SCANCODE_OEM_6,
"VK_OEM_6 ']' on US" },
97 { RDP_SCANCODE_RETURN,
"VK_RETURN Normal Enter" },
98 { RDP_SCANCODE_LCONTROL,
"VK_LCONTROL" },
99 { RDP_SCANCODE_KEY_A,
"VK_KEY_A" },
100 { RDP_SCANCODE_KEY_S,
"VK_KEY_S" },
101 { RDP_SCANCODE_KEY_D,
"VK_KEY_D" },
102 { RDP_SCANCODE_KEY_F,
"VK_KEY_F" },
103 { RDP_SCANCODE_KEY_G,
"VK_KEY_G" },
104 { RDP_SCANCODE_KEY_H,
"VK_KEY_H" },
105 { RDP_SCANCODE_KEY_J,
"VK_KEY_J" },
106 { RDP_SCANCODE_KEY_K,
"VK_KEY_K" },
107 { RDP_SCANCODE_KEY_L,
"VK_KEY_L" },
108 { RDP_SCANCODE_OEM_1,
"VK_OEM_1 ';' on US" },
109 { RDP_SCANCODE_OEM_7,
"VK_OEM_7 on US" },
110 { RDP_SCANCODE_OEM_3,
"VK_OEM_3 Top left, '`' on US, JP DBE_SBCSCHAR" },
111 { RDP_SCANCODE_LSHIFT,
"VK_LSHIFT" },
112 { RDP_SCANCODE_OEM_5,
"VK_OEM_5 Next to Enter, '\' on US" },
113 { RDP_SCANCODE_KEY_Z,
"VK_KEY_Z" },
114 { RDP_SCANCODE_KEY_X,
"VK_KEY_X" },
115 { RDP_SCANCODE_KEY_C,
"VK_KEY_C" },
116 { RDP_SCANCODE_KEY_V,
"VK_KEY_V" },
117 { RDP_SCANCODE_KEY_B,
"VK_KEY_B" },
118 { RDP_SCANCODE_KEY_N,
"VK_KEY_N" },
119 { RDP_SCANCODE_KEY_M,
"VK_KEY_M" },
120 { RDP_SCANCODE_OEM_COMMA,
"VK_OEM_COMMA" },
121 { RDP_SCANCODE_OEM_PERIOD,
"VK_OEM_PERIOD" },
122 { RDP_SCANCODE_OEM_2,
"VK_OEM_2 '/' on US" },
123 { RDP_SCANCODE_RSHIFT,
"VK_RSHIFT" },
124 { RDP_SCANCODE_MULTIPLY,
"VK_MULTIPLY Numerical" },
125 { RDP_SCANCODE_LMENU,
"VK_LMENU Left 'Alt' key" },
126 { RDP_SCANCODE_SPACE,
"VK_SPACE" },
127 { RDP_SCANCODE_CAPSLOCK,
"VK_CAPITAL 'Caps Lock', JP DBE_ALPHANUMERIC" },
128 { RDP_SCANCODE_F1,
"VK_F1" },
129 { RDP_SCANCODE_F2,
"VK_F2" },
130 { RDP_SCANCODE_F3,
"VK_F3" },
131 { RDP_SCANCODE_F4,
"VK_F4" },
132 { RDP_SCANCODE_F5,
"VK_F5" },
133 { RDP_SCANCODE_F6,
"VK_F6" },
134 { RDP_SCANCODE_F7,
"VK_F7" },
135 { RDP_SCANCODE_F8,
"VK_F8" },
136 { RDP_SCANCODE_F9,
"VK_F9" },
137 { RDP_SCANCODE_F10,
"VK_F10" },
138 { RDP_SCANCODE_NUMLOCK,
"VK_NUMLOCK" },
139 { RDP_SCANCODE_SCROLLLOCK,
"VK_SCROLL 'Scroll Lock', JP OEM_SCROLL" },
140 { RDP_SCANCODE_NUMPAD7,
"VK_NUMPAD7" },
141 { RDP_SCANCODE_NUMPAD8,
"VK_NUMPAD8" },
142 { RDP_SCANCODE_NUMPAD9,
"VK_NUMPAD9" },
143 { RDP_SCANCODE_SUBTRACT,
"VK_SUBTRACT" },
144 { RDP_SCANCODE_NUMPAD4,
"VK_NUMPAD4" },
145 { RDP_SCANCODE_NUMPAD5,
"VK_NUMPAD5" },
146 { RDP_SCANCODE_NUMPAD6,
"VK_NUMPAD6" },
147 { RDP_SCANCODE_ADD,
"VK_ADD" },
148 { RDP_SCANCODE_NUMPAD1,
"VK_NUMPAD1" },
149 { RDP_SCANCODE_NUMPAD2,
"VK_NUMPAD2" },
150 { RDP_SCANCODE_NUMPAD3,
"VK_NUMPAD3" },
151 { RDP_SCANCODE_NUMPAD0,
"VK_NUMPAD0" },
152 { RDP_SCANCODE_DECIMAL,
"VK_DECIMAL Numerical, '.' on US" },
153 { RDP_SCANCODE_SYSREQ,
"Sys Req" },
154 { RDP_SCANCODE_OEM_102,
"VK_OEM_102 Lower left '\' on US" },
155 { RDP_SCANCODE_F11,
"VK_F11" },
156 { RDP_SCANCODE_F12,
"VK_F12" },
157 { RDP_SCANCODE_SLEEP,
"VK_SLEEP OEM_8 on FR (undocumented?)" },
158 { RDP_SCANCODE_ZOOM,
"VK_ZOOM (undocumented?)" },
159 { RDP_SCANCODE_HELP,
"VK_HELP (undocumented?)" },
160 { RDP_SCANCODE_F13,
"VK_F13" },
161 { RDP_SCANCODE_F14,
"VK_F14" },
162 { RDP_SCANCODE_F15,
"VK_F15" },
163 { RDP_SCANCODE_F16,
"VK_F16" },
164 { RDP_SCANCODE_F17,
"VK_F17" },
165 { RDP_SCANCODE_F18,
"VK_F18" },
166 { RDP_SCANCODE_F19,
"VK_F19" },
167 { RDP_SCANCODE_F20,
"VK_F20" },
168 { RDP_SCANCODE_F21,
"VK_F21" },
169 { RDP_SCANCODE_F22,
"VK_F22" },
170 { RDP_SCANCODE_F23,
"VK_F23" },
171 { RDP_SCANCODE_F24,
"VK_F24" },
172 { RDP_SCANCODE_HIRAGANA,
"JP DBE_HIRAGANA" },
173 { RDP_SCANCODE_HANJA_KANJI,
"VK_HANJA / VK_KANJI (undocumented?)" },
174 { RDP_SCANCODE_KANA_HANGUL,
"VK_KANA / VK_HANGUL (undocumented?)" },
175 { RDP_SCANCODE_ABNT_C1,
"VK_ABNT_C1 JP OEM_102" },
176 { RDP_SCANCODE_F24_JP,
"JP F24" },
177 { RDP_SCANCODE_CONVERT_JP,
"JP VK_CONVERT" },
178 { RDP_SCANCODE_NONCONVERT_JP,
"JP VK_NONCONVERT" },
179 { RDP_SCANCODE_TAB_JP,
"JP TAB" },
180 { RDP_SCANCODE_BACKSLASH_JP,
"JP OEM_5 ('\')" },
181 { RDP_SCANCODE_ABNT_C2,
"VK_ABNT_C2, JP" },
182 { RDP_SCANCODE_HANJA,
"KR VK_HANJA" },
183 { RDP_SCANCODE_HANGUL,
"KR VK_HANGUL" },
184 { RDP_SCANCODE_RETURN_KP,
"not RDP_SCANCODE_RETURN Numerical Enter" },
185 { RDP_SCANCODE_RCONTROL,
"VK_RCONTROL" },
186 { RDP_SCANCODE_DIVIDE,
"VK_DIVIDE Numerical" },
187 { RDP_SCANCODE_PRINTSCREEN,
"VK_EXECUTE/VK_PRINT/VK_SNAPSHOT Print Screen" },
188 { RDP_SCANCODE_RMENU,
"VK_RMENU Right 'Alt' / 'Alt Gr'" },
189 { RDP_SCANCODE_PAUSE,
"VK_PAUSE Pause / Break (Slightly special handling)" },
190 { RDP_SCANCODE_HOME,
"VK_HOME" },
191 { RDP_SCANCODE_UP,
"VK_UP" },
192 { RDP_SCANCODE_PRIOR,
"VK_PRIOR 'Page Up'" },
193 { RDP_SCANCODE_LEFT,
"VK_LEFT" },
194 { RDP_SCANCODE_RIGHT,
"VK_RIGHT" },
195 { RDP_SCANCODE_END,
"VK_END" },
196 { RDP_SCANCODE_DOWN,
"VK_DOWN" },
197 { RDP_SCANCODE_NEXT,
"VK_NEXT 'Page Down'" },
198 { RDP_SCANCODE_INSERT,
"VK_INSERT" },
199 { RDP_SCANCODE_DELETE,
"VK_DELETE" },
200 { RDP_SCANCODE_NULL,
"<00>" },
201 { RDP_SCANCODE_HELP2,
"Help - documented, different from VK_HELP" },
202 { RDP_SCANCODE_LWIN,
"VK_LWIN" },
203 { RDP_SCANCODE_RWIN,
"VK_RWIN" },
204 { RDP_SCANCODE_APPS,
"VK_APPS Application" },
205 { RDP_SCANCODE_POWER_JP,
"JP POWER" },
206 { RDP_SCANCODE_SLEEP_JP,
"JP SLEEP" },
207 { RDP_SCANCODE_NUMLOCK_EXTENDED,
"should be RDP_SCANCODE_NUMLOCK" },
208 { RDP_SCANCODE_RSHIFT_EXTENDED,
"should be RDP_SCANCODE_RSHIFT" },
209 { RDP_SCANCODE_VOLUME_MUTE,
"VK_VOLUME_MUTE" },
210 { RDP_SCANCODE_VOLUME_DOWN,
"VK_VOLUME_DOWN" },
211 { RDP_SCANCODE_VOLUME_UP,
"VK_VOLUME_UP" },
212 { RDP_SCANCODE_MEDIA_NEXT_TRACK,
"VK_MEDIA_NEXT_TRACK" },
213 { RDP_SCANCODE_MEDIA_PREV_TRACK,
"VK_MEDIA_PREV_TRACK" },
214 { RDP_SCANCODE_MEDIA_STOP,
"VK_MEDIA_MEDIA_STOP" },
215 { RDP_SCANCODE_MEDIA_PLAY_PAUSE,
"VK_MEDIA_MEDIA_PLAY_PAUSE" },
216 { RDP_SCANCODE_BROWSER_BACK,
"VK_BROWSER_BACK" },
217 { RDP_SCANCODE_BROWSER_FORWARD,
"VK_BROWSER_FORWARD" },
218 { RDP_SCANCODE_BROWSER_REFRESH,
"VK_BROWSER_REFRESH" },
219 { RDP_SCANCODE_BROWSER_STOP,
"VK_BROWSER_STOP" },
220 { RDP_SCANCODE_BROWSER_SEARCH,
"VK_BROWSER_SEARCH" },
221 { RDP_SCANCODE_BROWSER_FAVORITES,
"VK_BROWSER_FAVORITES" },
222 { RDP_SCANCODE_BROWSER_HOME,
"VK_BROWSER_HOME" },
223 { RDP_SCANCODE_LAUNCH_MAIL,
"VK_LAUNCH_MAIL" },
224 { RDP_SCANCODE_LAUNCH_MEDIA_SELECT,
"VK_LAUNCH_MEDIA_SELECT" },
225 { RDP_SCANCODE_LAUNCH_APP1,
"VK_LAUNCH_APP1" },
226 { RDP_SCANCODE_LAUNCH_APP2,
"VK_LAUNCH_APP2" },
229 static int freerdp_detect_keyboard(DWORD* keyboardLayoutId)
232 CHAR name[KL_NAMELENGTH + 1] = { 0 };
233 if (GetKeyboardLayoutNameA(name))
238 rc = strtoul(name, NULL, 16);
240 *keyboardLayoutId = rc;
243 if (*keyboardLayoutId == 0)
244 *keyboardLayoutId = ((DWORD)GetKeyboardLayout(0) >> 16) & 0x0000FFFF;
247 #if defined(__MACOSX__)
248 if (*keyboardLayoutId == 0)
249 freerdp_detect_keyboard_layout_from_cf(keyboardLayoutId);
253 if (*keyboardLayoutId == 0)
254 freerdp_detect_keyboard_layout_from_xkb(keyboardLayoutId);
257 if (*keyboardLayoutId == 0)
258 freerdp_detect_keyboard_layout_from_system_locale(keyboardLayoutId);
260 if (*keyboardLayoutId == 0)
261 *keyboardLayoutId = ENGLISH_UNITED_STATES;
266 static int freerdp_keyboard_init_apple(
const DWORD* keyboardLayoutId,
267 DWORD* x11_keycode_to_rdp_scancode,
size_t count)
269 WINPR_ASSERT(x11_keycode_to_rdp_scancode);
270 WINPR_ASSERT(keyboardLayoutId);
271 WINPR_ASSERT(count <= UINT32_MAX);
272 for (
size_t keycode = 8; keycode < count; keycode++)
275 GetVirtualKeyCodeFromKeycode((UINT32)keycode - 8u, WINPR_KEYCODE_TYPE_APPLE);
276 x11_keycode_to_rdp_scancode[keycode] =
277 GetVirtualScanCodeFromVirtualKeyCode(vkcode, WINPR_KBD_TYPE_IBM_ENHANCED);
280 maptype = WINPR_KEYCODE_TYPE_APPLE;
284 static int freerdp_keyboard_init_x11_evdev(
const DWORD* keyboardLayoutId,
285 DWORD* x11_keycode_to_rdp_scancode,
size_t count)
287 WINPR_ASSERT(keyboardLayoutId);
288 WINPR_ASSERT(x11_keycode_to_rdp_scancode);
289 WINPR_ASSERT(count <= UINT32_MAX);
291 for (
size_t keycode = 0; keycode < count; keycode++)
294 GetVirtualKeyCodeFromKeycode((UINT32)keycode, WINPR_KEYCODE_TYPE_EVDEV);
295 x11_keycode_to_rdp_scancode[keycode] =
296 GetVirtualScanCodeFromVirtualKeyCode(vkcode, WINPR_KBD_TYPE_IBM_ENHANCED);
298 maptype = WINPR_KEYCODE_TYPE_EVDEV;
303 DWORD freerdp_keyboard_init(DWORD keyboardLayoutId)
307 #if defined(__APPLE__)
309 status = freerdp_keyboard_init_apple(&keyboardLayoutId, X11_KEYCODE_TO_VIRTUAL_SCANCODE,
310 ARRAYSIZE(X11_KEYCODE_TO_VIRTUAL_SCANCODE));
313 #if defined(WITH_X11) || defined(WITH_WAYLAND)
318 status = freerdp_keyboard_init_xkbfile(&keyboardLayoutId, X11_KEYCODE_TO_VIRTUAL_SCANCODE,
319 ARRAYSIZE(X11_KEYCODE_TO_VIRTUAL_SCANCODE));
321 maptype = WINPR_KEYCODE_TYPE_XKB;
326 status = freerdp_keyboard_init_x11_evdev(&keyboardLayoutId, X11_KEYCODE_TO_VIRTUAL_SCANCODE,
327 ARRAYSIZE(X11_KEYCODE_TO_VIRTUAL_SCANCODE));
332 WLog_DBG(TAG,
"Platform keyboard detection failed, trying autodetection");
334 freerdp_detect_keyboard(&keyboardLayoutId);
336 ZeroMemory(VIRTUAL_SCANCODE_TO_X11_KEYCODE,
sizeof(VIRTUAL_SCANCODE_TO_X11_KEYCODE));
338 WINPR_STATIC_ASSERT(ARRAYSIZE(VIRTUAL_SCANCODE_TO_X11_KEYCODE) <= UINT32_MAX);
339 for (
size_t keycode = 0; keycode < ARRAYSIZE(VIRTUAL_SCANCODE_TO_X11_KEYCODE); keycode++)
341 const DWORD x11 = X11_KEYCODE_TO_VIRTUAL_SCANCODE[keycode];
342 const DWORD sc = RDP_SCANCODE_CODE(x11);
343 const BOOL ex = RDP_SCANCODE_EXTENDED(x11);
344 VIRTUAL_SCANCODE_TO_X11_KEYCODE[sc][ex ? 1 : 0] = (UINT32)keycode;
347 return keyboardLayoutId;
350 FREERDP_REMAP_TABLE* freerdp_keyboard_remap_string_to_list(
const char* list)
352 const size_t remap_table_size = 0x10000;
354 FREERDP_REMAP_TABLE* remap_table = calloc(1,
sizeof(FREERDP_REMAP_TABLE));
358 for (
size_t x = 0; x < ARRAYSIZE(remap_table->table); x++)
359 remap_table->table[x] = (UINT32)x;
364 BOOL success = FALSE;
365 char* copy = _strdup(list);
369 char* context = NULL;
370 char* token = strtok_s(copy,
",", &context);
375 if (!freerdp_extract_key_value(token, &key, &value))
377 if (key >= remap_table_size)
379 remap_table->table[key] = value;
380 token = strtok_s(NULL,
",", &context);
396 DWORD freerdp_keyboard_init_ex(DWORD keyboardLayoutId,
const char* keyboardRemappingList)
398 DWORD res = freerdp_keyboard_init(keyboardLayoutId);
400 memset(REMAPPING_TABLE, 0,
sizeof(REMAPPING_TABLE));
401 if (keyboardRemappingList)
403 char* copy = _strdup(keyboardRemappingList);
404 char* context = NULL;
408 token = strtok_s(copy,
",", &context);
413 if (!freerdp_extract_key_value(token, &key, &value))
415 if (key >= ARRAYSIZE(REMAPPING_TABLE))
417 REMAPPING_TABLE[key] = value;
418 token = strtok_s(NULL,
",", &context);
426 DWORD freerdp_keyboard_get_rdp_scancode_from_x11_keycode(DWORD keycode)
428 if (keycode >= ARRAYSIZE(X11_KEYCODE_TO_VIRTUAL_SCANCODE))
430 WLog_ERR(TAG,
"KeyCode %" PRIu32
" exceeds allowed value range [0,%" PRIuz
"]", keycode,
431 ARRAYSIZE(X11_KEYCODE_TO_VIRTUAL_SCANCODE));
435 const DWORD scancode = X11_KEYCODE_TO_VIRTUAL_SCANCODE[keycode];
436 if (scancode >= ARRAYSIZE(REMAPPING_TABLE))
438 WLog_ERR(TAG,
"ScanCode %" PRIu32
" exceeds allowed value range [0,%" PRIuz
"]", scancode,
439 ARRAYSIZE(REMAPPING_TABLE));
443 const DWORD remapped = REMAPPING_TABLE[scancode];
444 #if defined(WITH_DEBUG_KBD)
445 const BOOL ex = RDP_SCANCODE_EXTENDED(scancode);
446 const DWORD sc = RDP_SCANCODE_CODE(scancode);
449 DEBUG_KBD(
"x11 keycode: %02" PRIX32
" -> rdp code: [%04" PRIx16
"] %02" PRIX8
"%s", keycode,
450 scancode, sc, ex ?
" extended" :
"");
454 #if defined(WITH_DEBUG_KBD)
455 const DWORD rsc = RDP_SCANCODE_CODE(remapped);
456 const BOOL rex = RDP_SCANCODE_EXTENDED(remapped);
459 DEBUG_KBD(
"remapped scancode: [%04" PRIx16
"] %02" PRIX8
"[%s] -> [%04" PRIx16
"] %02" PRIX8
461 scancode, sc, ex ?
" extended" :
"", remapped, rsc, rex ?
" extended" :
"");
467 DWORD freerdp_keyboard_get_x11_keycode_from_rdp_scancode(DWORD scancode, BOOL extended)
469 if (scancode >= ARRAYSIZE(VIRTUAL_SCANCODE_TO_X11_KEYCODE))
471 WLog_ERR(TAG,
"ScanCode %" PRIu32
" exceeds allowed value range [0,%" PRIuz
"]", scancode,
472 ARRAYSIZE(VIRTUAL_SCANCODE_TO_X11_KEYCODE));
476 const DWORD* x11 = VIRTUAL_SCANCODE_TO_X11_KEYCODE[scancode];
485 const char* freerdp_keyboard_scancode_name(DWORD scancode)
487 for (
size_t x = 0; x < ARRAYSIZE(RDP_SCANCODE_MAP); x++)
489 const struct scancode_map_entry* entry = &RDP_SCANCODE_MAP[x];
490 if (entry->scancode == scancode)
497 DWORD freerdp_keyboard_remap_key(
const FREERDP_REMAP_TABLE* remap_table, DWORD rdpScanCode)
499 if (!remap_table || (ARRAYSIZE(remap_table->table) <= rdpScanCode))
502 return remap_table->table[rdpScanCode];
505 void freerdp_keyboard_remap_free(FREERDP_REMAP_TABLE* table)