SDL: Added the ability to query the keymap for keycodes based on modifier state

From 679e4471ed0d766bffff3645e87ac69fe399d4ae Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 19 Jun 2024 15:00:12 -0700
Subject: [PATCH] Added the ability to query the keymap for keycodes based on
 modifier state

---
 VisualC-GDK/SDL/SDL.vcxproj             |   2 +
 VisualC-GDK/SDL/SDL.vcxproj.filters     |   2 +
 VisualC-WinRT/SDL-UWP.vcxproj           |   2 +
 VisualC-WinRT/SDL-UWP.vcxproj.filters   |   6 +
 VisualC/SDL/SDL.vcxproj                 |   2 +
 VisualC/SDL/SDL.vcxproj.filters         |   6 +
 Xcode/SDL/SDL.xcodeproj/project.pbxproj |   8 +
 docs/README-migration.md                |   2 +
 include/SDL3/SDL_events.h               |   8 +-
 include/SDL3/SDL_keyboard.h             |  48 +-
 include/SDL3/SDL_keycode.h              |  32 +-
 src/SDL_hashtable.c                     |   9 +-
 src/SDL_hashtable.h                     |   1 +
 src/SDL_utils.c                         |  25 +
 src/SDL_utils_c.h                       |   2 +
 src/dynapi/SDL_dynapi.sym               |   2 +
 src/dynapi/SDL_dynapi_overrides.h       |   2 +
 src/dynapi/SDL_dynapi_procs.h           |   8 +-
 src/events/SDL_keyboard.c               | 867 +-----------------------
 src/events/SDL_keyboard_c.h             |  19 +-
 src/events/SDL_keymap.c                 | 716 +++++++++++++++++++
 src/events/SDL_keymap_c.h               |  37 +
 src/events/scancodes_ascii.h            | 165 -----
 src/video/cocoa/SDL_cocoakeyboard.m     |  61 +-
 src/video/haiku/SDL_bkeyboard.cc        | 217 +++---
 src/video/ngage/SDL_ngageevents.cpp     |  50 +-
 src/video/psp/SDL_pspevents.c           | 226 +++---
 src/video/vita/SDL_vitavideo.c          |   6 +-
 src/video/wayland/SDL_waylandevents.c   |  14 +-
 src/video/windows/SDL_windowskeyboard.c |  98 ++-
 src/video/x11/SDL_x11events.c           |   2 +-
 src/video/x11/SDL_x11keyboard.c         |  31 +-
 test/checkkeys.c                        | 126 +++-
 test/testautomation_keyboard.c          |  19 +-
 34 files changed, 1400 insertions(+), 1421 deletions(-)
 create mode 100644 src/events/SDL_keymap.c
 create mode 100644 src/events/SDL_keymap_c.h
 delete mode 100644 src/events/scancodes_ascii.h

diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 057cb1c331ae9..82a5aaae01495 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -414,6 +414,7 @@
     <ClInclude Include="..\..\src\events\SDL_dropevents_c.h" />
     <ClInclude Include="..\..\src\events\SDL_events_c.h" />
     <ClInclude Include="..\..\src\events\SDL_keyboard_c.h" />
+    <ClInclude Include="..\..\src\events\SDL_keymap_c.h" />
     <ClInclude Include="..\..\src\events\SDL_mouse_c.h" />
     <ClInclude Include="..\..\src\events\SDL_touch_c.h" />
     <ClInclude Include="..\..\src\events\SDL_windowevents_c.h" />
@@ -634,6 +635,7 @@
     <ClCompile Include="..\..\src\events\SDL_dropevents.c" />
     <ClCompile Include="..\..\src\events\SDL_events.c" />
     <ClCompile Include="..\..\src\events\SDL_keyboard.c" />
+    <ClCompile Include="..\..\src\events\SDL_keymap.c" />
     <ClCompile Include="..\..\src\events\SDL_mouse.c" />
     <ClCompile Include="..\..\src\events\SDL_pen.c" />
     <ClCompile Include="..\..\src\events\SDL_quit.c" />
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 9ac9cd48fe5e4..91684485f4fa2 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -47,6 +47,7 @@
     <ClCompile Include="..\..\src\events\SDL_dropevents.c" />
     <ClCompile Include="..\..\src\events\SDL_events.c" />
     <ClCompile Include="..\..\src\events\SDL_keyboard.c" />
+    <ClCompile Include="..\..\src\events\SDL_keymap.c" />
     <ClCompile Include="..\..\src\events\SDL_mouse.c" />
     <ClCompile Include="..\..\src\events\SDL_pen.c" />
     <ClCompile Include="..\..\src\events\SDL_quit.c" />
@@ -326,6 +327,7 @@
     <ClInclude Include="..\..\src\events\SDL_dropevents_c.h" />
     <ClInclude Include="..\..\src\events\SDL_events_c.h" />
     <ClInclude Include="..\..\src\events\SDL_keyboard_c.h" />
