Atlas - SDL_x11keyboard.c

Home / ext / SDL / src / video / x11 Lines: 1 | Size: 33616 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 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 const Uint32 valid_mod_mask = ShiftMask | LockMask | data->keyboard.alt_mask | data->keyboard.level3_mask | data->keyboard.level5_mask; 412 413 for (Uint32 xkeycode = data->keyboard.xkb.desc_ptr->min_key_code; xkeycode < data->keyboard.xkb.desc_ptr->max_key_code; ++xkeycode) { 414 const SDL_Scancode scancode = data->keyboard.key_layout[xkeycode]; 415 if (scancode == SDL_SCANCODE_UNKNOWN) { 416 continue; 417 } 418 419 for (Uint32 group = 0; group < XkbNumKbdGroups; ++group) { 420 SDL_Keymap *keymap = data->keyboard.xkb.keymaps[group]; 421 422 Uint32 effective_group = group; 423 const unsigned char max_key_group = XkbKeyNumGroups(data->keyboard.xkb.desc_ptr, xkeycode); 424 const unsigned char key_group_info = XkbKeyGroupInfo(data->keyboard.xkb.desc_ptr, xkeycode); 425 426 if (max_key_group && effective_group >= max_key_group) { 427 const unsigned char action = XkbOutOfRangeGroupAction(key_group_info); 428 429 switch (action) { 430 default: 431 effective_group %= max_key_group; 432 break; 433 case XkbClampIntoRange: 434 effective_group = max_key_group - 1; 435 break; 436 case XkbRedirectIntoRange: 437 effective_group = XkbOutOfRangeGroupNumber(key_group_info); 438 if (effective_group >= max_key_group) { 439 effective_group = 0; 440 } 441 break; 442 } 443 } 444 445 XkbKeyTypePtr key_type = XkbKeyKeyType(data->keyboard.xkb.desc_ptr, xkeycode, effective_group); 446 447 for (Uint32 level = 0; level < key_type->num_levels; ++level) { 448 const KeySym keysym = X11_KeyCodeToSym(_this, xkeycode, effective_group, level); 449 450 if (keysym != NoSymbol) { 451 bool key_added = false; 452 453 for (int map_idx = 0; map_idx < key_type->map_count; ++map_idx) { 454 if (key_type->map[map_idx].active && key_type->map[map_idx].level == level) { 455 const unsigned int xkb_mod_mask = key_type->map[map_idx].mods.mask; 456 if ((xkb_mod_mask | valid_mod_mask) == valid_mod_mask) { 457 const SDL_Keymod sdl_mod_mask = (xkb_mod_mask & ShiftMask ? SDL_KMOD_SHIFT : 0) | 458 (xkb_mod_mask & LockMask ? SDL_KMOD_CAPS : 0) | 459 (xkb_mod_mask & data->keyboard.alt_mask ? SDL_KMOD_ALT : 0) | 460 (xkb_mod_mask & data->keyboard.level3_mask ? SDL_KMOD_MODE : 0) | 461 (xkb_mod_mask & data->keyboard.level5_mask ? SDL_KMOD_LEVEL5 : 0); 462 463 X11_AddKeymapEntry(keymap, xkeycode, keysym, scancode, sdl_mod_mask); 464 key_added = true; 465 } 466 } 467 } 468 469 // Add the unmodified key for level 0. 470 if (!level && !key_added) { 471 X11_AddKeymapEntry(keymap, xkeycode, keysym, scancode, 0); 472 } 473 } 474 } 475 } 476 } 477 478 SDL_SetKeymap(data->keyboard.xkb.keymaps[data->keyboard.xkb.current_group], send_event); 479 } else 480#endif 481 { 482 SDL_Keymap *keymap = SDL_CreateKeymap(true); 483 if (!keymap) { 484 return; 485 } 486 487 if (send_event) { 488 if (data->keyboard.core.keysym_map) { 489 X11_XFree(data->keyboard.core.keysym_map); 490 } 491 X11_XDisplayKeycodes(data->display, &data->keyboard.core.min_keycode, &data->keyboard.core.max_keycode); 492 data->keyboard.core.keysym_map = X11_XGetKeyboardMapping(data->display, data->keyboard.core.min_keycode, 493 data->keyboard.core.max_keycode - data->keyboard.core.min_keycode, 494 &data->keyboard.core.keysyms_per_key); 495 } 496 497 for (Uint32 xkeycode = data->keyboard.core.min_keycode; xkeycode <= data->keyboard.core.max_keycode; ++xkeycode) { 498 const SDL_Scancode scancode = data->keyboard.key_layout[xkeycode]; 499 if (scancode == SDL_SCANCODE_UNKNOWN) { 500 continue; 501 } 502 503 const KeySym keysym = X11_KeyCodeToSym(_this, xkeycode, 0, 0); 504 if (keysym != NoSymbol) { 505 X11_AddKeymapEntry(keymap, xkeycode, keysym, scancode, 0); 506 } 507 } 508 509 data->keyboard.alt_mask = Mod1Mask; // Alt or Meta 510 data->keyboard.gui_mask = Mod4Mask; // Super 511 data->keyboard.level3_mask = Mod5Mask; // Note: Not a typo, Mod5 = level 3 shift, and Mod3 = level 5 shift. 512 data->keyboard.level5_mask = Mod3Mask; 513 data->keyboard.numlock_mask = X11_GetXModifierMask(_this, SDL_SCANCODE_NUMLOCKCLEAR); 514 data->keyboard.scrolllock_mask = X11_GetXModifierMask(_this, SDL_SCANCODE_SCROLLLOCK); 515 516 SDL_SetKeymap(keymap, send_event); 517 } 518} 519 520void X11_QuitKeyboard(SDL_VideoDevice *_this) 521{ 522 SDL_VideoData *data = _this->internal; 523 524#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB 525 if (data->keyboard.xkb_enabled) { 526 for (int i = 0; i < XkbNumKbdGroups; ++i) { 527 SDL_DestroyKeymap(data->keyboard.xkb.keymaps[i]); 528 data->keyboard.xkb.keymaps[i] = NULL; 529 } 530 531 if (data->keyboard.xkb_enabled) { 532 X11_XkbFreeKeyboard(data->keyboard.xkb.desc_ptr, 0, True); 533 data->keyboard.xkb.desc_ptr = NULL; 534 } 535 } else 536#endif 537 if (data->keyboard.core.keysym_map) { 538 X11_XFree(data->keyboard.core.keysym_map); 539 data->keyboard.core.keysym_map = NULL; 540 } 541} 542 543void X11_ClearComposition(SDL_WindowData *data) 544{ 545 if (data->preedit_length > 0) { 546 data->preedit_text[0] = '\0'; 547 data->preedit_length = 0; 548 } 549 550 if (data->ime_needs_clear_composition) { 551 SDL_SendEditingText("", 0, 0); 552 data->ime_needs_clear_composition = false; 553 } 554} 555 556static void X11_SendEditingEvent(SDL_WindowData *data) 557{ 558 if (data->preedit_length == 0) { 559 X11_ClearComposition(data); 560 return; 561 } 562 563 bool in_highlight = false; 564 int start = -1, length = 0, i; 565 for (i = 0; i < data->preedit_length; ++i) { 566 if (data->preedit_feedback[i] & (XIMReverse | XIMHighlight)) { 567 if (start < 0) { 568 start = i; 569 in_highlight = true; 570 } 571 } else if (in_highlight) { 572 // Found the end of the highlight 573 break; 574 } 575 } 576 if (in_highlight) { 577 length = (i - start); 578 } else { 579 start = SDL_clamp(data->preedit_cursor, 0, data->preedit_length); 580 } 581 SDL_SendEditingText(data->preedit_text, start, length); 582 583 data->ime_needs_clear_composition = true; 584} 585 586static int preedit_start_callback(XIC xic, XPointer client_data, XPointer call_data) 587{ 588 // No limit on preedit text length 589 return -1; 590} 591 592static void preedit_done_callback(XIC xic, XPointer client_data, XPointer call_data) 593{ 594} 595 596static void preedit_draw_callback(XIC xic, XPointer client_data, XIMPreeditDrawCallbackStruct *call_data) 597{ 598 SDL_WindowData *data = (SDL_WindowData *)client_data; 599 int chg_first = SDL_clamp(call_data->chg_first, 0, data->preedit_length); 600 int chg_length = SDL_clamp(call_data->chg_length, 0, data->preedit_length - chg_first); 601 602 const char *start = data->preedit_text; 603 if (chg_length > 0) { 604 // Delete text in range 605 for (int i = 0; start && *start && i < chg_first; ++i) { 606 SDL_StepUTF8(&start, NULL); 607 } 608 609 const char *end = start; 610 for (int i = 0; end && *end && i < chg_length; ++i) { 611 SDL_StepUTF8(&end, NULL); 612 } 613 614 if (end > start) { 615 SDL_memmove((char *)start, end, SDL_strlen(end) + 1); 616 if ((chg_first + chg_length) > data->preedit_length) { 617 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)); 618 } 619 } 620 data->preedit_length -= chg_length; 621 } 622 623 XIMText *text = call_data->text; 624 if (text) { 625 // Insert text in range 626 SDL_assert(!text->encoding_is_wchar); 627 628 // The text length isn't calculated as directed by the spec, recalculate it now 629 if (text->string.multi_byte) { 630 text->length = SDL_utf8strlen(text->string.multi_byte); 631 } 632 633 size_t string_size = SDL_strlen(text->string.multi_byte); 634 size_t size = string_size + 1; 635 if (data->preedit_text) { 636 size += SDL_strlen(data->preedit_text); 637 } 638 char *preedit_text = (char *)SDL_malloc(size * sizeof(*preedit_text)); 639 if (preedit_text) { 640 size_t pre_size = (start - data->preedit_text); 641 size_t post_size = start ? SDL_strlen(start) : 0; 642 if (pre_size > 0) { 643 SDL_memcpy(&preedit_text[0], data->preedit_text, pre_size); 644 } 645 SDL_memcpy(&preedit_text[pre_size], text->string.multi_byte, string_size); 646 if (post_size > 0) { 647 SDL_memcpy(&preedit_text[pre_size + string_size], start, post_size); 648 } 649 preedit_text[size - 1] = '\0'; 650 } 651 652 size_t feedback_size = data->preedit_length + text->length; 653 XIMFeedback *feedback = (XIMFeedback *)SDL_malloc(feedback_size * sizeof(*feedback)); 654 if (feedback) { 655 size_t pre_size = (size_t)chg_first; 656 size_t post_size = (size_t)data->preedit_length - pre_size; 657 if (pre_size > 0) { 658 SDL_memcpy(&feedback[0], data->preedit_feedback, pre_size * sizeof(*feedback)); 659 } 660 SDL_memcpy(&feedback[pre_size], text->feedback, text->length * sizeof(*feedback)); 661 if (post_size > 0) { 662 SDL_memcpy(&feedback[pre_size + text->length], &data->preedit_feedback[pre_size], post_size * sizeof(*feedback)); 663 } 664 } 665 666 if (preedit_text && feedback) { 667 SDL_free(data->preedit_text); 668 data->preedit_text = preedit_text; 669 670 SDL_free(data->preedit_feedback); 671 data->preedit_feedback = feedback; 672 673 data->preedit_length += text->length; 674 } else { 675 SDL_free(preedit_text); 676 SDL_free(feedback); 677 } 678 } 679 680 data->preedit_cursor = call_data->caret; 681 682#ifdef DEBUG_XIM 683 if (call_data->chg_length > 0) { 684 SDL_Log("Draw callback deleted %d characters at %d", call_data->chg_length, call_data->chg_first); 685 } 686 if (text) { 687 SDL_Log("Draw callback inserted %s at %d, caret: %d", text->string.multi_byte, call_data->chg_first, call_data->caret); 688 } 689 SDL_Log("Pre-edit text: %s", data->preedit_text); 690#endif 691 692 X11_SendEditingEvent(data); 693} 694 695static void preedit_caret_callback(XIC xic, XPointer client_data, XIMPreeditCaretCallbackStruct *call_data) 696{ 697 SDL_WindowData *data = (SDL_WindowData *)client_data; 698 699 switch (call_data->direction) { 700 case XIMAbsolutePosition: 701 if (call_data->position != data->preedit_cursor) { 702 data->preedit_cursor = call_data->position; 703 X11_SendEditingEvent(data); 704 } 705 break; 706 case XIMDontChange: 707 break; 708 default: 709 // Not currently supported 710 break; 711 } 712} 713 714void X11_CreateInputContext(SDL_WindowData *data) 715{ 716#ifdef X_HAVE_UTF8_STRING 717 SDL_VideoData *videodata = data->videodata; 718 719 if (SDL_X11_HAVE_UTF8 && videodata->im) { 720 const char *hint = SDL_GetHint(SDL_HINT_IME_IMPLEMENTED_UI); 721 if (hint && SDL_strstr(hint, "composition")) { 722 XIMCallback draw_callback; 723 draw_callback.client_data = (XPointer)data; 724 draw_callback.callback = (XIMProc)preedit_draw_callback; 725 726 XIMCallback start_callback; 727 start_callback.client_data = (XPointer)data; 728 start_callback.callback = (XIMProc)preedit_start_callback; 729 730 XIMCallback done_callback; 731 done_callback.client_data = (XPointer)data; 732 done_callback.callback = (XIMProc)preedit_done_callback; 733 734 XIMCallback caret_callback; 735 caret_callback.client_data = (XPointer)data; 736 caret_callback.callback = (XIMProc)preedit_caret_callback; 737 738 XVaNestedList attr = X11_XVaCreateNestedList(0, 739 XNPreeditStartCallback, &start_callback, 740 XNPreeditDoneCallback, &done_callback, 741 XNPreeditDrawCallback, &draw_callback, 742 XNPreeditCaretCallback, &caret_callback, 743 NULL); 744 if (attr) { 745 data->ic = X11_XCreateIC(videodata->im, 746 XNInputStyle, XIMPreeditCallbacks | XIMStatusCallbacks, 747 XNPreeditAttributes, attr, 748 XNClientWindow, data->xwindow, 749 NULL); 750 X11_XFree(attr); 751 } 752 } 753 if (!data->ic) { 754 data->ic = X11_XCreateIC(videodata->im, 755 XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 756 XNClientWindow, data->xwindow, 757 NULL); 758 } 759 data->xim_spot.x = -1; 760 data->xim_spot.y = -1; 761 } 762#endif // X_HAVE_UTF8_STRING 763} 764 765 766void X11_DestroyInputContext(SDL_WindowData *data) 767{ 768#ifdef X_HAVE_UTF8_STRING 769 if (data->ic) { 770 X11_XDestroyIC(data->ic); 771 data->ic = NULL; 772 } 773 SDL_free(data->preedit_text); 774 SDL_free(data->preedit_feedback); 775 data->preedit_text = NULL; 776 data->preedit_feedback = NULL; 777#endif 778} 779 780static void X11_ResetXIM(SDL_VideoDevice *_this, SDL_Window *window) 781{ 782#ifdef X_HAVE_UTF8_STRING 783 SDL_WindowData *data = window->internal; 784 785 if (data && data->ic) { 786 // Clear any partially entered dead keys 787 char *contents = X11_Xutf8ResetIC(data->ic); 788 if (contents) { 789 X11_XFree(contents); 790 } 791 } 792#endif // X_HAVE_UTF8_STRING 793} 794 795bool X11_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) 796{ 797 X11_ResetXIM(_this, window); 798 799 return X11_UpdateTextInputArea(_this, window); 800} 801 802bool X11_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window) 803{ 804 X11_ResetXIM(_this, window); 805 return true; 806} 807 808bool X11_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window) 809{ 810#ifdef X_HAVE_UTF8_STRING 811 SDL_WindowData *data = window->internal; 812 813 if (data && data->ic) { 814 XPoint spot; 815 spot.x = window->text_input_rect.x + window->text_input_cursor; 816 spot.y = window->text_input_rect.y + window->text_input_rect.h; 817 if (spot.x != data->xim_spot.x || spot.y != data->xim_spot.y) { 818 XVaNestedList attr = X11_XVaCreateNestedList(0, XNSpotLocation, &spot, NULL); 819 if (attr) { 820 X11_XSetICValues(data->ic, XNPreeditAttributes, attr, NULL); 821 X11_XFree(attr); 822 } 823 SDL_copyp(&data->xim_spot, &spot); 824 } 825 } 826#endif 827 return true; 828} 829 830bool X11_HasScreenKeyboardSupport(SDL_VideoDevice *_this) 831{ 832 SDL_VideoData *videodata = _this->internal; 833 return videodata->is_steam_deck; 834} 835 836void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) 837{ 838 SDL_VideoData *videodata = _this->internal; 839 840 if (videodata->is_steam_deck) { 841 /* For more documentation of the URL parameters, see: 842 * https://partner.steamgames.com/doc/api/ISteamUtils#ShowFloatingGamepadTextInput 843 */ 844 const int k_EFloatingGamepadTextInputModeModeSingleLine = 0; // Enter dismisses the keyboard 845 const int k_EFloatingGamepadTextInputModeModeMultipleLines = 1; // User needs to explicitly dismiss the keyboard 846 const int k_EFloatingGamepadTextInputModeModeEmail = 2; // Keyboard is displayed in a special mode that makes it easier to enter emails 847 const int k_EFloatingGamepadTextInputModeModeNumeric = 3; // Numeric keypad is shown 848 char deeplink[128]; 849 int mode; 850 851 switch (SDL_GetTextInputType(props)) { 852 case SDL_TEXTINPUT_TYPE_TEXT_EMAIL: 853 mode = k_EFloatingGamepadTextInputModeModeEmail; 854 break; 855 case SDL_TEXTINPUT_TYPE_NUMBER: 856 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN: 857 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE: 858 mode = k_EFloatingGamepadTextInputModeModeNumeric; 859 break; 860 default: 861 if (SDL_GetTextInputMultiline(props)) { 862 mode = k_EFloatingGamepadTextInputModeModeMultipleLines; 863 } else { 864 mode = k_EFloatingGamepadTextInputModeModeSingleLine; 865 } 866 break; 867 } 868 (void)SDL_snprintf(deeplink, sizeof(deeplink), 869 "steam://open/keyboard?XPosition=%i&YPosition=%i&Width=%i&Height=%i&Mode=%d", 870 window->text_input_rect.x, window->text_input_rect.y, 871 window->text_input_rect.w, window->text_input_rect.h, 872 mode); 873 SDL_OpenURL(deeplink); 874 SDL_SendScreenKeyboardShown(); 875 } 876} 877 878void X11_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) 879{ 880 SDL_VideoData *videodata = _this->internal; 881 882 if (videodata->is_steam_deck) { 883 SDL_OpenURL("steam://close/keyboard"); 884 SDL_SendScreenKeyboardHidden(); 885 } 886} 887 888#endif // SDL_VIDEO_DRIVER_X11 889
[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.