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 | |
47 | typedef struct SDL_KeyboardInstance |
48 | { |
49 | SDL_KeyboardID instance_id; |
50 | char *name; |
51 | } SDL_KeyboardInstance; |
52 | |
53 | typedef 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 | |
70 | static SDL_Keyboard SDL_keyboard; |
71 | static int SDL_keyboard_count; |
72 | static SDL_KeyboardInstance *SDL_keyboards; |
73 | |
74 | static 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 |
97 | bool SDL_InitKeyboard(void) |
98 | { |
99 | SDL_AddHintCallback(SDL_HINT_KEYCODE_OPTIONS, |
100 | SDL_KeycodeOptionsChanged, &SDL_keyboard); |
101 | return true; |
102 | } |
103 | |
104 | bool 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 | |
115 | static 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 | |
125 | void 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 | |
154 | void 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 | |
178 | bool SDL_HasKeyboard(void) |
179 | { |
180 | return (SDL_keyboard_count > 0); |
181 | } |
182 | |
183 | SDL_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 | |
207 | const 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 | |
217 | void 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 | |
232 | SDL_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 | |
250 | void 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 | |
291 | static 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 | |
306 | static 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 | |
317 | SDL_Window *SDL_GetKeyboardFocus(void) |
318 | { |
319 | SDL_Keyboard *keyboard = &SDL_keyboard; |
320 | |
321 | return keyboard->focus; |
322 | } |
323 | |
324 | bool 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 | |
383 | static 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 | |
473 | SDL_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 | |
503 | SDL_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 | |
510 | static 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 | |
653 | void 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 | |
685 | bool 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 | |
690 | bool 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 | |
700 | bool 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 | |
705 | bool SDL_SendKeyboardKeyAutoRelease(Uint64 timestamp, SDL_Scancode scancode) |
706 | { |
707 | return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_AUTORELEASE, SDL_GLOBAL_KEYBOARD_ID, 0, scancode, true); |
708 | } |
709 | |
710 | void 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 | |
732 | bool 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 | |
746 | void 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 | |
777 | void 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 | |
806 | static 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 | |
837 | void 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 | |
871 | void 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 | |
888 | const 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 | |
898 | SDL_Keymod SDL_GetModState(void) |
899 | { |
900 | SDL_Keyboard *keyboard = &SDL_keyboard; |
901 | |
902 | return keyboard->modstate; |
903 | } |
904 | |
905 | void 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. |
913 | void 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 | |