+    <ClInclude Include="..\..\src\events\SDL_keymap_c.h" />
     <ClInclude Include="..\..\src\events\SDL_mouse_c.h" />
     <ClInclude Include="..\..\src\events\SDL_touch_c.h" />
     <ClInclude Include="..\..\src\events\SDL_windowevents_c.h" />
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj
index ac172fb96456a..0231e2dd03a3a 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj
+++ b/VisualC-WinRT/SDL-UWP.vcxproj
@@ -119,6 +119,7 @@
     <ClInclude Include="..\src\events\SDL_dropevents_c.h" />
     <ClInclude Include="..\src\events\SDL_events_c.h" />
     <ClInclude Include="..\src\events\SDL_keyboard_c.h" />
+    <ClInclude Include="..\src\events\SDL_keymap_c.h" />
     <ClInclude Include="..\src\events\SDL_mouse_c.h" />
     <ClInclude Include="..\src\events\SDL_touch_c.h" />
     <ClInclude Include="..\src\events\SDL_windowevents_c.h" />
@@ -320,6 +321,7 @@
     <ClCompile Include="..\src\events\SDL_dropevents.c" />
     <ClCompile Include="..\src\events\SDL_events.c" />
     <ClCompile Include="..\src\events\SDL_keyboard.c" />
+    <ClCompile Include="..\src\events\SDL_keymap.c" />
     <ClCompile Include="..\src\events\SDL_mouse.c" />
     <ClCompile Include="..\src\events\SDL_pen.c" />
     <ClCompile Include="..\src\events\SDL_quit.c" />
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters
index 8447d95db2bc3..7112f2f9cc378 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj.filters
+++ b/VisualC-WinRT/SDL-UWP.vcxproj.filters
@@ -273,6 +273,9 @@
     <ClInclude Include="..\src\events\SDL_keyboard_c.h">
       <Filter>Source Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\src\events\SDL_keymap_c.h">
+      <Filter>Source Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\src\events\SDL_mouse_c.h">
       <Filter>Source Files</Filter>
     </ClInclude>
@@ -585,6 +588,9 @@
     <ClCompile Include="..\src\events\SDL_keyboard.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\src\events\SDL_keymap.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\src\events\SDL_mouse.c">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 0164338ee5ba3..a9849a3a53957 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -338,6 +338,7 @@
     <ClInclude Include="..\..\src\events\SDL_dropevents_c.h" />
     <ClInclude Include="..\..\src\events\SDL_events_c.h" />
     <ClInclude Include="..\..\src\events\SDL_keyboard_c.h" />
+    <ClInclude Include="..\..\src\events\SDL_keymap_c.h" />
     <ClInclude Include="..\..\src\events\SDL_mouse_c.h" />
     <ClInclude Include="..\..\src\events\SDL_touch_c.h" />
     <ClInclude Include="..\..\src\events\SDL_windowevents_c.h" />
@@ -524,6 +525,7 @@
     <ClCompile Include="..\..\src\events\SDL_dropevents.c" />
     <ClCompile Include="..\..\src\events\SDL_events.c" />
     <ClCompile Include="..\..\src\events\SDL_keyboard.c" />
+    <ClCompile Include="..\..\src\events\SDL_keymap.c" />
     <ClCompile Include="..\..\src\events\SDL_mouse.c" />
     <ClCompile Include="..\..\src\events\SDL_pen.c" />
     <ClCompile Include="..\..\src\events\SDL_quit.c" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 17838de16a1dd..448bf3e4e49a5 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -507,6 +507,9 @@
     <ClInclude Include="..\..\src\events\SDL_keyboard_c.h">
       <Filter>events</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\events\SDL_keymap_c.h">
+      <Filter>events</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\events\SDL_mouse_c.h">
       <Filter>events</Filter>
     </ClInclude>
@@ -1000,6 +1003,9 @@
     <ClCompile Include="..\..\src\events\SDL_keyboard.c">
       <Filter>events</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\events\SDL_keymap.c">
+      <Filter>events</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\events\SDL_mouse.c">
       <Filter>events</Filter>
     </ClCompile>
diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
index 40e6effe10725..43ebab04d0429 100644
--- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj
+++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
@@ -386,6 +386,8 @@
 		F310138D2C1F2CB700FBE946 /* SDL_getenv_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F310138A2C1F2CB700FBE946 /* SDL_getenv_c.h */; };
 		F310138E2C1F2CB700FBE946 /* SDL_random.c in Sources */ = {isa = PBXBuildFile; fileRef = F310138B2C1F2CB700FBE946 /* SDL_random.c */; };
 		F310138F2C1F2CB700FBE946 /* SDL_sysstdlib.h in Headers */ = {isa = PBXBuildFile; fileRef = F310138C2C1F2CB700FBE946 /* SDL_sysstdlib.h */; };
