1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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// General keyboard handling code for SDL
24
25#include "SDL_events_c.h"
26#include "SDL_keymap_c.h"
27#include "../video/SDL_sysvideo.h"
28
29#if 0
30#define DEBUG_KEYBOARD
31#endif
32
33// Global keyboard information
34
35#define KEYBOARD_HARDWARE 0x01
36#define KEYBOARD_VIRTUAL 0x02
37#define KEYBOARD_AUTORELEASE 0x04
38#define KEYBOARD_IGNOREMODIFIERS 0x08
39
40#define KEYBOARD_SOURCE_MASK (KEYBOARD_HARDWARE | KEYBOARD_AUTORELEASE)
41
42#define KEYCODE_OPTION_HIDE_NUMPAD 0x01
43#define KEYCODE_OPTION_FRENCH_NUMBERS 0x02
44#define KEYCODE_OPTION_LATIN_LETTERS 0x04
45#define DEFAULT_KEYCODE_OPTIONS (KEYCODE_OPTION_FRENCH_NUMBERS | KEYCODE_OPTION_LATIN_LETTERS)
46
47typedef struct SDL_KeyboardInstance
48{
49 SDL_KeyboardID instance_id;
50 char *name;
51} SDL_KeyboardInstance;
52
53typedef struct SDL_Keyboard
54{
55 // Data common to all keyboards
56 SDL_Window *focus;
57 SDL_Keymod modstate;
58 Uint8 keysource[SDL_SCANCODE_COUNT];
59 bool keystate[SDL_SCANCODE_COUNT];
60 SDL_Keymap *keymap;
61 bool french_numbers;
62 bool latin_letters;
63 bool thai_keyboard;
64 Uint32 keycode_options;
65 bool autorelease_pending;
66 Uint64 hardware_timestamp;
67 int next_reserved_scancode;
68} SDL_Keyboard;
69
70static SDL_Keyboard SDL_keyboard;
71static int SDL_keyboard_count;
72static SDL_KeyboardInstance *SDL_keyboards;
73
74static void SDLCALL SDL_KeycodeOptionsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
75{
76 SDL_Keyboard *keyboard = (SDL_Keyboard *)userdata;
77
78 if (hint && *hint) {
79 keyboard->keycode_options = 0;
80 if (!SDL_strstr(hint, "none")) {
81 if (SDL_strstr(hint, "hide_numpad")) {
82 keyboard->keycode_options |= KEYCODE_OPTION_HIDE_NUMPAD;
83 }
84 if (SDL_strstr(hint, "french_numbers")) {
85 keyboard->keycode_options |= KEYCODE_OPTION_FRENCH_NUMBERS;
86 }
87 if (SDL_strstr(hint, "latin_letters")) {
88 keyboard->keycode_options |= KEYCODE_OPTION_LATIN_LETTERS;
89 }
90 }
91 } else {
92 keyboard->keycode_options = DEFAULT_KEYCODE_OPTIONS;
93 }
94}
95
96// Public functions
97bool SDL_InitKeyboard(void)
98{
99 SDL_AddHintCallback(SDL_HINT_KEYCODE_OPTIONS,
100 SDL_KeycodeOptionsChanged, &SDL_keyboard);
101 return true;
102}
103
104bool SDL_IsKeyboard(Uint16 vendor, Uint16 product, int num_keys)
105{
106 const int REAL_KEYBOARD_KEY_COUNT = 50;
107 if (num_keys > 0 && num_keys < REAL_KEYBOARD_KEY_COUNT) {
108 return false;
109 }
110
111 // Eventually we'll have a blacklist of devices that enumerate as keyboards but aren't really
112 return true;
113}
114
115static int SDL_GetKeyboardIndex(SDL_KeyboardID keyboardID)
116{
117 for (int i = 0; i < SDL_keyboard_count; ++i) {
118 if (keyboardID == SDL_keyboards[i].instance_id) {
119 return i;
120 }
121 }
122 return -1;
123}
124
125void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name, bool send_event)
126{
127 int keyboard_index = SDL_GetKeyboardIndex(keyboardID);
128 if (keyboard_index >= 0) {
129 // We already know about this keyboard
130 return;
131 }
132
133 SDL_assert(keyboardID != 0);
134
135 SDL_KeyboardInstance *keyboards = (SDL_KeyboardInstance *)SDL_realloc(SDL_keyboards, (SDL_keyboard_count + 1) * sizeof(*keyboards));
136 if (!keyboards) {
137 return;
138 }
139 SDL_KeyboardInstance *instance = &keyboards[SDL_keyboard_count];
140 instance->instance_id = keyboardID;
141 instance->name = SDL_strdup(name ? name : "");
142 SDL_keyboards = keyboards;
143 ++SDL_keyboard_count;
144
145 if (send_event) {
146 SDL_Event event;
147 SDL_zero(event);
148 event.type = SDL_EVENT_KEYBOARD_ADDED;
149 event.kdevice.which = keyboardID;
150 SDL_PushEvent(&event);
151 }
152}
153
154void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event)
155{
156 int keyboard_index = SDL_GetKeyboardIndex(keyboardID);
157 if (keyboard_index < 0) {
158 // We don't know about this keyboard
159 return;
160 }
161
162 SDL_free(SDL_keyboards[keyboard_index].name);
163
164 if (keyboard_index != SDL_keyboard_count - 1) {
165 SDL_memmove(&SDL_keyboards[keyboard_index], &SDL_keyboards[keyboard_index + 1], (SDL_keyboard_count - keyboard_index - 1) * sizeof(SDL_keyboards[keyboard_index]));
166 }
167 --SDL_keyboard_count;
168
169 if (send_event) {
170 SDL_Event event;
171 SDL_zero(event);
172 event.type = SDL_EVENT_KEYBOARD_REMOVED;
173 event.kdevice.which = keyboardID;
174 SDL_PushEvent(&event);
175 }
176}
177
178bool SDL_HasKeyboard(void)
179{
180 return (SDL_keyboard_count > 0);
181}
182
183SDL_KeyboardID *SDL_GetKeyboards(int *count)
184{
185 int i;
186 SDL_KeyboardID *keyboards;
187
188 keyboards = (SDL_JoystickID *)SDL_malloc((SDL_keyboard_count + 1) * sizeof(*keyboards));
189 if (keyboards) {
190 if (count) {
191 *count = SDL_keyboard_count;
192 }
193
194 for (i = 0; i < SDL_keyboard_count; ++i) {
195 keyboards[i] = SDL_keyboards[i].instance_id;
196 }
197 keyboards[i] = 0;
198 } else {
199 if (count) {
200 *count = 0;
201 }
202 }
203
204 return keyboards;
205}
206
207const char *SDL_GetKeyboardNameForID(SDL_KeyboardID instance_id)
208{
209 int keyboard_index = SDL_GetKeyboardIndex(instance_id);
210 if (keyboard_index < 0) {
211 SDL_SetError("Keyboard %" SDL_PRIu32 " not found", instance_id);
212 return NULL;
213 }
214 return SDL_GetPersistentString(SDL_keyboards[keyboard_index].name);
215}
216
217void SDL_ResetKeyboard(void)
218{
219 SDL_Keyboard *keyboard = &SDL_keyboard;
220 int scancode;
221
222#ifdef DEBUG_KEYBOARD
223 SDL_Log("Resetting keyboard");
224#endif
225 for (scancode = SDL_SCANCODE_UNKNOWN; scancode < SDL_SCANCODE_COUNT; ++scancode) {
226 if (keyboard->keystate[scancode]) {
227 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, (SDL_Scancode)scancode, false);
228 }
229 }
230}
231
232SDL_Keymap *SDL_GetCurrentKeymap(void)
233{
234 SDL_Keyboard *keyboard = &SDL_keyboard;
235
236 if (keyboard->thai_keyboard) {
237 // Thai keyboards are QWERTY plus Thai characters, use the default QWERTY keymap
238 return NULL;
239 }
240
241 if ((keyboard->keycode_options & KEYCODE_OPTION_LATIN_LETTERS) &&
242 !keyboard->latin_letters) {
243 // We'll use the default QWERTY keymap
244 return NULL;
245 }
246
247 return keyboard->keymap;
248}
249
250void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event)
251{
252 SDL_Keyboard *keyboard = &SDL_keyboard;
253
254 if (keyboard->keymap) {
255 SDL_DestroyKeymap(keyboard->keymap);
256 }
257
258 keyboard->keymap = keymap;
259
260 // Detect French number row (all symbols)
261 keyboard->french_numbers = true;
262 for (int i = SDL_SCANCODE_1; i <= SDL_SCANCODE_0; ++i) {
263 if (SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE)) ||
264 !SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_SHIFT))) {
265 keyboard->french_numbers = false;
266 break;
267 }
268 }
269
270 // Detect non-Latin keymap
271 keyboard->thai_keyboard = false;
272 keyboard->latin_letters = false;
273 for (int i = SDL_SCANCODE_A; i <= SDL_SCANCODE_D; ++i) {
274 SDL_Keycode key = SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE);
275 if (key <= 0xFF) {
276 keyboard->latin_letters = true;
277 break;
278 }
279
280 if (key >= 0x0E00 && key <= 0x0E7F) {
281 keyboard->thai_keyboard = true;
282 break;
283 }
284 }
285
286 if (send_event) {
287 SDL_SendKeymapChangedEvent();
288 }
289}
290
291static SDL_Scancode GetNextReservedScancode(void)
292{
293 SDL_Keyboard *keyboard = &SDL_keyboard;
294 SDL_Scancode scancode;
295
296 if (keyboard->next_reserved_scancode && keyboard->next_reserved_scancode < SDL_SCANCODE_RESERVED + 100) {
297 scancode = (SDL_Scancode)keyboard->next_reserved_scancode;
298 } else {
299 scancode = SDL_SCANCODE_RESERVED;
300 }
301 keyboard->next_reserved_scancode = (int)scancode + 1;
302
303 return scancode;
304}
305
306static void SetKeymapEntry(SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keycode keycode)
307{
308 SDL_Keyboard *keyboard = &SDL_keyboard;
309
310 if (!keyboard->keymap) {
311 keyboard->keymap = SDL_CreateKeymap();
312 }
313
314 SDL_SetKeymapEntry(keyboard->keymap, scancode, modstate, keycode);
315}
316
317SDL_Window *SDL_GetKeyboardFocus(void)
318{
319 SDL_Keyboard *keyboard = &SDL_keyboard;
320
321 return keyboard->focus;
322}
323
324bool SDL_SetKeyboardFocus(SDL_Window *window)
325{
326 SDL_VideoDevice *video = SDL_GetVideoDevice();
327 SDL_Keyboard *keyboard = &SDL_keyboard;
328 SDL_Mouse *mouse = SDL_GetMouse();
329
330 if (window) {
331 if (!SDL_ObjectValid(window, SDL_OBJECT_TYPE_WINDOW) || window->is_destroying) {
332 return SDL_SetError("Invalid window");
333 }
334 }
335
336 if (keyboard->focus && !window) {
337 // We won't get anymore keyboard messages, so reset keyboard state
338 SDL_ResetKeyboard();
339
340 // Also leave mouse relative mode
341 if (mouse->relative_mode) {
342 SDL_SetRelativeMouseMode(false);
343
344 SDL_Window *focus = keyboard->focus;
345 if ((focus->flags & SDL_WINDOW_MINIMIZED) != 0) {
346 // We can't warp the mouse within minimized windows, so manually restore the position
347 float x = focus->x + mouse->x;
348 float y = focus->y + mouse->y;
349 SDL_WarpMouseGlobal(x, y);
350 }
351 }
352 }
353
354 // See if the current window has lost focus
355 if (keyboard->focus && keyboard->focus != window) {
356 SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_LOST, 0, 0);
357
358 // Ensures IME compositions are committed
359 if (SDL_TextInputActive(keyboard->focus)) {
360 if (video && video->StopTextInput) {
361 video->StopTextInput(video, keyboard->focus);
362 }
363 }
364 }
365
366 keyboard->focus = window;
367
368 if (keyboard->focus) {
369 SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_GAINED, 0, 0);
370
371 if (SDL_TextInputActive(keyboard->focus)) {
372 if (video && video->StartTextInput) {
373 video->StartTextInput(video, keyboard->focus, keyboard->focus->text_input_props);
374 }
375 }
376 }
377
378 SDL_UpdateRelativeMouseMode();
379
380 return true;
381}
382
383static SDL_Keycode SDL_ConvertNumpadKeycode(SDL_Keycode keycode, bool numlock)
384{
385 switch (keycode) {
386 case SDLK_KP_DIVIDE:
387 return SDLK_SLASH;
388 case SDLK_KP_MULTIPLY:
389 return SDLK_ASTERISK;
390 case SDLK_KP_MINUS:
391 return SDLK_MINUS;
392 case SDLK_KP_PLUS:
393 return SDLK_PLUS;
394 case SDLK_KP_ENTER:
395 return SDLK_RETURN;
396 case SDLK_KP_1:
397 return numlock ? SDLK_1 : SDLK_END;
398 case SDLK_KP_2:
399 return numlock ? SDLK_2 : SDLK_DOWN;
400 case SDLK_KP_3:
401 return numlock ? SDLK_3 : SDLK_PAGEDOWN;
402 case SDLK_KP_4:
403 return numlock ? SDLK_4 : SDLK_LEFT;
404 case SDLK_KP_5:
405 return numlock ? SDLK_5 : SDLK_CLEAR;
406 case SDLK_KP_6:
407 return numlock ? SDLK_6 : SDLK_RIGHT;
408 case SDLK_KP_7:
409 return numlock ? SDLK_7 : SDLK_HOME;
410 case SDLK_KP_8:
411 return numlock ? SDLK_8 : SDLK_UP;
412 case SDLK_KP_9:
413 return numlock ? SDLK_9 : SDLK_PAGEUP;
414 case SDLK_KP_0:
415 return numlock ? SDLK_0 : SDLK_INSERT;
416 case SDLK_KP_PERIOD:
417 return numlock ? SDLK_PERIOD : SDLK_DELETE;
418 case SDLK_KP_EQUALS:
419 return SDLK_EQUALS;
420 case SDLK_KP_COMMA:
421 return SDLK_COMMA;
422 case SDLK_KP_EQUALSAS400:
423 return SDLK_EQUALS;
424 case SDLK_KP_LEFTPAREN:
425 return SDLK_LEFTPAREN;
426 case SDLK_KP_RIGHTPAREN:
427 return SDLK_RIGHTPAREN;
428 case SDLK_KP_LEFTBRACE:
429 return SDLK_LEFTBRACE;
430 case SDLK_KP_RIGHTBRACE:
431 return SDLK_RIGHTBRACE;
432 case SDLK_KP_TAB:
433 return SDLK_TAB;
434 case SDLK_KP_BACKSPACE:
435 return SDLK_BACKSPACE;
436 case SDLK_KP_A:
437 return SDLK_A;
438 case SDLK_KP_B:
439 return SDLK_B;
440 case SDLK_KP_C:
441 return SDLK_C;
442 case SDLK_KP_D:
443 return SDLK_D;
444 case SDLK_KP_E:
445 return SDLK_E;
446 case SDLK_KP_F:
447 return SDLK_F;
448 case SDLK_KP_PERCENT:
449 return SDLK_PERCENT;
450 case SDLK_KP_LESS:
451 return SDLK_LESS;
452 case SDLK_KP_GREATER:
453 return SDLK_GREATER;
454 case SDLK_KP_AMPERSAND:
455 return SDLK_AMPERSAND;
456 case SDLK_KP_COLON:
457 return SDLK_COLON;
458 case SDLK_KP_HASH:
459 return SDLK_HASH;
460 case SDLK_KP_SPACE:
461 return SDLK_SPACE;
462 case SDLK_KP_AT:
463 return SDLK_AT;
464 case SDLK_KP_EXCLAM:
465 return SDLK_EXCLAIM;
466 case SDLK_KP_PLUSMINUS:
467 return SDLK_PLUSMINUS;
468 default:
469 return keycode;
470 }
471}
472
473SDL_Keycode SDL_GetKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate, bool key_event)
474{
475 SDL_Keyboard *keyboard = &SDL_keyboard;
476
477 if (key_event) {
478 SDL_Keymap *keymap = SDL_GetCurrentKeymap();
479 bool numlock = (modstate & SDL_KMOD_NUM) != 0;
480 SDL_Keycode keycode;
481
482 // We won't be applying any modifiers by default
483 modstate = SDL_KMOD_NONE;
484
485 if ((keyboard->keycode_options & KEYCODE_OPTION_FRENCH_NUMBERS) &&
486 keyboard->french_numbers &&
487 (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_0)) {
488 // Add the shift state to generate a numeric keycode
489 modstate |= SDL_KMOD_SHIFT;
490 }
491
492 keycode = SDL_GetKeymapKeycode(keymap, scancode, modstate);
493
494 if (keyboard->keycode_options & KEYCODE_OPTION_HIDE_NUMPAD) {
495 keycode = SDL_ConvertNumpadKeycode(keycode, numlock);
496 }
497 return keycode;
498 }
499
500 return SDL_GetKeymapKeycode(keyboard->keymap, scancode, modstate);
501}
502
503SDL_Scancode SDL_GetScancodeFromKey(SDL_Keycode key, SDL_Keymod *modstate)
504{
505 SDL_Keyboard *keyboard = &SDL_keyboard;
506
507 return SDL_GetKeymapScancode(keyboard->keymap, key, modstate);
508}
509
510static bool SDL_SendKeyboardKeyInternal(Uint64 timestamp, Uint32 flags, SDL_KeyboardID keyboardID, int rawcode, SDL_Scancode scancode, bool down)
511{
512 SDL_Keyboard *keyboard = &SDL_keyboard;
513 bool posted = false;
514 SDL_Keycode keycode = SDLK_UNKNOWN;
515 Uint32 type;
516 bool repeat = false;
517 const Uint8 source = flags & KEYBOARD_SOURCE_MASK;
518
519#ifdef DEBUG_KEYBOARD
520 SDL_Log("The '%s' key has been %s", SDL_GetScancodeName(scancode), down ? "pressed" : "released");
521#endif
522
523 // Figure out what type of event this is
524 if (down) {
525 type = SDL_EVENT_KEY_DOWN;
526 } else {
527 type = SDL_EVENT_KEY_UP;
528 }
529
530 if (scancode > SDL_SCANCODE_UNKNOWN && scancode < SDL_SCANCODE_COUNT) {
531 // Drop events that don't change state
532 if (down) {
533 if (keyboard->keystate[scancode]) {
534 if (!(keyboard->keysource[scancode] & source)) {
535 keyboard->keysource[scancode] |= source;
536 return false;
537 }
538 repeat = true;
539 }
540 keyboard->keysource[scancode] |= source;
541 } else {
542 if (!keyboard->keystate[scancode]) {
543 return false;
544 }
545 keyboard->keysource[scancode] = 0;
546 }
547
548 // Update internal keyboard state
549 keyboard->keystate[scancode] = down;
550
551 keycode = SDL_GetKeyFromScancode(scancode, keyboard->modstate, true);
552
553 } else if (rawcode == 0) {
554 // Nothing to do!
555 return false;
556 }
557
558 if (source == KEYBOARD_HARDWARE) {
559 keyboard->hardware_timestamp = SDL_GetTicks();
560 } else if (source == KEYBOARD_AUTORELEASE) {
561 keyboard->autorelease_pending = true;
562 }
563
564 // Update modifiers state if applicable
565 if (!(flags & KEYBOARD_IGNOREMODIFIERS) && !repeat) {
566 SDL_Keymod modifier;
567
568 switch (keycode) {
569 case SDLK_LCTRL:
570 modifier = SDL_KMOD_LCTRL;
571 break;
572 case SDLK_RCTRL:
573 modifier = SDL_KMOD_RCTRL;
574 break;
575 case SDLK_LSHIFT:
576 modifier = SDL_KMOD_LSHIFT;
577 break;
578 case SDLK_RSHIFT:
579 modifier = SDL_KMOD_RSHIFT;
580 break;
581 case SDLK_LALT:
582 modifier = SDL_KMOD_LALT;
583 break;
584 case SDLK_RALT:
585 modifier = SDL_KMOD_RALT;
586 break;
587 case SDLK_LGUI:
588 modifier = SDL_KMOD_LGUI;
589 break;
590 case SDLK_RGUI:
591 modifier = SDL_KMOD_RGUI;
592 break;
593 case SDLK_MODE:
594 modifier = SDL_KMOD_MODE;
595 break;
596 default:
597 modifier = SDL_KMOD_NONE;
598 break;
599 }
600 if (SDL_EVENT_KEY_DOWN == type) {
601 switch (keycode) {
602 case SDLK_NUMLOCKCLEAR:
603 keyboard->modstate ^= SDL_KMOD_NUM;
604 break;
605 case SDLK_CAPSLOCK:
606 keyboard->modstate ^= SDL_KMOD_CAPS;
607 break;
608 case SDLK_SCROLLLOCK:
609 keyboard->modstate ^= SDL_KMOD_SCROLL;
610 break;
611 default:
612 keyboard->modstate |= modifier;
613 break;
614 }
615 } else {
616 keyboard->modstate &= ~modifier;
617 }
618 }
619
620 // Post the event, if desired
621 if (SDL_EventEnabled(type)) {
622 SDL_Event event;
623 event.type = type;
624 event.common.timestamp = timestamp;
625 event.key.scancode = scancode;
626 event.key.key = keycode;
627 event.key.mod = keyboard->modstate;
628 event.key.raw = (Uint16)rawcode;
629 event.key.down = down;
630 event.key.repeat = repeat;
631 event.key.windowID = keyboard->focus ? keyboard->focus->id : 0;
632 event.key.which = keyboardID;
633 posted = SDL_PushEvent(&event);
634 }
635
636 /* If the keyboard is grabbed and the grabbed window is in full-screen,
637 minimize the window when we receive Alt+Tab, unless the application
638 has explicitly opted out of this behavior. */
639 if (keycode == SDLK_TAB && down &&
640 (keyboard->modstate & SDL_KMOD_ALT) &&
641 keyboard->focus &&
642 (keyboard->focus->flags & SDL_WINDOW_KEYBOARD_GRABBED) &&
643 (keyboard->focus->flags & SDL_WINDOW_FULLSCREEN) &&
644 SDL_GetHintBoolean(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, true)) {
645 /* We will temporarily forfeit our grab by minimizing our window,
646 allowing the user to escape the application */
647 SDL_MinimizeWindow(keyboard->focus);
648 }
649
650 return posted;
651}
652
653void SDL_SendKeyboardUnicodeKey(Uint64 timestamp, Uint32 ch)
654{
655 SDL_Keyboard *keyboard = &SDL_keyboard;
656 SDL_Keymod modstate = SDL_KMOD_NONE;
657 SDL_Scancode scancode;
658
659 if (ch == '\n') {
660 ch = SDLK_RETURN;
661 }
662 scancode = SDL_GetKeymapScancode(keyboard->keymap, ch, &modstate);
663
664 // Make sure we have this keycode in our keymap
665 if (scancode == SDL_SCANCODE_UNKNOWN && ch < SDLK_SCANCODE_MASK) {
666 scancode = GetNextReservedScancode();
667 SetKeymapEntry(scancode, modstate, ch);
668 }
669
670 if (modstate & SDL_KMOD_SHIFT) {
671 // If the character uses shift, press shift down
672 SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LSHIFT, true);
673 }
674
675 // Send a keydown and keyup for the character
676 SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, SDL_GLOBAL_KEYBOARD_ID, 0, scancode, true);
677 SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, SDL_GLOBAL_KEYBOARD_ID, 0, scancode, false);
678
679 if (modstate & SDL_KMOD_SHIFT) {
680 // If the character uses shift, release shift
681 SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LSHIFT, false);
682 }
683}
684
685bool SDL_SendKeyboardKey(Uint64 timestamp, SDL_KeyboardID keyboardID, int rawcode, SDL_Scancode scancode, bool down)
686{
687 return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_HARDWARE, keyboardID, rawcode, scancode, down);
688}
689
690bool SDL_SendKeyboardKeyAndKeycode(Uint64 timestamp, SDL_KeyboardID keyboardID, int rawcode, SDL_Scancode scancode, SDL_Keycode keycode, bool down)
691{
692 if (down) {
693 // Make sure we have this keycode in our keymap
694 SetKeymapEntry(scancode, SDL_GetModState(), keycode);
695 }
696
697 return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_HARDWARE, keyboardID, rawcode, scancode, down);
698}
699
700bool SDL_SendKeyboardKeyIgnoreModifiers(Uint64 timestamp, SDL_KeyboardID keyboardID, int rawcode, SDL_Scancode scancode, bool down)
701{
702 return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_HARDWARE | KEYBOARD_IGNOREMODIFIERS, keyboardID, rawcode, scancode, down);
703}
704
705bool SDL_SendKeyboardKeyAutoRelease(Uint64 timestamp, SDL_Scancode scancode)
706{
707 return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_AUTORELEASE, SDL_GLOBAL_KEYBOARD_ID, 0, scancode, true);
708}
709
710void SDL_ReleaseAutoReleaseKeys(void)
711{
712 SDL_Keyboard *keyboard = &SDL_keyboard;
713 int scancode;
714
715 if (keyboard->autorelease_pending) {
716 for (scancode = SDL_SCANCODE_UNKNOWN; scancode < SDL_SCANCODE_COUNT; ++scancode) {
717 if (keyboard->keysource[scancode] == KEYBOARD_AUTORELEASE) {
718 SDL_SendKeyboardKeyInternal(0, KEYBOARD_AUTORELEASE, SDL_GLOBAL_KEYBOARD_ID, 0, (SDL_Scancode)scancode, false);
719 }
720 }
721 keyboard->autorelease_pending = false;
722 }
723
724 if (keyboard->hardware_timestamp) {
725 // Keep hardware keyboard "active" for 250 ms
726 if (SDL_GetTicks() >= keyboard->hardware_timestamp + 250) {
727 keyboard->hardware_timestamp = 0;
728 }
729 }
730}
731
732bool SDL_HardwareKeyboardKeyPressed(void)
733{
734 SDL_Keyboard *keyboard = &SDL_keyboard;
735 int scancode;
736
737 for (scancode = SDL_SCANCODE_UNKNOWN; scancode < SDL_SCANCODE_COUNT; ++scancode) {
738 if (keyboard->keysource[scancode] & KEYBOARD_HARDWARE) {
739 return true;
740 }
741 }
742
743 return keyboard->hardware_timestamp ? true : false;
744}
745
746void SDL_SendKeyboardText(const char *text)
747{
748 SDL_Keyboard *keyboard = &SDL_keyboard;
749
750 if (!SDL_TextInputActive(keyboard->focus)) {
751 return;
752 }
753
754 if (!text || !*text) {
755 return;
756 }
757
758 // Don't post text events for unprintable characters
759 if (SDL_iscntrl((unsigned char)*text)) {
760 return;
761 }
762
763 // Post the event, if desired
764 if (SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) {
765 SDL_Event event;
766 event.type = SDL_EVENT_TEXT_INPUT;
767 event.common.timestamp = 0;
768 event.text.windowID = keyboard->focus ? keyboard->focus->id : 0;
769 event.text.text = SDL_CreateTemporaryString(text);
770 if (!event.text.text) {
771 return;
772 }
773 SDL_PushEvent(&event);
774 }
775}
776
777void SDL_SendEditingText(const char *text, int start, int length)
778{
779 SDL_Keyboard *keyboard = &SDL_keyboard;
780
781 if (!SDL_TextInputActive(keyboard->focus)) {
782 return;
783 }
784
785 if (!text) {
786 return;
787 }
788
789 // Post the event, if desired
790 if (SDL_EventEnabled(SDL_EVENT_TEXT_EDITING)) {
791 SDL_Event event;
792
793 event.type = SDL_EVENT_TEXT_EDITING;
794 event.common.timestamp = 0;
795 event.edit.windowID = keyboard->focus ? keyboard->focus->id : 0;
796 event.edit.start = start;
797 event.edit.length = length;
798 event.edit.text = SDL_CreateTemporaryString(text);
799 if (!event.edit.text) {
800 return;
801 }
802 SDL_PushEvent(&event);
803 }
804}
805
806static const char * const *CreateCandidatesForEvent(char **candidates, int num_candidates)
807{
808 const char **event_candidates;
809 int i;
810 char *ptr;
811 size_t total_length = (num_candidates + 1) * sizeof(*event_candidates);
812
813 for (i = 0; i < num_candidates; ++i) {
814 size_t length = SDL_strlen(candidates[i]) + 1;
815
816 total_length += length;
817 }
818
819 event_candidates = (const char **)SDL_AllocateTemporaryMemory(total_length);
820 if (!event_candidates) {
821 return NULL;
822 }
823 ptr = (char *)(event_candidates + (num_candidates + 1));
824
825 for (i = 0; i < num_candidates; ++i) {
826 size_t length = SDL_strlen(candidates[i]) + 1;
827
828 event_candidates[i] = ptr;
829 SDL_memcpy(ptr, candidates[i], length);
830 ptr += length;
831 }
832 event_candidates[i] = NULL;
833
834 return event_candidates;
835}
836
837void SDL_SendEditingTextCandidates(char **candidates, int num_candidates, int selected_candidate, bool horizontal)
838{
839 SDL_Keyboard *keyboard = &SDL_keyboard;
840
841 if (!SDL_TextInputActive(keyboard->focus)) {
842 return;
843 }
844
845 // Post the event, if desired
846 if (SDL_EventEnabled(SDL_EVENT_TEXT_EDITING_CANDIDATES)) {
847 SDL_Event event;
848
849 event.type = SDL_EVENT_TEXT_EDITING_CANDIDATES;
850 event.common.timestamp = 0;
851 event.edit.windowID = keyboard->focus ? keyboard->focus->id : 0;
852 if (num_candidates > 0) {
853 const char * const *event_candidates = CreateCandidatesForEvent(candidates, num_candidates);
854 if (!event_candidates) {
855 return;
856 }
857 event.edit_candidates.candidates = event_candidates;
858 event.edit_candidates.num_candidates = num_candidates;
859 event.edit_candidates.selected_candidate = selected_candidate;
860 event.edit_candidates.horizontal = horizontal;
861 } else {
862 event.edit_candidates.candidates = NULL;
863 event.edit_candidates.num_candidates = 0;
864 event.edit_candidates.selected_candidate = -1;
865 event.edit_candidates.horizontal = false;
866 }
867 SDL_PushEvent(&event);
868 }
869}
870
871void SDL_QuitKeyboard(void)
872{
873 for (int i = SDL_keyboard_count; i--;) {
874 SDL_RemoveKeyboard(SDL_keyboards[i].instance_id, false);
875 }
876 SDL_free(SDL_keyboards);
877 SDL_keyboards = NULL;
878
879 if (SDL_keyboard.keymap) {
880 SDL_DestroyKeymap(SDL_keyboard.keymap);
881 SDL_keyboard.keymap = NULL;
882 }
883
884 SDL_RemoveHintCallback(SDL_HINT_KEYCODE_OPTIONS,
885 SDL_KeycodeOptionsChanged, &SDL_keyboard);
886}
887
888const bool *SDL_GetKeyboardState(int *numkeys)
889{
890 SDL_Keyboard *keyboard = &SDL_keyboard;
891
892 if (numkeys != (int *)0) {
893 *numkeys = SDL_SCANCODE_COUNT;
894 }
895 return keyboard->keystate;
896}
897
898SDL_Keymod SDL_GetModState(void)
899{
900 SDL_Keyboard *keyboard = &SDL_keyboard;
901
902 return keyboard->modstate;
903}
904
905void SDL_SetModState(SDL_Keymod modstate)
906{
907 SDL_Keyboard *keyboard = &SDL_keyboard;
908
909 keyboard->modstate = modstate;
910}
911
912// Note that SDL_ToggleModState() is not a public API. SDL_SetModState() is.
913void SDL_ToggleModState(SDL_Keymod modstate, bool toggle)
914{
915 SDL_Keyboard *keyboard = &SDL_keyboard;
916 if (toggle) {
917 keyboard->modstate |= modstate;
918 } else {
919 keyboard->modstate &= ~modstate;
920 }
921}
922
923