Selaa lähdekoodia

wayland: Refactor keyboard layout handling

Build all the available keyboard layouts at once; this adds a negligible bit of overhead when initially handling the keymap (which was already significantly lowered by previous commits), but reduces the later cost of changing layouts to just swapping a pointer.

Additionally, handling of unknown keysyms, particularly when dealing with virtual keyboards, is improved, as keys generating valid Unicode values with no corresponding scancode will be dynamically added to the keymap with reserved scancodes, allowing for proper round-trip lookup behavior.
Frank Praznik 2 viikkoa sitten
vanhempi
commit
ce9c6e40fd

+ 3 - 8
src/events/SDL_keyboard.c

@@ -61,7 +61,6 @@ typedef struct SDL_Keyboard
     Uint32 keycode_options;
     bool autorelease_pending;
     Uint64 hardware_timestamp;
-    int next_reserved_scancode;
 } SDL_Keyboard;
 
 static SDL_Keyboard SDL_keyboard;
@@ -295,16 +294,12 @@ void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event)
 static SDL_Scancode GetNextReservedScancode(void)
 {
     SDL_Keyboard *keyboard = &SDL_keyboard;
-    SDL_Scancode scancode;
 
-    if (keyboard->next_reserved_scancode && keyboard->next_reserved_scancode < SDL_SCANCODE_RESERVED + 100) {
-        scancode = (SDL_Scancode)keyboard->next_reserved_scancode;
-    } else {
-        scancode = SDL_SCANCODE_RESERVED;
+    if (!keyboard->keymap) {
+        keyboard->keymap = SDL_CreateKeymap(true);
     }
-    keyboard->next_reserved_scancode = (int)scancode + 1;
 
-    return scancode;
+    return SDL_GetKeymapNextReservedScancode(keyboard->keymap);
 }
 
 static void SetKeymapEntry(SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keycode keycode)

+ 18 - 0
src/events/SDL_keymap.c

@@ -183,6 +183,24 @@ SDL_Scancode SDL_GetKeymapScancode(SDL_Keymap *keymap, SDL_Keycode keycode, SDL_
     return scancode;
 }
 
+SDL_Scancode SDL_GetKeymapNextReservedScancode(SDL_Keymap *keymap)
+{
+    SDL_Scancode scancode;
+
+    if (!keymap) {
+        return SDL_SCANCODE_UNKNOWN;
+    }
+
+    if (keymap->next_reserved_scancode && keymap->next_reserved_scancode < SDL_SCANCODE_RESERVED + 100) {
+        scancode = keymap->next_reserved_scancode;
+    } else {
+        scancode = SDL_SCANCODE_RESERVED;
+    }
+    keymap->next_reserved_scancode = scancode + 1;
+
+    return scancode;
+}
+
 void SDL_DestroyKeymap(SDL_Keymap *keymap)
 {
     if (!keymap) {

+ 9 - 7
src/events/SDL_keymap_c.h

@@ -25,13 +25,14 @@
 
 typedef struct SDL_Keymap
 {
-  SDL_HashTable *scancode_to_keycode;
-  SDL_HashTable *keycode_to_scancode;
-  bool auto_release;
-  bool layout_determined;
-  bool french_numbers;
-  bool latin_letters;
-  bool thai_keyboard;
+    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.
@@ -42,6 +43,7 @@ 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);
 
 #endif // SDL_keymap_c_h_

+ 168 - 121
src/video/wayland/SDL_waylandevents.c

@@ -304,6 +304,16 @@ void Wayland_DisplayInitCursorShapeManager(SDL_VideoData *display)
     }
 }
 
+static void Wayland_SeatSetKeymap(SDL_WaylandSeat *seat)
+{
+    if (seat->keyboard.sdl_keymap &&
+        seat->keyboard.xkb.current_layout < seat->keyboard.xkb.num_layouts &&
+        seat->keyboard.sdl_keymap[seat->keyboard.xkb.current_layout] != SDL_GetCurrentKeymap(true)) {
+        SDL_SetKeymap(seat->keyboard.sdl_keymap[seat->keyboard.xkb.current_layout], true);
+        SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers);
+    }
+}
+
 // Returns true if a key repeat event was due
 static bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, Uint64 elapsed)
 {
@@ -440,10 +450,7 @@ int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
     // If key repeat is active, we'll need to cap our maximum wait time to handle repeats
     wl_list_for_each (seat, &d->seat_list, link) {
         if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
-            if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap(true)) {
-                SDL_SetKeymap(seat->keyboard.sdl_keymap, true);
-                SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers);
-            }
+            Wayland_SeatSetKeymap(seat);
 
             const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns;
             if (keyboard_repeat_handle(&seat->keyboard.repeat, elapsed)) {
@@ -479,14 +486,13 @@ int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
             // If key repeat is active, we might have woken up to generate a key event
             if (key_repeat_active) {
                 wl_list_for_each (seat, &d->seat_list, link) {
-                    if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap(true)) {
-                        SDL_SetKeymap(seat->keyboard.sdl_keymap, true);
-                        SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers);
-                    }
+                    if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
+                        Wayland_SeatSetKeymap(seat);
 
-                    const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns;
-                    if (keyboard_repeat_handle(&seat->keyboard.repeat, elapsed)) {
-                        ++ret;
+                        const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns;
+                        if (keyboard_repeat_handle(&seat->keyboard.repeat, elapsed)) {
+                            ++ret;
+                        }
                     }
                 }
             }