+		F31013C72C24E98200FBE946 /* SDL_keymap.c in Sources */ = {isa = PBXBuildFile; fileRef = F31013C52C24E98200FBE946 /* SDL_keymap.c */; };
+		F31013C82C24E98200FBE946 /* SDL_keymap_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F31013C62C24E98200FBE946 /* SDL_keymap_c.h */; };
 		F316ABD82B5C3185002EF551 /* SDL_memset.c in Sources */ = {isa = PBXBuildFile; fileRef = F316ABD62B5C3185002EF551 /* SDL_memset.c */; };
 		F316ABD92B5C3185002EF551 /* SDL_memcpy.c in Sources */ = {isa = PBXBuildFile; fileRef = F316ABD72B5C3185002EF551 /* SDL_memcpy.c */; };
 		F316ABDB2B5CA721002EF551 /* SDL_memmove.c in Sources */ = {isa = PBXBuildFile; fileRef = F316ABDA2B5CA721002EF551 /* SDL_memmove.c */; };
@@ -915,6 +917,8 @@
 		F310138A2C1F2CB700FBE946 /* SDL_getenv_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_getenv_c.h; sourceTree = "<group>"; };
 		F310138B2C1F2CB700FBE946 /* SDL_random.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_random.c; sourceTree = "<group>"; };
 		F310138C2C1F2CB700FBE946 /* SDL_sysstdlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_sysstdlib.h; sourceTree = "<group>"; };
+		F31013C52C24E98200FBE946 /* SDL_keymap.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_keymap.c; sourceTree = "<group>"; };
+		F31013C62C24E98200FBE946 /* SDL_keymap_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_keymap_c.h; sourceTree = "<group>"; };
 		F316ABD62B5C3185002EF551 /* SDL_memset.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_memset.c; sourceTree = "<group>"; };
 		F316ABD72B5C3185002EF551 /* SDL_memcpy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_memcpy.c; sourceTree = "<group>"; };
 		F316ABDA2B5CA721002EF551 /* SDL_memmove.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_memmove.c; sourceTree = "<group>"; };
@@ -2183,6 +2187,8 @@
 				A7D8A93523E2514000DCD162 /* SDL_events.c */,
 				A7D8A93D23E2514000DCD162 /* SDL_keyboard_c.h */,
 				A7D8A93823E2514000DCD162 /* SDL_keyboard.c */,
+				F31013C62C24E98200FBE946 /* SDL_keymap_c.h */,
+				F31013C52C24E98200FBE946 /* SDL_keymap.c */,
 				A7D8A92B23E2514000DCD162 /* SDL_mouse_c.h */,
 				A7D8A92A23E2514000DCD162 /* SDL_mouse.c */,
 				63134A232A7902FD0021E9A6 /* SDL_pen_c.h */,
@@ -2422,6 +2428,7 @@
 				F3F7D9392933074E00816151 /* SDL_opengles2_gl2ext.h in Headers */,
 				F3F7D9692933074E00816151 /* SDL_opengles2_gl2platform.h in Headers */,
 				F3F7D9092933074E00816151 /* SDL_opengles2_khrplatform.h in Headers */,
+				F31013C82C24E98200FBE946 /* SDL_keymap_c.h in Headers */,
 				63134A222A7902CF0021E9A6 /* SDL_pen.h in Headers */,
 				63134A252A7902FD0021E9A6 /* SDL_pen_c.h in Headers */,
 				F36C34312C0F876500991150 /* SDL_offscreenvulkan.h in Headers */,
@@ -2675,6 +2682,7 @@
 				A7D8BAC723E2514500DCD162 /* e_pow.c in Sources */,
 				A7D8B41C23E2514300DCD162 /* SDL_systls.c in Sources */,
 				9846B07C287A9020000C35C8 /* SDL_hidapi_shield.c in Sources */,
+				F31013C72C24E98200FBE946 /* SDL_keymap.c in Sources */,
 				A7D8BBD923E2574800DCD162 /* SDL_uikitmessagebox.m in Sources */,
 				F32DDAD42AB795A30041EAA5 /* SDL_audioresample.c in Sources */,
 				F3FA5A212B59ACE000FEAD97 /* yuv_rgb_std.c in Sources */,
diff --git a/docs/README-migration.md b/docs/README-migration.md
index a2ca542d15f4a..6420dacd6cc18 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -890,6 +890,8 @@ The following symbols have been removed:
 
 Text input is no longer automatically enabled when initializing video, you should call SDL_StartTextInput() when you want to receive text input and call SDL_StopTextInput() when you are done. Starting text input may shown an input method editor (IME) and cause key up/down events to be skipped, so should only be enabled when the application wants text input.
 
