1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "../../SDL_internal.h"
22
23#if SDL_VIDEO_DRIVER_X11
24
25#include "SDL_x11video.h"
26
27#include "../../events/SDL_keyboard_c.h"
28#include "../../events/scancodes_darwin.h"
29#include "../../events/scancodes_xfree86.h"
30
31#include <X11/keysym.h>
32#include <X11/XKBlib.h>
33
34#include "imKStoUCS.h"
35
36#ifdef X_HAVE_UTF8_STRING
37#include <locale.h>
38#endif
39
40/* *INDENT-OFF* */
41static const struct {
42 KeySym keysym;
43 SDL_Scancode scancode;
44} KeySymToSDLScancode[] = {
45 { XK_Return, SDL_SCANCODE_RETURN },
46 { XK_Escape, SDL_SCANCODE_ESCAPE },
47 { XK_BackSpace, SDL_SCANCODE_BACKSPACE },
48 { XK_Tab, SDL_SCANCODE_TAB },
49 { XK_Caps_Lock, SDL_SCANCODE_CAPSLOCK },
50 { XK_F1, SDL_SCANCODE_F1 },
51 { XK_F2, SDL_SCANCODE_F2 },
52 { XK_F3, SDL_SCANCODE_F3 },
53 { XK_F4, SDL_SCANCODE_F4 },
54 { XK_F5, SDL_SCANCODE_F5 },
55 { XK_F6, SDL_SCANCODE_F6 },
56 { XK_F7, SDL_SCANCODE_F7 },
57 { XK_F8, SDL_SCANCODE_F8 },
58 { XK_F9, SDL_SCANCODE_F9 },
59 { XK_F10, SDL_SCANCODE_F10 },
60 { XK_F11, SDL_SCANCODE_F11 },
61 { XK_F12, SDL_SCANCODE_F12 },
62 { XK_Print, SDL_SCANCODE_PRINTSCREEN },
63 { XK_Scroll_Lock, SDL_SCANCODE_SCROLLLOCK },
64 { XK_Pause, SDL_SCANCODE_PAUSE },
65 { XK_Insert, SDL_SCANCODE_INSERT },
66 { XK_Home, SDL_SCANCODE_HOME },
67 { XK_Prior, SDL_SCANCODE_PAGEUP },
68 { XK_Delete, SDL_SCANCODE_DELETE },
69 { XK_End, SDL_SCANCODE_END },
70 { XK_Next, SDL_SCANCODE_PAGEDOWN },
71 { XK_Right, SDL_SCANCODE_RIGHT },
72 { XK_Left, SDL_SCANCODE_LEFT },
73 { XK_Down, SDL_SCANCODE_DOWN },
74 { XK_Up, SDL_SCANCODE_UP },
75 { XK_Num_Lock, SDL_SCANCODE_NUMLOCKCLEAR },
76 { XK_KP_Divide, SDL_SCANCODE_KP_DIVIDE },
77 { XK_KP_Multiply, SDL_SCANCODE_KP_MULTIPLY },
78 { XK_KP_Subtract, SDL_SCANCODE_KP_MINUS },
79 { XK_KP_Add, SDL_SCANCODE_KP_PLUS },
80 { XK_KP_Enter, SDL_SCANCODE_KP_ENTER },
81 { XK_KP_Delete, SDL_SCANCODE_KP_PERIOD },
82 { XK_KP_End, SDL_SCANCODE_KP_1 },
83 { XK_KP_Down, SDL_SCANCODE_KP_2 },
84 { XK_KP_Next, SDL_SCANCODE_KP_3 },
85 { XK_KP_Left, SDL_SCANCODE_KP_4 },
86 { XK_KP_Begin, SDL_SCANCODE_KP_5 },
87 { XK_KP_Right, SDL_SCANCODE_KP_6 },
88 { XK_KP_Home, SDL_SCANCODE_KP_7 },
89 { XK_KP_Up, SDL_SCANCODE_KP_8 },
90 { XK_KP_Prior, SDL_SCANCODE_KP_9 },
91 { XK_KP_Insert, SDL_SCANCODE_KP_0 },
92 { XK_KP_Decimal, SDL_SCANCODE_KP_PERIOD },
93 { XK_KP_1, SDL_SCANCODE_KP_1 },
94 { XK_KP_2, SDL_SCANCODE_KP_2 },
95 { XK_KP_3, SDL_SCANCODE_KP_3 },
96 { XK_KP_4, SDL_SCANCODE_KP_4 },
97 { XK_KP_5, SDL_SCANCODE_KP_5 },
98 { XK_KP_6, SDL_SCANCODE_KP_6 },
99 { XK_KP_7, SDL_SCANCODE_KP_7 },
100 { XK_KP_8, SDL_SCANCODE_KP_8 },
101 { XK_KP_9, SDL_SCANCODE_KP_9 },
102 { XK_KP_0, SDL_SCANCODE_KP_0 },
103 { XK_KP_Decimal, SDL_SCANCODE_KP_PERIOD },
104 { XK_Hyper_R, SDL_SCANCODE_APPLICATION },
105 { XK_KP_Equal, SDL_SCANCODE_KP_EQUALS },
106 { XK_F13, SDL_SCANCODE_F13 },
107 { XK_F14, SDL_SCANCODE_F14 },
108 { XK_F15, SDL_SCANCODE_F15 },
109 { XK_F16, SDL_SCANCODE_F16 },
110 { XK_F17, SDL_SCANCODE_F17 },
111 { XK_F18, SDL_SCANCODE_F18 },
112 { XK_F19, SDL_SCANCODE_F19 },
113 { XK_F20, SDL_SCANCODE_F20 },
114 { XK_F21, SDL_SCANCODE_F21 },
115 { XK_F22, SDL_SCANCODE_F22 },
116 { XK_F23, SDL_SCANCODE_F23 },
117 { XK_F24, SDL_SCANCODE_F24 },
118 { XK_Execute, SDL_SCANCODE_EXECUTE },
119 { XK_Help, SDL_SCANCODE_HELP },
120 { XK_Menu, SDL_SCANCODE_MENU },
121 { XK_Select, SDL_SCANCODE_SELECT },
122 { XK_Cancel, SDL_SCANCODE_STOP },
123 { XK_Redo, SDL_SCANCODE_AGAIN },
124 { XK_Undo, SDL_SCANCODE_UNDO },
125 { XK_Find, SDL_SCANCODE_FIND },
126 { XK_KP_Separator, SDL_SCANCODE_KP_COMMA },
127 { XK_Sys_Req, SDL_SCANCODE_SYSREQ },
128 { XK_Control_L, SDL_SCANCODE_LCTRL },
129 { XK_Shift_L, SDL_SCANCODE_LSHIFT },
130 { XK_Alt_L, SDL_SCANCODE_LALT },
131 { XK_Meta_L, SDL_SCANCODE_LGUI },
132 { XK_Super_L, SDL_SCANCODE_LGUI },
133 { XK_Control_R, SDL_SCANCODE_RCTRL },
134 { XK_Shift_R, SDL_SCANCODE_RSHIFT },
135 { XK_Alt_R, SDL_SCANCODE_RALT },
136 { XK_ISO_Level3_Shift, SDL_SCANCODE_RALT },
137 { XK_Meta_R, SDL_SCANCODE_RGUI },
138 { XK_Super_R, SDL_SCANCODE_RGUI },
139 { XK_Mode_switch, SDL_SCANCODE_MODE },
140 { XK_period, SDL_SCANCODE_PERIOD },
141 { XK_comma, SDL_SCANCODE_COMMA },
142 { XK_slash, SDL_SCANCODE_SLASH },
143 { XK_backslash, SDL_SCANCODE_BACKSLASH },
144 { XK_minus, SDL_SCANCODE_MINUS },
145 { XK_equal, SDL_SCANCODE_EQUALS },
146 { XK_space, SDL_SCANCODE_SPACE },
147 { XK_grave, SDL_SCANCODE_GRAVE },
148 { XK_apostrophe, SDL_SCANCODE_APOSTROPHE },
149 { XK_bracketleft, SDL_SCANCODE_LEFTBRACKET },
150 { XK_bracketright, SDL_SCANCODE_RIGHTBRACKET },
151};
152
153static const struct
154{
155 SDL_Scancode const *table;
156 int table_size;
157} scancode_set[] = {
158 { darwin_scancode_table, SDL_arraysize(darwin_scancode_table) },
159 { xfree86_scancode_table, SDL_arraysize(xfree86_scancode_table) },
160 { xfree86_scancode_table2, SDL_arraysize(xfree86_scancode_table2) },
161 { xvnc_scancode_table, SDL_arraysize(xvnc_scancode_table) },
162};
163/* *INDENT-OFF* */
164
165/* This function only works for keyboards in US QWERTY layout */
166static SDL_Scancode
167X11_KeyCodeToSDLScancode(_THIS, KeyCode keycode)
168{
169 KeySym keysym;
170 int i;
171
172 keysym = X11_KeyCodeToSym(_this, keycode, 0);
173 if (keysym == NoSymbol) {
174 return SDL_SCANCODE_UNKNOWN;
175 }
176
177 if (keysym >= XK_a && keysym <= XK_z) {
178 return SDL_SCANCODE_A + (keysym - XK_a);
179 }
180 if (keysym >= XK_A && keysym <= XK_Z) {
181 return SDL_SCANCODE_A + (keysym - XK_A);
182 }
183
184 if (keysym == XK_0) {
185 return SDL_SCANCODE_0;
186 }
187 if (keysym >= XK_1 && keysym <= XK_9) {
188 return SDL_SCANCODE_1 + (keysym - XK_1);
189 }
190
191 for (i = 0; i < SDL_arraysize(KeySymToSDLScancode); ++i) {
192 if (keysym == KeySymToSDLScancode[i].keysym) {
193 return KeySymToSDLScancode[i].scancode;
194 }
195 }
196 return SDL_SCANCODE_UNKNOWN;
197}
198
199static Uint32
200X11_KeyCodeToUcs4(_THIS, KeyCode keycode, unsigned char group)
201{
202 KeySym keysym = X11_KeyCodeToSym(_this, keycode, group);
203
204 if (keysym == NoSymbol) {
205 return 0;
206 }
207
208 return X11_KeySymToUcs4(keysym);
209}
210
211KeySym
212X11_KeyCodeToSym(_THIS, KeyCode keycode, unsigned char group)
213{
214 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
215 KeySym keysym;
216
217#if SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM
218 if (data->xkb) {
219 int num_groups = XkbKeyNumGroups(data->xkb, keycode);
220 unsigned char info = XkbKeyGroupInfo(data->xkb, keycode);
221
222 if (num_groups && group >= num_groups) {
223
224 int action = XkbOutOfRangeGroupAction(info);
225
226 if (action == XkbRedirectIntoRange) {
227 if ((group = XkbOutOfRangeGroupNumber(info)) >= num_groups) {
228 group = 0;
229 }
230 } else if (action == XkbClampIntoRange) {
231 group = num_groups - 1;
232 } else {
233 group %= num_groups;
234 }
235 }
236 keysym = X11_XkbKeycodeToKeysym(data->display, keycode, group, 0);
237 } else {
238 keysym = X11_XKeycodeToKeysym(data->display, keycode, 0);
239 }
240#else
241 keysym = X11_XKeycodeToKeysym(data->display, keycode, 0);
242#endif
243
244 return keysym;
245}
246
247int
248X11_InitKeyboard(_THIS)
249{
250 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
251 int i = 0;
252 int j = 0;
253 int min_keycode, max_keycode;
254 struct {
255 SDL_Scancode scancode;
256 KeySym keysym;
257 int value;
258 } fingerprint[] = {
259 { SDL_SCANCODE_HOME, XK_Home, 0 },
260 { SDL_SCANCODE_PAGEUP, XK_Prior, 0 },
261 { SDL_SCANCODE_UP, XK_Up, 0 },
262 { SDL_SCANCODE_LEFT, XK_Left, 0 },
263 { SDL_SCANCODE_DELETE, XK_Delete, 0 },
264 { SDL_SCANCODE_KP_ENTER, XK_KP_Enter, 0 },
265 };
266 int best_distance;
267 int best_index;
268 int distance;
269 Bool xkb_repeat = 0;
270 XKeyboardState values = { .global_auto_repeat = AutoRepeatModeOff };
271
272 X11_XGetKeyboardControl(data->display, &values);
273 if (values.global_auto_repeat != AutoRepeatModeOn)
274 X11_XAutoRepeatOn(data->display);
275
276#if SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM
277 {
278 int xkb_major = XkbMajorVersion;
279 int xkb_minor = XkbMinorVersion;
280
281 if (X11_XkbQueryExtension(data->display, NULL, NULL, NULL, &xkb_major, &xkb_minor)) {
282 data->xkb = X11_XkbGetMap(data->display, XkbAllClientInfoMask, XkbUseCoreKbd);
283 }
284
285 /* This will remove KeyRelease events for held keys */
286 X11_XkbSetDetectableAutoRepeat(data->display, True, &xkb_repeat);
287 }
288#endif
289
290 /* Open a connection to the X input manager */
291#ifdef X_HAVE_UTF8_STRING
292 if (SDL_X11_HAVE_UTF8) {
293 /* Set the locale, and call XSetLocaleModifiers before XOpenIM so that
294 Compose keys will work correctly. */
295 char *prev_locale = setlocale(LC_ALL, NULL);
296 char *prev_xmods = X11_XSetLocaleModifiers(NULL);
297 const char *new_xmods = "";
298 const char *env_xmods = SDL_getenv("XMODIFIERS");
299 SDL_bool has_dbus_ime_support = SDL_FALSE;
300
301 if (prev_locale) {
302 prev_locale = SDL_strdup(prev_locale);
303 }
304
305 if (prev_xmods) {
306 prev_xmods = SDL_strdup(prev_xmods);
307 }
308
309 /* IBus resends some key events that were filtered by XFilterEvents
310 when it is used via XIM which causes issues. Prevent this by forcing
311 @im=none if XMODIFIERS contains @im=ibus. IBus can still be used via
312 the DBus implementation, which also has support for pre-editing. */
313 if (env_xmods && SDL_strstr(env_xmods, "@im=ibus") != NULL) {
314 has_dbus_ime_support = SDL_TRUE;
315 }
316 if (env_xmods && SDL_strstr(env_xmods, "@im=fcitx") != NULL) {
317 has_dbus_ime_support = SDL_TRUE;
318 }
319 if (has_dbus_ime_support || !xkb_repeat) {
320 new_xmods = "@im=none";
321 }
322
323 setlocale(LC_ALL, "");
324 X11_XSetLocaleModifiers(new_xmods);
325
326 data->im = X11_XOpenIM(data->display, NULL, data->classname, data->classname);
327
328 /* Reset the locale + X locale modifiers back to how they were,
329 locale first because the X locale modifiers depend on it. */
330 setlocale(LC_ALL, prev_locale);
331 X11_XSetLocaleModifiers(prev_xmods);
332
333 if (prev_locale) {
334 SDL_free(prev_locale);
335 }
336
337 if (prev_xmods) {
338 SDL_free(prev_xmods);
339 }
340 }
341#endif
342 /* Try to determine which scancodes are being used based on fingerprint */
343 best_distance = SDL_arraysize(fingerprint) + 1;
344 best_index = -1;
345 X11_XDisplayKeycodes(data->display, &min_keycode, &max_keycode);
346 for (i = 0; i < SDL_arraysize(fingerprint); ++i) {
347 fingerprint[i].value =
348 X11_XKeysymToKeycode(data->display, fingerprint[i].keysym) -
349 min_keycode;
350 }
351 for (i = 0; i < SDL_arraysize(scancode_set); ++i) {
352 /* Make sure the scancode set isn't too big */
353 if ((max_keycode - min_keycode + 1) <= scancode_set[i].table_size) {
354 continue;
355 }
356 distance = 0;
357 for (j = 0; j < SDL_arraysize(fingerprint); ++j) {
358 if (fingerprint[j].value < 0
359 || fingerprint[j].value >= scancode_set[i].table_size) {
360 distance += 1;
361 } else if (scancode_set[i].table[fingerprint[j].value] != fingerprint[j].scancode) {
362 distance += 1;
363 }
364 }
365 if (distance < best_distance) {
366 best_distance = distance;
367 best_index = i;
368 }
369 }
370 if (best_index >= 0 && best_distance <= 2) {
371#ifdef DEBUG_KEYBOARD
372 printf("Using scancode set %d, min_keycode = %d, max_keycode = %d, table_size = %d\n", best_index, min_keycode, max_keycode, scancode_set[best_index].table_size);
373#endif
374 SDL_memcpy(&data->key_layout[min_keycode], scancode_set[best_index].table,
375 sizeof(SDL_Scancode) * scancode_set[best_index].table_size);
376 } else {
377 SDL_Keycode keymap[SDL_NUM_SCANCODES];
378
379 printf
380 ("Keyboard layout unknown, please report the following to the SDL forums/mailing list (https://discourse.libsdl.org/):\n");
381
382 /* Determine key_layout - only works on US QWERTY layout */
383 SDL_GetDefaultKeymap(keymap);
384 for (i = min_keycode; i <= max_keycode; ++i) {
385 KeySym sym;
386 sym = X11_KeyCodeToSym(_this, (KeyCode) i, 0);
387 if (sym != NoSymbol) {
388 SDL_Scancode scancode;
389 printf("code = %d, sym = 0x%X (%s) ", i - min_keycode,
390 (unsigned int) sym, X11_XKeysymToString(sym));
391 scancode = X11_KeyCodeToSDLScancode(_this, i);
392 data->key_layout[i] = scancode;
393 if (scancode == SDL_SCANCODE_UNKNOWN) {
394 printf("scancode not found\n");
395 } else {
396 printf("scancode = %d (%s)\n", scancode, SDL_GetScancodeName(scancode));
397 }
398 }
399 }
400 }
401
402 X11_UpdateKeymap(_this);
403
404 SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu");
405
406#ifdef SDL_USE_IME
407 SDL_IME_Init();
408#endif
409
410 return 0;
411}
412
413void
414X11_UpdateKeymap(_THIS)
415{
416 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
417 int i;
418 SDL_Scancode scancode;
419 SDL_Keycode keymap[SDL_NUM_SCANCODES];
420 unsigned char group = 0;
421
422 SDL_GetDefaultKeymap(keymap);
423
424#if SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM
425 if (data->xkb) {
426 XkbStateRec state;
427 X11_XkbGetUpdatedMap(data->display, XkbAllClientInfoMask, data->xkb);
428
429 if (X11_XkbGetState(data->display, XkbUseCoreKbd, &state) == Success) {
430 group = state.group;
431 }
432 }
433#endif
434
435
436 for (i = 0; i < SDL_arraysize(data->key_layout); i++) {
437 Uint32 key;
438
439 /* Make sure this is a valid scancode */
440 scancode = data->key_layout[i];
441 if (scancode == SDL_SCANCODE_UNKNOWN) {
442 continue;
443 }
444
445 /* See if there is a UCS keycode for this scancode */
446 key = X11_KeyCodeToUcs4(_this, (KeyCode)i, group);
447 if (key) {
448 keymap[scancode] = key;
449 } else {
450 SDL_Scancode keyScancode = X11_KeyCodeToSDLScancode(_this, (KeyCode)i);
451
452 switch (keyScancode) {
453 case SDL_SCANCODE_RETURN:
454 keymap[scancode] = SDLK_RETURN;
455 break;
456 case SDL_SCANCODE_ESCAPE:
457 keymap[scancode] = SDLK_ESCAPE;
458 break;
459 case SDL_SCANCODE_BACKSPACE:
460 keymap[scancode] = SDLK_BACKSPACE;
461 break;
462 case SDL_SCANCODE_TAB:
463 keymap[scancode] = SDLK_TAB;
464 break;
465 case SDL_SCANCODE_DELETE:
466 keymap[scancode] = SDLK_DELETE;
467 break;
468 default:
469 keymap[scancode] = SDL_SCANCODE_TO_KEYCODE(keyScancode);
470 break;
471 }
472 }
473 }
474 SDL_SetKeymap(0, keymap, SDL_NUM_SCANCODES);
475}
476
477void
478X11_QuitKeyboard(_THIS)
479{
480 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
481
482#if SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM
483 if (data->xkb) {
484 X11_XkbFreeKeyboard(data->xkb, 0, True);
485 data->xkb = NULL;
486 }
487#endif
488
489#ifdef SDL_USE_IME
490 SDL_IME_Quit();
491#endif
492}
493
494static void
495X11_ResetXIM(_THIS)
496{
497#ifdef X_HAVE_UTF8_STRING
498 SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
499 int i;
500
501 if (videodata && videodata->windowlist) {
502 for (i = 0; i < videodata->numwindows; ++i) {
503 SDL_WindowData *data = videodata->windowlist[i];
504 if (data && data->ic) {
505 /* Clear any partially entered dead keys */
506 char *contents = X11_Xutf8ResetIC(data->ic);
507 if (contents) {
508 X11_XFree(contents);
509 }
510 }
511 }
512 }
513#endif
514}
515
516void
517X11_StartTextInput(_THIS)
518{
519 X11_ResetXIM(_this);
520}
521
522void
523X11_StopTextInput(_THIS)
524{
525 X11_ResetXIM(_this);
526#ifdef SDL_USE_IME
527 SDL_IME_Reset();
528#endif
529}
530
531void
532X11_SetTextInputRect(_THIS, SDL_Rect *rect)
533{
534 if (!rect) {
535 SDL_InvalidParamError("rect");
536 return;
537 }
538
539#ifdef SDL_USE_IME
540 SDL_IME_UpdateTextRect(rect);
541#endif
542}
543
544#endif /* SDL_VIDEO_DRIVER_X11 */
545
546/* vi: set ts=4 sw=4 expandtab: */
547