@@ -550,10 +556,7 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this)
 
     wl_list_for_each (seat, &d->seat_list, link) {
         if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
-            if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap(true)) {
-                SDL_SetKeymap(seat->keyboard.sdl_keymap, true);
-                SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers);
-            }
+            Wayland_SeatSetKeymap(seat);
 
             const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns;
             keyboard_repeat_handle(&seat->keyboard.repeat, elapsed);
@@ -1410,7 +1413,7 @@ static const struct wl_touch_listener touch_listener = {
     touch_handler_orientation // Version 6
 };
 
-static void Wayland_keymap_iter(struct xkb_keymap *keymap, xkb_keycode_t key, void *data)
+static void Wayland_KeymapIterator(struct xkb_keymap *keymap, xkb_keycode_t key, void *data)
 {
     SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
     const xkb_keysym_t *syms;
@@ -1420,73 +1423,72 @@ static void Wayland_keymap_iter(struct xkb_keymap *keymap, xkb_keycode_t key, vo
                                               seat->keyboard.xkb.level3_mask |
                                               seat->keyboard.xkb.level5_mask |
                                               seat->keyboard.xkb.caps_mask;
-    const SDL_Scancode scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, (key - 8));
-    if (scancode == SDL_SCANCODE_UNKNOWN) {
-        return;
+    SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
+
+    // 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;
+        }
     }
 