+SDL_GetDefaultKeyFromScancode(), SDL_GetKeyFromScancode(), and SDL_GetScancodeFromKey() take an SDL_Keymod parameter and use that to provide the correct result based on keyboard modifier state.
+
 The following functions have been renamed:
 * SDL_IsScreenKeyboardShown() => SDL_ScreenKeyboardShown()
 * SDL_IsTextInputActive() => SDL_TextInputActive()
diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index d578cdd93fdf7..6fe1c77ec27be 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -316,8 +316,7 @@ typedef struct SDL_KeyboardEvent
 /**
  * Keyboard text editing event structure (event.edit.*)
  *
- * The `text` is owned by SDL and should be copied if the application wants to
- * hold onto it beyond the scope of handling this event.
+ * The text string follows the SDL_GetStringRule.
  *
  * \since This struct is available since SDL 3.0.0.
  */
@@ -335,8 +334,7 @@ typedef struct SDL_TextEditingEvent
 /**
  * Keyboard text input event structure (event.text.*)
  *
- * The `text` is owned by SDL and should be copied if the application wants to
- * hold onto it beyond the scope of handling this event.
+ * The text string follows the SDL_GetStringRule.
  *
  * This event will never be delivered unless text input is enabled by calling
  * SDL_StartTextInput(). Text input is disabled by default!
@@ -733,7 +731,7 @@ typedef struct SDL_PenButtonEvent
  * An event used to drop text or request a file open by the system
  * (event.drop.*)
  *
- * The `source` and `data` are owned by SDL and should be copied if the application wants to hold onto them beyond the scope of handling this event.
+ * The source and data strings follow the SDL_GetStringRule.
  *
  * \since This struct is available since SDL 3.0.0.
  */
diff --git a/include/SDL3/SDL_keyboard.h b/include/SDL3/SDL_keyboard.h
index ffa15949504c6..9e17a5d175f3f 100644
--- a/include/SDL3/SDL_keyboard.h
+++ b/include/SDL3/SDL_keyboard.h
@@ -211,6 +211,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetModState(SDL_Keymod modstate);
  * See SDL_Keycode for details.
  *
  * \param scancode the desired SDL_Scancode to query.
+ * \param modstate the modifier state to use when translating the scancode to a keycode.
  * \returns the SDL_Keycode that corresponds to the given SDL_Scancode.
  *
  * \since This function is available since SDL 3.0.0.
@@ -218,7 +219,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetModState(SDL_Keymod modstate);
  * \sa SDL_GetKeyName
  * \sa SDL_GetScancodeFromKey
  */
-extern SDL_DECLSPEC SDL_Keycode SDLCALL SDL_GetDefaultKeyFromScancode(SDL_Scancode scancode);
+extern SDL_DECLSPEC SDL_Keycode SDLCALL SDL_GetDefaultKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate);
 
 /**
  * Get the key code corresponding to the given scancode according to the
@@ -227,35 +228,67 @@ extern SDL_DECLSPEC SDL_Keycode SDLCALL SDL_GetDefaultKeyFromScancode(SDL_Scanco
  * See SDL_Keycode for details.
  *
  * \param scancode the desired SDL_Scancode to query.
+ * \param modstate the modifier state to use when translating the scancode to a keycode.
  * \returns the SDL_Keycode that corresponds to the given SDL_Scancode.
  *
  * \since This function is available since SDL 3.0.0.
  *
+ * \sa SDL_GetDefaultKeyFromScancode
  * \sa SDL_GetKeyName
  * \sa SDL_GetScancodeFromKey
  */
-extern SDL_DECLSPEC SDL_Keycode SDLCALL SDL_GetKeyFromScancode(SDL_Scancode scancode);
+extern SDL_DECLSPEC SDL_Keycode SDLCALL SDL_GetKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate);
+
+/**
+ * Get the scancode corresponding to the given key code according to a default en_US keyboard layout.
+ *
+ * Note that there may be multiple scancode+modifier states that can generate this keycode, this will just return the first one found.
+ *
+ * \param key the desired SDL_Keycode to query.
+ * \param modstate a pointer to the modifier state that would be used when the scancode generates this key, may be NULL.
+ * \returns the SDL_Scancode that corresponds to the given SDL_Keycode.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetScancodeFromKey
+ * \sa SDL_GetScancodeName
+ */
+extern SDL_DECLSPEC SDL_Scancode SDLCALL SDL_GetDefaultScancodeFromKey(SDL_Keycode key, SDL_Keymod *modstate);
 
 /**
  * Get the scancode corresponding to the given key code according to the
  * current keyboard layout.
  *
- * See SDL_Scancode for details.
+ * Note that there may be multiple scancode+modifier states that can generate this keycode, this will just return the first one found.
  *
  * \param key the desired SDL_Keycode to query.
+ * \param modstate a pointer to the modifier state that would be used when the scancode generates this key, may be NULL.
  * \returns the SDL_Scancode that corresponds to the given SDL_Keycode.
  *
  * \since This function is available since SDL 3.0.0.
  *
+ * \sa SDL_GetDefaultScancodeFromKey
  * \sa SDL_GetKeyFromScancode
  * \sa SDL_GetScancodeName
  */
