SDL: The keycode in key events is affected by modifiers by default.

From 90034b16dcacc7c0642a9bc94d393b2a12bc108d Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
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 58e14fd546bb5..496feceee15a0 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 0fffe295600ca…28895af38cae0 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 ecbfaa107a935…befa6353856e8 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 e37b5d8f10176…84f2a1c6cae48 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 c011e6712511d…4bea5ac68b220 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 1f233b0ea75c2…28c1d0d7a5602 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;