Browse Source

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

This behavior can be customized with SDL_HINT_KEYCODE_OPTIONS.
Sam Lantinga 1 year ago
parent
commit
90034b16dc

+ 2 - 0
docs/README-migration.md

@@ -362,6 +362,8 @@ now looks like this:
     SDL_Keymod mod = event.key.mod;
     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 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.
 The mouseX and mouseY fields of SDL_MouseWheelEvent have been renamed mouse_x and mouse_y.

+ 22 - 0
include/SDL3/SDL_hints.h

@@ -2003,6 +2003,28 @@ extern "C" {
  */
  */
 #define SDL_HINT_JOYSTICK_ZERO_CENTERED_DEVICES "SDL_JOYSTICK_ZERO_CENTERED_DEVICES"
 #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.
  * A variable that controls what KMSDRM device to use.
  *
  *

+ 93 - 8
src/events/SDL_keyboard.c

@@ -30,16 +30,18 @@
 
 
 /* Global keyboard information */
 /* 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 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
 typedef struct SDL_KeyboardInstance
 {
 {
     SDL_KeyboardID instance_id;
     SDL_KeyboardID instance_id;
@@ -54,6 +56,9 @@ typedef struct SDL_Keyboard
     Uint8 keysource[SDL_NUM_SCANCODES];
     Uint8 keysource[SDL_NUM_SCANCODES];
     Uint8 keystate[SDL_NUM_SCANCODES];
     Uint8 keystate[SDL_NUM_SCANCODES];
     SDL_Keymap *keymap;
     SDL_Keymap *keymap;
+    SDL_bool french_numbers;
+    SDL_bool non_latin_letters;
+    Uint32 keycode_options;
     SDL_bool autorelease_pending;
     SDL_bool autorelease_pending;
     Uint64 hardware_timestamp;
     Uint64 hardware_timestamp;
 } SDL_Keyboard;
 } SDL_Keyboard;
@@ -62,9 +67,33 @@ static SDL_Keyboard SDL_keyboard;
 static int SDL_keyboard_count;
 static int SDL_keyboard_count;
 static SDL_KeyboardInstance *SDL_keyboards;
 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 */
 /* Public functions */
 int SDL_InitKeyboard(void)
 int SDL_InitKeyboard(void)
 {
 {
+    SDL_AddHintCallback(SDL_HINT_KEYCODE_OPTIONS,
+                        SDL_KeycodeOptionsChanged, &SDL_keyboard);
     return 0;
     return 0;
 }
 }
 
 
@@ -205,6 +234,25 @@ void SDL_SetKeymap(SDL_Keymap *keymap, SDL_bool send_event)
 
 
     keyboard->keymap = keymap;
     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) {
     if (send_event) {
         SDL_SendKeymapChangedEvent();
         SDL_SendKeymapChangedEvent();
     }
     }
@@ -276,6 +324,40 @@ int SDL_SetKeyboardFocus(SDL_Window *window)
     return 0;
     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)
 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;
     SDL_Keyboard *keyboard = &SDL_keyboard;
@@ -325,7 +407,7 @@ static int SDL_SendKeyboardKeyInternal(Uint64 timestamp, Uint32 flags, SDL_Keybo
         keyboard->keystate[scancode] = state;
         keyboard->keystate[scancode] = state;
 
 
         if (keycode == SDLK_UNKNOWN) {
         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) {
     } else if (keycode == SDLK_UNKNOWN && rawcode == 0) {
@@ -589,6 +671,9 @@ void SDL_QuitKeyboard(void)
         SDL_DestroyKeymap(SDL_keyboard.keymap);
         SDL_DestroyKeymap(SDL_keyboard.keymap);
         SDL_keyboard.keymap = NULL;
         SDL_keyboard.keymap = NULL;
     }
     }
+
+    SDL_DelHintCallback(SDL_HINT_KEYCODE_OPTIONS,
+                        SDL_KeycodeOptionsChanged, &SDL_keyboard);
 }
 }
 
 
 const Uint8 *SDL_GetKeyboardState(int *numkeys)
 const Uint8 *SDL_GetKeyboardState(int *numkeys)

+ 13 - 0
src/test/SDL_test_common.c

@@ -2198,6 +2198,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
                 }
                 }
             }
             }
             break;
             break;
+        case SDLK_O:
         case SDLK_o:
         case SDLK_o:
             if (withControl) {
             if (withControl) {
                 /* Ctrl-O (or Ctrl-Shift-O) changes window opacity. */
                 /* Ctrl-O (or Ctrl-Shift-O) changes window opacity. */
@@ -2215,6 +2216,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
                 }
                 }
             }
             }
             break;
             break;
+        case SDLK_H:
         case SDLK_h:
         case SDLK_h:
             if (withControl) {
             if (withControl) {
                 /* Ctrl-H changes cursor visibility. */
                 /* Ctrl-H changes cursor visibility. */
@@ -2225,6 +2227,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
                 }
                 }
             }
             }
             break;
             break;