-extern SDL_DECLSPEC SDL_Scancode SDLCALL SDL_GetScancodeFromKey(SDL_Keycode key);
+extern SDL_DECLSPEC SDL_Scancode SDLCALL SDL_GetScancodeFromKey(SDL_Keycode key, SDL_Keymod *modstate);
 
 /**
- * Get a human-readable name for a scancode.
+ * Set a human-readable name for a scancode.
+ *
+ * \param scancode the desired SDL_Scancode.
+ * \param name the name to use for the scancode, encoded as UTF-8. The string is not copied, so the pointer given to this function must stay valid while SDL is being used.
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
  *
- * See SDL_Scancode for details.
+ * \sa SDL_GetScancodeName
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetScancodeName(SDL_Scancode scancode, const char *name);
+
+/**
+ * Get a human-readable name for a scancode.
  *
  * The returned string follows the SDL_GetStringRule.
  *
@@ -276,6 +309,7 @@ extern SDL_DECLSPEC SDL_Scancode SDLCALL SDL_GetScancodeFromKey(SDL_Keycode key)
  *
  * \sa SDL_GetScancodeFromKey
  * \sa SDL_GetScancodeFromName
+ * \sa SDL_SetScancodeName
  */
 extern SDL_DECLSPEC const char *SDLCALL SDL_GetScancodeName(SDL_Scancode scancode);
 