-    const xkb_level_index_t num_levels = WAYLAND_xkb_keymap_num_levels_for_key(seat->keyboard.xkb.keymap, key, seat->keyboard.xkb.current_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, seat->keyboard.xkb.current_layout, level, &syms) > 0) {
-            xkb_mod_mask_t xkb_mod_masks[16];
-            const size_t num_masks = WAYLAND_xkb_keymap_key_get_mods_for_level(seat->keyboard.xkb.keymap, key, seat->keyboard.xkb.current_layout, level, xkb_mod_masks, SDL_arraysize(xkb_mod_masks));
-            for (size_t mask = 0; mask < num_masks; ++mask) {
-                // Ignore this modifier set if it uses unsupported modifier types.
-                if ((xkb_mod_masks[mask] | xkb_valid_mod_mask) != xkb_valid_mod_mask) {
-                    continue;
+    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, seat->keyboard.xkb.current_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 (scancode == SDL_SCANCODE_UNKNOWN) {
+                    scancode = SDL_GetScancodeFromKeySym(syms[0], key);
+                    if (scancode == SDL_SCANCODE_UNKNOWN) {
+                        continue;
+                    }
                 }
 
-                const SDL_Keymod sdl_mod = (xkb_mod_masks[mask] & seat->keyboard.xkb.shift_mask ? SDL_KMOD_SHIFT : 0) |
-                                           (xkb_mod_masks[mask] & seat->keyboard.xkb.alt_mask ? SDL_KMOD_ALT : 0) |
-                                           (xkb_mod_masks[mask] & seat->keyboard.xkb.gui_mask ? SDL_KMOD_GUI : 0) |
-                                           (xkb_mod_masks[mask] & seat->keyboard.xkb.level3_mask ? SDL_KMOD_MODE : 0) |
-                                           (xkb_mod_masks[mask] & seat->keyboard.xkb.level5_mask ? SDL_KMOD_LEVEL5 : 0) |
-                                           (xkb_mod_masks[mask] & seat->keyboard.xkb.caps_mask ? SDL_KMOD_CAPS : 0);
-
-                SDL_Keycode keycode = SDL_GetKeyCodeFromKeySym(syms[0], key, sdl_mod);
-
-                if (!keycode) {
-                    switch (scancode) {
-                    case SDL_SCANCODE_RETURN:
-                        keycode = SDLK_RETURN;
-                        break;
-                    case SDL_SCANCODE_ESCAPE:
-                        keycode = SDLK_ESCAPE;
-                        break;
-                    case SDL_SCANCODE_BACKSPACE:
-                        keycode = SDLK_BACKSPACE;
-                        break;
-                    case SDL_SCANCODE_DELETE:
-                        keycode = SDLK_DELETE;
-                        break;
-                    default:
-                        keycode = SDL_SCANCODE_TO_KEYCODE(scancode);
-                        break;
+                xkb_mod_mask_t xkb_mod_masks[16];
+                const size_t num_masks = WAYLAND_xkb_keymap_key_get_mods_for_level(seat->keyboard.xkb.keymap, key, layout, level, xkb_mod_masks, SDL_arraysize(xkb_mod_masks));
+                for (size_t mask = 0; mask < num_masks; ++mask) {
+                    // Ignore this modifier set if it uses unsupported modifier types.
+                    if ((xkb_mod_masks[mask] | xkb_valid_mod_mask) != xkb_valid_mod_mask) {
+                        continue;
                     }
-                }
 
-                SDL_SetKeymapEntry(seat->keyboard.sdl_keymap, scancode, sdl_mod, keycode);
-            }
-        }
-    }
-}
+                    const SDL_Keymod sdl_mod = (xkb_mod_masks[mask] & seat->keyboard.xkb.shift_mask ? SDL_KMOD_SHIFT : 0) |
+                                               (xkb_mod_masks[mask] & seat->keyboard.xkb.alt_mask ? SDL_KMOD_ALT : 0) |
+                                               (xkb_mod_masks[mask] & seat->keyboard.xkb.gui_mask ? SDL_KMOD_GUI : 0) |
+                                               (xkb_mod_masks[mask] & seat->keyboard.xkb.level3_mask ? SDL_KMOD_MODE : 0) |
+                                               (xkb_mod_masks[mask] & seat->keyboard.xkb.level5_mask ? SDL_KMOD_LEVEL5 : 0) |
+                                               (xkb_mod_masks[mask] & seat->keyboard.xkb.caps_mask ? SDL_KMOD_CAPS : 0);
+
+                    SDL_Keycode keycode = SDL_GetKeyCodeFromKeySym(syms[0], key, sdl_mod);
+
+                    if (!keycode) {
+                        switch (scancode) {
+                        case SDL_SCANCODE_RETURN:
+                            keycode = SDLK_RETURN;
+                            break;
+                        case SDL_SCANCODE_ESCAPE:
+                            keycode = SDLK_ESCAPE;
+                            break;
+                        case SDL_SCANCODE_BACKSPACE:
+                            keycode = SDLK_BACKSPACE;
+                            break;
+                        case SDL_SCANCODE_DELETE:
+                            keycode = SDLK_DELETE;
+                            break;
+                        default:
+                            keycode = SDL_SCANCODE_TO_KEYCODE(scancode);
+                            break;
+                        }
+                    }
 
-static void Wayland_UpdateKeymap(SDL_WaylandSeat *seat)
-{
-    if (!seat->keyboard.is_virtual) {
-        SDL_DestroyKeymap(seat->keyboard.sdl_keymap);
-        seat->keyboard.sdl_keymap = SDL_CreateKeymap(false);
-        if (!seat->keyboard.sdl_keymap) {
-            return;
+                    SDL_SetKeymapEntry(seat->keyboard.sdl_keymap[layout], scancode, sdl_mod, keycode);
+                }
+            }
         }
-
-        WAYLAND_xkb_keymap_key_for_each(seat->keyboard.xkb.keymap, Wayland_keymap_iter, seat);
-        SDL_SetKeymap(seat->keyboard.sdl_keymap, true);
-    } else {
-        // Virtual keyboards use the default keymap.
-        SDL_SetKeymap(NULL, true);
-        SDL_DestroyKeymap(seat->keyboard.sdl_keymap);
-        seat->keyboard.sdl_keymap = NULL;
     }
 }
 
