# SDL: wayland: Refine key mapping

**URL:** <https://discourse.libsdl.org/t/sdl-wayland-refine-key-mapping/69375>\
**Category:** SDL Commits\
**Created:** [September 22, 2026, 8:08pm UTC](https://discourse.libsdl.org/t/sdl-wayland-refine-key-mapping/69375 "2026-09-22T20:08:36Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![SDLPushPostBot](https://discourse.libsdl.org/user_avatar/discourse.libsdl.org/sdlpushpostbot/32/4370_2.png) [@SDLPushPostBot](https://discourse.libsdl.org/u/SDLPushPostBot)\
**Post date:** [September 22, 2026, 8:08pm UTC](https://discourse.libsdl.org/t/sdl-wayland-refine-key-mapping/69375/1 "2026-09-22T20:08:36Z")

</div>

> <https://github.com/libsdl-org/SDL/commit/03cd2de3fe2e98f86b49d395f8de5946d0e9b4dc>
>
> Wayland can map keys beyond the legacy X mapping limitations, so use the Linux k…ey table as a scancode source, as it mirrors the XFree86\_2 table, but has additional scancode mappings for higher keys.
> 
> Looking up unknown scancodes by keysym on hardware keyboards could cause scancode collisions, causing a fixed scancode mapping to potentially be overwritten by a dynamic one. If a hardware key with an unknown scancode has a valid SDL keycode, map the key to the reserved range. This prevents existing scancodes from potentially being overwritten, and ensures that all hardware keys have a unique scancode value.

```diff
From 03cd2de3fe2e98f86b49d395f8de5946d0e9b4dc Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Fri, 18 Sep 2026 14:20:15 -0400
Subject: [PATCH] wayland: Refine key mapping

Wayland can map keys beyond the legacy X mapping limitations, so use the Linux key table as a scancode source, as it mirrors the XFree86_2 table, but has additional scancode mappings for higher keys.

Looking up unknown scancodes by keysym on hardware keyboards could cause scancode collisions, causing a fixed scancode mapping to potentially be overwritten by a dynamic one. If a hardware key with an unknown scancode has a valid SDL keycode, map the key to the reserved range. This prevents existing scancodes from potentially being overwritten, and ensures that all hardware keys have a unique scancode value.
---
 src/video/wayland/SDL_waylandevents.c | 64 +++++++++++++++++++------
 src/video/wayland/SDL_waylandevents_c.h | 1 +
 src/video/wayland/SDL_waylandsym.h | 4 ++
 3 files changed, 54 insertions(+), 15 deletions(-)

diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 976c6b79fbba7..722aca62846ee 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -100,6 +100,12 @@
 // Scoped function declarations
 static void Wayland_SeatUpdateKeyboardGrab(SDL_WaylandSeat *seat);
 
+typedef struct
+{
+ Uint32 key;
+ SDL_Scancode scancode;
+} Wayland_ReservedKey;
+
 typedef struct
 {
     SDL_TouchID id;
@@ -1613,21 +1619,17 @@ static void Wayland_KeymapIterator(struct xkb_keymap *keymap, xkb_keycode_t key,
 
     // Look up the scancode for hardware keyboards. Virtual keyboards get the scancode from the keysym.
     if (!seat->keyboard.is_virtual) {
- scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, (key - 8));
- if (scancode == SDL_SCANCODE_UNKNOWN) {
- return;
- }
+ scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_LINUX, key - 8);
     }
 
     for (xkb_layout_index_t layout = 0; layout < seat->keyboard.xkb.num_layouts; ++layout) {
         const xkb_level_index_t num_levels = WAYLAND_xkb_keymap_num_levels_for_key(seat->keyboard.xkb.keymap, key, layout);
         for (xkb_level_index_t level = 0; level < num_levels; ++level) {
             if (WAYLAND_xkb_keymap_key_get_syms_by_level(seat->keyboard.xkb.keymap, key, layout, level, &syms) > 0) {
- /* If the keyboard is virtual or the key didn't have a corresponding hardware scancode, try to
- * look it up from the keysym. If there is still no corresponding scancode, skip this mapping
- * for now, as it will be dynamically added with a reserved scancode on first use.
+ /* If the keyboard is virtual, try to look up the scancode from the keysym. If there is still no corresponding
+ * scancode, skip this mapping for now, as it will be dynamically added with a reserved scancode on first use.
                  */
- if (scancode == SDL_SCANCODE_UNKNOWN) {
+ if (scancode == SDL_SCANCODE_UNKNOWN && seat->keyboard.is_virtual) {
                     scancode = SDL_GetScancodeFromKeySym(syms[0], key);
                     if (scancode == SDL_SCANCODE_UNKNOWN) {
                         continue;
@@ -1654,6 +1656,22 @@ static void Wayland_KeymapIterator(struct xkb_keymap *keymap, xkb_keycode_t key,
 
                     SDL_Keycode keycode = SDL_GetKeyCodeFromKeySym(syms[0], key, sdl_mod);
 
+ /* For hardware keyboards, map unknown keys with valid keycodes to the reserved scancode range.
+ * Reserved codes are always assigned from layout zero to avoid potential overlap.
+ */
+ if (keycode != SDLK_UNKNOWN && scancode == SDL_SCANCODE_UNKNOWN) {
+ scancode = SDL_GetKeymapNextReservedScancode(seat->keyboard.sdl_keymap[0]);
+ if (level) {
+ // Make sure the base level always has this scancode mapped, since it is a unique key.
+ SDL_SetKeymapEntry(seat->keyboard.sdl_keymap[layout], scancode, 0, SDLK_UNKNOWN);
+ }
+ Wayland_ReservedKey *res = WAYLAND_wl_array_add(&seat->keyboard.reserved_scancodes, sizeof(Wayland_ReservedKey));
+ if (res) {
+ res->scancode = scancode;
+ res->key = key - 8;
+ }
+ }
+
                     if (!keycode) {
                         switch (scancode) {
                         case SDL_SCANCODE_RETURN:
@@ -1728,6 +1746,8 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
     }
     SDL_free(seat->keyboard.sdl_keymap);
     seat->keyboard.sdl_keymap = NULL;
+ WAYLAND_wl_array_release(&seat->keyboard.reserved_scancodes);
+ WAYLAND_wl_array_init(&seat->keyboard.reserved_scancodes);
     seat->keyboard.xkb.num_layouts = 0;
 
 #if SDL_XKBCOMMON_CHECK_VERSION(1, 10, 0)
@@ -1783,6 +1803,8 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
             return;
         }
 
+ WAYLAND_wl_array_init(&seat->keyboard.reserved_scancodes);
+
         for (xkb_layout_index_t i = 0; i < seat->keyboard.xkb.num_layouts; ++i) {
             seat->keyboard.sdl_keymap[i] = SDL_CreateKeymap(false);
             if (!seat->keyboard.sdl_keymap[i]) {
@@ -1856,18 +1878,26 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
     }
 }
 
-/*
- * Virtual keyboards can have arbitrary layouts, arbitrary scancodes/keycodes, etc...
+/* Virtual keyboards can have arbitrary layouts, arbitrary scancodes/keycodes, etc...
  * Key presses from these devices must be looked up by their keysym value.
  */
-static SDL_Scancode Wayland_GetScancodeForKey(SDL_WaylandSeat *seat, uint32_t key, const xkb_keysym_t **syms)
+static SDL_Scancode Wayland_GetScancodeForKey(SDL_WaylandSeat *seat, Uint32 key, const xkb_keysym_t **syms)
 {
     SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
 
     if (!seat->keyboard.is_virtual) {
- scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, key);
- }
- if (scancode == SDL_SCANCODE_UNKNOWN) {
+ scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_LINUX, key);
+
+ // No table entry? Check the reserved list.
+ if (scancode == SDL_SCANCODE_UNKNOWN) {
+ Wayland_ReservedKey *i;
+ wl_array_for_each(i, &seat->keyboard.reserved_scancodes) {
+ if (i->key == key) {
+ return i->scancode;
+ }
+ }
+ }
+ } else {
         const xkb_keysym_t *keysym;
         if (WAYLAND_xkb_state_key_get_syms(seat->keyboard.xkb.state, key + 8, &keysym) > 0) {
             scancode = SDL_GetScancodeFromKeySym(keysym[0], key + 8);
@@ -2020,7 +2050,7 @@ static void Wayland_ReconcileModifiers(SDL_WaylandSeat *seat, bool key_pressed)
 
 static void Wayland_HandleModifierKeys(SDL_WaylandSeat *seat, SDL_Scancode scancode, bool pressed)
 {
- const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false);
+ const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers, false);
     SDL_Keymod mod;
 
     /* SDL clients expect modifier state to be activated at the same time as the
@@ -2464,6 +2494,8 @@ static void Wayland_SeatDestroyKeyboard(SDL_WaylandSeat *seat)
         seat->keyboard.sdl_keymap = NULL;
     }
 
+ WAYLAND_wl_array_release(&seat->keyboard.reserved_scancodes);
+
     if (seat->keyboard.key_inhibitor) {
         zwp_keyboard_shortcuts_inhibitor_v1_destroy(seat->keyboard.key_inhibitor);
     }
@@ -2574,6 +2606,7 @@ static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, uint32
 
     if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && !seat->keyboard.wl_keyboard) {
         seat->keyboard.wl_keyboard = wl_seat_get_keyboard(wl_seat);
+ WAYLAND_wl_array_init(&seat->keyboard.reserved_scancodes);
         wl_keyboard_set_user_data(seat->keyboard.wl_keyboard, seat);
         wl_keyboard_add_listener(seat->keyboard.wl_keyboard, &keyboard_listener, seat);
 
@@ -3542,6 +3575,7 @@ void Wayland_DisplayCreateSeat(SDL_VideoData *display, struct wl_seat *wl_seat,
     // Keep the seats in the order in which they were added.
     WAYLAND_wl_list_insert(display->seat_list.prev, &seat->link);
 
+ WAYLAND_wl_array_init(&seat->keyboard.reserved_scancodes);
     WAYLAND_wl_list_init(&seat->touch.points);
     WAYLAND_wl_list_init(&seat->tablet.tool_list);
     seat->wl_seat = wl_seat;
diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h
index 49bae2d8e671b..703a5fb140f09 100644
--- a/src/video/wayland/SDL_waylandevents_c.h
+++ b/src/video/wayland/SDL_waylandevents_c.h
@@ -137,6 +137,7 @@ typedef struct SDL_WaylandSeat
         struct zwp_keyboard_shortcuts_inhibitor_v1 *key_inhibitor;
         SDL_WindowData *focus;
         SDL_Keymap **sdl_keymap;
+ struct wl_array reserved_scancodes;
         char *current_locale;
 
         SDL_WaylandKeyboardRepeat repeat;
diff --git a/src/video/wayland/SDL_waylandsym.h b/src/video/wayland/SDL_waylandsym.h
index be15f533dfca5..2616cd90981d7 100644
--- a/src/video/wayland/SDL_waylandsym.h
+++ b/src/video/wayland/SDL_waylandsym.h
@@ -70,6 +70,10 @@ SDL_WAYLAND_SYM(void, wl_list_remove, (struct wl_list *))
 SDL_WAYLAND_SYM(int, wl_list_length, (const struct wl_list *))
 SDL_WAYLAND_SYM(int, wl_list_empty, (const struct wl_list *))
 SDL_WAYLAND_SYM(void, wl_list_insert_list, (struct wl_list *, struct wl_list *))
+SDL_WAYLAND_SYM(void, wl_array_init, (struct wl_array *))
+SDL_WAYLAND_SYM(void, wl_array_release, (struct wl_array *))
+SDL_WAYLAND_SYM(void *, wl_array_add, (struct wl_array *, size_t))
+SDL_WAYLAND_SYM(int, wl_array_copy, (struct wl_array *, struct wl_array *))
 SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_marshal_constructor, (struct wl_proxy *, uint32_t opcode, const struct wl_interface *interface, ...))
 SDL_WAYLAND_SYM(struct wl_proxy *, wl_proxy_marshal_constructor_versioned, (struct wl_proxy *proxy, uint32_t opcode, const struct wl_interface *interface, uint32_t version, ...))
 SDL_WAYLAND_SYM(void, wl_proxy_set_tag, (struct wl_proxy *, const char * const *))

```
