SDL: Use the templated hashtable for keymaps

From ea3042350d3aac4b83ae83047185ba703d8f0a5c Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Sat, 10 Oct 2026 13:07:00 -0400
Subject: [PATCH] Use the templated hashtable for keymaps

In addition to converting to the templated hashtable, keymaps are now opaque objects to avoid the hashtable template being pulled into anything that includes the keymap header. Where necessary, keymap information is now exposed as flags.
---
 src/events/SDL_keyboard.c   |  57 +++++------------
 src/events/SDL_keyboard_c.h |   5 ++
 src/events/SDL_keymap.c     | 123 ++++++++++++++++++++++++++----------
 src/events/SDL_keymap_c.h   |  38 +++++------
 4 files changed, 126 insertions(+), 97 deletions(-)

diff --git a/src/events/SDL_keyboard.c b/src/events/SDL_keyboard.c
index 796ca3dbde328..eea7f0236a3e8 100644
--- a/src/events/SDL_keyboard.c
+++ b/src/events/SDL_keyboard.c
@@ -224,12 +224,11 @@ const char *SDL_GetKeyboardNameForID(SDL_KeyboardID instance_id)
 void SDL_ResetKeyboard(void)
 {
     SDL_Keyboard *keyboard = &SDL_keyboard;
-    int scancode;
 
 #ifdef DEBUG_KEYBOARD
     SDL_Log("Resetting keyboard");
 #endif
-    for (scancode = SDL_SCANCODE_UNKNOWN; scancode < SDL_SCANCODE_COUNT; ++scancode) {
+    for (int scancode = SDL_SCANCODE_UNKNOWN; scancode < SDL_SCANCODE_COUNT; ++scancode) {
         if (keyboard->keystate[scancode]) {
             SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, (SDL_Scancode)scancode, false);
         }
@@ -241,61 +240,34 @@ SDL_Keymap *SDL_GetCurrentKeymap(bool ignore_options)
     SDL_Keyboard *keyboard = &SDL_keyboard;
     SDL_Keymap *keymap = SDL_keyboard.keymap;
 
-    if (!ignore_options) {
-        if (keymap && keymap->thai_keyboard) {
+    if (keymap && !ignore_options) {
+        const Uint32 keymap_flags = SDL_GetKeymapFlags(keymap);
+        if (keymap_flags & SDL_KEYMAP_THAI_KEYBOARD) {
             // Thai keyboards are QWERTY plus Thai characters, use the default QWERTY keymap
             return NULL;
         }
 
         if ((keyboard->keycode_options & KEYCODE_OPTION_LATIN_LETTERS) &&
-            keymap && !keymap->latin_letters) {
+            !(keymap_flags & SDL_KEYMAP_LATIN_LETTERS)) {
             // We'll use the default QWERTY keymap
             return NULL;
         }
     }
 
-    return keyboard->keymap;
+    return keymap;
 }
 
 void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event)
 {
     SDL_Keyboard *keyboard = &SDL_keyboard;
 
-    if (keyboard->keymap && keyboard->keymap->auto_release) {
+    const Uint32 keymap_flags = SDL_GetKeymapFlags(keymap);
+    if (keymap_flags & SDL_KEYMAP_AUTO_RELEASE) {
         SDL_DestroyKeymap(keyboard->keymap);
     }
 
     keyboard->keymap = keymap;
-
-    if (keymap && !keymap->layout_determined) {
-        keymap->layout_determined = true;
-
-        // Detect French number row (all symbols)
-        keymap->french_numbers = 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))) {
-                keymap->french_numbers = false;
-                break;
-            }
-        }
-
-        // Detect non-Latin keymap
-        keymap->thai_keyboard = false;
-        keymap->latin_letters = false;
-        for (int i = SDL_SCANCODE_A; i <= SDL_SCANCODE_D; ++i) {
-            SDL_Keycode key = SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE);
-            if (key <= 0xFF) {
-                keymap->latin_letters = true;
-                break;
-            }
-
-            if (key >= 0x0E00 && key <= 0x0E7F) {
-                keymap->thai_keyboard = true;
-                break;
-            }
-        }
-    }
+    SDL_DetermineKeymapLayout(keymap);
 
     if (send_event) {
         SDL_SendKeymapChangedEvent();
@@ -494,20 +466,20 @@ SDL_Keycode SDL_GetKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate, b
 
     if (key_event) {
         SDL_Keymap *keymap = SDL_GetCurrentKeymap(false);
-        bool numlock = (modstate & SDL_KMOD_NUM) != 0;
-        SDL_Keycode keycode;
+        const Uint32 keymap_flags = SDL_GetKeymapFlags(keymap);
+        const bool numlock = (modstate & SDL_KMOD_NUM) != 0;
 
         // We won't be applying any modifiers by default
         modstate = SDL_KMOD_NONE;
 
         if ((keyboard->keycode_options & KEYCODE_OPTION_FRENCH_NUMBERS) &&
-            keymap && keymap->french_numbers &&
+            (keymap_flags & SDL_KEYMAP_FRENCH_NUMBERS) &&
             (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_0)) {
             // Add the shift state to generate a numeric keycode
             modstate |= SDL_KMOD_SHIFT;
         }
 
-        keycode = SDL_GetKeymapKeycode(keymap, scancode, modstate);
+        SDL_Keycode keycode = SDL_GetKeymapKeycode(keymap, scancode, modstate);
 
         if (keyboard->keycode_options & KEYCODE_OPTION_HIDE_NUMPAD) {
             keycode = SDL_ConvertNumpadKeycode(keycode, numlock);
@@ -921,7 +893,8 @@ void SDL_QuitKeyboard(void)
     SDL_DestroyHashTable(SDL_keyboard_names);
     SDL_keyboard_names = NULL;
 
-    if (SDL_keyboard.keymap && SDL_keyboard.keymap->auto_release) {
+    const Uint32 keymap_flags = SDL_GetKeymapFlags(SDL_keyboard.keymap);
+    if (keymap_flags & SDL_KEYMAP_AUTO_RELEASE) {
         SDL_DestroyKeymap(SDL_keyboard.keymap);
         SDL_keyboard.keymap = NULL;
     }
diff --git a/src/events/SDL_keyboard_c.h b/src/events/SDL_keyboard_c.h
index a6bcfa0a892e2..a1e3bee6f9fd0 100644
--- a/src/events/SDL_keyboard_c.h
+++ b/src/events/SDL_keyboard_c.h
@@ -46,6 +46,11 @@ extern void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID);
 // Set the mapping of scancode to key codes
 extern void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event);
 
+/* This may return null even when a keymap is bound, depending on the current keyboard mapping options.
+ * Set 'ignore_options' to true to always return the keymap that is actually bound.
+ */
+extern SDL_Keymap *SDL_GetCurrentKeymap(bool ignore_options);
+
 // Set the keyboard focus window
 extern bool SDL_SetKeyboardFocus(SDL_Window *window);
 
diff --git a/src/events/SDL_keymap.c b/src/events/SDL_keymap.c
index ed161eb8984f0..ba6791ad025cf 100644
--- a/src/events/SDL_keymap.c
+++ b/src/events/SDL_keymap.c
@@ -24,19 +24,36 @@
 #include "SDL_keyboard_c.h"
 #include "../stdlib/SDL_sysstdlib.h"
 
+#define SDL_HASHTABLE_NAME                     SDL_KeycodeMap
+#define SDL_HASHTABLE_KEY                      Uint32 // Keycode or scancode + modifiers
+#define SDL_HASHTABLE_VALUE                    Uint32 // Keycode or scancode + modifiers
+#define SDL_HASHTABLE_HASH_KEY(unused, key)    (key)  // It's already a unique 32-bit value.
+#define SDL_HASHTABLE_KEYS_EQUAL(unused, a, b) (a == b)
+#include "../SDL_generic_hashtable.h"
+
+struct SDL_Keymap
+{
+    SDL_KeycodeMap *scancode_to_keycode;
+    SDL_KeycodeMap *keycode_to_scancode;
+    SDL_Scancode next_reserved_scancode;
+    Uint32 flags;
+};
+
 static SDL_Keycode SDL_GetDefaultKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate);
 static SDL_Scancode SDL_GetDefaultScancodeFromKey(SDL_Keycode key, SDL_Keymod *modstate);
 
 SDL_Keymap *SDL_CreateKeymap(bool auto_release)
 {
-    SDL_Keymap *keymap = (SDL_Keymap *)SDL_calloc(1, sizeof(*keymap));
+    SDL_Keymap *keymap = (SDL_Keymap *)SDL_calloc(1, sizeof(SDL_Keymap));
     if (!keymap) {
         return NULL;
     }
 
-    keymap->auto_release = auto_release;
-    keymap->scancode_to_keycode = SDL_CreateHashTable(256, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL);
-    keymap->keycode_to_scancode = SDL_CreateHashTable(256, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL);
+    if (auto_release) {
+        keymap->flags |= SDL_KEYMAP_AUTO_RELEASE;
+    }
+    keymap->scancode_to_keycode = SDL_KeycodeMapCreate(256, NULL);
+    keymap->keycode_to_scancode = SDL_KeycodeMapCreate(256, NULL);
     if (!keymap->scancode_to_keycode || !keymap->keycode_to_scancode) {
         SDL_DestroyKeymap(keymap);
         return NULL;
@@ -69,21 +86,21 @@ void SDL_SetKeymapEntry(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod mo
     }
 
     modstate = NormalizeModifierStateForKeymap(modstate);
-    Uint32 key = ((Uint32)modstate << 16) | scancode;
-    const void *value;
-    if (SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) {
-        const SDL_Keycode existing_keycode = (SDL_Keycode)(uintptr_t)value;
+    const Uint32 key = ((Uint32)modstate << 16) | scancode;
+    Uint32 value = 0;
+    if (SDL_KeycodeMapFind(keymap->scancode_to_keycode, key, &value)) {
+        const SDL_Keycode existing_keycode = (SDL_Keycode)value;
         if (existing_keycode == keycode) {
             // We already have this mapping
             return;
         }
-        // InsertIntoHashTable will replace the existing entry in the keymap atomically.
+        // SDL_KeycodeMapInsert will replace the existing entry in the keymap atomically.
     }
-    SDL_InsertIntoHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, (void *)(uintptr_t)keycode, true);
+    SDL_KeycodeMapInsert(keymap->scancode_to_keycode, key, keycode, true);
 
     bool update_keycode = true;
-    if (SDL_FindInHashTable(keymap->keycode_to_scancode, (void *)(uintptr_t)keycode, &value)) {
-        const Uint32 existing_value = (Uint32)(uintptr_t)value;
+    if (SDL_KeycodeMapFind(keymap->keycode_to_scancode, keycode, &value)) {
+        const Uint32 existing_value = value;
         const SDL_Keymod existing_modstate = (SDL_Keymod)(existing_value >> 16);
 
         // Keep the simplest combination of scancode and modifiers to generate this keycode
@@ -92,20 +109,20 @@ void SDL_SetKeymapEntry(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod mo
         }
     }
     if (update_keycode) {
-        SDL_InsertIntoHashTable(keymap->keycode_to_scancode, (void *)(uintptr_t)keycode, (void *)(uintptr_t)key, true);
+        SDL_KeycodeMapInsert(keymap->keycode_to_scancode, keycode, key, true);
     }
 }
 
 SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate)
 {
     if (keymap) {
-        const void *value;
+        Uint32 value = 0;
         const SDL_Keymod normalized_modstate = NormalizeModifierStateForKeymap(modstate);
         Uint32 key = ((Uint32)normalized_modstate << 16) | scancode;
 
         // First, try the requested set of modifiers.
-        if (SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) {
-            return (SDL_Keycode)(uintptr_t)value;
+        if (SDL_KeycodeMapFind(keymap->scancode_to_keycode, key, &value)) {
+            return (SDL_Keycode)value;
         }
 
         // If the requested set of modifiers was not found, search for the key from the highest to lowest modifier levels.
@@ -118,8 +135,8 @@ SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_
                     const SDL_Keymod shifted_modstate = SDL_KMOD_LEVEL5 | caps_mask;
                     key = ((Uint32)shifted_modstate << 16) | scancode;
 
-                    if (shifted_modstate != normalized_modstate && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) {
-                        return (SDL_Keycode)(uintptr_t)value;
+                    if (shifted_modstate != normalized_modstate && SDL_KeycodeMapFind(keymap->scancode_to_keycode, key, &value)) {
+                        return (SDL_Keycode)value;
                     }
                 }
 
@@ -128,8 +145,8 @@ SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_
                     const SDL_Keymod shifted_modstate = SDL_KMOD_MODE | SDL_KMOD_SHIFT | caps_mask;
                     key = ((Uint32)shifted_modstate << 16) | scancode;
 
-                    if (shifted_modstate != normalized_modstate && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) {
-                        return (SDL_Keycode)(uintptr_t)value;
+                    if (shifted_modstate != normalized_modstate && SDL_KeycodeMapFind(keymap->scancode_to_keycode, key, &value)) {
+                        return (SDL_Keycode)value;
                     }
                 }
 
@@ -138,8 +155,8 @@ SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_
                     const SDL_Keymod shifted_modstate = SDL_KMOD_MODE | caps_mask;
                     key = ((Uint32)shifted_modstate << 16) | scancode;
 
-                    if (shifted_modstate != normalized_modstate && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) {
-                        return (SDL_Keycode)(uintptr_t)value;
+                    if (shifted_modstate != normalized_modstate && SDL_KeycodeMapFind(keymap->scancode_to_keycode, key, &value)) {
+                        return (SDL_Keycode)value;
                     }
                 }
 
@@ -148,15 +165,15 @@ SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_
                     const SDL_Keymod shifted_modstate = SDL_KMOD_SHIFT | caps_mask;
                     key = ((Uint32)shifted_modstate << 16) | scancode;
 
-                    if (shifted_modstate != normalized_modstate && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) {
-                        return (SDL_Keycode)(uintptr_t)value;
+                    if (shifted_modstate != normalized_modstate && SDL_KeycodeMapFind(keymap->scancode_to_keycode, key, &value)) {
+                        return (SDL_Keycode)value;
                     }
                 }
 
                 // Shift Level 1 (unmodified)
                 key = ((Uint32)caps_mask << 16) | scancode;
-                if (SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) {
-                    return (SDL_Keycode)(uintptr_t)value;
+                if (SDL_KeycodeMapFind(keymap->scancode_to_keycode, key, &value)) {
+                    return (SDL_Keycode)value;
                 }
 
                 // Clear the capslock mask, if set.
@@ -171,12 +188,12 @@ SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_
 SDL_Scancode SDL_GetKeymapScancode(SDL_Keymap *keymap, SDL_Keycode keycode, SDL_Keymod *modstate)
 {
     SDL_Scancode scancode;
+    Uint32 value = 0;
 
-    const void *value;
-    if (keymap && SDL_FindInHashTable(keymap->keycode_to_scancode, (void *)(uintptr_t)keycode, &value)) {
-        scancode = (SDL_Scancode)((uintptr_t)value & 0xFFFF);
+    if (keymap && SDL_KeycodeMapFind(keymap->keycode_to_scancode, keycode, &value)) {
+        scancode = (SDL_Scancode)(value & 0xFFFF);
         if (modstate) {
-            *modstate = (SDL_Keymod)((uintptr_t)value >> 16);
+            *modstate = (SDL_Keymod)(value >> 16);
         }
     } else {
         scancode = SDL_GetDefaultScancodeFromKey(keycode, modstate);
@@ -208,12 +225,12 @@ void SDL_DestroyKeymap(SDL_Keymap *keymap)
         return;
     }
 
-    if (!keymap->auto_release && keymap == SDL_GetCurrentKeymap(true)) {
+    if (!(keymap->flags & SDL_KEYMAP_AUTO_RELEASE) && keymap == SDL_GetCurrentKeymap(true)) {
         SDL_SetKeymap(NULL, false);
     }
 
-    SDL_DestroyHashTable(keymap->scancode_to_keycode);
-    SDL_DestroyHashTable(keymap->keycode_to_scancode);
+    SDL_KeycodeMapDestroy(keymap->scancode_to_keycode);
+    SDL_KeycodeMapDestroy(keymap->keycode_to_scancode);
     SDL_free(keymap);
 }
 
@@ -1242,3 +1259,43 @@ SDL_Keycode SDL_GetKeyFromName(const char *name)
 
     return SDL_GetKeyFromScancode(SDL_GetScancodeFromName(name), SDL_KMOD_NONE, false);
 }
+
+Uint32 SDL_GetKeymapFlags(SDL_Keymap *keymap)
+{
+    if (keymap) {
+        return keymap->flags;
+    }
+
+    return 0;
+}
+
+void SDL_DetermineKeymapLayout(SDL_Keymap *keymap)
+{
+    if (keymap && !(keymap->flags & SDL_KEYMAP_LAYOUT_DETERMINED)) {
+        keymap->flags |= SDL_KEYMAP_LAYOUT_DETERMINED;
+
+        // Detect French number row (all symbols)
+        keymap->flags |= SDL_KEYMAP_FRENCH_NUMBERS;
+        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))) {
+                keymap->flags &= ~SDL_KEYMAP_FRENCH_NUMBERS;
+                break;
+            }
+        }
+
+        // Detect non-Latin keymap
+        for (int i = SDL_SCANCODE_A; i <= SDL_SCANCODE_D; ++i) {
+            const SDL_Keycode key = SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE);
+            if (key <= 0xFF) {
+                keymap->flags |= SDL_KEYMAP_LATIN_LETTERS;
+                break;
+            }
+
+            if (key >= 0x0E00 && key <= 0x0E7F) {
+                keymap->flags |= SDL_KEYMAP_THAI_KEYBOARD;
+                break;
+            }
+        }
+    }
+}
diff --git a/src/events/SDL_keymap_c.h b/src/events/SDL_keymap_c.h
index 0ed7e07568747..02cfde7a9401a 100644
--- a/src/events/SDL_keymap_c.h
+++ b/src/events/SDL_keymap_c.h
@@ -23,27 +23,21 @@
 #ifndef SDL_keymap_c_h_
 #define SDL_keymap_c_h_
 
-typedef struct SDL_Keymap
-{
-    SDL_HashTable *scancode_to_keycode;
-    SDL_HashTable *keycode_to_scancode;
-    SDL_Scancode next_reserved_scancode;
-    bool auto_release;
-    bool layout_determined;
-    bool french_numbers;
-    bool latin_letters;
-    bool thai_keyboard;
-} SDL_Keymap;
-
-/* This may return null even when a keymap is bound, depending on the current keyboard mapping options.
- * Set 'ignore_options' to true to always return the keymap that is actually bound.
- */
-SDL_Keymap *SDL_GetCurrentKeymap(bool ignore_options);
-SDL_Keymap *SDL_CreateKeymap(bool auto_release);
-void SDL_SetKeymapEntry(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keycode keycode);
-SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate);
-SDL_Scancode SDL_GetKeymapScancode(SDL_Keymap *keymap, SDL_Keycode keycode, SDL_Keymod *modstate);
-SDL_Scancode SDL_GetKeymapNextReservedScancode(SDL_Keymap *keymap);
-void SDL_DestroyKeymap(SDL_Keymap *keymap);
+typedef struct SDL_Keymap SDL_Keymap;
+
+#define SDL_KEYMAP_AUTO_RELEASE      0x01
+#define SDL_KEYMAP_LAYOUT_DETERMINED 0x02
+#define SDL_KEYMAP_FRENCH_NUMBERS    0x04
+#define SDL_KEYMAP_LATIN_LETTERS     0x08
+#define SDL_KEYMAP_THAI_KEYBOARD     0x10
+
+extern SDL_Keymap *SDL_CreateKeymap(bool auto_release);
+extern void SDL_SetKeymapEntry(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keycode keycode);
+extern SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate);
+extern SDL_Scancode SDL_GetKeymapScancode(SDL_Keymap *keymap, SDL_Keycode keycode, SDL_Keymod *modstate);
+extern SDL_Scancode SDL_GetKeymapNextReservedScancode(SDL_Keymap *keymap);
+extern Uint32 SDL_GetKeymapFlags(SDL_Keymap *keymap);
+extern void SDL_DetermineKeymapLayout(SDL_Keymap *keymap);
+extern void SDL_DestroyKeymap(SDL_Keymap *keymap);
 
 #endif // SDL_keymap_c_h_