@@ -1520,9 +1522,9 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
         seat->keyboard.xkb.keymap = NULL;
     }
     seat->keyboard.xkb.keymap = WAYLAND_xkb_keymap_new_from_string(seat->display->xkb_context,
-                                                           map_str,
-                                                           XKB_KEYMAP_FORMAT_TEXT_V1,
-                                                           0);
+                                                                   map_str,
+                                                                   XKB_KEYMAP_FORMAT_TEXT_V1,
+                                                                   0);
     munmap(map_str, size);
     close(fd);
 
@@ -1531,6 +1533,14 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
         return;
     }
 
+    // Clear the old layouts.
+    for (xkb_layout_index_t i = 0; i < seat->keyboard.xkb.num_layouts; ++i) {
+        SDL_DestroyKeymap(seat->keyboard.sdl_keymap[i]);
+    }
+    SDL_free(seat->keyboard.sdl_keymap);
+    seat->keyboard.sdl_keymap = NULL;
+    seat->keyboard.xkb.num_layouts = 0;
+
 #if SDL_XKBCOMMON_CHECK_VERSION(1, 10, 0)
     seat->keyboard.xkb.shift_mask = WAYLAND_xkb_keymap_mod_get_mask(seat->keyboard.xkb.keymap, XKB_MOD_NAME_SHIFT);
     seat->keyboard.xkb.ctrl_mask = WAYLAND_xkb_keymap_mod_get_mask(seat->keyboard.xkb.keymap, XKB_MOD_NAME_CTRL);
@@ -1576,9 +1586,19 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
      */
     seat->keyboard.is_virtual = WAYLAND_xkb_keymap_layout_get_name(seat->keyboard.xkb.keymap, 0) == NULL;
 