+        case SDLK_C:
         case SDLK_c:
         case SDLK_c:
             if (withAlt) {
             if (withAlt) {
                 /* Alt-C copy awesome text to the primary selection! */
                 /* Alt-C copy awesome text to the primary selection! */
@@ -2250,6 +2253,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
                 break;
                 break;
             }
             }
             break;
             break;
+        case SDLK_V:
         case SDLK_v:
         case SDLK_v:
             if (withAlt) {
             if (withAlt) {
                 /* Alt-V paste awesome text from the primary selection! */
                 /* Alt-V paste awesome text from the primary selection! */
@@ -2277,6 +2281,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
                 }
                 }
             }
             }
             break;
             break;
+        case SDLK_F:
         case SDLK_f:
         case SDLK_f:
             if (withControl) {
             if (withControl) {
                 /* Ctrl-F flash the window */
                 /* Ctrl-F flash the window */
@@ -2286,6 +2291,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
                 }
                 }
             }
             }
             break;
             break;
+        case SDLK_G:
         case SDLK_g:
         case SDLK_g:
             if (withControl) {
             if (withControl) {
                 /* Ctrl-G toggle mouse grab */
                 /* Ctrl-G toggle mouse grab */
@@ -2295,6 +2301,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
                 }
                 }
             }
             }
             break;
             break;
+        case SDLK_K:
         case SDLK_k:
         case SDLK_k:
             if (withControl) {
             if (withControl) {
                 /* Ctrl-K toggle keyboard grab */
                 /* Ctrl-K toggle keyboard grab */
@@ -2304,6 +2311,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
                 }
                 }
             }
             }
             break;
             break;
+        case SDLK_M:
         case SDLK_m:
         case SDLK_m:
             if (withControl) {
             if (withControl) {
                 /* Ctrl-M maximize */
                 /* Ctrl-M maximize */
@@ -2326,12 +2334,14 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
                 }
                 }
             }
             }
             break;
             break;
+        case SDLK_R:
         case SDLK_r:
         case SDLK_r:
             if (withControl) {
             if (withControl) {
                 /* Ctrl-R toggle mouse relative mode */
                 /* Ctrl-R toggle mouse relative mode */
                 SDL_SetRelativeMouseMode(!SDL_GetRelativeMouseMode());
                 SDL_SetRelativeMouseMode(!SDL_GetRelativeMouseMode());
             }
             }
             break;
             break;
+        case SDLK_T:
         case SDLK_t:
         case SDLK_t:
             if (withControl) {
             if (withControl) {
                 /* Ctrl-T toggle topmost mode */
                 /* Ctrl-T toggle topmost mode */
@@ -2346,6 +2356,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
                 }
                 }
             }
             }
             break;
             break;
+        case SDLK_Z:
         case SDLK_z:
         case SDLK_z:
             if (withControl) {
             if (withControl) {
                 /* Ctrl-Z minimize */
                 /* Ctrl-Z minimize */
@@ -2385,6 +2396,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
             }
             }
 
 
             break;
             break;
+        case SDLK_B:
         case SDLK_b:
         case SDLK_b:
             if (withControl) {
             if (withControl) {
                 /* Ctrl-B toggle window border */
                 /* Ctrl-B toggle window border */
@@ -2396,6 +2408,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
                 }
                 }
             }
             }
             break;
             break;
+        case SDLK_A:
         case SDLK_a:
         case SDLK_a:
             if (withControl) {
             if (withControl) {
                 /* Ctrl-A toggle aspect ratio */
                 /* Ctrl-A toggle aspect ratio */

+ 1 - 1
test/testaudiostreamdynamicresample.c

@@ -234,7 +234,7 @@ static void loop(void)
                 SDL_Log("Cleared audio stream");
                 SDL_Log("Cleared audio stream");
             } else if (sym == SDLK_s) {
             } else if (sym == SDLK_s) {
                 queue_audio();
                 queue_audio();
-            } else if (sym == SDLK_d) {
+            } else if (sym == SDLK_d || sym == SDLK_D) {
                 float amount = 1.0f;
                 float amount = 1.0f;
                 amount *= (e.key.mod & SDL_KMOD_CTRL) ? 10.0f : 1.0f;
                 amount *= (e.key.mod & SDL_KMOD_CTRL) ? 10.0f : 1.0f;
                 amount *= (e.key.mod & SDL_KMOD_SHIFT) ? 10.0f : 1.0f;
                 amount *= (e.key.mod & SDL_KMOD_SHIFT) ? 10.0f : 1.0f;

+ 18 - 20
test/testintersections.c

@@ -226,27 +226,25 @@ static void loop(void *arg)
             break;
             break;
         case SDL_EVENT_KEY_DOWN:
         case SDL_EVENT_KEY_DOWN:
             switch (event.key.key) {
             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;
                 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;
                 break;
             default:
             default:
                 break;
                 break;