From 90034b16dcacc7c0642a9bc94d393b2a12bc108d Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sat, 22 Jun 2024 00:04:33 -0700 Subject: [PATCH] The keycode in key events is affected by modifiers by default. This behavior can be customized with SDL_HINT_KEYCODE_OPTIONS. --- docs/README-migration.md | 2 + include/SDL3/SDL_hints.h | 22 ++++++ src/events/SDL_keyboard.c | 101 ++++++++++++++++++++++++-- src/test/SDL_test_common.c | 13 ++++ test/testaudiostreamdynamicresample.c | 2 +- test/testintersections.c | 38 +++++----- 6 files changed, 149 insertions(+), 29 deletions(-) diff --git a/docs/README-migration.md b/docs/README-migration.md index 58e14fd546..496feceee1 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -362,6 +362,8 @@ now looks like this: SDL_Keymod mod = event.key.mod; ``` +The keycode in key events is affected by modifiers by default. e.g. pressing the A key would generate the keycode `SDLK_a`, or 'a', and pressing it while holding the shift key would generate the keycode `SDLK_A`, or 'A'. This behavior can be customized with `SDL_HINT_KEYCODE_OPTIONS`. + The gamepad event structures caxis, cbutton, cdevice, ctouchpad, and csensor have been renamed gaxis, gbutton, gdevice, gtouchpad, and gsensor. The mouseX and mouseY fields of SDL_MouseWheelEvent have been renamed mouse_x and mouse_y. diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 0fffe29560..28895af38c 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -2003,6 +2003,28 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_ZERO_CENTERED_DEVICES "SDL_JOYSTICK_ZERO_CENTERED_DEVICES" +/** + * A variable that controls keycode representation in keyboard events. + * + * This variable is a comma separated set of options for translating keycodes in events: + * + * - "unmodified": The keycode is the symbol generated by pressing the key without any modifiers applied. e.g. Shift+A would yield the keycode SDLK_a, or 'a'. + * - "modified": The keycode is the symbol generated by pressing the key with modifiers applied. e.g. Shift+A would yield the keycode SDLK_A, or 'A'. + * - "french_numbers": The number row on French keyboards is inverted, so pressing the 1 key would yield the keycode SDLK_1, or '1', instead of SDLK_AMPERSAND, or '&' + * - "latin_letters": For keyboards using non-Latin letters, such as Russian or Thai, the letter keys generate keycodes as though it had an en_US layout. e.g. pressing the key associated with SDL_SCANCODE_A on a Russian keyboard would yield 'a' instead of 'ф'. + * + * The default value for this hint is equivalent to "modified,french_numbers" + * + * Some platforms like Emscripten only provide modified keycodes and the options are not used. + * + * These options do not affect the return value of SDL_GetKeyFromScancode() or SDL_GetScancodeFromKey(), they just apply to the keycode included in key events. + * + * This hint can be set anytime. + * + * \since This hint is available since SDL 3.0.0. + */ +#define SDL_HINT_KEYCODE_OPTIONS "SDL_KEYCODE_OPTIONS" + /** * A variable that controls what KMSDRM device to use. * diff --git a/src/events/SDL_keyboard.c b/src/events/SDL_keyboard.c index ecbfaa107a..befa635385 100644 --- a/src/events/SDL_keyboard.c +++ b/src/events/SDL_keyboard.c @@ -30,16 +30,18 @@ /* Global keyboard information */ -typedef enum -{ - KEYBOARD_HARDWARE = 0x01, - KEYBOARD_VIRTUAL = 0x02, - KEYBOARD_AUTORELEASE = 0x04, - KEYBOARD_IGNOREMODIFIERS = 0x08 -} SDL_KeyboardFlags; +#define KEYBOARD_HARDWARE 0x01 +#define KEYBOARD_VIRTUAL 0x02 +#define KEYBOARD_AUTORELEASE 0x04 +#define KEYBOARD_IGNOREMODIFIERS 0x0 #define KEYBOARD_SOURCE_MASK (KEYBOARD_HARDWARE | KEYBOARD_AUTORELEASE) +#define KEYCODE_OPTION_APPLY_MODIFIERS 0x01 +#define KEYCODE_OPTION_FRENCH_NUMBERS 0x02 +#define KEYCODE_OPTION_LATIN_LETTERS 0x04 +#define DEFAULT_KEYCODE_OPTIONS (KEYCODE_OPTION_APPLY_MODIFIERS | KEYCODE_OPTION_FRENCH_NUMBERS) + typedef struct SDL_KeyboardInstance { SDL_KeyboardID instance_id; @@ -54,6 +56,9 @@ typedef struct SDL_Keyboard Uint8 keysource[SDL_NUM_SCANCODES]; Uint8 keystate[SDL_NUM_SCANCODES]; SDL_Keymap *keymap; + SDL_bool french_numbers; + SDL_bool non_latin_letters; + Uint32 keycode_options; SDL_bool autorelease_pending; Uint64 hardware_timestamp; } SDL_Keyboard; @@ -62,9 +67,33 @@ static SDL_Keyboard SDL_keyboard; static int SDL_keyboard_count; static SDL_KeyboardInstance *SDL_keyboards; +static void SDLCALL SDL_KeycodeOptionsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_Keyboard *keyboard = (SDL_Keyboard *)userdata; + + if (hint && *hint) { + keyboard->keycode_options = 0; + if (SDL_strstr(hint, "unmodified")) { + keyboard->keycode_options &= ~KEYCODE_OPTION_APPLY_MODIFIERS; + } else if (SDL_strstr(hint, "modified")) { + keyboard->keycode_options |= KEYCODE_OPTION_APPLY_MODIFIERS; + } + if (SDL_strstr(hint, "french_numbers")) { + keyboard->keycode_options |= KEYCODE_OPTION_FRENCH_NUMBERS; + } + if (SDL_strstr(hint, "latin_letters")) { + keyboard->keycode_options |= KEYCODE_OPTION_LATIN_LETTERS; + } + } else { + keyboard->keycode_options = DEFAULT_KEYCODE_OPTIONS; + } +} + /* Public functions */ int SDL_InitKeyboard(void) { + SDL_AddHintCallback(SDL_HINT_KEYCODE_OPTIONS, + SDL_KeycodeOptionsChanged, &SDL_keyboard); return 0; } @@ -205,6 +234,25 @@ void SDL_SetKeymap(SDL_Keymap *keymap, SDL_bool send_event) keyboard->keymap = keymap; + // Detect French number row (all symbols) + keyboard->french_numbers = SDL_TRUE; + for (int i = SDL_SCANCODE_1; i <= SDL_SCANCODE_0; ++i) { + if (SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE)) || + !SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_SHIFT))) { + keyboard->french_numbers = SDL_FALSE; + break; + } + } + + // Detect non-Latin keymap + keyboard->non_latin_letters = SDL_TRUE; + for (int i = SDL_SCANCODE_A; i <= SDL_SCANCODE_D; ++i) { + if (SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE) <= 0xFF) { + keyboard->non_latin_letters = SDL_FALSE; + break; + } + } + if (send_event) { SDL_SendKeymapChangedEvent(); } @@ -276,6 +324,40 @@ int SDL_SetKeyboardFocus(SDL_Window *window) return 0; } +static SDL_Keycode SDL_GetEventKeycode(SDL_Keyboard *keyboard, SDL_Scancode scancode, SDL_Keymod modstate) +{ + SDL_Keycode keycode; + + if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_Z) { + if (keyboard->non_latin_letters && (keyboard->keycode_options & KEYCODE_OPTION_LATIN_LETTERS)) { + if (keyboard->keycode_options & KEYCODE_OPTION_APPLY_MODIFIERS) { + keycode = SDL_GetDefaultKeyFromScancode(scancode, modstate); + } else { + keycode = SDL_GetDefaultKeyFromScancode(scancode, SDL_KMOD_NONE); + } + return keycode; + } + } + + if (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_0) { + if (keyboard->french_numbers && (keyboard->keycode_options & KEYCODE_OPTION_FRENCH_NUMBERS)) { + // Invert the shift state to generate the correct keycode + if (modstate & SDL_KMOD_SHIFT) { + modstate &= ~SDL_KMOD_SHIFT; + } else { + modstate |= SDL_KMOD_SHIFT; + } + } + } + + if (keyboard->keycode_options & KEYCODE_OPTION_APPLY_MODIFIERS) { + keycode = SDL_GetKeyFromScancode(scancode, modstate); + } else { + keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE); + } + return keycode; +} + static int SDL_SendKeyboardKeyInternal(Uint64 timestamp, Uint32 flags, SDL_KeyboardID keyboardID, int rawcode, SDL_Scancode scancode, SDL_Keycode keycode, Uint8 state) { SDL_Keyboard *keyboard = &SDL_keyboard; @@ -325,7 +407,7 @@ static int SDL_SendKeyboardKeyInternal(Uint64 timestamp, Uint32 flags, SDL_Keybo keyboard->keystate[scancode] = state; if (keycode == SDLK_UNKNOWN) { - keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE); + keycode = SDL_GetEventKeycode(keyboard, scancode, keyboard->modstate); } } else if (keycode == SDLK_UNKNOWN && rawcode == 0) { @@ -589,6 +671,9 @@ void SDL_QuitKeyboard(void) SDL_DestroyKeymap(SDL_keyboard.keymap); SDL_keyboard.keymap = NULL; } + + SDL_DelHintCallback(SDL_HINT_KEYCODE_OPTIONS, + SDL_KeycodeOptionsChanged, &SDL_keyboard); } const Uint8 *SDL_GetKeyboardState(int *numkeys) diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index e37b5d8f10..84f2a1c6ca 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -2198,6 +2198,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event } } break; + case SDLK_O: case SDLK_o: if (withControl) { /* Ctrl-O (or Ctrl-Shift-O) changes window opacity. */ @@ -2215,6 +2216,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event } } break; + case SDLK_H: case SDLK_h: if (withControl) { /* Ctrl-H changes cursor visibility. */ @@ -2225,6 +2227,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event } } break; + case SDLK_C: case SDLK_c: if (withAlt) { /* Alt-C copy awesome text to the primary selection! */ @@ -2250,6 +2253,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event break; } break; + case SDLK_V: case SDLK_v: if (withAlt) { /* Alt-V paste awesome text from the primary selection! */ @@ -2277,6 +2281,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event } } break; + case SDLK_F: case SDLK_f: if (withControl) { /* Ctrl-F flash the window */ @@ -2286,6 +2291,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event } } break; + case SDLK_G: case SDLK_g: if (withControl) { /* Ctrl-G toggle mouse grab */ @@ -2295,6 +2301,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event } } break; + case SDLK_K: case SDLK_k: if (withControl) { /* Ctrl-K toggle keyboard grab */ @@ -2304,6 +2311,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event } } break; + case SDLK_M: case SDLK_m: if (withControl) { /* Ctrl-M maximize */ @@ -2326,12 +2334,14 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event } } break; + case SDLK_R: case SDLK_r: if (withControl) { /* Ctrl-R toggle mouse relative mode */ SDL_SetRelativeMouseMode(!SDL_GetRelativeMouseMode()); } break; + case SDLK_T: case SDLK_t: if (withControl) { /* Ctrl-T toggle topmost mode */ @@ -2346,6 +2356,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event } } break; + case SDLK_Z: case SDLK_z: if (withControl) { /* Ctrl-Z minimize */ @@ -2385,6 +2396,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event } break; + case SDLK_B: case SDLK_b: if (withControl) { /* Ctrl-B toggle window border */ @@ -2396,6 +2408,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event } } break; + case SDLK_A: case SDLK_a: if (withControl) { /* Ctrl-A toggle aspect ratio */ diff --git a/test/testaudiostreamdynamicresample.c b/test/testaudiostreamdynamicresample.c index c011e67125..4bea5ac68b 100644 --- a/test/testaudiostreamdynamicresample.c +++ b/test/testaudiostreamdynamicresample.c @@ -234,7 +234,7 @@ static void loop(void) SDL_Log("Cleared audio stream"); } else if (sym == SDLK_s) { queue_audio(); - } else if (sym == SDLK_d) { + } else if (sym == SDLK_d || sym == SDLK_D) { float amount = 1.0f; amount *= (e.key.mod & SDL_KMOD_CTRL) ? 10.0f : 1.0f; amount *= (e.key.mod & SDL_KMOD_SHIFT) ? 10.0f : 1.0f; diff --git a/test/testintersections.c b/test/testintersections.c index 1f233b0ea7..28c1d0d7a5 100644 --- a/test/testintersections.c +++ b/test/testintersections.c @@ -226,27 +226,25 @@ static void loop(void *arg) break; case SDL_EVENT_KEY_DOWN: switch (event.key.key) { - case 'l': - if (event.key.mod & SDL_KMOD_SHIFT) { - num_lines = 0; - } else { - add_line( - (float)SDL_rand_n(640), - (float)SDL_rand_n(480), - (float)SDL_rand_n(640), - (float)SDL_rand_n(480)); - } + case SDLK_L: + num_lines = 0; break; - case 'r': - if (event.key.mod & SDL_KMOD_SHIFT) { - num_rects = 0; - } else { - add_rect( - (float)SDL_rand_n(640), - (float)SDL_rand_n(480), - (float)SDL_rand_n(640), - (float)SDL_rand_n(480)); - } + case SDLK_l: + add_line( + (float)SDL_rand_n(640), + (float)SDL_rand_n(480), + (float)SDL_rand_n(640), + (float)SDL_rand_n(480)); + break; + case SDLK_R: + num_rects = 0; + break; + case SDLK_r: + add_rect( + (float)SDL_rand_n(640), + (float)SDL_rand_n(480), + (float)SDL_rand_n(640), + (float)SDL_rand_n(480)); break; default: break;