-    // Update the keymap if changed.
-    if (seat->keyboard.xkb.current_layout != XKB_LAYOUT_INVALID) {
-        Wayland_UpdateKeymap(seat);
+    // Allocate and populate the new layout maps.
+    seat->keyboard.xkb.num_layouts = WAYLAND_xkb_keymap_num_layouts(seat->keyboard.xkb.keymap);
+    if (seat->keyboard.xkb.num_layouts) {
+        seat->keyboard.sdl_keymap = SDL_calloc(seat->keyboard.xkb.num_layouts, sizeof(SDL_Keymap *));
+        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) {
+                return;
+            }
+        }
+
+        WAYLAND_xkb_keymap_key_for_each(seat->keyboard.xkb.keymap, Wayland_KeymapIterator, seat);
+        Wayland_SeatSetKeymap(seat);
     }
 
     /*
@@ -1637,16 +1657,20 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
  * 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)
+static SDL_Scancode Wayland_GetScancodeForKey(SDL_WaylandSeat *seat, uint32_t 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);
-    } else {
-        const xkb_keysym_t *syms;
-        if (WAYLAND_xkb_keymap_key_get_syms_by_level(seat->keyboard.xkb.keymap, key + 8, seat->keyboard.xkb.current_layout, 0, &syms) > 0) {
-            scancode = SDL_GetScancodeFromKeySym(syms[0], key);
+    }
+    if (scancode == SDL_SCANCODE_UNKNOWN) {
+        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);
+            if (syms) {
+                *syms = keysym;
+            }
         }
     }
 
@@ -1884,30 +1908,30 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
     Uint64 timestamp = SDL_GetTicksNS();
     window->last_focus_event_time_ns = timestamp;
 
-    if (SDL_GetCurrentKeymap(true) != seat->keyboard.sdl_keymap) {
-        SDL_SetKeymap(seat->keyboard.sdl_keymap, true);
-    }
+    Wayland_SeatSetKeymap(seat);
 
     wl_array_for_each (key, keys) {
-        const SDL_Scancode scancode = Wayland_GetScancodeForKey(seat, *key);
-        const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false);
-
-        switch (keycode) {
-        case SDLK_LSHIFT:
-        case SDLK_RSHIFT:
-        case SDLK_LCTRL:
-        case SDLK_RCTRL:
-        case SDLK_LALT:
-        case SDLK_RALT:
-        case SDLK_LGUI:
-        case SDLK_RGUI:
-        case SDLK_MODE:
-        case SDLK_LEVEL5_SHIFT:
-            Wayland_HandleModifierKeys(seat, scancode, true);
-            SDL_SendKeyboardKeyIgnoreModifiers(timestamp, seat->keyboard.sdl_id, *key, scancode, true);
-            break;
-        default:
-            break;
+        const SDL_Scancode scancode = Wayland_GetScancodeForKey(seat, *key, NULL);
+        if (scancode != SDL_SCANCODE_UNKNOWN) {
+            const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false);
+
+            switch (keycode) {
+            case SDLK_LSHIFT:
+            case SDLK_RSHIFT:
+            case SDLK_LCTRL:
+            case SDLK_RCTRL:
+            case SDLK_LALT:
+            case SDLK_RALT:
+            case SDLK_LGUI:
+            case SDLK_RGUI:
+            case SDLK_MODE:
+            case SDLK_LEVEL5_SHIFT:
+                Wayland_HandleModifierKeys(seat, scancode, true);
+                SDL_SendKeyboardKeyIgnoreModifiers(timestamp, seat->keyboard.sdl_id, *key, scancode, true);
+                break;
+            default:
+                break;
+            }
         }
     }
 }
@@ -2044,10 +2068,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
         state = WL_KEYBOARD_KEY_STATE_PRESSED;
     }
 
-    if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap(true)) {
-        SDL_SetKeymap(seat->keyboard.sdl_keymap, true);
-        SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers);
-    }
+    Wayland_SeatSetKeymap(seat);
 
     if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
         SDL_Window *keyboard_focus = SDL_GetKeyboardFocus();
@@ -2068,9 +2089,27 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
         keyboard_input_get_text(text, seat, key, false, &handled_by_ime);
     }
 
-    const SDL_Scancode scancode = Wayland_GetScancodeForKey(seat, key);
+    const xkb_keysym_t *syms = NULL;
+    SDL_Scancode scancode = Wayland_GetScancodeForKey(seat, key, &syms);
     Wayland_HandleModifierKeys(seat, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
 
+    // If we have a key with unknown scancode, check if the keysym corresponds to a valid Unicode value, and assign it a reserved scancode.
+    if (scancode == SDL_SCANCODE_UNKNOWN && syms) {
+        const SDL_Keycode keycode = (SDL_Keycode)SDL_KeySymToUcs4(syms[0]);
+        if (keycode != SDLK_UNKNOWN) {
+            SDL_Keymod modstate = SDL_KMOD_NONE;
+
+            // Check if this keycode already exists in the keymap.
+            scancode = SDL_GetKeymapScancode(seat->keyboard.sdl_keymap[seat->keyboard.xkb.current_layout], keycode, &modstate);
+
+            // Make sure we have this keycode in our keymap
+            if (scancode == SDL_SCANCODE_UNKNOWN && keycode < SDLK_SCANCODE_MASK) {
+                scancode = SDL_GetKeymapNextReservedScancode(seat->keyboard.sdl_keymap[seat->keyboard.xkb.current_layout]);
+                SDL_SetKeymapEntry(seat->keyboard.sdl_keymap[seat->keyboard.xkb.current_layout], scancode, modstate, keycode);
+            }
+        }
+    }
+
     SDL_SendKeyboardKeyIgnoreModifiers(timestamp_ns, seat->keyboard.sdl_id, key, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
 
     if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
@@ -2116,13 +2155,15 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard,
         }
     }
 
-    if (group == seat->keyboard.xkb.current_layout) {
-        return;
-    }
+    if (group != seat->keyboard.xkb.current_layout) {
+        seat->keyboard.xkb.current_layout = group;
+        Wayland_SeatSetKeymap(seat);
 
-    // The layout changed, remap and fire an event. Virtual keyboards use the default keymap.
-    seat->keyboard.xkb.current_layout = group;
-    Wayland_UpdateKeymap(seat);
+        if (seat->keyboard.xkb.compose_state) {
+            // Reset the compose state so composite and dead keys don't carry over.
+            WAYLAND_xkb_compose_state_reset(seat->keyboard.xkb.compose_state);
+        }
+    }
 }
 
 static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
@@ -2205,10 +2246,16 @@ static void Wayland_SeatDestroyKeyboard(SDL_WaylandSeat *seat, bool send_event)
     SDL_RemoveKeyboard(seat->keyboard.sdl_id, send_event);
 
     if (seat->keyboard.sdl_keymap) {
-        if (seat->keyboard.sdl_keymap == SDL_GetCurrentKeymap(true)) {
+        if (seat->keyboard.xkb.current_layout < seat->keyboard.xkb.num_layouts &&
+            seat->keyboard.sdl_keymap[seat->keyboard.xkb.current_layout] == SDL_GetCurrentKeymap(true)) {
             SDL_SetKeymap(NULL, false);
+            SDL_SetModState(SDL_KMOD_NONE);
         }
-        SDL_DestroyKeymap(seat->keyboard.sdl_keymap);
+        for (xkb_layout_index_t i = 0; i < seat->keyboard.xkb.num_layouts; ++i) {
+            SDL_DestroyKeymap(seat->keyboard.sdl_keymap[i]);
+        }
+        SDL_free(seat->keyboard.sdl_keymap);
+        seat->keyboard.sdl_keymap = NULL;
     }
 
     if (seat->keyboard.key_inhibitor) {

+ 2 - 1
src/video/wayland/SDL_waylandevents_c.h

@@ -75,7 +75,7 @@ typedef struct SDL_WaylandSeat
         struct zwp_input_timestamps_v1 *timestamps;
         struct zwp_keyboard_shortcuts_inhibitor_v1 *key_inhibitor;
         SDL_WindowData *focus;
-        SDL_Keymap *sdl_keymap;
+        SDL_Keymap **sdl_keymap;
         char *current_locale;
 
         SDL_WaylandKeyboardRepeat repeat;
@@ -96,6 +96,7 @@ typedef struct SDL_WaylandSeat
             struct xkb_compose_state *compose_state;
 
             // Current keyboard layout (aka 'group')
+            xkb_layout_index_t num_layouts;
             xkb_layout_index_t current_layout;
 
             // Modifier bitshift values

+ 1 - 0
src/video/wayland/SDL_waylandsym.h

@@ -150,6 +150,7 @@ SDL_WAYLAND_SYM(enum xkb_compose_feed_result, xkb_compose_state_feed, (struct xk
 SDL_WAYLAND_SYM(enum xkb_compose_status, xkb_compose_state_get_status, (struct xkb_compose_state *) )
 SDL_WAYLAND_SYM(xkb_keysym_t, xkb_compose_state_get_one_sym, (struct xkb_compose_state *) )
 SDL_WAYLAND_SYM(void, xkb_keymap_key_for_each, (struct xkb_keymap *, xkb_keymap_key_iter_t, void *) )
+SDL_WAYLAND_SYM(xkb_layout_index_t, xkb_keymap_num_layouts, (struct xkb_keymap *) )
 SDL_WAYLAND_SYM(int, xkb_keymap_key_get_syms_by_level, (struct xkb_keymap *,
                                                         xkb_keycode_t,
                                                         xkb_layout_index_t,