@@ -297,8 +331,6 @@ extern SDL_DECLSPEC SDL_Scancode SDLCALL SDL_GetScancodeFromName(const char *nam
 /**
  * Get a human-readable name for a key.
  *
- * See SDL_Scancode and SDL_Keycode for details.
- *
  * The returned string follows the SDL_GetStringRule.
  *
  * \param key the desired SDL_Keycode to query.
diff --git a/include/SDL3/SDL_keycode.h b/include/SDL3/SDL_keycode.h
index 26c03da20c8d5..257b36e5bd96d 100644
--- a/include/SDL3/SDL_keycode.h
+++ b/include/SDL3/SDL_keycode.h
@@ -88,6 +88,32 @@ typedef Uint32 SDL_Keycode;
 #define SDLK_GREATER    '>'
 #define SDLK_QUESTION   '?'
 #define SDLK_AT '@'
+#define SDLK_A  'A'
+#define SDLK_B  'B'
+#define SDLK_C  'C'
+#define SDLK_D  'D'
+#define SDLK_E  'E'
+#define SDLK_F  'F'
+#define SDLK_G  'G'
+#define SDLK_H  'H'
+#define SDLK_I  'I'
+#define SDLK_J  'J'
+#define SDLK_K  'K'
+#define SDLK_L  'L'
+#define SDLK_M  'M'
+#define SDLK_N  'N'
+#define SDLK_O  'O'
+#define SDLK_P  'P'
+#define SDLK_Q  'Q'
+#define SDLK_R  'R'
+#define SDLK_S  'S'
+#define SDLK_T  'T'
+#define SDLK_U  'U'
+#define SDLK_V  'V'
+#define SDLK_W  'W'
+#define SDLK_X  'X'
+#define SDLK_Y  'Y'
+#define SDLK_Z  'Z'
 #define SDLK_LEFTBRACKET    '['
 #define SDLK_BACKSLASH  '\\'
 #define SDLK_RIGHTBRACKET   ']'
@@ -120,6 +146,11 @@ typedef Uint32 SDL_Keycode;
 #define SDLK_x  'x'
 #define SDLK_y  'y'
 #define SDLK_z  'z'
+#define SDLK_LEFTBRACE  '{'
+#define SDLK_PIPE   '|'
+#define SDLK_RIGHTBRACE '}'
+#define SDLK_TILDE  '~'
+#define SDLK_DELETE '\x7F'
 #define SDLK_CAPSLOCK   SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CAPSLOCK)
 #define SDLK_F1 SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F1)
 #define SDLK_F2 SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F2)
@@ -139,7 +170,6 @@ typedef Uint32 SDL_Keycode;
 #define SDLK_INSERT SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_INSERT)
 #define SDLK_HOME   SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_HOME)
 #define SDLK_PAGEUP SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PAGEUP)
-#define SDLK_DELETE '\x7F'
 #define SDLK_END    SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_END)
 #define SDLK_PAGEDOWN   SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PAGEDOWN)
 #define SDLK_RIGHT  SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RIGHT)
diff --git a/src/SDL_hashtable.c b/src/SDL_hashtable.c
index 60488ed18be97..fc7a44791c9a4 100644
--- a/src/SDL_hashtable.c
+++ b/src/SDL_hashtable.c
@@ -241,7 +241,7 @@ SDL_bool SDL_HashTableEmpty(SDL_HashTable *table)
     return SDL_TRUE;
 }
 
-void SDL_DestroyHashTable(SDL_HashTable *table)
+void SDL_EmptyHashTable(SDL_HashTable *table)
 {
     if (table) {
         void *data = table->data;
@@ -257,8 +257,15 @@ void SDL_DestroyHashTable(SDL_HashTable *table)
                 SDL_free(item);
                 item = next;
             }
+            table->table[i] = NULL;
         }
+    }
+}
 
+void SDL_DestroyHashTable(SDL_HashTable *table)
+{
+    if (table) {
+        SDL_EmptyHashTable(table);
         SDL_free(table->table);
         SDL_free(table);
     }
diff --git a/src/SDL_hashtable.h b/src/SDL_hashtable.h
index 96614bc17d7bd..ef0945ee0c47b 100644
--- a/src/SDL_hashtable.h
+++ b/src/SDL_hashtable.h
@@ -36,6 +36,7 @@ SDL_HashTable *SDL_CreateHashTable(void *data,
                                    const SDL_HashTable_NukeFn nukefn,
                                    const SDL_bool stackable);
 
+void SDL_EmptyHashTable(SDL_HashTable *table);
 void SDL_DestroyHashTable(SDL_HashTable *table);
 SDL_bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value);
 SDL_bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key);
diff --git a/src/SDL_utils.c b/src/SDL_utils.c
index 222cf49a3ed13..0aaa3dc391bc3 100644
--- a/src/SDL_utils.c
+++ b/src/SDL_utils.c
@@ -101,6 +101,31 @@ SDL_bool SDL_endswith(const char *string, const char *suffix)
     return SDL_FALSE;
 }
 
+char *SDL_UCS4ToUTF8(Uint32 ch, char *dst)
+{
+    Uint8 *p = (Uint8 *)dst;
+    if (ch <= 0x7F) {
+        *p = (Uint8)ch;
+        ++dst;
+    } else if (ch <= 0x7FF) {
+        p[0] = 0xC0 | (Uint8)((ch >> 6) & 0x1F);
+        p[1] = 0x80 | (Uint8)(ch & 0x3F);
+        dst += 2;
+    } else if (ch <= 0xFFFF) {
+        p[0] = 0xE0 | (Uint8)((ch >> 12) & 0x0F);
+        p[1] = 0x80 | (Uint8)((ch >> 6) & 0x3F);
+        p[2] = 0x80 | (Uint8)(ch & 0x3F);
+        dst += 3;
+    } else {
+        p[0] = 0xF0 | (Uint8)((ch >> 18) & 0x07);
+        p[1] = 0x80 | (Uint8)((ch >> 12) & 0x3F);
+        p[2] = 0x80 | (Uint8)((ch >> 6) & 0x3F);
+        p[3] = 0x80 | (Uint8)(ch & 0x3F);
+        dst += 4;
+    }
+    return dst;
+}
+
 /* Assume we can wrap SDL_AtomicInt values and cast to Uint32 */
 SDL_COMPILE_TIME_ASSERT(sizeof_object_id, sizeof(int) == sizeof(Uint32));
 
diff --git a/src/SDL_utils_c.h b/src/SDL_utils_c.h
index 35d8543e8a04f..fb83c30be9ef8 100644
--- a/src/SDL_utils_c.h
+++ b/src/SDL_utils_c.h
@@ -32,6 +32,8 @@ extern void SDL_CalculateFraction(float x, int *numerator, int *denominator);
 
 extern SDL_bool SDL_endswith(const char *string, const char *suffix);
 
+extern char *SDL_UCS4ToUTF8(Uint32 ch, char *dst);
+
 typedef enum
 {
     SDL_OBJECT_TYPE_UNKNOWN,
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 56a0041d4bdab..070835992ebca 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -210,6 +210,7 @@ SDL3_0.0.0 {
     SDL_GetDefaultAssertionHandler;
     SDL_GetDefaultCursor;
     SDL_GetDefaultKeyFromScancode;
+    SDL_GetDefaultScancodeFromKey;
     SDL_GetDesktopDisplayMode;
     SDL_GetDisplayBounds;
     SDL_GetDisplayContentScale;
@@ -732,6 +733,7 @@ SDL3_0.0.0 {
     SDL_SetRenderTarget;
     SDL_SetRenderVSync;
     SDL_SetRenderViewport;
+    SDL_SetScancodeName;
     SDL_SetStringProperty;
     SDL_SetSurfaceAlphaMod;
     SDL_SetSurfaceBlendMode;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 3c8b756f64c81..9b3d0aed2e383 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -235,6 +235,7 @@
 #define SDL_GetDefaultAssertionHandler SDL_GetDefaultAssertionHandler_REAL
 #define SDL_GetDefaultCursor SDL_GetDefaultCursor_REAL
 #define SDL_GetDefaultKeyFromScancode SDL_GetDefaultKeyFromScancode_REAL
+#define SDL_GetDefaultScancodeFromKey SDL_GetDefaultScancodeFromKey_REAL
 #define SDL_GetDesktopDisplayMode SDL_GetDesktopDisplayMode_REAL
 #define SDL_GetDisplayBounds SDL_GetDisplayBounds_REAL
 #define SDL_GetDisplayContentScale SDL_GetDisplayContentScale_REAL
@@ -757,6 +758,7 @@
 #define SDL_SetRenderTarget SDL_SetRenderTarget_REAL
 #define SDL_SetRenderVSync SDL_SetRenderVSync_REAL
 #define SDL_SetRenderViewport SDL_SetRenderViewport_REAL
+#define SDL_SetScancodeName SDL_SetScancodeName_REAL
 #define SDL_SetStringProperty SDL_SetStringProperty_REAL
 #define SDL_SetSurfaceAlphaMod SDL_SetSurfaceAlphaMod_REAL
 #define SDL_SetSurfaceBlendMode SDL_SetSurfaceBlendMode_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index dd042001c461e..09cc85694a0ce 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -254,7 +254,8 @@ SDL_DYNAPI_PROC(int,SDL_GetDayOfYear,(int a, int b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_GetDaysInMonth,(int a, int b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_AssertionHandler,SDL_GetDefaultAssertionHandler,(void),(),return)
 SDL_DYNAPI_PROC(SDL_Cursor*,SDL_GetDefaultCursor,(void),(),return)
-SDL_DYNAPI_PROC(SDL_Keycode,SDL_GetDefaultKeyFromScancode,(SDL_Scancode a),(a),return)
+SDL_DYNAPI_PROC(SDL_Keycode,SDL_GetDefaultKeyFromScancode,(SDL_Scancode a, SDL_Keymod b),(a,b),return)
+SDL_DYNAPI_PROC(SDL_Scancode,SDL_GetDefaultScancodeFromKey,(SDL_Keycode a, SDL_Keymod *b),(a,b),return)
 SDL_DYNAPI_PROC(const SDL_DisplayMode*,SDL_GetDesktopDisplayMode,(SDL_DisplayID a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_GetDisplayBounds,(SDL_DisplayID a, SDL_Rect *b),(a,b),return)
 SDL_DYNAPI_PROC(float,SDL_GetDisplayContentScale,(SDL_DisplayID a),(a),return)
@@ -364,7 +365,7 @@ SDL_DYNAPI_PROC(SDL_JoystickType,SDL_GetJoystickType,(SDL_Joystick *a),(a),retur
 SDL_DYNAPI_PROC(Uint16,SDL_GetJoystickVendor,(SDL_Joystick *a),(a),return)
 SDL_DYNAPI_PROC(SDL_JoystickID*,SDL_GetJoysticks,(int *a),(a),return)
 SDL_DYNAPI_PROC(SDL_Keycode,SDL_GetKeyFromName,(const char *a),(a),return)
-SDL_DYNAPI_PROC(SDL_Keycode,SDL_GetKeyFromScancode,(SDL_Scancode a),(a),return)
+SDL_DYNAPI_PROC(SDL_Keycode,SDL_GetKeyFromScancode,(SDL_Scancode a, SDL_Keymod b),(a,b),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetKeyName,(SDL_Keycode a),(a),return)
 SDL_DYNAPI_PROC(SDL_Window*,SDL_GetKeyboardFocus,(void),(),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetKeyboardInstanceName,(SDL_KeyboardID a),(a),return)
@@ -451,7 +452,7 @@ SDL_DYNAPI_PROC(const char *,SDL_GetRendererName,(SDL_Renderer *a),(a),return)
 SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetRendererProperties,(SDL_Renderer *a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetRevision,(void),(),return)
 SDL_DYNAPI_PROC(size_t,SDL_GetSIMDAlignment,(void),(),return)
-SDL_DYNAPI_PROC(SDL_Scancode,SDL_GetScancodeFromKey,(SDL_Keycode a),(a),return)
+SDL_DYNAPI_PROC(SDL_Scancode,SDL_GetScancodeFromKey,(SDL_Keycode a, SDL_Keymod *b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_Scancode,SDL_GetScancodeFromName,(const char *a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetScancodeName,(SDL_Scancode a),(a),return)
 SDL_DYNAPI_PROC(Uint32,SDL_GetSemaphoreValue,(SDL_Semaphore *a),(a),return)
@@ -767,6 +768,7 @@ SDL_DYNAPI_PROC(int,SDL_SetRenderScale,(SDL_Renderer *a, float b, float c),(a,b,
 SDL_DYNAPI_PROC(int,SDL_SetRenderTarget,(SDL_Renderer *a, SDL_Texture *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetRenderVSync,(SDL_Renderer *a, int b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetRenderViewport,(SDL_Renderer *a, const SDL_Rect *b),(a,b),return)
+SDL_DYNAPI_PROC(int,SDL_SetScancodeName,(SDL_Scancode a, const char *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetStringProperty,(SDL_PropertiesID a, const char *b, const char *c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SetSurfaceAlphaMod,(SDL_Surface *a, Uint8 b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetSurfaceBlendMode,(SDL_Surface *a, SDL_BlendMode b),(a,b),return)
diff --git a/src/events/SDL_keyboard.c b/src/events/SDL_keyboard.c
index 3492adc8c7b14..2bfc2cda7e10e 100644
--- a/src/events/SDL_keyboard.c
+++ b/src/events/SDL_keyboard.c
@@ -23,8 +23,8 @@
 /* General keyboard handling code for SDL */
 
 #include "SDL_events_c.h"
+#include "SDL_keymap_c.h"
 #include "../video/SDL_sysvideo.h"
-#include "scancodes_ascii.h"
 
 /* #define DEBUG_KEYBOARD */
 
@@ -53,7 +53,7 @@ typedef struct SDL_Keyboard
     SDL_Keymod modstate;
     Uint8 keysource[SDL_NUM_SCANCODES];
     Uint8 keystate[SDL_NUM_SCANCODES];
-    SDL_Keycode keymap[SDL_NUM_SCANCODES];
+    SDL_Keymap *keymap;
     SDL_bool autorelease_pending;
     Uint64 hardware_timestamp;
 } SDL_Keyboard;
@@ -62,625 +62,9 @@ static SDL_Keyboard SDL_keyboard;
 static int SDL_keyboard_count;
 static SDL_KeyboardInstance *SDL_keyboards;
 
-static const SDL_Keycode SDL_default_keymap[SDL_NUM_SCANCODES] = {
-    /* 0 */ SDLK_UNKNOWN,
-    /* 1 */ SDLK_UNKNOWN,
-    /* 2 */ SDLK_UNKNOWN,
-    /* 3 */ SDLK_UNKNOWN,
-    /* 4 */ 'a',
-    /* 5 */ 'b',
-    /* 6 */ 'c',
-    /* 7 */ 'd',
-    /* 8 */ 'e',
-    /* 9 */ 'f',
-    /* 10 */ 'g',
-    /* 11 */ 'h',
-    /* 12 */ 'i',
-    /* 13 */ 'j',
-    /* 14 */ 'k',
-    /* 15 */ 'l',
-    /* 16 */ 'm',
-    /* 17 */ 'n',
-    /* 18 */ 'o',
-    /* 19 */ 'p',
-    /* 20 */ 'q',
-    /* 21 */ 'r',
-    /* 22 */ 's',
-    /* 23 */ 't',
-    /* 24 */ 'u',
-    /* 25 */ 'v',
-    /* 26 */ 'w',
-    /* 27 */ 'x',
-    /* 28 */ 'y',
-    /* 29 */ 'z',
-    /* 30 */ '1',
-    /* 31 */ '2',
-    /* 32 */ '3',
-    /* 33 */ '4',
-    /* 34 */ '5',
-    /* 35 */ '6',
-    /* 36 */ '7',
-    /* 37 */ '8',
-    /* 38 */ '9',
-    /* 39 */ '0',
-    /* 40 */ SDLK_RETURN,
-    /* 41 */ SDLK_ESCAPE,
-    /* 42 */ SDLK_BACKSPACE,
-    /* 43 */ SDLK_TAB,
-    /* 44 */ SDLK_SPACE,
-    /* 45 */ '-',
-    /* 46 */ '=',
-    /* 47 */ '[',
-    /* 48 */ ']',
-    /* 49 */ '\\',
-    /* 50 */ '#',
-    /* 51 */ ';',
-    /* 52 */ '\'',
-    /* 53 */ '`',
-    /* 54 */ ',',
-    /* 55 */ '.',
-    /* 56 */ '/',
-    /* 57 */ SDLK_CAPSLOCK,
-    /* 58 */ SDLK_F1,
-    /* 59 */ SDLK_F2,
-    /* 60 */ SDLK_F3,
-    /* 61 */ SDLK_F4,
-    /* 62 */ SDLK_F5,
-    /* 63 */ SDLK_F6,
-    /* 64 */ SDLK_F7,
-    /* 65 */ SDLK_F8,
-    /* 66 */ SDLK_F9,
-    /* 67 */ SDLK_F10,
-    /* 68 */ SDLK_F11,
-    /* 69 */ 

(Patch may be truncated, please check the link at the top of this post.)