Atlas - SDL_x11keyboard.c
Home / ext / SDL / src / video / x11 Lines: 1 | Size: 33709 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 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#ifdef SDL_VIDEO_DRIVER_X11 24 25#include "SDL_x11video.h" 26 27#include "../../events/SDL_keyboard_c.h" 28#include "../../events/SDL_scancode_tables_c.h" 29 30#include <X11/keysym.h> 31 32#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB 33#include <X11/XKBlib.h> 34#endif 35 36#include "../../events/imKStoUCS.h" 37#include "../../events/SDL_keysym_to_scancode_c.h" 38#include "../../events/SDL_keysym_to_keycode_c.h" 39 40#ifdef X_HAVE_UTF8_STRING 41#include <locale.h> 42#endif 43 44static SDL_ScancodeTable scancode_set[] = { 45 SDL_SCANCODE_TABLE_DARWIN, 46 SDL_SCANCODE_TABLE_XFREE86_1, 47 SDL_SCANCODE_TABLE_XFREE86_2, 48 SDL_SCANCODE_TABLE_XVNC, 49}; 50 51static bool X11_ScancodeIsRemappable(SDL_Scancode scancode) 52{ 53 /* 54 * XKB remappings can assign different keysyms for these scancodes, but 55 * as these keys are in fixed positions, the scancodes themselves shouldn't 56 * be switched. Mark them as not being remappable. 57 */ 58 switch (scancode) { 59 case SDL_SCANCODE_ESCAPE: 60 case SDL_SCANCODE_CAPSLOCK: 61 case SDL_SCANCODE_NUMLOCKCLEAR: 62 case SDL_SCANCODE_LSHIFT: 63 case SDL_SCANCODE_RSHIFT: 64 case SDL_SCANCODE_LCTRL: 65 case SDL_SCANCODE_RCTRL: 66 case SDL_SCANCODE_LALT: 67 case SDL_SCANCODE_RALT: 68 case SDL_SCANCODE_LGUI: 69 case SDL_SCANCODE_RGUI: 70 return false; 71 default: 72 return true; 73 } 74} 75 76static KeySym X11_KeyCodeToSym(SDL_VideoDevice *_this, KeyCode keycode, unsigned int group, unsigned int level) 77{ 78 SDL_VideoData *data = _this->internal; 79 KeySym keysym; 80 81#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB 82 if (data->keyboard.xkb_enabled) { 83 keysym = X11_XkbKeycodeToKeysym(data->display, keycode, group, level); 84 } else 85#endif 86 { 87 // TODO: Handle groups on the legacy path. 88 if (keycode >= data->keyboard.core.min_keycode && keycode <= data->keyboard.core.max_keycode) { 89 keysym = data->keyboard.core.keysym_map[(keycode - data->keyboard.core.min_keycode) * data->keyboard.core.keysyms_per_key]; 90 } else { 91 keysym = NoSymbol; 92 } 93 } 94 95 return keysym; 96} 97 98// This function only correctly maps letters and numbers for keyboards in US QWERTY layout 99static SDL_Scancode X11_KeyCodeToSDLScancode(SDL_VideoDevice *_this, KeyCode keycode) 100{ 101 const KeySym keysym = X11_KeyCodeToSym(_this, keycode, 0, 0); 102 103 if (keysym == NoSymbol) { 104 return SDL_SCANCODE_UNKNOWN; 105 } 106 107 return SDL_GetScancodeFromKeySym(keysym, keycode); 108} 109 110bool X11_InitKeyboard(SDL_VideoDevice *_this) 111{ 112 SDL_VideoData *data = _this->internal; 113 int i = 0; 114 int j = 0; 115 int min_keycode, max_keycode; 116 struct 117 { 118 SDL_Scancode scancode; 119 KeySym keysym; 120 int value; 121 } fingerprint[] = { 122 { SDL_SCANCODE_HOME, XK_Home, 0 }, 123 { SDL_SCANCODE_PAGEUP, XK_Prior, 0 }, 124 { SDL_SCANCODE_UP, XK_Up, 0 }, 125 { SDL_SCANCODE_LEFT, XK_Left, 0 }, 126 { SDL_SCANCODE_DELETE, XK_Delete, 0 }, 127 { SDL_SCANCODE_KP_ENTER, XK_KP_Enter, 0 }, 128 }; 129 int best_distance; 130 int best_index; 131 int distance; 132 133#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB 134 int xkb_major = XkbMajorVersion; 135 int xkb_minor = XkbMinorVersion; 136 137 if (X11_XkbQueryExtension(data->display, NULL, &data->keyboard.xkb.event, NULL, &xkb_major, &xkb_minor)) { 138 Bool xkb_repeat = 0; 139 data->keyboard.xkb_enabled = true; 140 data->keyboard.xkb.desc_ptr = X11_XkbGetMap(data->display, XkbAllClientInfoMask, XkbUseCoreKbd); 141 142 // This will remove KeyRelease events for held keys. 143 X11_XkbSetDetectableAutoRepeat(data->display, True, &xkb_repeat); 144 145 // Enable the key mapping and state events. 146 X11_XkbSelectEvents(data->display, XkbUseCoreKbd, 147 XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbStateNotifyMask, 148 XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbStateNotifyMask); 149 X11_XkbSelectEventDetails(data->display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask | XkbModifierStateMask, XkbGroupStateMask | XkbModifierStateMask); 150 } else 151#endif 152 { 153 // If XKB isn't available, initialize the legacy path. 154 X11_XDisplayKeycodes(data->display, &data->keyboard.core.min_keycode, &data->keyboard.core.max_keycode); 155 data->keyboard.core.keysym_map = X11_XGetKeyboardMapping(data->display, data->keyboard.core.min_keycode, 156 data->keyboard.core.max_keycode - data->keyboard.core.min_keycode, 157 &data->keyboard.core.keysyms_per_key); 158 } 159 160 // Open a connection to the X input manager 161#ifdef X_HAVE_UTF8_STRING 162 if (SDL_X11_HAVE_UTF8) { 163 /* Set the locale, and call XSetLocaleModifiers before XOpenIM so that 164 Compose keys will work correctly. */ 165 char *prev_locale = setlocale(LC_ALL, NULL); 166 char *prev_xmods = X11_XSetLocaleModifiers(NULL); 167 168 if (prev_locale) { 169 prev_locale = SDL_strdup(prev_locale); 170 } 171 172 if (prev_xmods) { 173 prev_xmods = SDL_strdup(prev_xmods); 174 } 175 176 (void)setlocale(LC_ALL, ""); 177 X11_XSetLocaleModifiers(""); 178 179 data->im = X11_XOpenIM(data->display, NULL, NULL, NULL); 180 181 /* Reset the locale + X locale modifiers back to how they were, 182 locale first because the X locale modifiers depend on it. */ 183 (void)setlocale(LC_ALL, prev_locale); 184 X11_XSetLocaleModifiers(prev_xmods); 185 186 SDL_free(prev_locale); 187 188 SDL_free(prev_xmods); 189 } 190#endif 191 // Try to determine which scancodes are being used based on fingerprint 192 best_distance = SDL_arraysize(fingerprint) + 1; 193 best_index = -1; 194 X11_XDisplayKeycodes(data->display, &min_keycode, &max_keycode); 195 for (i = 0; i < SDL_arraysize(fingerprint); ++i) { 196 fingerprint[i].value = X11_XKeysymToKeycode(data->display, fingerprint[i].keysym) - min_keycode; 197 } 198 for (i = 0; i < SDL_arraysize(scancode_set); ++i) { 199 int table_size; 200 const SDL_Scancode *table = SDL_GetScancodeTable(scancode_set[i], &table_size); 201 202 distance = 0; 203 for (j = 0; j < SDL_arraysize(fingerprint); ++j) { 204 if (fingerprint[j].value < 0 || fingerprint[j].value >= table_size) { 205 distance += 1; 206 } else if (table[fingerprint[j].value] != fingerprint[j].scancode) { 207 distance += 1; 208 } 209 } 210 if (distance < best_distance) { 211 best_distance = distance; 212 best_index = i; 213 } 214 } 215 if (best_index < 0 || best_distance > 2) { 216 // This is likely to be SDL_SCANCODE_TABLE_XFREE86_2 with remapped keys, double check a rarely remapped value 217 int fingerprint_value = X11_XKeysymToKeycode(data->display, 0x1008FF5B /* XF86Documents */) - min_keycode; 218 if (fingerprint_value == 235) { 219 for (i = 0; i < SDL_arraysize(scancode_set); ++i) { 220 if (scancode_set[i] == SDL_SCANCODE_TABLE_XFREE86_2) { 221 best_index = i; 222 best_distance = 0; 223 break; 224 } 225 } 226 } 227 } 228 if (best_index >= 0 && best_distance <= 2) { 229 int table_size; 230 const SDL_Scancode *table = SDL_GetScancodeTable(scancode_set[best_index], &table_size); 231 232#ifdef DEBUG_KEYBOARD 233 SDL_Log("Using scancode set %d, min_keycode = %d, max_keycode = %d, table_size = %d", best_index, min_keycode, max_keycode, table_size); 234#endif 235 // This should never happen, but just in case... 236 if (table_size > (SDL_arraysize(data->keyboard.key_layout) - min_keycode)) { 237 table_size = (SDL_arraysize(data->keyboard.key_layout) - min_keycode); 238 } 239 SDL_memcpy(&data->keyboard.key_layout[min_keycode], table, sizeof(SDL_Scancode) * table_size); 240 241 /* Scancodes represent physical locations on the keyboard, unaffected by keyboard mapping. 242 However, there are a number of extended scancodes that have no standard location, so use 243 the X11 mapping for all non-character keys. 244 */ 245 for (i = min_keycode; i <= max_keycode; ++i) { 246 SDL_Scancode scancode = X11_KeyCodeToSDLScancode(_this, i); 247#ifdef DEBUG_KEYBOARD 248 { 249 KeySym sym; 250 sym = X11_KeyCodeToSym(_this, (KeyCode)i, 0); 251 SDL_Log("code = %d, sym = 0x%X (%s) ", i - min_keycode, 252 (unsigned int)sym, sym == NoSymbol ? "NoSymbol" : X11_XKeysymToString(sym)); 253 } 254#endif 255 if (scancode == data->keyboard.key_layout[i]) { 256 continue; 257 } 258 if ((SDL_GetKeymapKeycode(NULL, scancode, SDL_KMOD_NONE) & (SDLK_SCANCODE_MASK | SDLK_EXTENDED_MASK)) && X11_ScancodeIsRemappable(scancode)) { 259 // Not a character key and the scancode is safe to remap 260#ifdef DEBUG_KEYBOARD 261 SDL_Log("Changing scancode, was %d (%s), now %d (%s)", data->key_layout[i], SDL_GetScancodeName(data->key_layout[i]), scancode, SDL_GetScancodeName(scancode)); 262#endif 263 data->keyboard.key_layout[i] = scancode; 264 } 265 } 266 } else { 267#ifdef DEBUG_SCANCODES 268 SDL_Log("Keyboard layout unknown, please report the following to the SDL forums/mailing list (https://discourse.libsdl.org/):"); 269#endif 270 271 // Determine key_layout - only works on US QWERTY layout 272 for (i = min_keycode; i <= max_keycode; ++i) { 273 SDL_Scancode scancode = X11_KeyCodeToSDLScancode(_this, i); 274#ifdef DEBUG_SCANCODES 275 { 276 KeySym sym; 277 sym = X11_KeyCodeToSym(_this, (KeyCode)i, 0); 278 SDL_Log("code = %d, sym = 0x%X (%s) ", i - min_keycode, 279 (unsigned int)sym, sym == NoSymbol ? "NoSymbol" : X11_XKeysymToString(sym)); 280 } 281 if (scancode == SDL_SCANCODE_UNKNOWN) { 282 SDL_Log("scancode not found"); 283 } else { 284 SDL_Log("scancode = %d (%s)", scancode, SDL_GetScancodeName(scancode)); 285 } 286#endif 287 data->keyboard.key_layout[i] = scancode; 288 } 289 } 290 291 X11_UpdateKeymap(_this, false); 292 293 SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu"); 294 295 X11_ReconcileKeyboardState(_this); 296 297 return true; 298} 299 300#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB 301static unsigned int X11_GetXkbVirtualModifierMask(SDL_VideoDevice *_this, const char *vmod_name) 302{ 303 SDL_VideoData *videodata = _this->internal; 304 unsigned int mod_mask = 0; 305 306 const Atom vmod = X11_XInternAtom(videodata->display, vmod_name, True); 307 if (vmod != None) { 308 for (int i = 0; i < XkbNumVirtualMods; ++i) { 309 if (vmod == videodata->keyboard.xkb.desc_ptr->names->vmods[i]) { 310 mod_mask = videodata->keyboard.xkb.desc_ptr->server->vmods[i]; 311 break; 312 } 313 } 314 } 315 316 return mod_mask; 317} 318#endif 319 320static unsigned X11_GetXModifierMask(SDL_VideoDevice *_this, SDL_Scancode scancode) 321{ 322 SDL_VideoData *videodata = _this->internal; 323 Display *display = videodata->display; 324 unsigned int mod_mask = 0; 325 326 XModifierKeymap *xmods = X11_XGetModifierMapping(display); 327 unsigned int n = xmods->max_keypermod; 328 for (int i = 3; i < 8; i++) { 329 for (int j = 0; j < n; j++) { 330 const KeyCode kc = xmods->modifiermap[i * n + j]; 331 if (videodata->keyboard.key_layout[kc] == scancode) { 332 mod_mask = 1 << i; 333 break; 334 } 335 } 336 } 337 X11_XFreeModifiermap(xmods); 338 339 return mod_mask; 340} 341 342static void X11_AddKeymapEntry(SDL_Keymap *keymap, Uint32 xkeycode, KeySym xkeysym, SDL_Scancode sdl_scancode, SDL_Keymod sdl_mod_mask) 343{ 344 SDL_Keycode keycode = SDL_GetKeyCodeFromKeySym(xkeysym, xkeycode, sdl_mod_mask); 345 346 if (!keycode) { 347 switch (sdl_scancode) { 348 case SDL_SCANCODE_RETURN: 349 keycode = SDLK_RETURN; 350 break; 351 case SDL_SCANCODE_ESCAPE: 352 keycode = SDLK_ESCAPE; 353 break; 354 case SDL_SCANCODE_BACKSPACE: 355 keycode = SDLK_BACKSPACE; 356 break; 357 case SDL_SCANCODE_DELETE: 358 keycode = SDLK_DELETE; 359 break; 360 default: 361 keycode = SDL_SCANCODE_TO_KEYCODE(sdl_scancode); 362 break; 363 } 364 } 365 366 SDL_SetKeymapEntry(keymap, sdl_scancode, sdl_mod_mask, keycode); 367} 368 369void X11_UpdateKeymap(SDL_VideoDevice *_this, bool send_event) 370{ 371 SDL_VideoData *data = _this->internal; 372 373#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB 374 if (data->keyboard.xkb_enabled) { 375 XkbStateRec state; 376 377 for (unsigned int i = 0; i < XkbNumKbdGroups; ++i) { 378 SDL_DestroyKeymap(data->keyboard.xkb.keymaps[i]); 379 data->keyboard.xkb.keymaps[i] = NULL; 380 } 381 382 X11_XkbGetNames(data->display, XkbVirtualModNamesMask, data->keyboard.xkb.desc_ptr); 383 X11_XkbGetUpdatedMap(data->display, XkbAllClientInfoMask | XkbVirtualModsMask, data->keyboard.xkb.desc_ptr); 384 385 if (X11_XkbGetState(data->display, XkbUseCoreKbd, &state) == Success) { 386 data->keyboard.xkb.current_group = state.group; 387 } 388 389 data->keyboard.alt_mask = X11_GetXkbVirtualModifierMask(_this, "Alt"); 390 if (!data->keyboard.alt_mask) { 391 data->keyboard.alt_mask = X11_GetXkbVirtualModifierMask(_this, "Meta"); 392 } 393 data->keyboard.gui_mask = X11_GetXkbVirtualModifierMask(_this, "Super"); 394 data->keyboard.level3_mask = X11_GetXkbVirtualModifierMask(_this, "LevelThree"); 395 data->keyboard.level5_mask = X11_GetXkbVirtualModifierMask(_this, "LevelFive"); 396 data->keyboard.numlock_mask = X11_GetXkbVirtualModifierMask(_this, "NumLock"); 397 data->keyboard.scrolllock_mask = X11_GetXkbVirtualModifierMask(_this, "ScrollLock"); 398 399 for (unsigned int i = 0; i < XkbNumKbdGroups; ++i) { 400 data->keyboard.xkb.keymaps[i] = SDL_CreateKeymap(false); 401 if (!data->keyboard.xkb.keymaps[i]) { 402 for (unsigned int j = 0; j < i; ++j) { 403 SDL_DestroyKeymap(data->keyboard.xkb.keymaps[i]); 404 data->keyboard.xkb.keymaps[j] = NULL; 405 } 406 407 return; 408 } 409 } 410 411 // Only the shift, alt, level 3, level 5 and caps lock modifiers affect SDL keymaps. 412 const Uint32 valid_mod_mask = ShiftMask | LockMask | data->keyboard.alt_mask | data->keyboard.level3_mask | data->keyboard.level5_mask; 413 414 for (Uint32 xkeycode = data->keyboard.xkb.desc_ptr->min_key_code; xkeycode < data->keyboard.xkb.desc_ptr->max_key_code; ++xkeycode) { 415 const SDL_Scancode scancode = data->keyboard.key_layout[xkeycode]; 416 if (scancode == SDL_SCANCODE_UNKNOWN) { 417 continue; 418 } 419 420 for (Uint32 group = 0; group < XkbNumKbdGroups; ++group) { 421 SDL_Keymap *keymap = data->keyboard.xkb.keymaps[group]; 422 423 Uint32 effective_group = group; 424 const unsigned char max_key_group = XkbKeyNumGroups(data->keyboard.xkb.desc_ptr, xkeycode); 425 const unsigned char key_group_info = XkbKeyGroupInfo(data->keyboard.xkb.desc_ptr, xkeycode); 426 427 if (max_key_group && effective_group >= max_key_group) { 428 const unsigned char action = XkbOutOfRangeGroupAction(key_group_info); 429 430 switch (action) { 431 default: 432 effective_group %= max_key_group; 433 break; 434 case XkbClampIntoRange: 435 effective_group = max_key_group - 1; 436 break; 437 case XkbRedirectIntoRange: 438 effective_group = XkbOutOfRangeGroupNumber(key_group_info); 439 if (effective_group >= max_key_group) { 440 effective_group = 0; 441 } 442 break; 443 } 444 } 445 446 XkbKeyTypePtr key_type = XkbKeyKeyType(data->keyboard.xkb.desc_ptr, xkeycode, effective_group); 447 448 for (Uint32 level = 0; level < key_type->num_levels; ++level) { 449 const KeySym keysym = X11_KeyCodeToSym(_this, xkeycode, effective_group, level); 450 451 if (keysym != NoSymbol) { 452 bool key_added = false; 453 454 for (int map_idx = 0; map_idx < key_type->map_count; ++map_idx) { 455 if (key_type->map[map_idx].active && key_type->map[map_idx].level == level) { 456 const unsigned int xkb_mod_mask = key_type->map[map_idx].mods.mask; 457 if ((xkb_mod_mask | valid_mod_mask) == valid_mod_mask) { 458 const SDL_Keymod sdl_mod_mask = (xkb_mod_mask & ShiftMask ? SDL_KMOD_SHIFT : 0) | 459 (xkb_mod_mask & LockMask ? SDL_KMOD_CAPS : 0) | 460 (xkb_mod_mask & data->keyboard.alt_mask ? SDL_KMOD_ALT : 0) | 461 (xkb_mod_mask & data->keyboard.level3_mask ? SDL_KMOD_MODE : 0) | 462 (xkb_mod_mask & data->keyboard.level5_mask ? SDL_KMOD_LEVEL5 : 0); 463 464 X11_AddKeymapEntry(keymap, xkeycode, keysym, scancode, sdl_mod_mask); 465 key_added = true; 466 } 467 } 468 } 469 470 // Add the unmodified key for level 0. 471 if (!level && !key_added) { 472 X11_AddKeymapEntry(keymap, xkeycode, keysym, scancode, 0); 473 } 474 } 475 } 476 } 477 } 478 479 SDL_SetKeymap(data->keyboard.xkb.keymaps[data->keyboard.xkb.current_group], send_event); 480 } else 481#endif 482 { 483 SDL_Keymap *keymap = SDL_CreateKeymap(true); 484 if (!keymap) { 485 return; 486 } 487 488 if (send_event) { 489 if (data->keyboard.core.keysym_map) { 490 X11_XFree(data->keyboard.core.keysym_map); 491 } 492 X11_XDisplayKeycodes(data->display, &data->keyboard.core.min_keycode, &data->keyboard.core.max_keycode); 493 data->keyboard.core.keysym_map = X11_XGetKeyboardMapping(data->display, data->keyboard.core.min_keycode, 494 data->keyboard.core.max_keycode - data->keyboard.core.min_keycode, 495 &data->keyboard.core.keysyms_per_key); 496 } 497 498 for (Uint32 xkeycode = data->keyboard.core.min_keycode; xkeycode <= data->keyboard.core.max_keycode; ++xkeycode) { 499 const SDL_Scancode scancode = data->keyboard.key_layout[xkeycode]; 500 if (scancode == SDL_SCANCODE_UNKNOWN) { 501 continue; 502 } 503 504 const KeySym keysym = X11_KeyCodeToSym(_this, xkeycode, 0, 0); 505 if (keysym != NoSymbol) { 506 X11_AddKeymapEntry(keymap, xkeycode, keysym, scancode, 0); 507 } 508 } 509 510 data->keyboard.alt_mask = Mod1Mask; // Alt or Meta 511 data->keyboard.gui_mask = Mod4Mask; // Super 512 data->keyboard.level3_mask = Mod5Mask; // Note: Not a typo, Mod5 = level 3 shift, and Mod3 = level 5 shift. 513 data->keyboard.level5_mask = Mod3Mask; 514 data->keyboard.numlock_mask = X11_GetXModifierMask(_this, SDL_SCANCODE_NUMLOCKCLEAR); 515 data->keyboard.scrolllock_mask = X11_GetXModifierMask(_this, SDL_SCANCODE_SCROLLLOCK); 516 517 SDL_SetKeymap(keymap, send_event); 518 } 519} 520 521void X11_QuitKeyboard(SDL_VideoDevice *_this) 522{ 523 SDL_VideoData *data = _this->internal; 524 525#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB 526 if (data->keyboard.xkb_enabled) { 527 for (int i = 0; i < XkbNumKbdGroups; ++i) { 528 SDL_DestroyKeymap(data->keyboard.xkb.keymaps[i]); 529 data->keyboard.xkb.keymaps[i] = NULL; 530 } 531 532 if (data->keyboard.xkb_enabled) { 533 X11_XkbFreeKeyboard(data->keyboard.xkb.desc_ptr, 0, True); 534 data->keyboard.xkb.desc_ptr = NULL; 535 } 536 } else 537#endif 538 if (data->keyboard.core.keysym_map) { 539 X11_XFree(data->keyboard.core.keysym_map); 540 data->keyboard.core.keysym_map = NULL; 541 } 542} 543 544void X11_ClearComposition(SDL_WindowData *data) 545{ 546 if (data->preedit_length > 0) { 547 data->preedit_text[0] = '\0'; 548 data->preedit_length = 0; 549 } 550 551 if (data->ime_needs_clear_composition) { 552 SDL_SendEditingText("", 0, 0); 553 data->ime_needs_clear_composition = false; 554 } 555} 556 557static void X11_SendEditingEvent(SDL_WindowData *data) 558{ 559 if (data->preedit_length == 0) { 560 X11_ClearComposition(data); 561 return; 562 } 563 564 bool in_highlight = false; 565 int start = -1, length = 0, i; 566 for (i = 0; i < data->preedit_length; ++i) { 567 if (data->preedit_feedback[i] & (XIMReverse | XIMHighlight)) { 568 if (start < 0) { 569 start = i; 570 in_highlight = true; 571 } 572 } else if (in_highlight) { 573 // Found the end of the highlight 574 break; 575 } 576 } 577 if (in_highlight) { 578 length = (i - start); 579 } else { 580 start = SDL_clamp(data->preedit_cursor, 0, data->preedit_length); 581 } 582 SDL_SendEditingText(data->preedit_text, start, length); 583 584 data->ime_needs_clear_composition = true; 585} 586 587static int preedit_start_callback(XIC xic, XPointer client_data, XPointer call_data) 588{ 589 // No limit on preedit text length 590 return -1; 591} 592 593static void preedit_done_callback(XIC xic, XPointer client_data, XPointer call_data) 594{ 595} 596 597static void preedit_draw_callback(XIC xic, XPointer client_data, XIMPreeditDrawCallbackStruct *call_data) 598{ 599 SDL_WindowData *data = (SDL_WindowData *)client_data; 600 int chg_first = SDL_clamp(call_data->chg_first, 0, data->preedit_length); 601 int chg_length = SDL_clamp(call_data->chg_length, 0, data->preedit_length - chg_first); 602 603 const char *start = data->preedit_text; 604 if (chg_length > 0) { 605 // Delete text in range 606 for (int i = 0; start && *start && i < chg_first; ++i) { 607 SDL_StepUTF8(&start, NULL); 608 } 609 610 const char *end = start; 611 for (int i = 0; end && *end && i < chg_length; ++i) { 612 SDL_StepUTF8(&end, NULL); 613 } 614 615 if (end > start) { 616 SDL_memmove((char *)start, end, SDL_strlen(end) + 1); 617 if ((chg_first + chg_length) > data->preedit_length) { 618 SDL_memmove(&data->preedit_feedback[chg_first], &data->preedit_feedback[chg_first + chg_length], (data->preedit_length - chg_first - chg_length) * sizeof(*data->preedit_feedback)); 619 } 620 } 621 data->preedit_length -= chg_length; 622 } 623 624 XIMText *text = call_data->text; 625 if (text) { 626 // Insert text in range 627 SDL_assert(!text->encoding_is_wchar); 628 629 // The text length isn't calculated as directed by the spec, recalculate it now 630 if (text->string.multi_byte) { 631 text->length = SDL_utf8strlen(text->string.multi_byte); 632 } 633 634 size_t string_size = SDL_strlen(text->string.multi_byte); 635 size_t size = string_size + 1; 636 if (data->preedit_text) { 637 size += SDL_strlen(data->preedit_text); 638 } 639 char *preedit_text = (char *)SDL_malloc(size * sizeof(*preedit_text)); 640 if (preedit_text) { 641 size_t pre_size = (start - data->preedit_text); 642 size_t post_size = start ? SDL_strlen(start) : 0; 643 if (pre_size > 0) { 644 SDL_memcpy(&preedit_text[0], data->preedit_text, pre_size); 645 } 646 SDL_memcpy(&preedit_text[pre_size], text->string.multi_byte, string_size); 647 if (post_size > 0) { 648 SDL_memcpy(&preedit_text[pre_size + string_size], start, post_size); 649 } 650 preedit_text[size - 1] = '\0'; 651 } 652 653 size_t feedback_size = data->preedit_length + text->length; 654 XIMFeedback *feedback = (XIMFeedback *)SDL_malloc(feedback_size * sizeof(*feedback)); 655 if (feedback) { 656 size_t pre_size = (size_t)chg_first; 657 size_t post_size = (size_t)data->preedit_length - pre_size; 658 if (pre_size > 0) { 659 SDL_memcpy(&feedback[0], data->preedit_feedback, pre_size * sizeof(*feedback)); 660 } 661 SDL_memcpy(&feedback[pre_size], text->feedback, text->length * sizeof(*feedback)); 662 if (post_size > 0) { 663 SDL_memcpy(&feedback[pre_size + text->length], &data->preedit_feedback[pre_size], post_size * sizeof(*feedback)); 664 } 665 } 666 667 if (preedit_text && feedback) { 668 SDL_free(data->preedit_text); 669 data->preedit_text = preedit_text; 670 671 SDL_free(data->preedit_feedback); 672 data->preedit_feedback = feedback; 673 674 data->preedit_length += text->length; 675 } else { 676 SDL_free(preedit_text); 677 SDL_free(feedback); 678 } 679 } 680 681 data->preedit_cursor = call_data->caret; 682 683#ifdef DEBUG_XIM 684 if (call_data->chg_length > 0) { 685 SDL_Log("Draw callback deleted %d characters at %d", call_data->chg_length, call_data->chg_first); 686 } 687 if (text) { 688 SDL_Log("Draw callback inserted %s at %d, caret: %d", text->string.multi_byte, call_data->chg_first, call_data->caret); 689 } 690 SDL_Log("Pre-edit text: %s", data->preedit_text); 691#endif 692 693 X11_SendEditingEvent(data); 694} 695 696static void preedit_caret_callback(XIC xic, XPointer client_data, XIMPreeditCaretCallbackStruct *call_data) 697{ 698 SDL_WindowData *data = (SDL_WindowData *)client_data; 699 700 switch (call_data->direction) { 701 case XIMAbsolutePosition: 702 if (call_data->position != data->preedit_cursor) { 703 data->preedit_cursor = call_data->position; 704 X11_SendEditingEvent(data); 705 } 706 break; 707 case XIMDontChange: 708 break; 709 default: 710 // Not currently supported 711 break; 712 } 713} 714 715void X11_CreateInputContext(SDL_WindowData *data) 716{ 717#ifdef X_HAVE_UTF8_STRING 718 SDL_VideoData *videodata = data->videodata; 719 720 if (SDL_X11_HAVE_UTF8 && videodata->im) { 721 const char *hint = SDL_GetHint(SDL_HINT_IME_IMPLEMENTED_UI); 722 if (hint && SDL_strstr(hint, "composition")) { 723 XIMCallback draw_callback; 724 draw_callback.client_data = (XPointer)data; 725 draw_callback.callback = (XIMProc)preedit_draw_callback; 726 727 XIMCallback start_callback; 728 start_callback.client_data = (XPointer)data; 729 start_callback.callback = (XIMProc)preedit_start_callback; 730 731 XIMCallback done_callback; 732 done_callback.client_data = (XPointer)data; 733 done_callback.callback = (XIMProc)preedit_done_callback; 734 735 XIMCallback caret_callback; 736 caret_callback.client_data = (XPointer)data; 737 caret_callback.callback = (XIMProc)preedit_caret_callback; 738 739 XVaNestedList attr = X11_XVaCreateNestedList(0, 740 XNPreeditStartCallback, &start_callback, 741 XNPreeditDoneCallback, &done_callback, 742 XNPreeditDrawCallback, &draw_callback, 743 XNPreeditCaretCallback, &caret_callback, 744 NULL); 745 if (attr) { 746 data->ic = X11_XCreateIC(videodata->im, 747 XNInputStyle, XIMPreeditCallbacks | XIMStatusCallbacks, 748 XNPreeditAttributes, attr, 749 XNClientWindow, data->xwindow, 750 NULL); 751 X11_XFree(attr); 752 } 753 } 754 if (!data->ic) { 755 data->ic = X11_XCreateIC(videodata->im, 756 XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 757 XNClientWindow, data->xwindow, 758 NULL); 759 } 760 data->xim_spot.x = -1; 761 data->xim_spot.y = -1; 762 } 763#endif // X_HAVE_UTF8_STRING 764} 765 766 767void X11_DestroyInputContext(SDL_WindowData *data) 768{ 769#ifdef X_HAVE_UTF8_STRING 770 if (data->ic) { 771 X11_XDestroyIC(data->ic); 772 data->ic = NULL; 773 } 774 SDL_free(data->preedit_text); 775 SDL_free(data->preedit_feedback); 776 data->preedit_text = NULL; 777 data->preedit_feedback = NULL; 778#endif 779} 780 781static void X11_ResetXIM(SDL_VideoDevice *_this, SDL_Window *window) 782{ 783#ifdef X_HAVE_UTF8_STRING 784 SDL_WindowData *data = window->internal; 785 786 if (data && data->ic) { 787 // Clear any partially entered dead keys 788 char *contents = X11_Xutf8ResetIC(data->ic); 789 if (contents) { 790 X11_XFree(contents); 791 } 792 } 793#endif // X_HAVE_UTF8_STRING 794} 795 796bool X11_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) 797{ 798 X11_ResetXIM(_this, window); 799 800 return X11_UpdateTextInputArea(_this, window); 801} 802 803bool X11_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window) 804{ 805 X11_ResetXIM(_this, window); 806 return true; 807} 808 809bool X11_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window) 810{ 811#ifdef X_HAVE_UTF8_STRING 812 SDL_WindowData *data = window->internal; 813 814 if (data && data->ic) { 815 XPoint spot; 816 spot.x = window->text_input_rect.x + window->text_input_cursor; 817 spot.y = window->text_input_rect.y + window->text_input_rect.h; 818 if (spot.x != data->xim_spot.x || spot.y != data->xim_spot.y) { 819 XVaNestedList attr = X11_XVaCreateNestedList(0, XNSpotLocation, &spot, NULL); 820 if (attr) { 821 X11_XSetICValues(data->ic, XNPreeditAttributes, attr, NULL); 822 X11_XFree(attr); 823 } 824 SDL_copyp(&data->xim_spot, &spot); 825 } 826 } 827#endif 828 return true; 829} 830 831bool X11_HasScreenKeyboardSupport(SDL_VideoDevice *_this) 832{ 833 SDL_VideoData *videodata = _this->internal; 834 return videodata->is_steam_deck; 835} 836 837void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) 838{ 839 SDL_VideoData *videodata = _this->internal; 840 841 if (videodata->is_steam_deck) { 842 /* For more documentation of the URL parameters, see: 843 * https://partner.steamgames.com/doc/api/ISteamUtils#ShowFloatingGamepadTextInput 844 */ 845 const int k_EFloatingGamepadTextInputModeModeSingleLine = 0; // Enter dismisses the keyboard 846 const int k_EFloatingGamepadTextInputModeModeMultipleLines = 1; // User needs to explicitly dismiss the keyboard 847 const int k_EFloatingGamepadTextInputModeModeEmail = 2; // Keyboard is displayed in a special mode that makes it easier to enter emails 848 const int k_EFloatingGamepadTextInputModeModeNumeric = 3; // Numeric keypad is shown 849 char deeplink[128]; 850 int mode; 851 852 switch (SDL_GetTextInputType(props)) { 853 case SDL_TEXTINPUT_TYPE_TEXT_EMAIL: 854 mode = k_EFloatingGamepadTextInputModeModeEmail; 855 break; 856 case SDL_TEXTINPUT_TYPE_NUMBER: 857 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN: 858 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE: 859 mode = k_EFloatingGamepadTextInputModeModeNumeric; 860 break; 861 default: 862 if (SDL_GetTextInputMultiline(props)) { 863 mode = k_EFloatingGamepadTextInputModeModeMultipleLines; 864 } else { 865 mode = k_EFloatingGamepadTextInputModeModeSingleLine; 866 } 867 break; 868 } 869 (void)SDL_snprintf(deeplink, sizeof(deeplink), 870 "steam://open/keyboard?XPosition=%i&YPosition=%i&Width=%i&Height=%i&Mode=%d", 871 window->text_input_rect.x, window->text_input_rect.y, 872 window->text_input_rect.w, window->text_input_rect.h, 873 mode); 874 SDL_OpenURL(deeplink); 875 SDL_SendScreenKeyboardShown(); 876 } 877} 878 879void X11_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) 880{ 881 SDL_VideoData *videodata = _this->internal; 882 883 if (videodata->is_steam_deck) { 884 SDL_OpenURL("steam://close/keyboard"); 885 SDL_SendScreenKeyboardHidden(); 886 } 887} 888 889#endif // SDL_VIDEO_DRIVER_X11 890[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.