Parcourir la source

wayland: Add multi-seat support

Wayland environments can expose more than one seat for multiple collections of input devices, which can include multiple, simultaneously active, desktop pointers and keyboards with independent layouts. The Wayland input backend previously presumed that only one seat could exist, which caused broken behavior if the compositor exposed more than one, which is possible on wlroots based compositors such as Sway. This introduces support for handling multiple seats, including proper handling of dynamically added and removed seats and capabilities at run time.

The SDL Wayland input system was accreted over time, and the assumption that only one seat will ever exist resulted in state and related objects not always being tied to their most appropriate owner in a multi-seat scenario, so refactoring was required to manage several bits of state per-seat, instead of per-window or globally.

As Wayland keyboards can have per-seat layouts, fast keymap switching is required when multiplexing input from multiple seats to the global SDL keyboard device. A parameter was added to the keymap creation function to specify if the keymap lifetime should be externally managed to facilitate keymap reuse, and some layout info was moved from the global keyboard state to the keymap state to avoid unnecessarily redetermining it whenever a reused keymap is bound. This reduces the overhead of switching keymaps to setting a single pointer.

Multiple seats also means that multiple windows can have keyboard and/or mouse focus at the same time on some compositors, but this is not currently a well-handled case in SDL, and will require more work to support, if necessary.
Frank Praznik il y a 1 mois
Parent
commit
113475acbd

+ 44 - 29
src/events/SDL_keyboard.c

@@ -58,9 +58,6 @@ typedef struct SDL_Keyboard
     Uint8 keysource[SDL_SCANCODE_COUNT];
     bool keystate[SDL_SCANCODE_COUNT];
     SDL_Keymap *keymap;
-    bool french_numbers;
-    bool latin_letters;
-    bool thai_keyboard;
     Uint32 keycode_options;
     bool autorelease_pending;
     Uint64 hardware_timestamp;
@@ -175,6 +172,19 @@ void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event)
     }
 }
 
+void SDL_SetKeyboardName(SDL_KeyboardID keyboardID, const char *name)
+{
+    SDL_assert(keyboardID != 0);
+
+    const int keyboard_index = SDL_GetKeyboardIndex(keyboardID);
+
+    if (keyboard_index >= 0) {
+        SDL_KeyboardInstance *instance = &SDL_keyboards[keyboard_index];
+        SDL_free(instance->name);
+        instance->name = SDL_strdup(name ? name : "");
+    }
+}
+
 bool SDL_HasKeyboard(void)
 {
     return (SDL_keyboard_count > 0);
@@ -232,14 +242,15 @@ void SDL_ResetKeyboard(void)
 SDL_Keymap *SDL_GetCurrentKeymap(void)
 {
     SDL_Keyboard *keyboard = &SDL_keyboard;
+    SDL_Keymap *keymap = SDL_keyboard.keymap;
 
-    if (keyboard->thai_keyboard) {
+    if (keymap && 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) &&
-        !keyboard->latin_letters) {
+        keymap && !keymap->latin_letters) {
         // We'll use the default QWERTY keymap
         return NULL;
     }
@@ -251,35 +262,39 @@ void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event)
 {
     SDL_Keyboard *keyboard = &SDL_keyboard;
 
-    if (keyboard->keymap) {
+    if (keyboard->keymap && keyboard->keymap->auto_release) {
         SDL_DestroyKeymap(keyboard->keymap);
     }
 
     keyboard->keymap = keymap;
 
-    // Detect French number row (all symbols)
-    keyboard->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))) {
-            keyboard->french_numbers = false;
-            break;
-        }
-    }
+    if (keymap && !keymap->layout_determined) {
+        keymap->layout_determined = true;
 
-    // Detect non-Latin keymap
-    keyboard->thai_keyboard = false;
-    keyboard->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) {
-            keyboard->latin_letters = true;
-            break;
+        // 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;
+                }
         }
 
-        if (key >= 0x0E00 && key <= 0x0E7F) {
-            keyboard->thai_keyboard = true;
-            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;
+            }
         }
     }
 
@@ -308,7 +323,7 @@ static void SetKeymapEntry(SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keyco
     SDL_Keyboard *keyboard = &SDL_keyboard;
 
     if (!keyboard->keymap) {
-        keyboard->keymap = SDL_CreateKeymap();
+        keyboard->keymap = SDL_CreateKeymap(true);
     }
 
     SDL_SetKeymapEntry(keyboard->keymap, scancode, modstate, keycode);
@@ -483,7 +498,7 @@ SDL_Keycode SDL_GetKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate, b
         modstate = SDL_KMOD_NONE;
 
         if ((keyboard->keycode_options & KEYCODE_OPTION_FRENCH_NUMBERS) &&
-            keyboard->french_numbers &&
+            keymap && keymap->french_numbers &&
             (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_0)) {
             // Add the shift state to generate a numeric keycode
             modstate |= SDL_KMOD_SHIFT;
@@ -876,7 +891,7 @@ void SDL_QuitKeyboard(void)
     SDL_free(SDL_keyboards);
     SDL_keyboards = NULL;
 
-    if (SDL_keyboard.keymap) {
+    if (SDL_keyboard.keymap && SDL_keyboard.keymap->auto_release) {
         SDL_DestroyKeymap(SDL_keyboard.keymap);
         SDL_keyboard.keymap = NULL;
     }

+ 3 - 0
src/events/SDL_keyboard_c.h

@@ -43,6 +43,9 @@ extern void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name, bool se
 // A keyboard has been removed from the system
 extern void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event);
 
+// Set or update the name of a keyboard instance.
+extern void SDL_SetKeyboardName(SDL_KeyboardID keyboardID, const char *name);
+
 // Set the mapping of scancode to key codes
 extern void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event);
 

+ 3 - 8
src/events/SDL_keymap.c

@@ -23,22 +23,17 @@
 #include "SDL_keymap_c.h"
 #include "SDL_keyboard_c.h"
 
-struct SDL_Keymap
-{
-    SDL_HashTable *scancode_to_keycode;
-    SDL_HashTable *keycode_to_scancode;
-};
-
 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(void)
+SDL_Keymap *SDL_CreateKeymap(bool auto_release)
 {
-    SDL_Keymap *keymap = (SDL_Keymap *)SDL_malloc(sizeof(*keymap));
+    SDL_Keymap *keymap = (SDL_Keymap *)SDL_calloc(1, sizeof(*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 (!keymap->scancode_to_keycode || !keymap->keycode_to_scancode) {

+ 11 - 2
src/events/SDL_keymap_c.h

@@ -23,10 +23,19 @@
 #ifndef SDL_keymap_c_h_
 #define SDL_keymap_c_h_
 
-typedef struct SDL_Keymap SDL_Keymap;
+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_Keymap;
 
 SDL_Keymap *SDL_GetCurrentKeymap(void);
-SDL_Keymap *SDL_CreateKeymap(void);
+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);

+ 13 - 0
src/events/SDL_mouse.c

@@ -409,6 +409,19 @@ void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event)
     }
 }
 
+void SDL_SetMouseName(SDL_MouseID mouseID, const char *name)
+{
+    SDL_assert(mouseID != 0);
+
+    const int mouse_index = SDL_GetMouseIndex(mouseID);
+
+    if (mouse_index >= 0) {
+        SDL_MouseInstance *instance = &SDL_mice[mouse_index];
+        SDL_free(instance->name);
+        instance->name = SDL_strdup(name ? name : "");
+    }
+}
+
 bool SDL_HasMouse(void)
 {
     return (SDL_mouse_count > 0);

+ 3 - 0
src/events/SDL_mouse_c.h

@@ -169,6 +169,9 @@ extern void SDL_AddMouse(SDL_MouseID mouseID, const char *name, bool send_event)
 // A mouse has been removed from the system
 extern void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event);
 
+// Set or update the name of a mouse instance.
+extern void SDL_SetMouseName(SDL_MouseID mouseID, const char *name);
+
 // Get the mouse state structure
 extern SDL_Mouse *SDL_GetMouse(void);
 

+ 10 - 0
src/events/SDL_touch.c

@@ -205,6 +205,16 @@ int SDL_AddTouch(SDL_TouchID touchID, SDL_TouchDeviceType type, const char *name
     return index;
 }
 
+// Set or update the name of a touch.
+void SDL_SetTouchName(SDL_TouchID id, const char *name)
+{
+    SDL_Touch *touch = SDL_GetTouch(id);
+    if (touch) {
+        SDL_free(touch->name);
+        touch->name = SDL_strdup(name ? name : "");
+    }
+}
+
 static bool SDL_AddFinger(SDL_Touch *touch, SDL_FingerID fingerid, float x, float y, float pressure)
 {
     SDL_Finger *finger;

+ 3 - 0
src/events/SDL_touch_c.h

@@ -42,6 +42,9 @@ extern bool SDL_TouchDevicesAvailable(void);
 // Add a touch, returning the index of the touch, or -1 if there was an error.
 extern int SDL_AddTouch(SDL_TouchID id, SDL_TouchDeviceType type, const char *name);
 
+// Set or update the name of a touch.
+extern void SDL_SetTouchName(SDL_TouchID id, const char *name);
+
 // Get the touch with a given id
 extern SDL_Touch *SDL_GetTouch(SDL_TouchID id);
 

+ 7 - 1
src/test/SDL_test_common.c

@@ -2511,7 +2511,13 @@ SDL_AppResult SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const
                 /* Ctrl-G toggle mouse grab */
                 SDL_Window *window = SDL_GetWindowFromEvent(event);
                 if (window) {
-                    SDL_SetWindowMouseGrab(window, !SDL_GetWindowMouseGrab(window));
+                    if (SDL_RectEmpty(SDL_GetWindowMouseRect(window))) {
+                        SDL_Rect r = { 10, 10, 200, 200};
+                        SDL_SetWindowMouseRect(window, &r);
+                    } else {
+                        SDL_SetWindowMouseRect(window, NULL);
+                    }
+                    //SDL_SetWindowMouseGrab(window, !SDL_GetWindowMouseGrab(window));
                 }
             }
             break;

+ 1 - 1
src/video/cocoa/SDL_cocoakeyboard.m

@@ -327,7 +327,7 @@ static void UpdateKeymap(SDL_CocoaVideoData *data, bool send_event)
 
     UInt32 keyboard_type = LMGetKbdType();
 
-    SDL_Keymap *keymap = SDL_CreateKeymap();
+    SDL_Keymap *keymap = SDL_CreateKeymap(true);
     for (int m = 0; m < SDL_arraysize(mods); ++m) {
         for (int i = 0; i < SDL_arraysize(darwin_scancode_table); i++) {
             OSStatus err;

+ 29 - 19
src/video/wayland/SDL_waylandclipboard.c

@@ -32,11 +32,16 @@
 bool Wayland_SetClipboardData(SDL_VideoDevice *_this)
 {
     SDL_VideoData *video_data = _this->internal;
-    SDL_WaylandDataDevice *data_device = NULL;
-    bool result = true;
+    SDL_WaylandSeat *seat = video_data->last_implicit_grab_seat;
+    bool result = false;
+
+    // If no implicit grab is available yet, just attach it to the first available seat.
+    if (!seat && !WAYLAND_wl_list_empty(&video_data->seat_list)) {
+        seat = wl_container_of(video_data->seat_list.next, seat, link);
+    }
 
-    if (video_data->input && video_data->input->data_device) {
-        data_device = video_data->input->data_device;
+    if (seat && seat->data_device) {
+        SDL_WaylandDataDevice *data_device = seat->data_device;
 
         if (_this->clipboard_callback && _this->clipboard_mime_types) {
             SDL_WaylandDataSource *source = Wayland_data_source_create(_this);
@@ -57,11 +62,11 @@ bool Wayland_SetClipboardData(SDL_VideoDevice *_this)
 void *Wayland_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *length)
 {
     SDL_VideoData *video_data = _this->internal;
-    SDL_WaylandDataDevice *data_device = NULL;
+    SDL_WaylandSeat *seat = video_data->last_incoming_data_offer_seat;
     void *buffer = NULL;
 
-    if (video_data->input && video_data->input->data_device) {
-        data_device = video_data->input->data_device;
+    if (seat && seat->data_device) {
+        SDL_WaylandDataDevice *data_device = seat->data_device;
         if (data_device->selection_source) {
             buffer = SDL_GetInternalClipboardData(_this, mime_type, length);
         } else if (Wayland_data_offer_has_mime(data_device->selection_offer, mime_type)) {
@@ -75,11 +80,11 @@ void *Wayland_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, si
 bool Wayland_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
 {
     SDL_VideoData *video_data = _this->internal;
-    SDL_WaylandDataDevice *data_device = NULL;
+    SDL_WaylandSeat *seat = video_data->last_incoming_data_offer_seat;
     bool result = false;
 
-    if (video_data->input && video_data->input->data_device) {
-        data_device = video_data->input->data_device;
+    if (seat && seat->data_device) {
+        SDL_WaylandDataDevice *data_device = seat->data_device;
         if (data_device->selection_source) {
             result = SDL_HasInternalClipboardData(_this, mime_type);
         } else {
@@ -106,11 +111,16 @@ const char **Wayland_GetTextMimeTypes(SDL_VideoDevice *_this, size_t *num_mime_t
 bool Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text)
 {
     SDL_VideoData *video_data = _this->internal;
-    SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
+    SDL_WaylandSeat *seat = video_data->last_implicit_grab_seat;
     bool result;
 
-    if (video_data->input && video_data->input->primary_selection_device) {
-        primary_selection_device = video_data->input->primary_selection_device;
+    // If no implicit grab is available yet, just attach it to the first available seat.
+    if (!seat && !WAYLAND_wl_list_empty(&video_data->seat_list)) {
+        seat = wl_container_of(video_data->seat_list.next, seat, link);
+    }
+
+    if (seat && seat->primary_selection_device) {
+        SDL_WaylandPrimarySelectionDevice *primary_selection_device = seat->primary_selection_device;
         if (text[0] != '\0') {
             SDL_WaylandPrimarySelectionSource *source = Wayland_primary_selection_source_create(_this);
             Wayland_primary_selection_source_set_callback(source, SDL_ClipboardTextCallback, SDL_strdup(text));
@@ -134,12 +144,12 @@ bool Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text)
 char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this)
 {
     SDL_VideoData *video_data = _this->internal;
-    SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
+    SDL_WaylandSeat *seat = video_data->last_incoming_primary_selection_seat;
     char *text = NULL;
     size_t length = 0;
 
-    if (video_data->input && video_data->input->primary_selection_device) {
-        primary_selection_device = video_data->input->primary_selection_device;
+    if (seat && seat->primary_selection_device) {
+        SDL_WaylandPrimarySelectionDevice *primary_selection_device = seat->primary_selection_device;
         if (primary_selection_device->selection_source) {
             text = Wayland_primary_selection_source_get_data(primary_selection_device->selection_source, TEXT_MIME, &length);
         } else {
@@ -162,11 +172,11 @@ char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this)
 bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this)
 {
     SDL_VideoData *video_data = _this->internal;
-    SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
+    SDL_WaylandSeat *seat = video_data->last_incoming_primary_selection_seat;
     bool result = false;
 
-    if (video_data->input && video_data->input->primary_selection_device) {
-        primary_selection_device = video_data->input->primary_selection_device;
+    if (seat && seat->primary_selection_device) {
+        SDL_WaylandPrimarySelectionDevice *primary_selection_device = seat->primary_selection_device;
         if (primary_selection_device->selection_source) {
             result = true;
         } else {

+ 7 - 6
src/video/wayland/SDL_waylanddatamanager.c

@@ -33,6 +33,7 @@
 #include "../SDL_clipboard_c.h"
 
 #include "SDL_waylandvideo.h"
+#include "SDL_waylandevents_c.h"
 #include "SDL_waylanddatamanager.h"
 #include "primary-selection-unstable-v1-client-protocol.h"
 
@@ -373,7 +374,7 @@ void *Wayland_data_offer_receive(SDL_WaylandDataOffer *offer,
         wl_data_offer_receive(offer->offer, mime_type, pipefd[1]);
         close(pipefd[1]);
 
-        WAYLAND_wl_display_flush(data_device->video_data->display);
+        WAYLAND_wl_display_flush(data_device->seat->display->display);
 
         while (read_pipe(pipefd[0], &buffer, length) > 0) {
         }
@@ -407,7 +408,7 @@ void *Wayland_primary_selection_offer_receive(SDL_WaylandPrimarySelectionOffer *
         zwp_primary_selection_offer_v1_receive(offer->offer, mime_type, pipefd[1]);
         close(pipefd[1]);
 
-        WAYLAND_wl_display_flush(primary_selection_device->video_data->display);
+        WAYLAND_wl_display_flush(primary_selection_device->seat->display->display);
 
         while (read_pipe(pipefd[0], &buffer, length) > 0) {
         }
@@ -586,14 +587,14 @@ bool Wayland_primary_selection_device_set_selection(SDL_WaylandPrimarySelectionD
 void Wayland_data_device_set_serial(SDL_WaylandDataDevice *data_device, uint32_t serial)
 {
     if (data_device) {
+        data_device->selection_serial = serial;
+
         // If there was no serial and there is a pending selection set it now.
         if (data_device->selection_serial == 0 && data_device->selection_source) {
             wl_data_device_set_selection(data_device->data_device,
                                          data_device->selection_source->source,
                                          data_device->selection_serial);
         }
-
-        data_device->selection_serial = serial;
     }
 }
 
@@ -601,14 +602,14 @@ void Wayland_primary_selection_device_set_serial(SDL_WaylandPrimarySelectionDevi
                                                  uint32_t serial)
 {
     if (primary_selection_device) {
+        primary_selection_device->selection_serial = serial;
+
         // If there was no serial and there is a pending selection set it now.
         if (primary_selection_device->selection_serial == 0 && primary_selection_device->selection_source) {
             zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device,
                                                           primary_selection_device->selection_source->source,
                                                           primary_selection_device->selection_serial);
         }
-
-        primary_selection_device->selection_serial = serial;
     }
 }
 

+ 3 - 2
src/video/wayland/SDL_waylanddatamanager.h

@@ -20,6 +20,7 @@
 */
 
 #include "SDL_internal.h"
+#include "SDL_waylandevents_c.h"
 
 #ifndef SDL_waylanddatamanager_h_
 #define SDL_waylanddatamanager_h_
@@ -79,7 +80,7 @@ typedef struct
 typedef struct
 {
     struct wl_data_device *data_device;
-    SDL_VideoData *video_data;
+    struct SDL_WaylandSeat *seat;
 
     // Drag and Drop
     uint32_t drag_serial;
@@ -97,7 +98,7 @@ typedef struct
 typedef struct
 {
     struct zwp_primary_selection_device_v1 *primary_selection_device;
-    SDL_VideoData *video_data;
+    struct SDL_WaylandSeat *seat;
 
     uint32_t selection_serial;
     SDL_WaylandPrimarySelectionSource *selection_source;

+ 1053 - 868
src/video/wayland/SDL_waylandevents.c

@@ -66,6 +66,7 @@
 #include <xkbcommon/xkbcommon-compose.h>
 #include <xkbcommon/xkbcommon.h>
 #include "cursor-shape-v1-client-protocol.h"
+#include "viewporter-client-protocol.h"
 
 // Weston uses a ratio of 10 units per scroll tick
 #define WAYLAND_WHEEL_AXIS_UNIT 10
@@ -81,10 +82,14 @@
 // Keyboard and mouse names to match XWayland
 #define WAYLAND_DEFAULT_KEYBOARD_NAME "Virtual core keyboard"
 #define WAYLAND_DEFAULT_POINTER_NAME "Virtual core pointer"
+#define WAYLAND_DEFAULT_TOUCH_NAME "Virtual core touch"
 
 // Focus clickthrough timeout
 #define WAYLAND_FOCUS_CLICK_TIMEOUT_NS SDL_MS_TO_NS(10)
 
+// Scoped function declarations
+static void Wayland_SeatUpdateKeyboardGrab(SDL_WaylandSeat *seat);
+
 struct SDL_WaylandTouchPoint
 {
     SDL_TouchID id;
@@ -95,9 +100,7 @@ struct SDL_WaylandTouchPoint
     struct wl_list link;
 };
 
-static struct wl_list touch_points;
-
-static void touch_add(SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface *surface)
+static void Wayland_SeatAddTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface *surface)
 {
     struct SDL_WaylandTouchPoint *tp = SDL_malloc(sizeof(struct SDL_WaylandTouchPoint));
 
@@ -107,14 +110,14 @@ static void touch_add(SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_su
     tp->fy = fy;
     tp->surface = surface;
 
-    WAYLAND_wl_list_insert(&touch_points, &tp->link);
+    WAYLAND_wl_list_insert(&seat->touch.points, &tp->link);
 }
 
-static void touch_update(SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface **surface)
+static void Wayland_SeatUpdateTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface **surface)
 {
     struct SDL_WaylandTouchPoint *tp;
 
-    wl_list_for_each (tp, &touch_points, link) {
+    wl_list_for_each (tp, &seat->touch.points, link) {
         if (tp->id == id) {
             tp->fx = fx;
             tp->fy = fy;
@@ -126,11 +129,11 @@ static void touch_update(SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl
     }
 }
 
-static void touch_del(SDL_TouchID id, wl_fixed_t *fx, wl_fixed_t *fy, struct wl_surface **surface)
+static void Wayland_SeatRemoveTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t *fx, wl_fixed_t *fy, struct wl_surface **surface)
 {
     struct SDL_WaylandTouchPoint *tp;
 
-    wl_list_for_each (tp, &touch_points, link) {
+    wl_list_for_each (tp, &seat->touch.points, link) {
         if (tp->id == id) {
             if (fx) {
                 *fx = tp->fx;
@@ -149,19 +152,33 @@ static void touch_del(SDL_TouchID id, wl_fixed_t *fx, wl_fixed_t *fy, struct wl_
     }
 }
 
-static bool Wayland_SurfaceHasActiveTouches(struct wl_surface *surface)
+static bool Wayland_SurfaceHasActiveTouches(SDL_VideoData *display, struct wl_surface *surface)
 {
     struct SDL_WaylandTouchPoint *tp;
+    SDL_WaylandSeat *seat;
 
-    wl_list_for_each (tp, &touch_points, link) {
-        if (tp->surface == surface) {
-            return true;
+    // Check all seats for active touches on the surface.
+    wl_list_for_each (seat, &display->seat_list, link) {
+        wl_list_for_each (tp, &seat->touch.points, link) {
+            if (tp->surface == surface) {
+                return true;
+            }
         }
     }
 
     return false;
 }
 
+static void Wayland_GetScaledMouseRect(SDL_Window *window, SDL_Rect *scaled_rect)
+{
+    SDL_WindowData *window_data = window->internal;
+
+    scaled_rect->x = (int)SDL_floor(window->mouse_rect.x / window_data->pointer_scale.x);
+    scaled_rect->y = (int)SDL_floor(window->mouse_rect.y / window_data->pointer_scale.y);
+    scaled_rect->w = (int)SDL_ceil(window->mouse_rect.w / window_data->pointer_scale.x);
+    scaled_rect->h = (int)SDL_ceil(window->mouse_rect.h / window_data->pointer_scale.y);
+}
+
 static Uint64 Wayland_GetEventTimestamp(Uint64 nsTimestamp)
 {
     static Uint64 last;
@@ -187,85 +204,99 @@ static Uint64 Wayland_GetEventTimestamp(Uint64 nsTimestamp)
     return nsTimestamp;
 }
 
-static void Wayland_input_timestamp_listener(void *data, struct zwp_input_timestamps_v1 *zwp_input_timestamps_v1,
+static void input_timestamp_listener(void *data, struct zwp_input_timestamps_v1 *zwp_input_timestamps_v1,
                                              uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec)
 {
     *((Uint64 *)data) = ((((Uint64)tv_sec_hi << 32) | (Uint64)tv_sec_lo) * SDL_NS_PER_SECOND) + tv_nsec;
 }
 
 static const struct zwp_input_timestamps_v1_listener timestamp_listener = {
-    Wayland_input_timestamp_listener
+    input_timestamp_listener
 };
 
-static Uint64 Wayland_GetKeyboardTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms)
+static Uint64 Wayland_GetKeyboardTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms)
 {
     if (wl_timestamp_ms) {
-        return Wayland_GetEventTimestamp(input->keyboard_timestamp_ns ? input->keyboard_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms));
+        return Wayland_GetEventTimestamp(seat->keyboard.highres_timestamp_ns ? seat->keyboard.highres_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms));
     }
 
     return 0;
 }
 
-static Uint64 Wayland_GetKeyboardTimestampRaw(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms)
+static Uint64 Wayland_GetKeyboardTimestampRaw(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms)
 {
     if (wl_timestamp_ms) {
-        return input->keyboard_timestamp_ns ? input->keyboard_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms);
+        return seat->keyboard.highres_timestamp_ns ? seat->keyboard.highres_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms);
     }
 
     return 0;
 }
 
-static Uint64 Wayland_GetPointerTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms)
+static Uint64 Wayland_GetPointerTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms)
 {
     if (wl_timestamp_ms) {
-        return Wayland_GetEventTimestamp(input->pointer_timestamp_ns ? input->pointer_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms));
+        return Wayland_GetEventTimestamp(seat->pointer.highres_timestamp_ns ? seat->pointer.highres_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms));
     }
 
     return 0;
 }
 
-Uint64 Wayland_GetTouchTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms)
+Uint64 Wayland_GetTouchTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms)
 {
     if (wl_timestamp_ms) {
-        return Wayland_GetEventTimestamp(input->touch_timestamp_ns ? input->touch_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms));
+        return Wayland_GetEventTimestamp(seat->touch.highres_timestamp_ns ? seat->touch.highres_timestamp_ns : SDL_MS_TO_NS(wl_timestamp_ms));
     }
 
     return 0;
 }
 
-void Wayland_RegisterTimestampListeners(struct SDL_WaylandInput *input)
+static void Wayland_SeatRegisterInputTimestampListeners(SDL_WaylandSeat *seat)
 {
-    SDL_VideoData *viddata = input->display;
-
-    if (viddata->input_timestamps_manager) {
-        if (input->keyboard && !input->keyboard_timestamps) {
-            input->keyboard_timestamps = zwp_input_timestamps_manager_v1_get_keyboard_timestamps(viddata->input_timestamps_manager, input->keyboard);
-            zwp_input_timestamps_v1_add_listener(input->keyboard_timestamps, &timestamp_listener, &input->keyboard_timestamp_ns);
+    if (seat->display->input_timestamps_manager) {
+        if (seat->keyboard.wl_keyboard && !seat->keyboard.timestamps) {
+            seat->keyboard.timestamps = zwp_input_timestamps_manager_v1_get_keyboard_timestamps(seat->display->input_timestamps_manager, seat->keyboard.wl_keyboard);
+            zwp_input_timestamps_v1_add_listener(seat->keyboard.timestamps, &timestamp_listener, &seat->keyboard.highres_timestamp_ns);
         }
 
-        if (input->pointer && !input->pointer_timestamps) {
-            input->pointer_timestamps = zwp_input_timestamps_manager_v1_get_pointer_timestamps(viddata->input_timestamps_manager, input->pointer);
-            zwp_input_timestamps_v1_add_listener(input->pointer_timestamps, &timestamp_listener, &input->pointer_timestamp_ns);
+        if (seat->pointer.wl_pointer && !seat->pointer.timestamps) {
+            seat->pointer.timestamps = zwp_input_timestamps_manager_v1_get_pointer_timestamps(seat->display->input_timestamps_manager, seat->pointer.wl_pointer);
+            zwp_input_timestamps_v1_add_listener(seat->pointer.timestamps, &timestamp_listener, &seat->pointer.highres_timestamp_ns);
         }
 
-        if (input->touch && !input->touch_timestamps) {
-            input->touch_timestamps = zwp_input_timestamps_manager_v1_get_touch_timestamps(viddata->input_timestamps_manager, input->touch);
-            zwp_input_timestamps_v1_add_listener(input->touch_timestamps, &timestamp_listener, &input->touch_timestamp_ns);
+        if (seat->touch.wl_touch && !seat->touch.timestamps) {
+            seat->touch.timestamps = zwp_input_timestamps_manager_v1_get_touch_timestamps(seat->display->input_timestamps_manager, seat->touch.wl_touch);
+            zwp_input_timestamps_v1_add_listener(seat->touch.timestamps, &timestamp_listener, &seat->touch.highres_timestamp_ns);
         }
     }
 }
 
-void Wayland_CreateCursorShapeDevice(struct SDL_WaylandInput *input)
+void Wayland_DisplayInitInputTimestampManager(SDL_VideoData *display)
 {
-    SDL_VideoData *viddata = input->display;
+    if (display->input_timestamps_manager) {
+        SDL_WaylandSeat *seat;
+        wl_list_for_each (seat, &display->seat_list, link) {
+            Wayland_SeatRegisterInputTimestampListeners(seat);
+        }
+    }
+}
 
-    if (viddata->cursor_shape_manager) {
-        if (input->pointer && !input->cursor_shape) {
-            input->cursor_shape = wp_cursor_shape_manager_v1_get_pointer(viddata->cursor_shape_manager, input->pointer);
+static void Wayland_SeatCreateCursorShape(SDL_WaylandSeat *seat)
+{
+    if (seat->display->cursor_shape_manager) {
+        if (seat->pointer.wl_pointer && !seat->pointer.cursor_shape) {
+            seat->pointer.cursor_shape = wp_cursor_shape_manager_v1_get_pointer(seat->display->cursor_shape_manager, seat->pointer.wl_pointer);
         }
     }
 }
 
+void Wayland_DisplayInitCursorShapeManager(SDL_VideoData *display)
+{
+    SDL_WaylandSeat *seat;
+    wl_list_for_each (seat, &display->seat_list, link) {
+        Wayland_SeatCreateCursorShape(seat);
+    }
+}
+
 // Returns true if a key repeat event was due
 static bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, Uint64 elapsed)
 {
@@ -381,7 +412,7 @@ static int dispatch_queued_events(SDL_VideoData *viddata)
 int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
 {
     SDL_VideoData *d = _this->internal;
-    struct SDL_WaylandInput *input = d->input;
+    SDL_WaylandSeat *seat;
     bool key_repeat_active = false;
 
     WAYLAND_wl_display_flush(d->display);
@@ -398,19 +429,26 @@ int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
 #endif
 
     // If key repeat is active, we'll need to cap our maximum wait time to handle repeats
-    if (input && keyboard_repeat_is_set(&input->keyboard_repeat)) {
-        const Uint64 elapsed = SDL_GetTicksNS() - input->keyboard_repeat.sdl_press_time_ns;
-        if (keyboard_repeat_handle(&input->keyboard_repeat, elapsed)) {
-            // A repeat key event was already due
-            return 1;
-        } else {
-            const Uint64 next_repeat_wait_time = (input->keyboard_repeat.next_repeat_ns - elapsed) + 1;
-            if (timeoutNS >= 0) {
-                timeoutNS = SDL_min(timeoutNS, next_repeat_wait_time);
+    wl_list_for_each (seat, &d->seat_list, link) {
+        if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
+            if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap()) {
+                SDL_SetKeymap(seat->keyboard.sdl_keymap, true);
+                SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers);
+            }
+
+            const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns;
+            if (keyboard_repeat_handle(&seat->keyboard.repeat, elapsed)) {
+                // A repeat key event was already due
+                return 1;
             } else {
-                timeoutNS = next_repeat_wait_time;
+                const Uint64 next_repeat_wait_time = (seat->keyboard.repeat.next_repeat_ns - elapsed) + 1;
+                if (timeoutNS >= 0) {
+                    timeoutNS = SDL_min(timeoutNS, next_repeat_wait_time);
+                } else {
+                    timeoutNS = next_repeat_wait_time;
+                }
+                key_repeat_active = true;
             }
-            key_repeat_active = true;
         }
     }
 
@@ -424,18 +462,27 @@ int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
             WAYLAND_wl_display_read_events(d->display);
             return dispatch_queued_events(d);
         } else if (err == 0) {
+            int ret = 0;
+
             // No events available within the timeout
             WAYLAND_wl_display_cancel_read(d->display);
 
             // If key repeat is active, we might have woken up to generate a key event
             if (key_repeat_active) {
-                const Uint64 elapsed = SDL_GetTicksNS() - input->keyboard_repeat.sdl_press_time_ns;
-                if (keyboard_repeat_handle(&input->keyboard_repeat, elapsed)) {
-                    return 1;
+                wl_list_for_each (seat, &d->seat_list, link) {
+                    if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap()) {
+                        SDL_SetKeymap(seat->keyboard.sdl_keymap, true);
+                        SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers);
+                    }
+
+                    const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns;
+                    if (keyboard_repeat_handle(&seat->keyboard.repeat, elapsed)) {
+                        ++ret;
+                    }
                 }
             }
 
-            return 0;
+            return ret;
         } else {
             // Error returned from poll()/select()
             WAYLAND_wl_display_cancel_read(d->display);
@@ -457,7 +504,7 @@ int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
 void Wayland_PumpEvents(SDL_VideoDevice *_this)
 {
     SDL_VideoData *d = _this->internal;
-    struct SDL_WaylandInput *input = d->input;
+    SDL_WaylandSeat *seat;
     int err;
 
 #ifdef SDL_USE_IME
@@ -492,9 +539,16 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this)
     // Dispatch any pre-existing pending events or new events we may have read
     err = WAYLAND_wl_display_dispatch_pending(d->display);
 
-    if (input && keyboard_repeat_is_set(&input->keyboard_repeat)) {
-        const Uint64 elapsed = SDL_GetTicksNS() - input->keyboard_repeat.sdl_press_time_ns;
-        keyboard_repeat_handle(&input->keyboard_repeat, elapsed);
+    wl_list_for_each (seat, &d->seat_list, link) {
+        if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
+            if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap()) {
+                SDL_SetKeymap(seat->keyboard.sdl_keymap, true);
+                SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers);
+            }
+
+            const Uint64 elapsed = SDL_GetTicksNS() - seat->keyboard.repeat.sdl_press_time_ns;
+            keyboard_repeat_handle(&seat->keyboard.repeat, elapsed);
+        }
     }
 
     if (err < 0 && !d->display_disconnected) {
@@ -519,28 +573,37 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this)
 static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
                                   uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w)
 {
-    struct SDL_WaylandInput *input = data;
-    SDL_WindowData *window_data = input->pointer_focus;
+    SDL_WaylandSeat *seat = data;
+    SDL_WindowData *window_data = seat->pointer.focus;
     SDL_Window *window = window_data ? window_data->sdlwindow : NULL;
 
-    input->sx_w = sx_w;
-    input->sy_w = sy_w;
-    if (input->pointer_focus) {
+    if (window_data) {
         const float sx = (float)(wl_fixed_to_double(sx_w) * window_data->pointer_scale.x);
         const float sy = (float)(wl_fixed_to_double(sy_w) * window_data->pointer_scale.y);
-        SDL_SendMouseMotion(Wayland_GetPointerTimestamp(input, time), window_data->sdlwindow, input->pointer_id, false, sx, sy);
-    }
+        SDL_SendMouseMotion(Wayland_GetPointerTimestamp(seat, time), window_data->sdlwindow, seat->pointer.sdl_id, false, sx, sy);
 
-    if (window && window->hit_test) {
-        const SDL_Point point = { (int)SDL_floor(wl_fixed_to_double(sx_w) * window_data->pointer_scale.x),
-                                  (int)SDL_floor(wl_fixed_to_double(sy_w) * window_data->pointer_scale.y) };
-        SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
-        if (rc == window_data->hit_test_result) {
-            return;
+        seat->pointer.last_motion.x = (int)SDL_floorf(sx);
+        seat->pointer.last_motion.y = (int)SDL_floorf(sy);
+
+        /* Pointer confinement regions are created only when the pointer actually enters the region via
+         * a motion event received from the compositor.
+         */
+        if (!SDL_RectEmpty(&window->mouse_rect) && !seat->pointer.confined_pointer) {
+            SDL_Rect scaled_mouse_rect;
+            Wayland_GetScaledMouseRect(window, &scaled_mouse_rect);
+
+            if (SDL_PointInRect(&seat->pointer.last_motion, &scaled_mouse_rect)) {
+                Wayland_SeatUpdatePointerGrab(seat);
+            }
         }
 
-        Wayland_SetHitTestCursor(rc);
-        window_data->hit_test_result = rc;
+        if (window->hit_test) {
+            SDL_HitTestResult rc = window->hit_test(window, &seat->pointer.last_motion, window->hit_test_data);
+            if (rc != window_data->hit_test_result) {
+                window_data->hit_test_result = rc;
+                Wayland_SeatUpdateCursor(seat);
+            }
+        }
     }
 }
 
@@ -548,79 +611,93 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
                                  uint32_t serial, struct wl_surface *surface,
                                  wl_fixed_t sx_w, wl_fixed_t sy_w)
 {
-    struct SDL_WaylandInput *input = data;
-    SDL_WindowData *window;
-
     if (!surface) {
-        // enter event for a window we've just destroyed
+        // Enter event for a destroyed surface.
         return;
     }
 
-    /* This handler will be called twice in Wayland 1.4
-     * Once for the window surface which has valid user data
-     * and again for the mouse cursor surface which does not have valid user data
-     * We ignore the later
+    SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface);
+    if (!window) {
+        // Not a surface owned by SDL.
+        return;
+    }
+
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
+    seat->pointer.focus = window;
+    seat->pointer.enter_serial = serial;
+    ++window->pointer_focus_count;
+    SDL_SetMouseFocus(window->sdlwindow);
+
+    /* In the case of e.g. a pointer confine warp, we may receive an enter
+     * event with no following motion event, but with the new coordinates
+     * as part of the enter event.
+     *
+     * FIXME: This causes a movement event with an anomalous timestamp when
+     *        the cursor enters the window.
      */
-    window = Wayland_GetWindowDataForOwnedSurface(surface);
+    pointer_handle_motion(data, pointer, 0, sx_w, sy_w);
 
-    if (window) {
-        input->pointer_focus = window;
-        input->pointer_enter_serial = serial;
-        SDL_SetMouseFocus(window->sdlwindow);
-        /* In the case of e.g. a pointer confine warp, we may receive an enter
-         * event with no following motion event, but with the new coordinates
-         * as part of the enter event.
-         *
-         * FIXME: This causes a movement event with an anomalous timestamp when
-         *        the cursor enters the window.
-         */
-        pointer_handle_motion(data, pointer, 0, sx_w, sy_w);
-        /* If the cursor was changed while our window didn't have pointer
-         * focus, we might need to trigger another call to
-         * wl_pointer_set_cursor() for the new cursor to be displayed. */
-        Wayland_SetHitTestCursor(window->hit_test_result);
-    }
+    // Update the pointer grab state.
+    Wayland_SeatUpdatePointerGrab(seat);
+
+    /* If the cursor was changed while our window didn't have pointer
+     * focus, we might need to trigger another call to
+     * wl_pointer_set_cursor() for the new cursor to be displayed.
+     *
+     * This will also update the cursor if a second pointer entered a
+     * window that already has focus, as the focus change sequence
+     * won't be run.
+     */
+    Wayland_SeatUpdateCursor(seat);
 }
 
 static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
                                  uint32_t serial, struct wl_surface *surface)
 {
-    struct SDL_WaylandInput *input = data;
-
     if (!surface) {
+        // Leave event for a destroyed surface.
         return;
     }
 
-    if (input->pointer_focus) {
-        SDL_WindowData *wind = Wayland_GetWindowDataForOwnedSurface(surface);
+    SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface);
+    if (!window) {
+        // Not a surface owned by SDL.
+        return;
+    }
 
-        if (wind) {
-            // Clear the capture flag and raise all buttons
-            wind->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
+    // Clear the capture flag and raise all buttons
+    window->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
 
-            input->buttons_pressed = 0;
-            SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_LEFT, false);
-            SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_RIGHT, false);
-            SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_MIDDLE, false);
-            SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_X1, false);
-            SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, input->pointer_id, SDL_BUTTON_X2, false);
-        }
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
+    seat->pointer.focus = NULL;
+    seat->pointer.buttons_pressed = 0;
+    SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_LEFT, false);
+    SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_RIGHT, false);
+    SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_MIDDLE, false);
+    SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_X1, false);
+    SDL_SendMouseButton(Wayland_GetPointerTimestamp(seat, 0), window->sdlwindow, seat->pointer.sdl_id, SDL_BUTTON_X2, false);
 
-        /* A pointer leave event may be emitted if the compositor hides the pointer in response to receiving a touch event.
-         * Don't relinquish focus if the surface has active touches, as the compositor is just transitioning from mouse to touch mode.
-         */
-        if (!Wayland_SurfaceHasActiveTouches(surface)) {
-            SDL_SetMouseFocus(NULL);
-        }
-        input->pointer_focus = NULL;
+    /* A pointer leave event may be emitted if the compositor hides the pointer in response to receiving a touch event.
+     * Don't relinquish focus if the surface has active touches, as the compositor is just transitioning from mouse to touch mode.
+     */
+    SDL_Window *mouse_focus = SDL_GetMouseFocus();
+    const bool had_focus = mouse_focus && window->sdlwindow == mouse_focus;
+    if (!--window->pointer_focus_count && had_focus && !Wayland_SurfaceHasActiveTouches(seat->display, surface)) {
+        SDL_SetMouseFocus(NULL);
     }
+
+    Wayland_SeatUpdatePointerGrab(seat);
+    Wayland_SeatUpdateCursor(seat);
 }
 
-static bool ProcessHitTest(SDL_WindowData *window_data,
-			       struct wl_seat *seat,
-			       wl_fixed_t sx_w, wl_fixed_t sy_w,
-			       uint32_t serial)
+static bool Wayland_ProcessHitTest(SDL_WaylandSeat *seat, Uint32 serial)
 {
+    // Locked in relative mode, do nothing.
+    if (seat->pointer.locked_pointer) {
+        return false;
+    }
+
+    SDL_WindowData *window_data = seat->pointer.focus;
     SDL_Window *window = window_data->sdlwindow;
 
     if (window->hit_test) {
@@ -646,7 +723,7 @@ static bool ProcessHitTest(SDL_WindowData *window_data,
             if (window_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
                 if (window_data->shell_surface.libdecor.frame) {
                     libdecor_frame_move(window_data->shell_surface.libdecor.frame,
-                                        seat,
+                                        seat->wl_seat,
                                         serial);
                 }
             } else
@@ -654,7 +731,7 @@ static bool ProcessHitTest(SDL_WindowData *window_data,
                 if (window_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
                 if (window_data->shell_surface.xdg.toplevel.xdg_toplevel) {
                     xdg_toplevel_move(window_data->shell_surface.xdg.toplevel.xdg_toplevel,
-                                      seat,
+                                      seat->wl_seat,
                                       serial);
                 }
             }
@@ -672,7 +749,7 @@ static bool ProcessHitTest(SDL_WindowData *window_data,
             if (window_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
                 if (window_data->shell_surface.libdecor.frame) {
                     libdecor_frame_resize(window_data->shell_surface.libdecor.frame,
-                                          seat,
+                                          seat->wl_seat,
                                           serial,
                                           directions_libdecor[window_data->hit_test_result - SDL_HITTEST_RESIZE_TOPLEFT]);
                 }
@@ -681,7 +758,7 @@ static bool ProcessHitTest(SDL_WindowData *window_data,
                 if (window_data->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
                 if (window_data->shell_surface.xdg.toplevel.xdg_toplevel) {
                     xdg_toplevel_resize(window_data->shell_surface.xdg.toplevel.xdg_toplevel,
-                                        seat,
+                                        seat->wl_seat,
                                         serial,
                                         directions[window_data->hit_test_result - SDL_HITTEST_RESIZE_TOPLEFT]);
                 }
@@ -696,12 +773,12 @@ static bool ProcessHitTest(SDL_WindowData *window_data,
     return false;
 }
 
-static void pointer_handle_button_common(struct SDL_WaylandInput *input, uint32_t serial,
+static void pointer_handle_button_common(SDL_WaylandSeat *seat, uint32_t serial,
                                          uint32_t time, uint32_t button, uint32_t state_w)
 {
-    SDL_WindowData *window = input->pointer_focus;
+    SDL_WindowData *window = seat->pointer.focus;
     enum wl_pointer_button_state state = state_w;
-    Uint64 timestamp = Wayland_GetPointerTimestamp(input, time);
+    Uint64 timestamp = Wayland_GetPointerTimestamp(seat, time);
     Uint8 sdl_button;
     const bool down = (state != 0);
 
@@ -726,18 +803,16 @@ static void pointer_handle_button_common(struct SDL_WaylandInput *input, uint32_
     }
 
     if (window) {
-        SDL_VideoData *viddata = window->waylandData;
         bool ignore_click = false;
 
         if (state) {
-            Wayland_UpdateImplicitGrabSerial(input, serial);
-            input->buttons_pressed |= SDL_BUTTON_MASK(sdl_button);
+            Wayland_UpdateImplicitGrabSerial(seat, serial);
+            seat->pointer.buttons_pressed |= SDL_BUTTON_MASK(sdl_button);
         } else {
-            input->buttons_pressed &= ~(SDL_BUTTON_MASK(sdl_button));
+            seat->pointer.buttons_pressed &= ~(SDL_BUTTON_MASK(sdl_button));
         }
 
-        if (sdl_button == SDL_BUTTON_LEFT &&
-            ProcessHitTest(input->pointer_focus, input->seat, input->sx_w, input->sy_w, serial)) {
+        if (sdl_button == SDL_BUTTON_LEFT && Wayland_ProcessHitTest(seat, serial)) {
             return; // don't pass this event on to app.
         }
 
@@ -758,8 +833,8 @@ static void pointer_handle_button_common(struct SDL_WaylandInput *input, uint32_
          *
          * The mouse is not captured in relative mode.
          */
-        if (!viddata->relative_mouse_mode) {
-            if (input->buttons_pressed != 0) {
+        if (!seat->display->relative_mode_enabled || !Wayland_SeatHasRelativePointerFocus(seat)) {
+            if (seat->pointer.buttons_pressed != 0) {
                 window->sdlwindow->flags |= SDL_WINDOW_MOUSE_CAPTURE;
             } else {
                 window->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
@@ -767,7 +842,7 @@ static void pointer_handle_button_common(struct SDL_WaylandInput *input, uint32_
         }
 
         if (!ignore_click) {
-            SDL_SendMouseButton(timestamp, window->sdlwindow, input->pointer_id, sdl_button, down);
+            SDL_SendMouseButton(timestamp, window->sdlwindow, seat->pointer.sdl_id, sdl_button, down);
         }
     }
 }
@@ -775,19 +850,19 @@ static void pointer_handle_button_common(struct SDL_WaylandInput *input, uint32_
 static void pointer_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial,
                                   uint32_t time, uint32_t button, uint32_t state_w)
 {
-    struct SDL_WaylandInput *input = data;
+    SDL_WaylandSeat *seat = data;
 
-    pointer_handle_button_common(input, serial, time, button, state_w);
+    pointer_handle_button_common(seat, serial, time, button, state_w);
 }
 
-static void pointer_handle_axis_common_v1(struct SDL_WaylandInput *input,
+static void pointer_handle_axis_common_v1(SDL_WaylandSeat *seat,
                                           uint32_t time, uint32_t axis, wl_fixed_t value)
 {
-    SDL_WindowData *window = input->pointer_focus;
-    const Uint64 timestamp = Wayland_GetPointerTimestamp(input, time);
+    SDL_WindowData *window = seat->pointer.focus;
+    const Uint64 timestamp = Wayland_GetPointerTimestamp(seat, time);
     const enum wl_pointer_axis a = axis;
 
-    if (input->pointer_focus) {
+    if (seat->pointer.focus) {
         float x, y;
 
         switch (a) {
@@ -806,16 +881,16 @@ static void pointer_handle_axis_common_v1(struct SDL_WaylandInput *input,
         x /= WAYLAND_WHEEL_AXIS_UNIT;
         y /= WAYLAND_WHEEL_AXIS_UNIT;
 
-        SDL_SendMouseWheel(timestamp, window->sdlwindow, input->pointer_id, x, y, SDL_MOUSEWHEEL_NORMAL);
+        SDL_SendMouseWheel(timestamp, window->sdlwindow, seat->pointer.sdl_id, x, y, SDL_MOUSEWHEEL_NORMAL);
     }
 }
 
-static void pointer_handle_axis_common(struct SDL_WaylandInput *input, enum SDL_WaylandAxisEvent type,
+static void pointer_handle_axis_common(SDL_WaylandSeat *seat, enum SDL_WaylandAxisEvent type,
                                        uint32_t axis, wl_fixed_t value)
 {
     const enum wl_pointer_axis a = axis;
 
-    if (input->pointer_focus) {
+    if (seat->pointer.focus) {
         switch (a) {
         case WL_POINTER_AXIS_VERTICAL_SCROLL:
             switch (type) {
@@ -824,26 +899,26 @@ static void pointer_handle_axis_common(struct SDL_WaylandInput *input, enum SDL_
                  * High resolution scroll event. The spec doesn't state that axis_value120
                  * events are limited to one per frame, so the values are accumulated.
                  */
-                if (input->pointer_curr_axis_info.y_axis_type != AXIS_EVENT_VALUE120) {
-                    input->pointer_curr_axis_info.y_axis_type = AXIS_EVENT_VALUE120;
-                    input->pointer_curr_axis_info.y = 0.0f;
+                if (seat->pointer.current_axis_info.y_axis_type != AXIS_EVENT_VALUE120) {
+                    seat->pointer.current_axis_info.y_axis_type = AXIS_EVENT_VALUE120;
+                    seat->pointer.current_axis_info.y = 0.0f;
                 }
-                input->pointer_curr_axis_info.y += 0 - (float)wl_fixed_to_double(value);
+                seat->pointer.current_axis_info.y += 0 - (float)wl_fixed_to_double(value);
                 break;
             case AXIS_EVENT_DISCRETE:
                 /*
                  * This is a discrete axis event, so we process it and set the
                  * flag to ignore future continuous axis events in this frame.
                  */
-                if (input->pointer_curr_axis_info.y_axis_type != AXIS_EVENT_DISCRETE) {
-                    input->pointer_curr_axis_info.y_axis_type = AXIS_EVENT_DISCRETE;
-                    input->pointer_curr_axis_info.y = 0 - (float)wl_fixed_to_double(value);
+                if (seat->pointer.current_axis_info.y_axis_type != AXIS_EVENT_DISCRETE) {
+                    seat->pointer.current_axis_info.y_axis_type = AXIS_EVENT_DISCRETE;
+                    seat->pointer.current_axis_info.y = 0 - (float)wl_fixed_to_double(value);
                 }
                 break;
             case AXIS_EVENT_CONTINUOUS:
                 // Only process continuous events if no discrete events have been received.
-                if (input->pointer_curr_axis_info.y_axis_type == AXIS_EVENT_CONTINUOUS) {
-                    input->pointer_curr_axis_info.y = 0 - (float)wl_fixed_to_double(value);
+                if (seat->pointer.current_axis_info.y_axis_type == AXIS_EVENT_CONTINUOUS) {
+                    seat->pointer.current_axis_info.y = 0 - (float)wl_fixed_to_double(value);
                 }
                 break;
             }
@@ -855,26 +930,26 @@ static void pointer_handle_axis_common(struct SDL_WaylandInput *input, enum SDL_
                  * High resolution scroll event. The spec doesn't state that axis_value120
                  * events are limited to one per frame, so the values are accumulated.
                  */
-                if (input->pointer_curr_axis_info.x_axis_type != AXIS_EVENT_VALUE120) {
-                    input->pointer_curr_axis_info.x_axis_type = AXIS_EVENT_VALUE120;
-                    input->pointer_curr_axis_info.x = 0.0f;
+                if (seat->pointer.current_axis_info.x_axis_type != AXIS_EVENT_VALUE120) {
+                    seat->pointer.current_axis_info.x_axis_type = AXIS_EVENT_VALUE120;
+                    seat->pointer.current_axis_info.x = 0.0f;
                 }
-                input->pointer_curr_axis_info.x += (float)wl_fixed_to_double(value);
+                seat->pointer.current_axis_info.x += (float)wl_fixed_to_double(value);
                 break;
             case AXIS_EVENT_DISCRETE:
                 /*
                  * This is a discrete axis event, so we process it and set the
                  * flag to ignore future continuous axis events in this frame.
                  */
-                if (input->pointer_curr_axis_info.x_axis_type != AXIS_EVENT_DISCRETE) {
-                    input->pointer_curr_axis_info.x_axis_type = AXIS_EVENT_DISCRETE;
-                    input->pointer_curr_axis_info.x = (float)wl_fixed_to_double(value);
+                if (seat->pointer.current_axis_info.x_axis_type != AXIS_EVENT_DISCRETE) {
+                    seat->pointer.current_axis_info.x_axis_type = AXIS_EVENT_DISCRETE;
+                    seat->pointer.current_axis_info.x = (float)wl_fixed_to_double(value);
                 }
                 break;
             case AXIS_EVENT_CONTINUOUS:
                 // Only process continuous events if no discrete events have been received.
-                if (input->pointer_curr_axis_info.x_axis_type == AXIS_EVENT_CONTINUOUS) {
-                    input->pointer_curr_axis_info.x = (float)wl_fixed_to_double(value);
+                if (seat->pointer.current_axis_info.x_axis_type == AXIS_EVENT_CONTINUOUS) {
+                    seat->pointer.current_axis_info.x = (float)wl_fixed_to_double(value);
                 }
                 break;
             }
@@ -886,76 +961,76 @@ static void pointer_handle_axis_common(struct SDL_WaylandInput *input, enum SDL_
 static void pointer_handle_axis(void *data, struct wl_pointer *pointer,
                                 uint32_t time, uint32_t axis, wl_fixed_t value)
 {
-    struct SDL_WaylandInput *input = data;
+    SDL_WaylandSeat *seat = data;
 
-    if (wl_seat_get_version(input->seat) >= WL_POINTER_FRAME_SINCE_VERSION) {
-        input->pointer_curr_axis_info.timestamp_ns = Wayland_GetPointerTimestamp(input, time);
-        pointer_handle_axis_common(input, AXIS_EVENT_CONTINUOUS, axis, value);
+    if (wl_seat_get_version(seat->wl_seat) >= WL_POINTER_FRAME_SINCE_VERSION) {
+        seat->pointer.current_axis_info.timestamp_ns = Wayland_GetPointerTimestamp(seat, time);
+        pointer_handle_axis_common(seat, AXIS_EVENT_CONTINUOUS, axis, value);
     } else {
-        pointer_handle_axis_common_v1(input, time, axis, value);
+        pointer_handle_axis_common_v1(seat, time, axis, value);
     }
 }
 
 static void pointer_handle_axis_relative_direction(void *data, struct wl_pointer *pointer,
                                                    uint32_t axis, uint32_t axis_relative_direction)
 {
-    struct SDL_WaylandInput *input = data;
+    SDL_WaylandSeat *seat = data;
     if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) {
         return;
     }
     switch (axis_relative_direction) {
     case WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL:
-        input->pointer_curr_axis_info.direction = SDL_MOUSEWHEEL_NORMAL;
+        seat->pointer.current_axis_info.direction = SDL_MOUSEWHEEL_NORMAL;
         break;
     case WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED:
-        input->pointer_curr_axis_info.direction = SDL_MOUSEWHEEL_FLIPPED;
+        seat->pointer.current_axis_info.direction = SDL_MOUSEWHEEL_FLIPPED;
         break;
     }
 }
 
 static void pointer_handle_frame(void *data, struct wl_pointer *pointer)
 {
-    struct SDL_WaylandInput *input = data;
-    SDL_WindowData *window = input->pointer_focus;
+    SDL_WaylandSeat *seat = data;
+    SDL_WindowData *window = seat->pointer.focus;
     float x, y;
-    SDL_MouseWheelDirection direction = input->pointer_curr_axis_info.direction;
+    SDL_MouseWheelDirection direction = seat->pointer.current_axis_info.direction;
 
-    switch (input->pointer_curr_axis_info.x_axis_type) {
+    switch (seat->pointer.current_axis_info.x_axis_type) {
     case AXIS_EVENT_CONTINUOUS:
-        x = input->pointer_curr_axis_info.x / WAYLAND_WHEEL_AXIS_UNIT;
+        x = seat->pointer.current_axis_info.x / WAYLAND_WHEEL_AXIS_UNIT;
         break;
     case AXIS_EVENT_DISCRETE:
-        x = input->pointer_curr_axis_info.x;
+        x = seat->pointer.current_axis_info.x;
         break;
     case AXIS_EVENT_VALUE120:
-        x = input->pointer_curr_axis_info.x / 120.0f;
+        x = seat->pointer.current_axis_info.x / 120.0f;
         break;
     default:
         x = 0.0f;
         break;
     }
 
-    switch (input->pointer_curr_axis_info.y_axis_type) {
+    switch (seat->pointer.current_axis_info.y_axis_type) {
     case AXIS_EVENT_CONTINUOUS:
-        y = input->pointer_curr_axis_info.y / WAYLAND_WHEEL_AXIS_UNIT;
+        y = seat->pointer.current_axis_info.y / WAYLAND_WHEEL_AXIS_UNIT;
         break;
     case AXIS_EVENT_DISCRETE:
-        y = input->pointer_curr_axis_info.y;
+        y = seat->pointer.current_axis_info.y;
         break;
     case AXIS_EVENT_VALUE120:
-        y = input->pointer_curr_axis_info.y / 120.0f;
+        y = seat->pointer.current_axis_info.y / 120.0f;
         break;
     default:
         y = 0.0f;
         break;
     }
 
-    // clear pointer_curr_axis_info for next frame
-    SDL_memset(&input->pointer_curr_axis_info, 0, sizeof(input->pointer_curr_axis_info));
+    // clear pointer.current_axis_info for next frame
+    SDL_memset(&seat->pointer.current_axis_info, 0, sizeof(seat->pointer.current_axis_info));
 
     if (x != 0.0f || y != 0.0f) {
-        SDL_SendMouseWheel(input->pointer_curr_axis_info.timestamp_ns,
-                           window->sdlwindow, input->pointer_id, x, y, direction);
+        SDL_SendMouseWheel(seat->pointer.current_axis_info.timestamp_ns,
+                           window->sdlwindow, seat->pointer.sdl_id, x, y, direction);
     }
 }
 
@@ -974,17 +1049,17 @@ static void pointer_handle_axis_stop(void *data, struct wl_pointer *pointer,
 static void pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer,
                                          uint32_t axis, int32_t discrete)
 {
-    struct SDL_WaylandInput *input = data;
+    SDL_WaylandSeat *seat = data;
 
-    pointer_handle_axis_common(input, AXIS_EVENT_DISCRETE, axis, wl_fixed_from_int(discrete));
+    pointer_handle_axis_common(seat, AXIS_EVENT_DISCRETE, axis, wl_fixed_from_int(discrete));
 }
 
 static void pointer_handle_axis_value120(void *data, struct wl_pointer *pointer,
                                          uint32_t axis, int32_t value120)
 {
-    struct SDL_WaylandInput *input = data;
+    SDL_WaylandSeat *seat = data;
 
-    pointer_handle_axis_common(input, AXIS_EVENT_VALUE120, axis, wl_fixed_from_int(value120));
+    pointer_handle_axis_common(seat, AXIS_EVENT_VALUE120, axis, wl_fixed_from_int(value120));
 }
 
 static const struct wl_pointer_listener pointer_listener = {
@@ -1010,26 +1085,30 @@ static void relative_pointer_handle_relative_motion(void *data,
                                                     wl_fixed_t dx_unaccel_w,
                                                     wl_fixed_t dy_unaccel_w)
 {
-    struct SDL_WaylandInput *input = data;
-    SDL_VideoData *d = input->display;
-    SDL_WindowData *window = input->pointer_focus;
-    SDL_Mouse *mouse = SDL_GetMouse();
+    SDL_WaylandSeat *seat = data;
 
-    // Relative pointer event times are in microsecond granularity.
-    const Uint64 timestamp = Wayland_GetEventTimestamp(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo));
+    if (seat->display->relative_mode_enabled) {
+        SDL_WindowData *window = seat->pointer.focus;
 
-    if (input->pointer_focus && d->relative_mouse_mode) {
-        double dx;
-        double dy;
-        if (mouse->InputTransform || !mouse->enable_relative_system_scale) {
-            dx = wl_fixed_to_double(dx_unaccel_w);
-            dy = wl_fixed_to_double(dy_unaccel_w);
-        } else {
-            dx = wl_fixed_to_double(dx_w) * window->pointer_scale.x;
-            dy = wl_fixed_to_double(dy_w) * window->pointer_scale.y;
-        }
+        // Relative motion follows keyboard focus.
+        if (Wayland_SeatHasRelativePointerFocus(seat)) {
+            SDL_Mouse *mouse = SDL_GetMouse();
+
+            // Relative pointer event times are in microsecond granularity.
+            const Uint64 timestamp = Wayland_GetEventTimestamp(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo));
 
-        SDL_SendMouseMotion(timestamp, window->sdlwindow, input->pointer_id, true, (float)dx, (float)dy);
+            double dx;
+            double dy;
+            if (mouse->InputTransform || !mouse->enable_relative_system_scale) {
+                dx = wl_fixed_to_double(dx_unaccel_w);
+                dy = wl_fixed_to_double(dy_unaccel_w);
+            } else {
+                dx = wl_fixed_to_double(dx_w) * window->pointer_scale.x;
+                dy = wl_fixed_to_double(dy_w) * window->pointer_scale.y;
+            }
+
+            SDL_SendMouseMotion(timestamp, window->sdlwindow, seat->pointer.sdl_id, true, (float)dx, (float)dy);
+        }
     }
 }
 
@@ -1052,58 +1131,6 @@ static const struct zwp_locked_pointer_v1_listener locked_pointer_listener = {
     locked_pointer_unlocked,
 };
 
-bool Wayland_input_lock_pointer(struct SDL_WaylandInput *input, SDL_Window *window)
-{
-    SDL_WindowData *w = window->internal;
-    SDL_VideoData *d = input->display;
-
-    if (!d->pointer_constraints || !input->pointer) {
-        return false;
-    }
-
-    if (!w->locked_pointer) {
-        if (w->confined_pointer) {
-            // If the pointer is already confined to the surface, the lock will fail with a protocol error.
-            Wayland_input_unconfine_pointer(input, window);
-        }
-
-        w->locked_pointer = zwp_pointer_constraints_v1_lock_pointer(d->pointer_constraints,
-                                                                    w->surface,
-                                                                    input->pointer,
-                                                                    NULL,
-                                                                    ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
-        zwp_locked_pointer_v1_add_listener(w->locked_pointer,
-                                           &locked_pointer_listener,
-                                           window);
-    }
-
-    return true;
-}
-
-bool Wayland_input_unlock_pointer(struct SDL_WaylandInput *input, SDL_Window *window)
-{
-    SDL_WindowData *w = window->internal;
-
-    if (w->locked_pointer) {
-        zwp_locked_pointer_v1_destroy(w->locked_pointer);
-        w->locked_pointer = NULL;
-    }
-
-    // Restore existing pointer confinement.
-    Wayland_input_confine_pointer(input, window);
-
-    return true;
-}
-
-static void pointer_confine_destroy(SDL_Window *window)
-{
-    SDL_WindowData *w = window->internal;
-    if (w->confined_pointer) {
-        zwp_confined_pointer_v1_destroy(w->confined_pointer);
-        w->confined_pointer = NULL;
-    }
-}
-
 static void confined_pointer_confined(void *data,
                                       struct zwp_confined_pointer_v1 *confined_pointer)
 {
@@ -1123,7 +1150,7 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
                                uint32_t timestamp, struct wl_surface *surface,
                                int id, wl_fixed_t fx, wl_fixed_t fy)
 {
-    struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
     SDL_WindowData *window_data;
 
     // Check that this surface is valid.
@@ -1131,8 +1158,8 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
         return;
     }
 
-    touch_add(id, fx, fy, surface);
-    Wayland_UpdateImplicitGrabSerial(input, serial);
+    Wayland_SeatAddTouch(seat, id, fx, fy, surface);
+    Wayland_UpdateImplicitGrabSerial(seat, serial);
     window_data = Wayland_GetWindowDataForOwnedSurface(surface);
 
     if (window_data) {
@@ -1151,7 +1178,7 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
 
         SDL_SetMouseFocus(window_data->sdlwindow);
 
-        SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(uintptr_t)touch,
+        SDL_SendTouch(Wayland_GetTouchTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch,
                       (SDL_FingerID)(id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_DOWN, x, y, 1.0f);
     }
 }
@@ -1159,11 +1186,11 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
 static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial,
                              uint32_t timestamp, int id)
 {
-    struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
     wl_fixed_t fx = 0, fy = 0;
     struct wl_surface *surface = NULL;
 
-    touch_del(id, &fx, &fy, &surface);
+    Wayland_SeatRemoveTouch(seat, id, &fx, &fy, &surface);
 
     if (surface) {
         SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(surface);
@@ -1172,14 +1199,14 @@ static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial
             const float x = (float)wl_fixed_to_double(fx) / window_data->current.logical_width;
             const float y = (float)wl_fixed_to_double(fy) / window_data->current.logical_height;
 
-            SDL_SendTouch(Wayland_GetTouchTimestamp(input, timestamp), (SDL_TouchID)(uintptr_t)touch,
+            SDL_SendTouch(Wayland_GetTouchTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch,
                           (SDL_FingerID)(id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_UP, x, y, 0.0f);
 
-            /* If the seat lacks pointer focus, the seat's keyboard focus is another window or NULL, this window currently
-             * has mouse focus, and the surface has no active touch events, consider mouse focus to be lost.
+            /* If the window currently has mouse focus, the keyboard focus is another window or NULL, the window has no
+             * pointers active on it, and the surface has no active touch events, then consider mouse focus to be lost.
              */
-            if (!input->pointer_focus && input->keyboard_focus != window_data &&
-                SDL_GetMouseFocus() == window_data->sdlwindow && !Wayland_SurfaceHasActiveTouches(surface)) {
+            if (SDL_GetMouseFocus() == window_data->sdlwindow && seat->keyboard.focus != window_data &&
+                !window_data->pointer_focus_count && !Wayland_SurfaceHasActiveTouches(seat->display, surface)) {
                 SDL_SetMouseFocus(NULL);
             }
         }
@@ -1189,10 +1216,10 @@ static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial
 static void touch_handler_motion(void *data, struct wl_touch *touch, uint32_t timestamp,
                                  int id, wl_fixed_t fx, wl_fixed_t fy)
 {
-    struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
     struct wl_surface *surface = NULL;
 
-    touch_update(id, fx, fy, &surface);
+    Wayland_SeatUpdateTouch(seat, id, fx, fy, &surface);
 
     if (surface) {
         SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(surface);
@@ -1201,7 +1228,7 @@ static void touch_handler_motion(void *data, struct wl_touch *touch, uint32_t ti
             const float x = (float)wl_fixed_to_double(fx) / window_data->current.logical_width;
             const float y = (float)wl_fixed_to_double(fy) / window_data->current.logical_height;
 
-            SDL_SendTouchMotion(Wayland_GetPointerTimestamp(input, timestamp), (SDL_TouchID)(uintptr_t)touch,
+            SDL_SendTouchMotion(Wayland_GetPointerTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch,
                                 (SDL_FingerID)(id + 1), window_data->sdlwindow, x, y, 1.0f);
         }
     }
@@ -1213,10 +1240,10 @@ static void touch_handler_frame(void *data, struct wl_touch *touch)
 
 static void touch_handler_cancel(void *data, struct wl_touch *touch)
 {
-    struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
     struct SDL_WaylandTouchPoint *tp, *temp;
 
-    wl_list_for_each_safe (tp, temp, &touch_points, link) {
+    wl_list_for_each_safe (tp, temp, &seat->touch.points, link) {
         bool removed = false;
 
         if (tp->surface) {
@@ -1233,11 +1260,11 @@ static void touch_handler_cancel(void *data, struct wl_touch *touch)
                 WAYLAND_wl_list_remove(&tp->link);
                 removed = true;
 
-                /* If the seat lacks pointer focus, the seat's keyboard focus is another window or NULL, this window currently
-                 * has mouse focus, and the surface has no active touch events, consider mouse focus to be lost.
+                /* If the window currently has mouse focus, the keyboard focus is another window or NULL, the window has no
+                 * pointers active on it, and the surface has no active touch events, then consider mouse focus to be lost.
                  */
-                if (!input->pointer_focus && input->keyboard_focus != window_data &&
-                    SDL_GetMouseFocus() == window_data->sdlwindow && !Wayland_SurfaceHasActiveTouches(tp->surface)) {
+                if (SDL_GetMouseFocus() == window_data->sdlwindow && seat->keyboard.focus != window_data &&
+                    !window_data->pointer_focus_count && !Wayland_SurfaceHasActiveTouches(seat->display, tp->surface)) {
                     SDL_SetMouseFocus(NULL);
                 }
             }
@@ -1250,26 +1277,34 @@ static void touch_handler_cancel(void *data, struct wl_touch *touch)
     }
 }
 
+static void touch_handler_shape(void *data, struct wl_touch *wl_touch, int32_t id, wl_fixed_t major, wl_fixed_t minor)
+{
+}
+
+static void touch_handler_orientation(void *data, struct wl_touch *wl_touch, int32_t id, wl_fixed_t orientation)
+{
+}
+
 static const struct wl_touch_listener touch_listener = {
     touch_handler_down,
     touch_handler_up,
     touch_handler_motion,
     touch_handler_frame,
     touch_handler_cancel,
-    NULL, // shape
-    NULL, // orientation
+    touch_handler_shape,      // Version 6
+    touch_handler_orientation // Version 6
 };
 
-typedef struct Wayland_Keymap
+typedef struct Wayland_KeymapBuilderState
 {
     SDL_Keymap *keymap;
     struct xkb_state *state;
     SDL_Keymod modstate;
-} Wayland_Keymap;
+} Wayland_KeymapBuilderState;
 
 static void Wayland_keymap_iter(struct xkb_keymap *keymap, xkb_keycode_t key, void *data)
 {
-    Wayland_Keymap *sdlKeymap = (Wayland_Keymap *)data;
+    Wayland_KeymapBuilderState *sdlKeymap = (Wayland_KeymapBuilderState *)data;
     const xkb_keysym_t *syms;
     const SDL_Scancode scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, (key - 8));
     if (scancode == SDL_SCANCODE_UNKNOWN) {
@@ -1303,7 +1338,7 @@ static void Wayland_keymap_iter(struct xkb_keymap *keymap, xkb_keycode_t key, vo
     }
 }
 
-static void Wayland_UpdateKeymap(struct SDL_WaylandInput *input)
+static void Wayland_UpdateKeymap(SDL_WaylandSeat *seat)
 {
     struct Keymod_masks
     {
@@ -1311,32 +1346,32 @@ static void Wayland_UpdateKeymap(struct SDL_WaylandInput *input)
         xkb_mod_mask_t xkb_mask;
     } const keymod_masks[] = {
         { SDL_KMOD_NONE, 0 },
-        { SDL_KMOD_SHIFT, input->xkb.idx_shift },
-        { SDL_KMOD_CAPS, input->xkb.idx_caps },
-        { SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_shift | input->xkb.idx_caps },
-        { SDL_KMOD_MODE, input->xkb.idx_mod5 },
-        { SDL_KMOD_MODE | SDL_KMOD_SHIFT, input->xkb.idx_mod5 | input->xkb.idx_shift },
-        { SDL_KMOD_MODE | SDL_KMOD_CAPS, input->xkb.idx_mod5 | input->xkb.idx_caps },
-        { SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_mod5 | input->xkb.idx_shift | input->xkb.idx_caps },
-        { SDL_KMOD_LEVEL5, input->xkb.idx_mod3 },
-        { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT, input->xkb.idx_mod3 | input->xkb.idx_shift },
-        { SDL_KMOD_LEVEL5 | SDL_KMOD_CAPS, input->xkb.idx_mod3 | input->xkb.idx_caps },
-        { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_mod3 | input->xkb.idx_shift | input->xkb.idx_caps },
-        { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE, input->xkb.idx_mod3 | input->xkb.idx_mod5 },
-        { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT, input->xkb.idx_mod3 | input->xkb.idx_mod5 | input->xkb.idx_shift },
-        { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_CAPS, input->xkb.idx_mod3 | input->xkb.idx_mod5 | input->xkb.idx_caps },
-        { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_mod3 | input->xkb.idx_mod5 | input->xkb.idx_shift | input->xkb.idx_caps },
+        { SDL_KMOD_SHIFT, seat->keyboard.xkb.idx_shift },
+        { SDL_KMOD_CAPS, seat->keyboard.xkb.idx_caps },
+        { SDL_KMOD_SHIFT | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_shift | seat->keyboard.xkb.idx_caps },
+        { SDL_KMOD_MODE, seat->keyboard.xkb.idx_mod5 },
+        { SDL_KMOD_MODE | SDL_KMOD_SHIFT, seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_shift },
+        { SDL_KMOD_MODE | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_caps },
+        { SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_shift | seat->keyboard.xkb.idx_caps },
+        { SDL_KMOD_LEVEL5, seat->keyboard.xkb.idx_mod3 },
+        { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_shift },
+        { SDL_KMOD_LEVEL5 | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_caps },
+        { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_shift | seat->keyboard.xkb.idx_caps },
+        { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_mod5 },
+        { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_shift },
+        { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_caps },
+        { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, seat->keyboard.xkb.idx_mod3 | seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_shift | seat->keyboard.xkb.idx_caps },
     };
 
-    if (!input->keyboard_is_virtual) {
-        Wayland_Keymap keymap;
+    if (!seat->keyboard.is_virtual) {
+        Wayland_KeymapBuilderState keymap;
 
-        keymap.keymap = SDL_CreateKeymap();
+        keymap.keymap = SDL_CreateKeymap(false);
         if (!keymap.keymap) {
             return;
         }
 
-        keymap.state = WAYLAND_xkb_state_new(input->xkb.keymap);
+        keymap.state = WAYLAND_xkb_state_new(seat->keyboard.xkb.keymap);
         if (!keymap.state) {
             SDL_SetError("failed to create XKB state");
             SDL_DestroyKeymap(keymap.keymap);
@@ -1346,25 +1381,29 @@ static void Wayland_UpdateKeymap(struct SDL_WaylandInput *input)
         for (int i = 0; i < SDL_arraysize(keymod_masks); ++i) {
             keymap.modstate = keymod_masks[i].sdl_mask;
             WAYLAND_xkb_state_update_mask(keymap.state,
-                                          keymod_masks[i].xkb_mask & (input->xkb.idx_shift | input->xkb.idx_mod5 | input->xkb.idx_mod3), 0, keymod_masks[i].xkb_mask & input->xkb.idx_caps,
-                                          0, 0, input->xkb.current_group);
-            WAYLAND_xkb_keymap_key_for_each(input->xkb.keymap,
+                                          keymod_masks[i].xkb_mask & (seat->keyboard.xkb.idx_shift | seat->keyboard.xkb.idx_mod5 | seat->keyboard.xkb.idx_mod3), 0, keymod_masks[i].xkb_mask & seat->keyboard.xkb.idx_caps,
+                                          0, 0, seat->keyboard.xkb.current_group);
+            WAYLAND_xkb_keymap_key_for_each(seat->keyboard.xkb.keymap,
                                             Wayland_keymap_iter,
                                             &keymap);
         }
 
         WAYLAND_xkb_state_unref(keymap.state);
         SDL_SetKeymap(keymap.keymap, true);
+        SDL_DestroyKeymap(seat->keyboard.sdl_keymap);
+        seat->keyboard.sdl_keymap = keymap.keymap;
     } else {
         // Virtual keyboards use the default keymap.
         SDL_SetKeymap(NULL, true);
+        SDL_DestroyKeymap(seat->keyboard.sdl_keymap);
+        seat->keyboard.sdl_keymap = NULL;
     }
 }
 
 static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
                                    uint32_t format, int fd, uint32_t size)
 {
-    struct SDL_WaylandInput *input = data;
+    SDL_WaylandSeat *seat = data;
     char *map_str;
     const char *locale;
 
@@ -1384,49 +1423,49 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
         return;
     }
 
-    if (input->xkb.keymap != NULL) {
+    if (seat->keyboard.xkb.keymap != NULL) {
         /* if there's already a keymap loaded, throw it away rather than leaking it before
          * parsing the new one
          */
-        WAYLAND_xkb_keymap_unref(input->xkb.keymap);
-        input->xkb.keymap = NULL;
+        WAYLAND_xkb_keymap_unref(seat->keyboard.xkb.keymap);
+        seat->keyboard.xkb.keymap = NULL;
     }
-    input->xkb.keymap = WAYLAND_xkb_keymap_new_from_string(input->display->xkb_context,
+    seat->keyboard.xkb.keymap = WAYLAND_xkb_keymap_new_from_string(seat->display->xkb_context,
                                                            map_str,
                                                            XKB_KEYMAP_FORMAT_TEXT_V1,
                                                            0);
     munmap(map_str, size);
     close(fd);
 
-    if (!input->xkb.keymap) {
+    if (!seat->keyboard.xkb.keymap) {
         SDL_SetError("failed to compile keymap");
         return;
     }
 
 #define GET_MOD_INDEX(mod) \
-    WAYLAND_xkb_keymap_mod_get_index(input->xkb.keymap, XKB_MOD_NAME_##mod)
-    input->xkb.idx_shift = 1 << GET_MOD_INDEX(SHIFT);
-    input->xkb.idx_ctrl = 1 << GET_MOD_INDEX(CTRL);
-    input->xkb.idx_alt = 1 << GET_MOD_INDEX(ALT);
-    input->xkb.idx_gui = 1 << GET_MOD_INDEX(LOGO);
-    input->xkb.idx_mod3 = 1 << GET_MOD_INDEX(MOD3);
-    input->xkb.idx_mod5 = 1 << GET_MOD_INDEX(MOD5);
-    input->xkb.idx_num = 1 << GET_MOD_INDEX(NUM);
-    input->xkb.idx_caps = 1 << GET_MOD_INDEX(CAPS);
+    WAYLAND_xkb_keymap_mod_get_index(seat->keyboard.xkb.keymap, XKB_MOD_NAME_##mod)
+    seat->keyboard.xkb.idx_shift = 1 << GET_MOD_INDEX(SHIFT);
+    seat->keyboard.xkb.idx_ctrl = 1 << GET_MOD_INDEX(CTRL);
+    seat->keyboard.xkb.idx_alt = 1 << GET_MOD_INDEX(ALT);
+    seat->keyboard.xkb.idx_gui = 1 << GET_MOD_INDEX(LOGO);
+    seat->keyboard.xkb.idx_mod3 = 1 << GET_MOD_INDEX(MOD3);
+    seat->keyboard.xkb.idx_mod5 = 1 << GET_MOD_INDEX(MOD5);
+    seat->keyboard.xkb.idx_num = 1 << GET_MOD_INDEX(NUM);
+    seat->keyboard.xkb.idx_caps = 1 << GET_MOD_INDEX(CAPS);
 #undef GET_MOD_INDEX
 
-    if (input->xkb.state != NULL) {
+    if (seat->keyboard.xkb.state != NULL) {
         /* if there's already a state, throw it away rather than leaking it before
          * trying to create a new one with the new keymap.
          */
-        WAYLAND_xkb_state_unref(input->xkb.state);
-        input->xkb.state = NULL;
+        WAYLAND_xkb_state_unref(seat->keyboard.xkb.state);
+        seat->keyboard.xkb.state = NULL;
     }
-    input->xkb.state = WAYLAND_xkb_state_new(input->xkb.keymap);
-    if (!input->xkb.state) {
+    seat->keyboard.xkb.state = WAYLAND_xkb_state_new(seat->keyboard.xkb.keymap);
+    if (!seat->keyboard.xkb.state) {
         SDL_SetError("failed to create XKB state");
-        WAYLAND_xkb_keymap_unref(input->xkb.keymap);
-        input->xkb.keymap = NULL;
+        WAYLAND_xkb_keymap_unref(seat->keyboard.xkb.keymap);
+        seat->keyboard.xkb.keymap = NULL;
         return;
     }
 
@@ -1434,11 +1473,11 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
      * Assume that a nameless layout implies a virtual keyboard with an arbitrary layout.
      * TODO: Use a better method of detection?
      */
-    input->keyboard_is_virtual = WAYLAND_xkb_keymap_layout_get_name(input->xkb.keymap, 0) == NULL;
+    seat->keyboard.is_virtual = WAYLAND_xkb_keymap_layout_get_name(seat->keyboard.xkb.keymap, 0) == NULL;
 
     // Update the keymap if changed.
-    if (input->xkb.current_group != XKB_GROUP_INVALID) {
-        Wayland_UpdateKeymap(input);
+    if (seat->keyboard.xkb.current_group != XKB_GROUP_INVALID) {
+        Wayland_UpdateKeymap(seat);
     }
 
     /*
@@ -1459,24 +1498,24 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
     }
 
     // Set up XKB compose table
-    if (input->xkb.compose_table != NULL) {
-        WAYLAND_xkb_compose_table_unref(input->xkb.compose_table);
-        input->xkb.compose_table = NULL;
+    if (seat->keyboard.xkb.compose_table != NULL) {
+        WAYLAND_xkb_compose_table_unref(seat->keyboard.xkb.compose_table);
+        seat->keyboard.xkb.compose_table = NULL;
     }
-    input->xkb.compose_table = WAYLAND_xkb_compose_table_new_from_locale(input->display->xkb_context,
+    seat->keyboard.xkb.compose_table = WAYLAND_xkb_compose_table_new_from_locale(seat->display->xkb_context,
                                                                          locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
-    if (input->xkb.compose_table) {
+    if (seat->keyboard.xkb.compose_table) {
         // Set up XKB compose state
-        if (input->xkb.compose_state != NULL) {
-            WAYLAND_xkb_compose_state_unref(input->xkb.compose_state);
-            input->xkb.compose_state = NULL;
+        if (seat->keyboard.xkb.compose_state != NULL) {
+            WAYLAND_xkb_compose_state_unref(seat->keyboard.xkb.compose_state);
+            seat->keyboard.xkb.compose_state = NULL;
         }
-        input->xkb.compose_state = WAYLAND_xkb_compose_state_new(input->xkb.compose_table,
+        seat->keyboard.xkb.compose_state = WAYLAND_xkb_compose_state_new(seat->keyboard.xkb.compose_table,
                                                                  XKB_COMPOSE_STATE_NO_FLAGS);
-        if (!input->xkb.compose_state) {
+        if (!seat->keyboard.xkb.compose_state) {
             SDL_SetError("could not create XKB compose state");
-            WAYLAND_xkb_compose_table_unref(input->xkb.compose_table);
-            input->xkb.compose_table = NULL;
+            WAYLAND_xkb_compose_table_unref(seat->keyboard.xkb.compose_table);
+            seat->keyboard.xkb.compose_table = NULL;
         }
     }
 }
@@ -1485,15 +1524,15 @@ 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(struct SDL_WaylandInput *input, uint32_t key)
+static SDL_Scancode Wayland_GetScancodeForKey(SDL_WaylandSeat *seat, uint32_t key)
 {
     SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
 
-    if (!input->keyboard_is_virtual) {
+    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(input->xkb.keymap, key + 8, input->xkb.current_group, 0, &syms) > 0) {
+        if (WAYLAND_xkb_keymap_key_get_syms_by_level(seat->keyboard.xkb.keymap, key + 8, seat->keyboard.xkb.current_group, 0, &syms) > 0) {
             scancode = SDL_GetScancodeFromKeySym(syms[0], key);
         }
     }
@@ -1501,7 +1540,7 @@ static SDL_Scancode Wayland_GetScancodeForKey(struct SDL_WaylandInput *input, ui
     return scancode;
 }
 
-static void Wayland_ReconcileModifiers(struct SDL_WaylandInput *input, bool key_pressed)
+static void Wayland_ReconcileModifiers(SDL_WaylandSeat *seat, bool key_pressed)
 {
     /* Handle explicit pressed modifier state. This will correct the modifier state
      * if common modifier keys were remapped and the modifiers presumed to be set
@@ -1509,55 +1548,55 @@ static void Wayland_ReconcileModifiers(struct SDL_WaylandInput *input, bool key_
      * pressed state via means other than pressing the physical key.
      */
     if (!key_pressed) {
-        if (input->xkb.wl_pressed_modifiers & input->xkb.idx_shift) {
-            if (!(input->pressed_modifiers & SDL_KMOD_SHIFT)) {
-                input->pressed_modifiers |= SDL_KMOD_SHIFT;
+        if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.idx_shift) {
+            if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_SHIFT)) {
+                seat->keyboard.pressed_modifiers |= SDL_KMOD_SHIFT;
             }
         } else {
-            input->pressed_modifiers &= ~SDL_KMOD_SHIFT;
+            seat->keyboard.pressed_modifiers &= ~SDL_KMOD_SHIFT;
         }
 
-        if (input->xkb.wl_pressed_modifiers & input->xkb.idx_ctrl) {
-            if (!(input->pressed_modifiers & SDL_KMOD_CTRL)) {
-                input->pressed_modifiers |= SDL_KMOD_CTRL;
+        if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.idx_ctrl) {
+            if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_CTRL)) {
+                seat->keyboard.pressed_modifiers |= SDL_KMOD_CTRL;
             }
         } else {
-            input->pressed_modifiers &= ~SDL_KMOD_CTRL;
+            seat->keyboard.pressed_modifiers &= ~SDL_KMOD_CTRL;
         }
 
-        if (input->xkb.wl_pressed_modifiers & input->xkb.idx_alt) {
-            if (!(input->pressed_modifiers & SDL_KMOD_ALT)) {
-                input->pressed_modifiers |= SDL_KMOD_ALT;
+        if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.idx_alt) {
+            if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_ALT)) {
+                seat->keyboard.pressed_modifiers |= SDL_KMOD_ALT;
             }
         } else {
-            input->pressed_modifiers &= ~SDL_KMOD_ALT;
+            seat->keyboard.pressed_modifiers &= ~SDL_KMOD_ALT;
         }
 
-        if (input->xkb.wl_pressed_modifiers & input->xkb.idx_gui) {
-            if (!(input->pressed_modifiers & SDL_KMOD_GUI)) {
-                input->pressed_modifiers |= SDL_KMOD_GUI;
+        if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.idx_gui) {
+            if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_GUI)) {
+                seat->keyboard.pressed_modifiers |= SDL_KMOD_GUI;
             }
         } else {
-            input->pressed_modifiers &= ~SDL_KMOD_GUI;
+            seat->keyboard.pressed_modifiers &= ~SDL_KMOD_GUI;
         }
 
         /* Note: This is not backwards: in the default keymap, Mod5 is typically
          *       level 3 shift, and Mod3 is typically level 5 shift.
          */
-        if (input->xkb.wl_pressed_modifiers & input->xkb.idx_mod3) {
-            if (!(input->pressed_modifiers & SDL_KMOD_LEVEL5)) {
-                input->pressed_modifiers |= SDL_KMOD_LEVEL5;
+        if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.idx_mod3) {
+            if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_LEVEL5)) {
+                seat->keyboard.pressed_modifiers |= SDL_KMOD_LEVEL5;
             }
         } else {
-            input->pressed_modifiers &= ~SDL_KMOD_LEVEL5;
+            seat->keyboard.pressed_modifiers &= ~SDL_KMOD_LEVEL5;
         }
 
-        if (input->xkb.wl_pressed_modifiers & input->xkb.idx_mod5) {
-            if (!(input->pressed_modifiers & SDL_KMOD_MODE)) {
-                input->pressed_modifiers |= SDL_KMOD_MODE;
+        if (seat->keyboard.xkb.wl_pressed_modifiers & seat->keyboard.xkb.idx_mod5) {
+            if (!(seat->keyboard.pressed_modifiers & SDL_KMOD_MODE)) {
+                seat->keyboard.pressed_modifiers |= SDL_KMOD_MODE;
             }
         } else {
-            input->pressed_modifiers &= ~SDL_KMOD_MODE;
+            seat->keyboard.pressed_modifiers &= ~SDL_KMOD_MODE;
         }
     }
 
@@ -1568,80 +1607,80 @@ static void Wayland_ReconcileModifiers(struct SDL_WaylandInput *input, bool key_
      * The modifier will remain active until the latch/lock is released by
      * the system.
      */
-    if (input->xkb.wl_locked_modifiers & input->xkb.idx_shift) {
-        if (input->pressed_modifiers & SDL_KMOD_SHIFT) {
-            input->locked_modifiers &= ~SDL_KMOD_SHIFT;
-            input->locked_modifiers |= (input->pressed_modifiers & SDL_KMOD_SHIFT);
-        } else if (!(input->locked_modifiers & SDL_KMOD_SHIFT)) {
-            input->locked_modifiers |= SDL_KMOD_SHIFT;
+    if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_shift) {
+        if (seat->keyboard.pressed_modifiers & SDL_KMOD_SHIFT) {
+            seat->keyboard.locked_modifiers &= ~SDL_KMOD_SHIFT;
+            seat->keyboard.locked_modifiers |= (seat->keyboard.pressed_modifiers & SDL_KMOD_SHIFT);
+        } else if (!(seat->keyboard.locked_modifiers & SDL_KMOD_SHIFT)) {
+            seat->keyboard.locked_modifiers |= SDL_KMOD_SHIFT;
         }
     } else {
-        input->locked_modifiers &= ~SDL_KMOD_SHIFT;
+        seat->keyboard.locked_modifiers &= ~SDL_KMOD_SHIFT;
     }
 
-    if (input->xkb.wl_locked_modifiers & input->xkb.idx_ctrl) {
-        if (input->pressed_modifiers & SDL_KMOD_CTRL) {
-            input->locked_modifiers &= ~SDL_KMOD_CTRL;
-            input->locked_modifiers |= (input->pressed_modifiers & SDL_KMOD_CTRL);
-        } else if (!(input->locked_modifiers & SDL_KMOD_CTRL)) {
-            input->locked_modifiers |= SDL_KMOD_CTRL;
+    if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_ctrl) {
+        if (seat->keyboard.pressed_modifiers & SDL_KMOD_CTRL) {
+            seat->keyboard.locked_modifiers &= ~SDL_KMOD_CTRL;
+            seat->keyboard.locked_modifiers |= (seat->keyboard.pressed_modifiers & SDL_KMOD_CTRL);
+        } else if (!(seat->keyboard.locked_modifiers & SDL_KMOD_CTRL)) {
+            seat->keyboard.locked_modifiers |= SDL_KMOD_CTRL;
         }
     } else {
-        input->locked_modifiers &= ~SDL_KMOD_CTRL;
+        seat->keyboard.locked_modifiers &= ~SDL_KMOD_CTRL;
     }
 
-    if (input->xkb.wl_locked_modifiers & input->xkb.idx_alt) {
-        if (input->pressed_modifiers & SDL_KMOD_ALT) {
-            input->locked_modifiers &= ~SDL_KMOD_ALT;
-            input->locked_modifiers |= (input->pressed_modifiers & SDL_KMOD_ALT);
-        } else if (!(input->locked_modifiers & SDL_KMOD_ALT)) {
-            input->locked_modifiers |= SDL_KMOD_ALT;
+    if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_alt) {
+        if (seat->keyboard.pressed_modifiers & SDL_KMOD_ALT) {
+            seat->keyboard.locked_modifiers &= ~SDL_KMOD_ALT;
+            seat->keyboard.locked_modifiers |= (seat->keyboard.pressed_modifiers & SDL_KMOD_ALT);
+        } else if (!(seat->keyboard.locked_modifiers & SDL_KMOD_ALT)) {
+            seat->keyboard.locked_modifiers |= SDL_KMOD_ALT;
         }
     } else {
-        input->locked_modifiers &= ~SDL_KMOD_ALT;
+        seat->keyboard.locked_modifiers &= ~SDL_KMOD_ALT;
     }
 
-    if (input->xkb.wl_locked_modifiers & input->xkb.idx_gui) {
-        if (input->pressed_modifiers & SDL_KMOD_GUI) {
-            input->locked_modifiers &= ~SDL_KMOD_GUI;
-            input->locked_modifiers |= (input->pressed_modifiers & SDL_KMOD_GUI);
-        } else if (!(input->locked_modifiers & SDL_KMOD_GUI)) {
-            input->locked_modifiers |= SDL_KMOD_GUI;
+    if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_gui) {
+        if (seat->keyboard.pressed_modifiers & SDL_KMOD_GUI) {
+            seat->keyboard.locked_modifiers &= ~SDL_KMOD_GUI;
+            seat->keyboard.locked_modifiers |= (seat->keyboard.pressed_modifiers & SDL_KMOD_GUI);
+        } else if (!(seat->keyboard.locked_modifiers & SDL_KMOD_GUI)) {
+            seat->keyboard.locked_modifiers |= SDL_KMOD_GUI;
         }
     } else {
-        input->locked_modifiers &= ~SDL_KMOD_GUI;
+        seat->keyboard.locked_modifiers &= ~SDL_KMOD_GUI;
     }
 
     // As above, this is correct: Mod3 is typically level 5 shift, and Mod5 is typically level 3 shift.
-    if (input->xkb.wl_locked_modifiers & input->xkb.idx_mod3) {
-        input->locked_modifiers |= SDL_KMOD_LEVEL5;
+    if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_mod3) {
+        seat->keyboard.locked_modifiers |= SDL_KMOD_LEVEL5;
     } else {
-        input->locked_modifiers &= ~SDL_KMOD_LEVEL5;
+        seat->keyboard.locked_modifiers &= ~SDL_KMOD_LEVEL5;
     }
 
-    if (input->xkb.wl_locked_modifiers & input->xkb.idx_mod5) {
-        input->locked_modifiers |= SDL_KMOD_MODE;
+    if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_mod5) {
+        seat->keyboard.locked_modifiers |= SDL_KMOD_MODE;
     } else {
-        input->locked_modifiers &= ~SDL_KMOD_MODE;
+        seat->keyboard.locked_modifiers &= ~SDL_KMOD_MODE;
     }
 
     // Capslock and Numlock can only be locked, not pressed.
-    if (input->xkb.wl_locked_modifiers & input->xkb.idx_caps) {
-        input->locked_modifiers |= SDL_KMOD_CAPS;
+    if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_caps) {
+        seat->keyboard.locked_modifiers |= SDL_KMOD_CAPS;
     } else {
-        input->locked_modifiers &= ~SDL_KMOD_CAPS;
+        seat->keyboard.locked_modifiers &= ~SDL_KMOD_CAPS;
     }
 
-    if (input->xkb.wl_locked_modifiers & input->xkb.idx_num) {
-        input->locked_modifiers |= SDL_KMOD_NUM;
+    if (seat->keyboard.xkb.wl_locked_modifiers & seat->keyboard.xkb.idx_num) {
+        seat->keyboard.locked_modifiers |= SDL_KMOD_NUM;
     } else {
-        input->locked_modifiers &= ~SDL_KMOD_NUM;
+        seat->keyboard.locked_modifiers &= ~SDL_KMOD_NUM;
     }
 
-    SDL_SetModState(input->pressed_modifiers | input->locked_modifiers);
+    SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers);
 }
 
-static void Wayland_HandleModifierKeys(struct SDL_WaylandInput *input, SDL_Scancode scancode, bool pressed)
+static void Wayland_HandleModifierKeys(SDL_WaylandSeat *seat, SDL_Scancode scancode, bool pressed)
 {
     const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false);
     SDL_Keymod mod;
@@ -1688,41 +1727,47 @@ static void Wayland_HandleModifierKeys(struct SDL_WaylandInput *input, SDL_Scanc
     }
 
     if (pressed) {
-        input->pressed_modifiers |= mod;
+        seat->keyboard.pressed_modifiers |= mod;
     } else {
-        input->pressed_modifiers &= ~mod;
+        seat->keyboard.pressed_modifiers &= ~mod;
     }
 
-    Wayland_ReconcileModifiers(input, true);
+    Wayland_ReconcileModifiers(seat, true);
 }
 
 static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
                                   uint32_t serial, struct wl_surface *surface,
                                   struct wl_array *keys)
 {
-    struct SDL_WaylandInput *input = data;
-    SDL_WindowData *window;
+    SDL_WaylandSeat *seat = data;
     uint32_t *key;
 
     if (!surface) {
-        // enter event for a window we've just destroyed
+        // Enter event for a destroyed surface.
         return;
     }
 
-    window = Wayland_GetWindowDataForOwnedSurface(surface);
-
+    SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface);
     if (!window) {
+        // Not a surface owned by SDL.
         return;
     }
 
-    input->keyboard_focus = window;
-    window->keyboard_device = input;
+    ++window->keyboard_focus_count;
+    seat->keyboard.focus = window;
 
     // Restore the keyboard focus to the child popup that was holding it
     SDL_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window->sdlwindow);
 
+    // Update the keyboard grab and any relative pointer grabs related to this keyboard focus.
+    Wayland_SeatUpdateKeyboardGrab(seat);
+    Wayland_DisplayUpdatePointerGrabs(seat->display, window);
+
+    // Update text input and IME focus.
+    Wayland_UpdateTextInput(seat->display);
+
 #ifdef SDL_USE_IME
-    if (!input->text_input) {
+    if (!seat->text_input.zwp_text_input) {
         SDL_IME_SetFocus(true);
     }
 #endif
@@ -1730,8 +1775,12 @@ 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() != seat->keyboard.sdl_keymap) {
+        SDL_SetKeymap(seat->keyboard.sdl_keymap, true);
+    }
+
     wl_array_for_each (key, keys) {
-        const SDL_Scancode scancode = Wayland_GetScancodeForKey(input, *key);
+        const SDL_Scancode scancode = Wayland_GetScancodeForKey(seat, *key);
         const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false);
 
         switch (keycode) {
@@ -1745,8 +1794,8 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
         case SDLK_RGUI:
         case SDLK_MODE:
         case SDLK_LEVEL5_SHIFT:
-            Wayland_HandleModifierKeys(input, scancode, true);
-            SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, *key, scancode, true);
+            Wayland_HandleModifierKeys(seat, scancode, true);
+            SDL_SendKeyboardKeyIgnoreModifiers(timestamp, seat->keyboard.sdl_id, *key, scancode, true);
             break;
         default:
             break;
@@ -1757,58 +1806,74 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
 static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
                                   uint32_t serial, struct wl_surface *surface)
 {
-    struct SDL_WaylandInput *input = data;
-    SDL_WindowData *wind;
-    SDL_Window *window = NULL;
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
 
     if (!surface) {
+        // Leave event for a destroyed surface.
         return;
     }
 
-    wind = Wayland_GetWindowDataForOwnedSurface(surface);
-    if (!wind) {
+    SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface);
+    if (!window) {
+        // Not a surface owned by SDL.
         return;
     }
 
-    wind->keyboard_device = NULL;
-    window = wind->sdlwindow;
-
     // Stop key repeat before clearing keyboard focus
-    keyboard_repeat_clear(&input->keyboard_repeat);
+    keyboard_repeat_clear(&seat->keyboard.repeat);
+
+    SDL_Window *keyboard_focus = SDL_GetKeyboardFocus();
+
+    // The keyboard focus may be a child popup
+    while (keyboard_focus && SDL_WINDOW_IS_POPUP(keyboard_focus)) {
+        keyboard_focus = keyboard_focus->parent;
+    }
 
-    // This will release any keys still pressed
-    SDL_SetKeyboardFocus(NULL);
-    input->keyboard_focus = NULL;
+    const bool had_focus = keyboard_focus && window->sdlwindow == keyboard_focus;
+    seat->keyboard.focus = NULL;
+    --window->keyboard_focus_count;
+
+    // Only relinquish focus if this window has the active focus, and no other keyboards have focus on the window.
+    if (!window->keyboard_focus_count && had_focus) {
+        SDL_SetKeyboardFocus(NULL);
+    }
+
+    // Release the keyboard grab and any relative pointer grabs related to this keyboard focus.
+    Wayland_SeatUpdateKeyboardGrab(seat);
+    Wayland_DisplayUpdatePointerGrabs(seat->display, window);
 
     // Clear the pressed modifiers.
-    input->pressed_modifiers = SDL_KMOD_NONE;
+    seat->keyboard.pressed_modifiers = SDL_KMOD_NONE;
+
+    // Update text input and IME focus.
+    Wayland_UpdateTextInput(seat->display);
 
 #ifdef SDL_USE_IME
-    if (!input->text_input) {
+    if (!seat->text_input.zwp_text_input && !window->keyboard_focus_count) {
         SDL_IME_SetFocus(false);
     }
 #endif
 
-    /* If the surface had a pointer leave event while still having active touch events, it retained mouse focus.
-     * Clear it now if all touch events are raised.
+    /* If the window has mouse focus, has no pointers within it, and no active touches, consider
+     * mouse focus to be lost.
      */
-    if (!input->pointer_focus && SDL_GetMouseFocus() == window && !Wayland_SurfaceHasActiveTouches(surface)) {
+    if (SDL_GetMouseFocus() == window->sdlwindow && !window->pointer_focus_count &&
+        !Wayland_SurfaceHasActiveTouches(seat->display, surface)) {
         SDL_SetMouseFocus(NULL);
     }
 }
 
-static bool keyboard_input_get_text(char text[8], const struct SDL_WaylandInput *input, uint32_t key, bool down, bool *handled_by_ime)
+static bool keyboard_input_get_text(char text[8], const SDL_WaylandSeat *seat, uint32_t key, bool down, bool *handled_by_ime)
 {
-    SDL_WindowData *window = input->keyboard_focus;
     const xkb_keysym_t *syms;
     xkb_keysym_t sym;
 
-    if (!window || window->keyboard_device != input || !input->xkb.state) {
+    if (!seat->keyboard.focus || !seat->keyboard.xkb.state) {
         return false;
     }
 
     // TODO: Can this happen?
-    if (WAYLAND_xkb_state_key_get_syms(input->xkb.state, key + 8, &syms) != 1) {
+    if (WAYLAND_xkb_state_key_get_syms(seat->keyboard.xkb.state, key + 8, &syms) != 1) {
         return false;
     }
     sym = syms[0];
@@ -1826,8 +1891,8 @@ static bool keyboard_input_get_text(char text[8], const struct SDL_WaylandInput
         return false;
     }
 
-    if (input->xkb.compose_state && WAYLAND_xkb_compose_state_feed(input->xkb.compose_state, sym) == XKB_COMPOSE_FEED_ACCEPTED) {
-        switch (WAYLAND_xkb_compose_state_get_status(input->xkb.compose_state)) {
+    if (seat->keyboard.xkb.compose_state && WAYLAND_xkb_compose_state_feed(seat->keyboard.xkb.compose_state, sym) == XKB_COMPOSE_FEED_ACCEPTED) {
+        switch (WAYLAND_xkb_compose_state_get_status(seat->keyboard.xkb.compose_state)) {
         case XKB_COMPOSE_COMPOSING:
             if (handled_by_ime) {
                 *handled_by_ime = true;
@@ -1840,7 +1905,7 @@ static bool keyboard_input_get_text(char text[8], const struct SDL_WaylandInput
         case XKB_COMPOSE_NOTHING:
             break;
         case XKB_COMPOSE_COMPOSED:
-            sym = WAYLAND_xkb_compose_state_get_one_sym(input->xkb.compose_state);
+            sym = WAYLAND_xkb_compose_state_get_one_sym(seat->keyboard.xkb.compose_state);
             break;
         }
     }
@@ -1852,38 +1917,43 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
                                 uint32_t serial, uint32_t time, uint32_t key,
                                 uint32_t state_w)
 {
-    struct SDL_WaylandInput *input = data;
+    SDL_WaylandSeat *seat = data;
     enum wl_keyboard_key_state state = state_w;
     char text[8];
     bool has_text = false;
     bool handled_by_ime = false;
-    const Uint64 timestamp_raw_ns = Wayland_GetKeyboardTimestampRaw(input, time);
+    const Uint64 timestamp_raw_ns = Wayland_GetKeyboardTimestampRaw(seat, time);
 
-    Wayland_UpdateImplicitGrabSerial(input, serial);
+    Wayland_UpdateImplicitGrabSerial(seat, serial);
+
+    if (seat->keyboard.sdl_keymap != SDL_GetCurrentKeymap()) {
+        SDL_SetKeymap(seat->keyboard.sdl_keymap, true);
+        SDL_SetModState(seat->keyboard.pressed_modifiers | seat->keyboard.locked_modifiers);
+    }
 
     if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
         SDL_Window *keyboard_focus = SDL_GetKeyboardFocus();
         if (keyboard_focus && SDL_TextInputActive(keyboard_focus)) {
-            has_text = keyboard_input_get_text(text, input, key, true, &handled_by_ime);
+            has_text = keyboard_input_get_text(text, seat, key, true, &handled_by_ime);
         }
     } else {
-        if (keyboard_repeat_key_is_set(&input->keyboard_repeat, key)) {
+        if (keyboard_repeat_key_is_set(&seat->keyboard.repeat, key)) {
             /* Send any due key repeat events before stopping the repeat and generating the key up event.
              * Compute time based on the Wayland time, as it reports when the release event happened.
              * Using SDL_GetTicks would be wrong, as it would report when the release event is processed,
              * which may be off if the application hasn't pumped events for a while.
              */
-            keyboard_repeat_handle(&input->keyboard_repeat, timestamp_raw_ns - input->keyboard_repeat.wl_press_time_ns);
-            keyboard_repeat_clear(&input->keyboard_repeat);
+            keyboard_repeat_handle(&seat->keyboard.repeat, timestamp_raw_ns - seat->keyboard.repeat.wl_press_time_ns);
+            keyboard_repeat_clear(&seat->keyboard.repeat);
         }
-        keyboard_input_get_text(text, input, key, false, &handled_by_ime);
+        keyboard_input_get_text(text, seat, key, false, &handled_by_ime);
     }
 
-    const SDL_Scancode scancode = Wayland_GetScancodeForKey(input, key);
-    Wayland_HandleModifierKeys(input, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
-    Uint64 timestamp = Wayland_GetKeyboardTimestamp(input, time);
+    const SDL_Scancode scancode = Wayland_GetScancodeForKey(seat, key);
+    Wayland_HandleModifierKeys(seat, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
+    Uint64 timestamp = Wayland_GetKeyboardTimestamp(seat, time);
 
-    SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, key, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
+    SDL_SendKeyboardKeyIgnoreModifiers(timestamp, seat->keyboard.sdl_id, key, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
 
     if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
         if (has_text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) {
@@ -1891,8 +1961,8 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
                 SDL_SendKeyboardText(text);
             }
         }
-        if (input->xkb.keymap && WAYLAND_xkb_keymap_key_repeats(input->xkb.keymap, key + 8)) {
-            keyboard_repeat_set(&input->keyboard_repeat, input->keyboard_id, key, timestamp_raw_ns, scancode, has_text, text);
+        if (seat->keyboard.xkb.keymap && WAYLAND_xkb_keymap_key_repeats(seat->keyboard.xkb.keymap, key + 8)) {
+            keyboard_repeat_set(&seat->keyboard.repeat, seat->keyboard.sdl_id, key, timestamp_raw_ns, scancode, has_text, text);
         }
     }
 }
@@ -1902,48 +1972,48 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard,
                                       uint32_t mods_latched, uint32_t mods_locked,
                                       uint32_t group)
 {
-    struct SDL_WaylandInput *input = data;
+    SDL_WaylandSeat *seat = data;
 
-    if (input->xkb.state == NULL) {
+    if (seat->keyboard.xkb.state == NULL) {
         /* if we get a modifier notification before the keymap, there's nothing we can do with the information
         */
         return;
     }
 
-    WAYLAND_xkb_state_update_mask(input->xkb.state, mods_depressed, mods_latched,
+    WAYLAND_xkb_state_update_mask(seat->keyboard.xkb.state, mods_depressed, mods_latched,
                                   mods_locked, 0, 0, group);
 
-    input->xkb.wl_pressed_modifiers = mods_depressed;
-    input->xkb.wl_locked_modifiers = mods_latched | mods_locked;
+    seat->keyboard.xkb.wl_pressed_modifiers = mods_depressed;
+    seat->keyboard.xkb.wl_locked_modifiers = mods_latched | mods_locked;
 
-    Wayland_ReconcileModifiers(input, false);
+    Wayland_ReconcileModifiers(seat, false);
 
     // If a key is repeating, update the text to apply the modifier.
-    if (keyboard_repeat_is_set(&input->keyboard_repeat)) {
+    if (keyboard_repeat_is_set(&seat->keyboard.repeat)) {
         char text[8];
-        const uint32_t key = keyboard_repeat_get_key(&input->keyboard_repeat);
+        const uint32_t key = keyboard_repeat_get_key(&seat->keyboard.repeat);
 
-        if (keyboard_input_get_text(text, input, key, true, NULL)) {
-            keyboard_repeat_set_text(&input->keyboard_repeat, text);
+        if (keyboard_input_get_text(text, seat, key, true, NULL)) {
+            keyboard_repeat_set_text(&seat->keyboard.repeat, text);
         }
     }
 
-    if (group == input->xkb.current_group) {
+    if (group == seat->keyboard.xkb.current_group) {
         return;
     }
 
     // The layout changed, remap and fire an event. Virtual keyboards use the default keymap.
-    input->xkb.current_group = group;
-    Wayland_UpdateKeymap(input);
+    seat->keyboard.xkb.current_group = group;
+    Wayland_UpdateKeymap(seat);
 }
 
 static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
                                         int32_t rate, int32_t delay)
 {
-    struct SDL_WaylandInput *input = data;
-    input->keyboard_repeat.repeat_rate = SDL_clamp(rate, 0, 1000);
-    input->keyboard_repeat.repeat_delay_ms = delay;
-    input->keyboard_repeat.is_initialized = true;
+    SDL_WaylandSeat *seat = data;
+    seat->keyboard.repeat.repeat_rate = SDL_clamp(rate, 0, 1000);
+    seat->keyboard.repeat.repeat_delay_ms = delay;
+    seat->keyboard.repeat.is_initialized = true;
 }
 
 static const struct wl_keyboard_listener keyboard_listener = {
@@ -1955,96 +2025,243 @@ static const struct wl_keyboard_listener keyboard_listener = {
     keyboard_handle_repeat_info, // Version 4
 };
 
-void Wayland_input_init_relative_pointer(SDL_VideoData *d)
+static void Wayland_SeatCreateRelativePointer(SDL_WaylandSeat *seat)
 {
-    struct SDL_WaylandInput *input = d->input;
+    if (seat->display->relative_pointer_manager) {
+        if (seat->pointer.wl_pointer && !seat->pointer.relative_pointer) {
+            seat->pointer.relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(seat->display->relative_pointer_manager, seat->pointer.wl_pointer);
+            zwp_relative_pointer_v1_add_listener(seat->pointer.relative_pointer,
+                                                 &relative_pointer_listener,
+                                                 seat);
+        }
+    }
+}
 
-    if (!d->relative_pointer_manager) {
-        return;
+void Wayland_DisplayInitRelativePointerManager(SDL_VideoData *display)
+{
+    SDL_WaylandSeat *seat;
+    wl_list_for_each(seat, &display->seat_list, link) {
+        Wayland_SeatCreateRelativePointer(seat);
+    }
+}
+
+static void Wayland_SeatDestroyPointer(SDL_WaylandSeat *seat, bool send_event)
+{
+    // Make sure focus is removed from a surface before the pointer is destroyed.
+    if (seat->pointer.focus) {
+        pointer_handle_leave(seat, seat->pointer.wl_pointer, 0, seat->pointer.focus->surface);
     }
 
-    if (input->pointer && !input->relative_pointer) {
-        input->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(input->display->relative_pointer_manager, input->pointer);
-        zwp_relative_pointer_v1_add_listener(input->relative_pointer,
-                                             &relative_pointer_listener,
-                                             input);
+    SDL_RemoveMouse(seat->pointer.sdl_id, send_event);
+
+    if (seat->pointer.confined_pointer) {
+        zwp_confined_pointer_v1_destroy(seat->pointer.confined_pointer);
+    }
+
+    if (seat->pointer.locked_pointer) {
+        zwp_locked_pointer_v1_destroy(seat->pointer.locked_pointer);
+    }
+
+    if (seat->pointer.relative_pointer) {
+        zwp_relative_pointer_v1_destroy(seat->pointer.relative_pointer);
+    }
+
+    if (seat->pointer.timestamps) {
+        zwp_input_timestamps_v1_destroy(seat->pointer.timestamps);
+    }
+
+    if (seat->pointer.cursor_state.frame_callback) {
+        wl_callback_destroy(seat->pointer.cursor_state.frame_callback);
+    }
+
+    if (seat->pointer.cursor_state.surface) {
+        wl_surface_destroy(seat->pointer.cursor_state.surface);
     }
+
+    if (seat->pointer.cursor_state.viewport) {
+        wp_viewport_destroy(seat->pointer.cursor_state.viewport);
+    }
+
+    if (seat->pointer.cursor_shape) {
+        wp_cursor_shape_device_v1_destroy(seat->pointer.cursor_shape);
+    }
+
+    if (seat->pointer.wl_pointer) {
+        if (wl_pointer_get_version(seat->pointer.wl_pointer) >= WL_POINTER_RELEASE_SINCE_VERSION) {
+            wl_pointer_release(seat->pointer.wl_pointer);
+        } else {
+            wl_pointer_destroy(seat->pointer.wl_pointer);
+        }
+    }
+
+    SDL_zero(seat->pointer);
 }
 
-static void seat_handle_capabilities(void *data, struct wl_seat *seat,
-                                     enum wl_seat_capability caps)
+static void Wayland_SeatDestroyKeyboard(SDL_WaylandSeat *seat, bool send_event)
 {
-    struct SDL_WaylandInput *input = data;
+    // Make sure focus is removed from a surface before the keyboard is destroyed.
+    if (seat->keyboard.focus) {
+        keyboard_handle_leave(seat, seat->keyboard.wl_keyboard, 0, seat->keyboard.focus->surface);
+    }
 
-    if ((caps & WL_SEAT_CAPABILITY_POINTER) && !input->pointer) {
-        input->pointer = wl_seat_get_pointer(seat);
-        SDL_memset(&input->pointer_curr_axis_info, 0, sizeof(input->pointer_curr_axis_info));
-        input->display->pointer = input->pointer;
+    SDL_RemoveKeyboard(seat->keyboard.sdl_id, send_event);
 
-        Wayland_CreateCursorShapeDevice(input);
+    if (seat->keyboard.sdl_keymap) {
+        if (seat->keyboard.sdl_keymap == SDL_GetCurrentKeymap()) {
+            SDL_SetKeymap(NULL, false);
+        }
+        SDL_DestroyKeymap(seat->keyboard.sdl_keymap);
+    }
 
-        wl_pointer_set_user_data(input->pointer, input);
-        wl_pointer_add_listener(input->pointer, &pointer_listener, input);
+    if (seat->keyboard.key_inhibitor) {
+        zwp_keyboard_shortcuts_inhibitor_v1_destroy(seat->keyboard.key_inhibitor);
+    }
 
-        Wayland_input_init_relative_pointer(input->display);
+    if (seat->keyboard.timestamps) {
+        zwp_input_timestamps_v1_destroy(seat->keyboard.timestamps);
+    }
 
-        input->pointer_id = SDL_GetNextObjectID();
-        SDL_AddMouse(input->pointer_id, WAYLAND_DEFAULT_POINTER_NAME, !input->display->initializing);
-    } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && input->pointer) {
-        if (input->relative_pointer) {
-            zwp_relative_pointer_v1_destroy(input->relative_pointer);
-            input->relative_pointer = NULL;
+    if (seat->keyboard.wl_keyboard) {
+        if (wl_keyboard_get_version(seat->keyboard.wl_keyboard) >= WL_KEYBOARD_RELEASE_SINCE_VERSION) {
+            wl_keyboard_release(seat->keyboard.wl_keyboard);
+        } else {
+            wl_keyboard_destroy(seat->keyboard.wl_keyboard);
         }
-        if (input->cursor_shape) {
-            wp_cursor_shape_device_v1_destroy(input->cursor_shape);
-            input->cursor_shape = NULL;
+    }
+
+    if (seat->keyboard.xkb.compose_state) {
+        WAYLAND_xkb_compose_state_unref(seat->keyboard.xkb.compose_state);
+    }
+
+    if (seat->keyboard.xkb.compose_table) {
+        WAYLAND_xkb_compose_table_unref(seat->keyboard.xkb.compose_table);
+    }
+
+    if (seat->keyboard.xkb.state) {
+        WAYLAND_xkb_state_unref(seat->keyboard.xkb.state);
+    }
+
+    if (seat->keyboard.xkb.keymap) {
+        WAYLAND_xkb_keymap_unref(seat->keyboard.xkb.keymap);
+    }
+
+    SDL_zero(seat->keyboard);
+}
+
+static void Wayland_SeatDestroyTouch(SDL_WaylandSeat *seat)
+{
+    // Cancel any active touches before the touch object is destroyed.
+    if (seat->touch.wl_touch) {
+        touch_handler_cancel(seat, seat->touch.wl_touch);
+    }
+
+    SDL_DelTouch((SDL_TouchID)(uintptr_t)seat->touch.wl_touch);
+
+    if (seat->touch.timestamps) {
+        zwp_input_timestamps_v1_destroy(seat->touch.timestamps);
+    }
+
+    if (seat->touch.wl_touch) {
+        if (wl_touch_get_version(seat->touch.wl_touch) >= WL_TOUCH_RELEASE_SINCE_VERSION) {
+            wl_touch_release(seat->touch.wl_touch);
+        } else {
+            wl_touch_destroy(seat->touch.wl_touch);
         }
-        if (wl_pointer_get_version(input->pointer) >= WL_POINTER_RELEASE_SINCE_VERSION) {
-            wl_pointer_release(input->pointer);
+    }
+
+    SDL_zero(seat->touch);
+    WAYLAND_wl_list_init(&seat->touch.points);
+}
+
+static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum wl_seat_capability capabilities)
+{
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
+    char name_fmt[256];
+
+    if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && !seat->pointer.wl_pointer) {
+        seat->pointer.wl_pointer = wl_seat_get_pointer(wl_seat);
+        SDL_memset(&seat->pointer.current_axis_info, 0, sizeof(seat->pointer.current_axis_info));
+
+        Wayland_SeatCreateCursorShape(seat);
+
+        wl_pointer_set_user_data(seat->pointer.wl_pointer, seat);
+        wl_pointer_add_listener(seat->pointer.wl_pointer, &pointer_listener, seat);
+
+        Wayland_SeatCreateRelativePointer(seat);
+
+        seat->pointer.sdl_id = SDL_GetNextObjectID();
+
+        if (seat->name) {
+            SDL_snprintf(name_fmt, sizeof(name_fmt), "%s (%s)", WAYLAND_DEFAULT_POINTER_NAME, seat->name);
         } else {
-            wl_pointer_destroy(input->pointer);
+            SDL_snprintf(name_fmt, sizeof(name_fmt), "%s %" SDL_PRIu32, WAYLAND_DEFAULT_POINTER_NAME, seat->pointer.sdl_id);
         }
-        input->pointer = NULL;
-        input->display->pointer = NULL;
 
-        SDL_RemoveMouse(input->pointer_id, true);
-        input->pointer_id = 0;
+        SDL_AddMouse(seat->pointer.sdl_id, name_fmt, !seat->display->initializing);
+    } else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && seat->pointer.wl_pointer) {
+        Wayland_SeatDestroyPointer(seat, true);
     }
 
-    if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->touch) {
-        input->touch = wl_seat_get_touch(seat);
-        SDL_AddTouch((SDL_TouchID)(uintptr_t)input->touch, SDL_TOUCH_DEVICE_DIRECT, "wayland_touch");
-        wl_touch_set_user_data(input->touch, input);
-        wl_touch_add_listener(input->touch, &touch_listener,
-                              input);
-    } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && input->touch) {
-        SDL_DelTouch((SDL_TouchID)(intptr_t)input->touch);
-        wl_touch_destroy(input->touch);
-        input->touch = NULL;
+    if ((capabilities & WL_SEAT_CAPABILITY_TOUCH) && !seat->touch.wl_touch) {
+        seat->touch.wl_touch = wl_seat_get_touch(wl_seat);
+        wl_touch_set_user_data(seat->touch.wl_touch, seat);
+        wl_touch_add_listener(seat->touch.wl_touch, &touch_listener, seat);
+
+        if (seat->name) {
+            SDL_snprintf(name_fmt, sizeof(name_fmt), "%s (%s)", WAYLAND_DEFAULT_TOUCH_NAME, seat->name);
+        } else {
+            SDL_snprintf(name_fmt, sizeof(name_fmt), "%s %" SDL_PRIu64, WAYLAND_DEFAULT_TOUCH_NAME, (SDL_TouchID)(uintptr_t)seat->touch.wl_touch);
+        }
+
+        SDL_AddTouch((SDL_TouchID)(uintptr_t)seat->touch.wl_touch, SDL_TOUCH_DEVICE_DIRECT, name_fmt);
+    } else if (!(capabilities & WL_SEAT_CAPABILITY_TOUCH) && seat->touch.wl_touch) {
+        Wayland_SeatDestroyTouch(seat);
     }
 
-    if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->keyboard) {
-        input->keyboard = wl_seat_get_keyboard(seat);
-        wl_keyboard_set_user_data(input->keyboard, input);
-        wl_keyboard_add_listener(input->keyboard, &keyboard_listener,
-                                 input);
+    if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && !seat->keyboard.wl_keyboard) {
+        seat->keyboard.wl_keyboard = wl_seat_get_keyboard(wl_seat);
+        wl_keyboard_set_user_data(seat->keyboard.wl_keyboard, seat);
+        wl_keyboard_add_listener(seat->keyboard.wl_keyboard, &keyboard_listener, seat);
+
+        seat->keyboard.sdl_id = SDL_GetNextObjectID();
 
-        input->keyboard_id = SDL_GetNextObjectID();
-        SDL_AddKeyboard(input->keyboard_id, WAYLAND_DEFAULT_KEYBOARD_NAME, !input->display->initializing);
-    } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->keyboard) {
-        wl_keyboard_destroy(input->keyboard);
-        input->keyboard = NULL;
+        if (seat->name) {
+            SDL_snprintf(name_fmt, sizeof(name_fmt), "%s (%s)", WAYLAND_DEFAULT_KEYBOARD_NAME, seat->name);
+        } else {
+            SDL_snprintf(name_fmt, sizeof(name_fmt), "%s %" SDL_PRIu32, WAYLAND_DEFAULT_KEYBOARD_NAME, seat->keyboard.sdl_id);
+        }
 
-        SDL_RemoveKeyboard(input->keyboard_id, true);
-        input->keyboard_id = 0;
+        SDL_AddKeyboard(seat->keyboard.sdl_id, name_fmt, !seat->display->initializing);
+    } else if (!(capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && seat->keyboard.wl_keyboard) {
+        Wayland_SeatDestroyKeyboard(seat, true);
     }
 
-    Wayland_RegisterTimestampListeners(input);
+    Wayland_SeatRegisterInputTimestampListeners(seat);
 }
 
 static void seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name)
 {
-    // unimplemented
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
+    char name_fmt[256];
+
+    if (name && *name != '\0') {
+        seat->name = SDL_strdup(name);
+
+        if (seat->keyboard.wl_keyboard) {
+            SDL_snprintf(name_fmt, sizeof(name_fmt), "%s (%s)", WAYLAND_DEFAULT_KEYBOARD_NAME, seat->name);
+            SDL_SetKeyboardName(seat->keyboard.sdl_id, name_fmt);
+        }
+
+        if (seat->pointer.wl_pointer) {
+            SDL_snprintf(name_fmt, sizeof(name_fmt), "%s (%s)", WAYLAND_DEFAULT_POINTER_NAME, seat->name);
+            SDL_SetMouseName(seat->pointer.sdl_id, name_fmt);
+        }
+
+        if (seat->touch.wl_touch) {
+            SDL_snprintf(name_fmt, sizeof(name_fmt), "%s (%s)", WAYLAND_DEFAULT_TOUCH_NAME, seat->name);
+            SDL_SetTouchName((SDL_TouchID)(uintptr_t)seat->touch.wl_touch, name_fmt);
+        }
+    }
 }
 
 static const struct wl_seat_listener seat_listener = {
@@ -2230,8 +2447,10 @@ static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_
 {
     SDL_WaylandDataOffer *data_offer = SDL_calloc(1, sizeof(*data_offer));
     if (data_offer) {
+        SDL_WaylandDataDevice *data_device = (SDL_WaylandDataDevice *)data;
+        data_device->seat->display->last_incoming_data_offer_seat = data_device->seat;
         data_offer->offer = id;
-        data_offer->data_device = data;
+        data_offer->data_device = data_device;
         WAYLAND_wl_list_init(&(data_offer->mimes));
         wl_data_offer_set_user_data(id, data_offer);
         wl_data_offer_add_listener(id, &data_offer_listener, data_offer);
@@ -2543,8 +2762,10 @@ static void primary_selection_device_handle_offer(void *data, struct zwp_primary
 {
     SDL_WaylandPrimarySelectionOffer *primary_selection_offer = SDL_calloc(1, sizeof(*primary_selection_offer));
     if (primary_selection_offer) {
+        SDL_WaylandPrimarySelectionDevice *primary_selection_device = (SDL_WaylandPrimarySelectionDevice *)data;
+        primary_selection_device->seat->display->last_incoming_primary_selection_seat = primary_selection_device->seat;
         primary_selection_offer->offer = id;
-        primary_selection_offer->primary_selection_device = data;
+        primary_selection_offer->primary_selection_device = primary_selection_device;
         WAYLAND_wl_list_init(&(primary_selection_offer->mimes));
         zwp_primary_selection_offer_v1_set_user_data(id, primary_selection_offer);
         zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, primary_selection_offer);
@@ -2598,8 +2819,8 @@ static void text_input_preedit_string(void *data,
                                       int32_t cursor_begin,
                                       int32_t cursor_end)
 {
-    SDL_WaylandTextInput *text_input = data;
-    text_input->has_preedit = true;
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
+    seat->text_input.has_preedit = true;
     if (text) {
         int cursor_begin_utf8 = cursor_begin >= 0 ? (int)SDL_utf8strnlen(text, cursor_begin) : -1;
         int cursor_end_utf8 = cursor_end >= 0 ? (int)SDL_utf8strnlen(text, cursor_end) : -1;
@@ -2638,11 +2859,11 @@ static void text_input_done(void *data,
                             struct zwp_text_input_v3 *zwp_text_input_v3,
                             uint32_t serial)
 {
-    SDL_WaylandTextInput *text_input = data;
-    if (!text_input->has_preedit) {
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
+    if (!seat->text_input.has_preedit) {
         SDL_SendEditingText("", 0, 0);
     }
-    text_input->has_preedit = false;
+    seat->text_input.has_preedit = false;
 }
 
 static const struct zwp_text_input_v3_listener text_input_listener = {
@@ -2654,23 +2875,20 @@ static const struct zwp_text_input_v3_listener text_input_listener = {
     text_input_done
 };
 
-void Wayland_create_data_device(SDL_VideoData *d)
+static void Wayland_SeatCreateDataDevice(SDL_WaylandSeat *seat)
 {
-    SDL_WaylandDataDevice *data_device = NULL;
-
-    if (!d->input->seat) {
-        // No seat yet, will be initialized later.
+    if (!seat->display->data_device_manager) {
         return;
     }
 
-    data_device = SDL_calloc(1, sizeof(*data_device));
+    SDL_WaylandDataDevice *data_device = SDL_calloc(1, sizeof(*data_device));
     if (!data_device) {
         return;
     }
 
     data_device->data_device = wl_data_device_manager_get_data_device(
-        d->data_device_manager, d->input->seat);
-    data_device->video_data = d;
+        seat->display->data_device_manager, seat->wl_seat);
+    data_device->seat = seat;
 
     if (!data_device->data_device) {
         SDL_free(data_device);
@@ -2678,27 +2896,32 @@ void Wayland_create_data_device(SDL_VideoData *d)
         wl_data_device_set_user_data(data_device->data_device, data_device);
         wl_data_device_add_listener(data_device->data_device,
                                     &data_device_listener, data_device);
-        d->input->data_device = data_device;
+        seat->data_device = data_device;
     }
 }
 
-void Wayland_create_primary_selection_device(SDL_VideoData *d)
+void Wayland_DisplayInitDataDeviceManager(SDL_VideoData *display)
 {
-    SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
+    SDL_WaylandSeat *seat;
+    wl_list_for_each (seat, &display->seat_list, link) {
+        Wayland_SeatCreateDataDevice(seat);
+    }
+}
 
-    if (!d->input->seat) {
-        // No seat yet, will be initialized later.
+static void Wayland_SeatCreatePrimarySelectionDevice(SDL_WaylandSeat *seat)
+{
+    if (!seat->display->primary_selection_device_manager) {
         return;
     }
 
-    primary_selection_device = SDL_calloc(1, sizeof(*primary_selection_device));
+    SDL_WaylandPrimarySelectionDevice *primary_selection_device = SDL_calloc(1, sizeof(*primary_selection_device));
     if (!primary_selection_device) {
         return;
     }
 
     primary_selection_device->primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(
-        d->primary_selection_device_manager, d->input->seat);
-    primary_selection_device->video_data = d;
+        seat->display->primary_selection_device_manager, seat->wl_seat);
+    primary_selection_device->seat = seat;
 
     if (!primary_selection_device->primary_selection_device) {
         SDL_free(primary_selection_device);
@@ -2707,38 +2930,32 @@ void Wayland_create_primary_selection_device(SDL_VideoData *d)
                                                       primary_selection_device);
         zwp_primary_selection_device_v1_add_listener(primary_selection_device->primary_selection_device,
                                                      &primary_selection_device_listener, primary_selection_device);
-        d->input->primary_selection_device = primary_selection_device;
+        seat->primary_selection_device = primary_selection_device;
     }
 }
 
-static void Wayland_create_text_input(SDL_VideoData *d)
+void Wayland_DisplayInitPrimarySelectionDeviceManager(SDL_VideoData *display)
 {
-    SDL_WaylandTextInput *text_input = NULL;
-
-    if (!d->input->seat) {
-        // No seat yet, will be initialized later.
-        return;
-    }
-
-    text_input = SDL_calloc(1, sizeof(*text_input));
-    if (!text_input) {
-        return;
+    SDL_WaylandSeat *seat;
+    wl_list_for_each (seat, &display->seat_list, link) {
+        Wayland_SeatCreatePrimarySelectionDevice(seat);
     }
+}
 
-    text_input->text_input = zwp_text_input_manager_v3_get_text_input(
-        d->text_input_manager, d->input->seat);
+static void Wayland_SeatCreateTextInput(SDL_WaylandSeat *seat)
+{
+    if (seat->display->text_input_manager) {
+        seat->text_input.zwp_text_input = zwp_text_input_manager_v3_get_text_input(seat->display->text_input_manager, seat->wl_seat);
 
-    if (!text_input->text_input) {
-        SDL_free(text_input);
-    } else {
-        zwp_text_input_v3_set_user_data(text_input->text_input, text_input);
-        zwp_text_input_v3_add_listener(text_input->text_input,
-                                       &text_input_listener, text_input);
-        d->input->text_input = text_input;
+        if (seat->text_input.zwp_text_input) {
+            zwp_text_input_v3_set_user_data(seat->text_input.zwp_text_input, seat);
+            zwp_text_input_v3_add_listener(seat->text_input.zwp_text_input,
+                                           &text_input_listener, seat);
+        }
     }
 }
 
-void Wayland_create_text_input_manager(SDL_VideoData *d, uint32_t id)
+void Wayland_DisplayCreateTextInputManager(SDL_VideoData *d, uint32_t id)
 {
 #ifdef HAVE_FCITX
     const char *im_module = SDL_getenv("SDL_IM_MODULE");
@@ -2753,7 +2970,11 @@ void Wayland_create_text_input_manager(SDL_VideoData *d, uint32_t id)
 #endif
 
     d->text_input_manager = wl_registry_bind(d->registry, id, &zwp_text_input_manager_v3_interface, 1);
-    Wayland_create_text_input(d);
+
+    SDL_WaylandSeat *seat;
+    wl_list_for_each(seat, &d->seat_list, link) {
+        Wayland_SeatCreateTextInput(seat);
+    }
 }
 
 // Pen/Tablet support...
@@ -2771,6 +2992,7 @@ typedef struct SDL_WaylandPenTool  // a stylus, etc, on a tablet.
     Uint32 frame_axes_set;
     int frame_pen_down;
     int frame_buttons[3];
+    struct wl_list link;
 } SDL_WaylandPenTool;
 
 static void tablet_tool_handle_type(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t type)
@@ -2825,6 +3047,7 @@ static void tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *to
         SDL_RemovePenDevice(0, sdltool->instance_id);
     }
     zwp_tablet_tool_v2_destroy(tool);
+    WAYLAND_wl_list_remove(&sdltool->link);
     SDL_free(sdltool);
 }
 
@@ -3025,13 +3248,14 @@ static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = {
 };
 
 
-static void tablet_seat_handle_tablet_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_v2 *tablet)
+static void tablet_seat_handle_tablet_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_v2 *tablet)
 {
     // don't care atm.
 }
 
-static void tablet_seat_handle_tool_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_tool_v2 *tool)
+static void tablet_seat_handle_tool_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_tool_v2 *tool)
 {
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
     SDL_WaylandPenTool *sdltool = SDL_calloc(1, sizeof(*sdltool));
 
     if (sdltool) {  // if allocation failed, oh well, we won't report this device.
@@ -3042,6 +3266,7 @@ static void tablet_seat_handle_tool_added(void *data, struct zwp_tablet_seat_v2
         for (int i = 0; i < SDL_arraysize(sdltool->frame_buttons); i++) {
             sdltool->frame_buttons[i] = -1;
         }
+        WAYLAND_wl_list_insert(&seat->tablet.tool_list, &sdltool->link);
 
         // this will send a bunch of zwp_tablet_tool_v2 events right up front to tell
         // us device details, with a "done" event to let us know we have everything.
@@ -3049,7 +3274,7 @@ static void tablet_seat_handle_tool_added(void *data, struct zwp_tablet_seat_v2
     }
 }
 
-static void tablet_seat_handle_pad_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_pad_v2 *pad)
+static void tablet_seat_handle_pad_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_pad_v2 *pad)
 {
     // we don't care atm.
 }
@@ -3060,23 +3285,19 @@ static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = {
     tablet_seat_handle_pad_added
 };
 
-void Wayland_input_init_tablet_support(struct SDL_WaylandInput *input, struct zwp_tablet_manager_v2 *tablet_manager)
+static void Wayland_SeatInitTabletSupport(SDL_WaylandSeat *seat)
 {
-    if (!tablet_manager || !input->seat) {
-        return;
-    }
+    WAYLAND_wl_list_init(&seat->tablet.tool_list);
+    seat->tablet.wl_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(seat->display->tablet_manager, seat->wl_seat);
+    zwp_tablet_seat_v2_add_listener(seat->tablet.wl_tablet_seat, &tablet_seat_listener, seat);
+}
 
-    SDL_WaylandTabletInput *tablet_input = SDL_calloc(1, sizeof(*tablet_input));
-    if (!tablet_input) {
-        return;
+void Wayland_DisplayInitTabletManager(SDL_VideoData *display)
+{
+    SDL_WaylandSeat *seat;
+    wl_list_for_each (seat, &display->seat_list, link) {
+        Wayland_SeatInitTabletSupport(seat);
     }
-
-    tablet_input->input = input;
-    tablet_input->seat = zwp_tablet_manager_v2_get_tablet_seat(tablet_manager, input->seat);
-
-    zwp_tablet_seat_v2_add_listener(tablet_input->seat, &tablet_seat_listener, tablet_input);
-
-    input->tablet_input = tablet_input;
 }
 
 static void Wayland_remove_all_pens_callback(SDL_PenID instance_id, void *handle, void *userdata)
@@ -3086,338 +3307,302 @@ static void Wayland_remove_all_pens_callback(SDL_PenID instance_id, void *handle
     SDL_free(sdltool);
 }
 
-void Wayland_input_quit_tablet_support(struct SDL_WaylandInput *input)
+static void Wayland_SeatDestroyTablet(SDL_WaylandSeat *seat, bool send_events)
 {
-    SDL_RemoveAllPenDevices(Wayland_remove_all_pens_callback, NULL);
+    if (send_events) {
+        SDL_WaylandPenTool *pen, *temp;
+        wl_list_for_each_safe (pen, temp, &seat->tablet.tool_list, link) {
+            // Remove all tools for this seat, sending PROXIMITY_OUT events.
+            tablet_tool_handle_removed(pen, pen->wltool);
+        }
+    } else {
+        // Shutting down, just delete everything.
+        SDL_RemoveAllPenDevices(Wayland_remove_all_pens_callback, NULL);
+    }
 
-    if (input && input->tablet_input) {
-        zwp_tablet_seat_v2_destroy(input->tablet_input->seat);
-        SDL_free(input->tablet_input);
-        input->tablet_input = NULL;
+    if (seat && seat->tablet.wl_tablet_seat) {
+        zwp_tablet_seat_v2_destroy(seat->tablet.wl_tablet_seat);
+        seat->tablet.wl_tablet_seat = NULL;
     }
+
+    SDL_zero(seat->tablet);
+    WAYLAND_wl_list_init(&seat->tablet.tool_list);
 }
 
-void Wayland_input_initialize_seat(SDL_VideoData *d)
+void Wayland_DisplayCreateSeat(SDL_VideoData *display, struct wl_seat *wl_seat, Uint32 id)
 {
-    struct SDL_WaylandInput *input = d->input;
+    SDL_WaylandSeat *seat = SDL_calloc(1, sizeof(SDL_WaylandSeat));
+    if (!seat) {
+        return;
+    }
 
-    WAYLAND_wl_list_init(&touch_points);
+    // Keep the seats in the order in which they were added.
+    WAYLAND_wl_list_insert(display->seat_list.prev, &seat->link);
 
-    if (d->data_device_manager) {
-        Wayland_create_data_device(d);
-    }
-    if (d->primary_selection_device_manager) {
-        Wayland_create_primary_selection_device(d);
-    }
-    if (d->text_input_manager) {
-        Wayland_create_text_input(d);
-    }
+    WAYLAND_wl_list_init(&seat->touch.points);
+    seat->wl_seat = wl_seat;
+    seat->display = display;
+    seat->registry_id = id;
+    seat->keyboard.xkb.current_group = XKB_GROUP_INVALID;
 
-    wl_seat_add_listener(input->seat, &seat_listener, input);
-    wl_seat_set_user_data(input->seat, input);
+    Wayland_SeatCreateDataDevice(seat);
+    Wayland_SeatCreatePrimarySelectionDevice(seat);
+    Wayland_SeatCreateTextInput(seat);
 
-    if (d->tablet_manager) {
-        Wayland_input_init_tablet_support(d->input, d->tablet_manager);
+    wl_seat_set_user_data(seat->wl_seat, seat);
+    wl_seat_add_listener(seat->wl_seat, &seat_listener, seat);
+
+    if (display->tablet_manager) {
+        Wayland_SeatInitTabletSupport(seat);
     }
 
-    WAYLAND_wl_display_flush(d->display);
+    WAYLAND_wl_display_flush(display->display);
 }
 
-void Wayland_display_destroy_input(SDL_VideoData *d)
+void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events)
 {
-    struct SDL_WaylandInput *input = d->input;
-
-    if (input->keyboard_timestamps) {
-        zwp_input_timestamps_v1_destroy(input->keyboard_timestamps);
-    }
-    if (input->pointer_timestamps) {
-        zwp_input_timestamps_v1_destroy(input->pointer_timestamps);
-    }
-    if (input->touch_timestamps) {
-        zwp_input_timestamps_v1_destroy(input->touch_timestamps);
+    if (!seat) {
+        return;
     }
 
-    if (input->data_device) {
-        Wayland_data_device_clear_selection(input->data_device);
-        if (input->data_device->selection_offer) {
-            Wayland_data_offer_destroy(input->data_device->selection_offer);
+    SDL_free(seat->name);
+
+    if (seat->data_device) {
+        Wayland_data_device_clear_selection(seat->data_device);
+        if (seat->data_device->selection_offer) {
+            Wayland_data_offer_destroy(seat->data_device->selection_offer);
         }
-        if (input->data_device->drag_offer) {
-            Wayland_data_offer_destroy(input->data_device->drag_offer);
+        if (seat->data_device->drag_offer) {
+            Wayland_data_offer_destroy(seat->data_device->drag_offer);
         }
-        if (input->data_device->data_device) {
-            if (wl_data_device_get_version(input->data_device->data_device) >= WL_DATA_DEVICE_RELEASE_SINCE_VERSION) {
-                wl_data_device_release(input->data_device->data_device);
+        if (seat->data_device->data_device) {
+            if (wl_data_device_get_version(seat->data_device->data_device) >= WL_DATA_DEVICE_RELEASE_SINCE_VERSION) {
+                wl_data_device_release(seat->data_device->data_device);
             } else {
-                wl_data_device_destroy(input->data_device->data_device);
+                wl_data_device_destroy(seat->data_device->data_device);
             }
         }
-        SDL_free(input->data_device);
+        SDL_free(seat->data_device);
     }
 
-    if (input->primary_selection_device) {
-        if (input->primary_selection_device->selection_offer) {
-            Wayland_primary_selection_offer_destroy(input->primary_selection_device->selection_offer);
+    if (seat->primary_selection_device) {
+        if (seat->primary_selection_device->selection_offer) {
+            Wayland_primary_selection_offer_destroy(seat->primary_selection_device->selection_offer);
         }
-        if (input->primary_selection_device->selection_source) {
-            Wayland_primary_selection_source_destroy(input->primary_selection_device->selection_source);
+        if (seat->primary_selection_device->selection_source) {
+            Wayland_primary_selection_source_destroy(seat->primary_selection_device->selection_source);
         }
-        if (input->primary_selection_device->primary_selection_device) {
-            zwp_primary_selection_device_v1_destroy(input->primary_selection_device->primary_selection_device);
+        if (seat->primary_selection_device->primary_selection_device) {
+            zwp_primary_selection_device_v1_destroy(seat->primary_selection_device->primary_selection_device);
         }
-        SDL_free(input->primary_selection_device);
+        SDL_free(seat->primary_selection_device);
     }
 
-    if (input->text_input) {
-        zwp_text_input_v3_destroy(input->text_input->text_input);
-        SDL_free(input->text_input);
+    if (seat->text_input.zwp_text_input) {
+        zwp_text_input_v3_destroy(seat->text_input.zwp_text_input);
     }
 
-    if (input->keyboard) {
-        if (wl_keyboard_get_version(input->keyboard) >= WL_KEYBOARD_RELEASE_SINCE_VERSION) {
-            wl_keyboard_release(input->keyboard);
-        } else {
-            wl_keyboard_destroy(input->keyboard);
-        }
-    }
+    Wayland_SeatDestroyKeyboard(seat, send_events);
+    Wayland_SeatDestroyPointer(seat, send_events);
+    Wayland_SeatDestroyTouch(seat);
+    Wayland_SeatDestroyTablet(seat, send_events);
 
-    if (input->relative_pointer) {
-        zwp_relative_pointer_v1_destroy(input->relative_pointer);
+    if (wl_seat_get_version(seat->wl_seat) >= WL_SEAT_RELEASE_SINCE_VERSION) {
+        wl_seat_release(seat->wl_seat);
+    } else {
+        wl_seat_destroy(seat->wl_seat);
     }
 
-    if (input->cursor_shape) {
-        wp_cursor_shape_device_v1_destroy(input->cursor_shape);
-    }
+    WAYLAND_wl_list_remove(&seat->link);
+    SDL_free(seat);
+}
 
-    if (input->pointer) {
-        if (wl_pointer_get_version(input->pointer) >= WL_POINTER_RELEASE_SINCE_VERSION) {
-            wl_pointer_release(input->pointer);
-        } else {
-            wl_pointer_destroy(input->pointer);
-        }
+bool Wayland_SeatHasRelativePointerFocus(SDL_WaylandSeat *seat)
+{
+    /* If a seat has both keyboard and pointer capabilities, relative focus will follow the keyboard
+     * attached to that seat. Otherwise, relative focus will be gained if any other seat has keyboard
+     * focus on the window with pointer focus.
+     */
+    if (seat->keyboard.wl_keyboard) {
+        return seat->keyboard.focus && seat->keyboard.focus == seat->pointer.focus;
+    } else {
+        return seat->pointer.focus && seat->pointer.focus->keyboard_focus_count != 0;
     }
+}
 
-    if (input->touch) {
-        struct SDL_WaylandTouchPoint *tp, *tmp;
-
-        SDL_DelTouch(1);
-        if (wl_touch_get_version(input->touch) >= WL_TOUCH_RELEASE_SINCE_VERSION) {
-            wl_touch_release(input->touch);
-        } else {
-            wl_touch_destroy(input->touch);
-        }
+static void Wayland_SeatUpdateKeyboardGrab(SDL_WaylandSeat *seat)
+{
+    SDL_VideoData *display = seat->display;
 
-        wl_list_for_each_safe (tp, tmp, &touch_points, link) {
-            WAYLAND_wl_list_remove(&tp->link);
-            SDL_free(tp);
+    if (display->key_inhibitor_manager) {
+        // Destroy the existing key inhibitor.
+        if (seat->keyboard.key_inhibitor) {
+            zwp_keyboard_shortcuts_inhibitor_v1_destroy(seat->keyboard.key_inhibitor);
+            seat->keyboard.key_inhibitor = NULL;
         }
-    }
 
-    if (input->tablet_input) {
-        Wayland_input_quit_tablet_support(input);
-    }
+        if (seat->keyboard.wl_keyboard) {
+            SDL_WindowData *w = seat->keyboard.focus;
+            if (w) {
+                SDL_Window *window = w->sdlwindow;
 
-    if (input->seat) {
-        if (wl_seat_get_version(input->seat) >= WL_SEAT_RELEASE_SINCE_VERSION) {
-            wl_seat_release(input->seat);
-        } else {
-            wl_seat_destroy(input->seat);
+                // Don't grab the keyboard if it shouldn't be grabbed.
+                if (window->flags & SDL_WINDOW_KEYBOARD_GRABBED) {
+                    seat->keyboard.key_inhibitor =
+                        zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(display->key_inhibitor_manager, w->surface, seat->wl_seat);
+                }
+            }
         }
     }
-
-    if (input->xkb.compose_state) {
-        WAYLAND_xkb_compose_state_unref(input->xkb.compose_state);
-    }
-
-    if (input->xkb.compose_table) {
-        WAYLAND_xkb_compose_table_unref(input->xkb.compose_table);
-    }
-
-    if (input->xkb.state) {
-        WAYLAND_xkb_state_unref(input->xkb.state);
-    }
-
-    if (input->xkb.keymap) {
-        WAYLAND_xkb_keymap_unref(input->xkb.keymap);
-    }
-
-    SDL_free(input);
-    d->input = NULL;
 }
 
-bool Wayland_input_enable_relative_pointer(struct SDL_WaylandInput *input)
+void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat)
 {
-    SDL_VideoDevice *vd = SDL_GetVideoDevice();
-    SDL_VideoData *d = input->display;
-    SDL_Window *window;
-
-    if (!d->relative_pointer_manager) {
-        return false;
-    }
+    SDL_VideoData *display = seat->display;
 
-    if (!d->pointer_constraints) {
-        return false;
-    }
+    if (display->pointer_constraints) {
+        const bool has_relative_focus = Wayland_SeatHasRelativePointerFocus(seat);
 
-    if (!input->pointer) {
-        return false;
-    }
+        if (seat->pointer.locked_pointer && (!display->relative_mode_enabled || !has_relative_focus)) {
+            zwp_locked_pointer_v1_destroy(seat->pointer.locked_pointer);
+            seat->pointer.locked_pointer = NULL;
 
-    /* If we have a pointer confine active, we must destroy it here because
-     * creating a locked pointer otherwise would be a protocol error.
-     */
-    for (window = vd->windows; window; window = window->next) {
-        pointer_confine_destroy(window);
-    }
-
-    for (window = vd->windows; window; window = window->next) {
-        Wayland_input_lock_pointer(input, window);
-    }
-
-    d->relative_mouse_mode = 1;
-
-    return true;
-}
-
-bool Wayland_input_disable_relative_pointer(struct SDL_WaylandInput *input)
-{
-    SDL_VideoDevice *vd = SDL_GetVideoDevice();
-    SDL_VideoData *d = input->display;
-    SDL_Window *window;
-
-    for (window = vd->windows; window; window = window->next) {
-        Wayland_input_unlock_pointer(input, window);
-    }
+            // Update the cursor after destroying a relative move lock.
+            Wayland_SeatUpdateCursor(seat);
+        }
 
-    d->relative_mouse_mode = 0;
+        if (seat->pointer.wl_pointer) {
+            // If relative mode is active, and the pointer focus matches the keyboard focus, lock it.
+            if (seat->display->relative_mode_enabled && has_relative_focus) {
+                if (!seat->pointer.locked_pointer) {
+                    // Creating a lock on a surface with an active confinement region on the same seat is a protocol error.
+                    if (seat->pointer.confined_pointer) {
+                        zwp_confined_pointer_v1_destroy(seat->pointer.confined_pointer);
+                        seat->pointer.confined_pointer = NULL;
+                    }
 
-    for (window = vd->windows; window; window = window->next) {
-        Wayland_input_confine_pointer(input, window);
-    }
+                    seat->pointer.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(display->pointer_constraints,
+                                                                                           seat->pointer.focus->surface,
+                                                                                           seat->pointer.wl_pointer, NULL,
+                                                                                           ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+                    zwp_locked_pointer_v1_add_listener(seat->pointer.locked_pointer, &locked_pointer_listener, seat);
 
-    return true;
-}
+                    // Ensure that the relative pointer is hidden, if required.
+                    Wayland_SeatUpdateCursor(seat);
+                }
 
-bool Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *window)
-{
-    SDL_WindowData *w = window->internal;
-    SDL_VideoData *d = input->display;
-    struct wl_region *confine_rect;
+                // Locked the cursor for relative mode, nothing more to do.
+                return;
+            }
 
-    if (!d->pointer_constraints) {
-        return SDL_SetError("Failed to confine pointer: compositor lacks support for the required zwp_pointer_constraints_v1 protocol");
-    }
+            /* A confine may already be active, in which case we should destroy it and create a new one
+             * in case it changed size.
+             */
+            if (seat->pointer.confined_pointer) {
+                zwp_confined_pointer_v1_destroy(seat->pointer.confined_pointer);
+                seat->pointer.confined_pointer = NULL;
+            }
 
-    if (!input->pointer) {
-        return SDL_SetError("No pointer to confine");
-    }
+            SDL_WindowData *w = seat->pointer.focus;
+            if (!w) {
+                return;
+            }
 
-    // The confinement region will be created when the window is mapped.
-    if (w->shell_surface_status != WAYLAND_SHELL_SURFACE_STATUS_SHOWN) {
-        return true;
-    }
+            SDL_Window *window = w->sdlwindow;
 
-    /* A confine may already be active, in which case we should destroy it and
-     * create a new one.
-     */
-    pointer_confine_destroy(window);
+            // Don't confine the pointer if the window doesn't have input focus, or it shouldn't be confined.
+            if (!(window->flags & SDL_WINDOW_INPUT_FOCUS) ||
+                (!(window->flags & SDL_WINDOW_MOUSE_GRABBED) && SDL_RectEmpty(&window->mouse_rect))) {
+                return;
+            }
 
-    /* We cannot create a confine if the pointer is already locked. Defer until
-     * the pointer is unlocked.
-     */
-    if (d->relative_mouse_mode) {
-        return true;
-    }
+            struct wl_region *confine_rect = NULL;
+            if (!SDL_RectEmpty(&window->mouse_rect)) {
+                SDL_Rect scaled_mouse_rect;
+                Wayland_GetScaledMouseRect(window, &scaled_mouse_rect);
 
-    // Don't confine the pointer if it shouldn't be confined.
-    if (SDL_RectEmpty(&window->mouse_rect) && !(window->flags & SDL_WINDOW_MOUSE_GRABBED)) {
-        return true;
-    }
+                /* Some compositors will only confine the pointer to an arbitrary region if the pointer
+                 * is already within the confinement area when it is created.
+                 */
+                if (SDL_PointInRect(&seat->pointer.last_motion, &scaled_mouse_rect)) {
+                    confine_rect = wl_compositor_create_region(display->compositor);
+                    wl_region_add(confine_rect,
+                                  scaled_mouse_rect.x,
+                                  scaled_mouse_rect.y,
+                                  scaled_mouse_rect.w,
+                                  scaled_mouse_rect.h);
+                } else {
+                    /* Warp the pointer to the closest point within the confinement zone if outside,
+                     * The confinement region will be created when a true position event is received.
+                     */
+                    int closest_x = seat->pointer.last_motion.x;
+                    int closest_y = seat->pointer.last_motion.y;
 
-    if (SDL_RectEmpty(&window->mouse_rect)) {
-        confine_rect = NULL;
-    } else {
-        SDL_Rect scaled_mouse_rect;
+                    if (closest_x < scaled_mouse_rect.x) {
+                        closest_x = scaled_mouse_rect.x;
+                    } else if (closest_x >= scaled_mouse_rect.x + scaled_mouse_rect.w) {
+                        closest_x = (scaled_mouse_rect.x + scaled_mouse_rect.w) - 1;
+                    }
 
-        scaled_mouse_rect.x = (int)SDL_floor(window->mouse_rect.x / w->pointer_scale.x);
-        scaled_mouse_rect.y = (int)SDL_floor(window->mouse_rect.y / w->pointer_scale.y);
-        scaled_mouse_rect.w = (int)SDL_ceil(window->mouse_rect.w / w->pointer_scale.x);
-        scaled_mouse_rect.h = (int)SDL_ceil(window->mouse_rect.h / w->pointer_scale.y);
+                    if (closest_y < scaled_mouse_rect.y) {
+                        closest_y = scaled_mouse_rect.y;
+                    } else if (closest_y >= scaled_mouse_rect.y + scaled_mouse_rect.h) {
+                        closest_y = (scaled_mouse_rect.y + scaled_mouse_rect.h) - 1;
+                    }
 
-        confine_rect = wl_compositor_create_region(d->compositor);
-        wl_region_add(confine_rect,
-                      scaled_mouse_rect.x,
-                      scaled_mouse_rect.y,
-                      scaled_mouse_rect.w,
-                      scaled_mouse_rect.h);
-    }
+                    Wayland_SeatWarpMouse(seat, w, closest_x, closest_y);
+                }
+            }
 
-    struct zwp_confined_pointer_v1 *confined_pointer =
-        zwp_pointer_constraints_v1_confine_pointer(d->pointer_constraints,
-                                                   w->surface,
-                                                   input->pointer,
-                                                   confine_rect,
-                                                   ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
-    zwp_confined_pointer_v1_add_listener(confined_pointer,
-                                         &confined_pointer_listener,
-                                         window);
+            if (confine_rect || (window->flags & SDL_WINDOW_MOUSE_GRABBED)) {
+                seat->pointer.confined_pointer =
+                    zwp_pointer_constraints_v1_confine_pointer(display->pointer_constraints,
+                                                               w->surface,
+                                                               seat->pointer.wl_pointer,
+                                                               confine_rect,
+                                                               ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+                zwp_confined_pointer_v1_add_listener(seat->pointer.confined_pointer,
+                                                     &confined_pointer_listener,
+                                                     window);
+
+                if (confine_rect) {
+                    wl_region_destroy(confine_rect);
+                }
 
-    if (confine_rect) {
-        wl_region_destroy(confine_rect);
+                // Commit the new confinement region immediately.
+                wl_surface_commit(w->surface);
+            }
+        }
     }
-
-    // Commit the double-buffered confinement region.
-    wl_surface_commit(w->surface);
-
-    w->confined_pointer = confined_pointer;
-    return true;
-}
-
-bool Wayland_input_unconfine_pointer(struct SDL_WaylandInput *input, SDL_Window *window)
-{
-    pointer_confine_destroy(window);
-    return true;
 }
 
-bool Wayland_input_grab_keyboard(SDL_Window *window, struct SDL_WaylandInput *input)
+void Wayland_DisplayUpdatePointerGrabs(SDL_VideoData *display, SDL_WindowData *window)
 {
-    SDL_WindowData *w = window->internal;
-    SDL_VideoData *d = input->display;
-
-    if (!d->key_inhibitor_manager) {
-        return SDL_SetError("Failed to grab keyboard: compositor lacks support for the required zwp_keyboard_shortcuts_inhibit_manager_v1 protocol");
-    }
-
-    if (w->key_inhibitor) {
-        return true;
+    SDL_WaylandSeat *seat;
+    wl_list_for_each (seat, &display->seat_list, link) {
+        if (!window || seat->pointer.focus == window) {
+            Wayland_SeatUpdatePointerGrab(seat);
+        }
     }
-
-    w->key_inhibitor =
-        zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(d->key_inhibitor_manager,
-                                                                    w->surface,
-                                                                    input->seat);
-
-    return true;
 }
 
-bool Wayland_input_ungrab_keyboard(SDL_Window *window)
+void Wayland_DisplayUpdateKeyboardGrabs(SDL_VideoData *display, SDL_WindowData *window)
 {
-    SDL_WindowData *w = window->internal;
-
-    if (w->key_inhibitor) {
-        zwp_keyboard_shortcuts_inhibitor_v1_destroy(w->key_inhibitor);
-        w->key_inhibitor = NULL;
+    SDL_WaylandSeat *seat;
+    wl_list_for_each (seat, &display->seat_list, link) {
+        if (!window || seat->keyboard.focus == window) {
+            Wayland_SeatUpdateKeyboardGrab(seat);
+        }
     }
-
-    return true;
 }
 
-void Wayland_UpdateImplicitGrabSerial(struct SDL_WaylandInput *input, Uint32 serial)
+void Wayland_UpdateImplicitGrabSerial(SDL_WaylandSeat *seat, Uint32 serial)
 {
-    if (serial > input->last_implicit_grab_serial) {
-        input->last_implicit_grab_serial = serial;
-        Wayland_data_device_set_serial(input->data_device, serial);
-        Wayland_primary_selection_device_set_serial(input->primary_selection_device, serial);
+    if (serial > seat->last_implicit_grab_serial) {
+        seat->last_implicit_grab_serial = serial;
+        seat->display->last_implicit_grab_seat = seat;
+        Wayland_data_device_set_serial(seat->data_device, serial);
+        Wayland_primary_selection_device_set_serial(seat->primary_selection_device, serial);
     }
 }
 

+ 129 - 106
src/video/wayland/SDL_waylandevents_c.h

@@ -24,6 +24,7 @@
 #ifndef SDL_waylandevents_h_
 #define SDL_waylandevents_h_
 
+#include "../../events/SDL_keymap_c.h"
 #include "../../events/SDL_mouse_c.h"
 #include "../../events/SDL_pen_c.h"
 
@@ -39,14 +40,6 @@ enum SDL_WaylandAxisEvent
     AXIS_EVENT_VALUE120
 };
 
-struct SDL_WaylandTabletSeat;
-
-typedef struct SDL_WaylandTabletInput
-{
-    struct SDL_WaylandInput *input;
-    struct zwp_tablet_seat_v2 *seat;
-} SDL_WaylandTabletInput;
-
 typedef struct
 {
     int32_t repeat_rate;     // Repeat rate in range of [1, 1000] character(s) per second
@@ -63,125 +56,155 @@ typedef struct
     char text[8];
 } SDL_WaylandKeyboardRepeat;
 
-struct SDL_WaylandInput
+typedef struct SDL_WaylandSeat
 {
     SDL_VideoData *display;
-    struct wl_seat *seat;
-    struct wl_pointer *pointer;
-    struct wl_touch *touch;
-    struct wl_keyboard *keyboard;
+    struct wl_seat *wl_seat;
     SDL_WaylandDataDevice *data_device;
     SDL_WaylandPrimarySelectionDevice *primary_selection_device;
-    SDL_WaylandTextInput *text_input;
-    struct wp_cursor_shape_device_v1 *cursor_shape;
-    struct zwp_relative_pointer_v1 *relative_pointer;
-    struct zwp_input_timestamps_v1 *keyboard_timestamps;
-    struct zwp_input_timestamps_v1 *pointer_timestamps;
-    struct zwp_input_timestamps_v1 *touch_timestamps;
-    SDL_WindowData *pointer_focus;
-    SDL_WindowData *keyboard_focus;
-    SDL_CursorData *current_cursor;
-    SDL_KeyboardID keyboard_id;
-    SDL_MouseID pointer_id;
-    uint32_t pointer_enter_serial;
-
-    // High-resolution event timestamps
-    Uint64 keyboard_timestamp_ns;
-    Uint64 pointer_timestamp_ns;
-    Uint64 touch_timestamp_ns;
-
-    // Last motion location
-    wl_fixed_t sx_w;
-    wl_fixed_t sy_w;
-
-    SDL_MouseButtonFlags buttons_pressed;
-
-    // The serial of the last implicit grab event for window activation and selection data.
-    Uint32 last_implicit_grab_serial;
+    char *name;
+    struct wl_list link;
+
+    Uint32 last_implicit_grab_serial; // The serial of the last implicit grab event for window activation and selection data.
+    Uint32 registry_id;                        // The ID of the Wayland seat object,
 
     struct
     {
-        struct xkb_keymap *keymap;
-        struct xkb_state *state;
-        struct xkb_compose_table *compose_table;
-        struct xkb_compose_state *compose_state;
-
-        // Keyboard layout "group"
-        uint32_t current_group;
-
-        // Modifier bitshift values
-        uint32_t idx_shift;
-        uint32_t idx_ctrl;
-        uint32_t idx_alt;
-        uint32_t idx_gui;
-        uint32_t idx_mod3;
-        uint32_t idx_mod5;
-        uint32_t idx_num;
-        uint32_t idx_caps;
-
-        // Current system modifier flags
-        uint32_t wl_pressed_modifiers;
-        uint32_t wl_locked_modifiers;
-    } xkb;
-
-    // information about axis events on current frame
+        struct wl_keyboard *wl_keyboard;
+        struct zwp_input_timestamps_v1 *timestamps;
+        struct zwp_keyboard_shortcuts_inhibitor_v1 *key_inhibitor;
+        SDL_WindowData *focus;
+        SDL_Keymap *sdl_keymap;
+
+        SDL_WaylandKeyboardRepeat repeat;
+        Uint64 highres_timestamp_ns;
+
+        // Current SDL modifier flags
+        SDL_Keymod pressed_modifiers;
+        SDL_Keymod locked_modifiers;
+
+        SDL_KeyboardID sdl_id;
+        bool is_virtual;
+
+        struct
+        {
+            struct xkb_keymap *keymap;
+            struct xkb_state *state;
+            struct xkb_compose_table *compose_table;
+            struct xkb_compose_state *compose_state;
+
+            // Keyboard layout "group"
+            Uint32 current_group;
+
+            // Modifier bitshift values
+            Uint32 idx_shift;
+            Uint32 idx_ctrl;
+            Uint32 idx_alt;
+            Uint32 idx_gui;
+            Uint32 idx_mod3;
+            Uint32 idx_mod5;
+            Uint32 idx_num;
+            Uint32 idx_caps;
+
+            // Current system modifier flags
+            Uint32 wl_pressed_modifiers;
+            Uint32 wl_locked_modifiers;
+        } xkb;
+    } keyboard;
+
     struct
     {
-        enum SDL_WaylandAxisEvent x_axis_type;
-        float x;
+        struct wl_pointer *wl_pointer;
+        struct zwp_relative_pointer_v1 *relative_pointer;
+        struct zwp_input_timestamps_v1 *timestamps;
+        struct wp_cursor_shape_device_v1 *cursor_shape;
+        struct zwp_locked_pointer_v1 *locked_pointer;
+        struct zwp_confined_pointer_v1 *confined_pointer;
+
+        SDL_WindowData *focus;
+        SDL_CursorData *current_cursor;
+
+        Uint64 highres_timestamp_ns;
+        Uint32 enter_serial;
+        SDL_MouseButtonFlags buttons_pressed;
+        SDL_Point last_motion;
+
+        SDL_MouseID sdl_id;
+
+        // Information about axis events on the current frame
+        struct
+        {
+            enum SDL_WaylandAxisEvent x_axis_type;
+            float x;
+
+            enum SDL_WaylandAxisEvent y_axis_type;
+            float y;
+
+            // Event timestamp in nanoseconds
+            Uint64 timestamp_ns;
+            SDL_MouseWheelDirection direction;
+        } current_axis_info;
+
+        // Cursor state
+        struct
+        {
+            struct wl_surface *surface;
+            struct wp_viewport *viewport;
+
+            // Animation state for legacy animated cursors
+            struct wl_callback *frame_callback;
+            Uint64 last_frame_callback_time_ns;
+            Uint64 current_frame_time_ns;
+            int current_frame;
+        } cursor_state;
+    } pointer;
 
-        enum SDL_WaylandAxisEvent y_axis_type;
-        float y;
-
-        // Event timestamp in nanoseconds
-        Uint64 timestamp_ns;
-        SDL_MouseWheelDirection direction;
-    } pointer_curr_axis_info;
-
-    SDL_WaylandKeyboardRepeat keyboard_repeat;
-
-    SDL_WaylandTabletInput *tablet_input;
+    struct
+    {
+        struct wl_touch *wl_touch;
+        struct zwp_input_timestamps_v1 *timestamps;
+        Uint64 highres_timestamp_ns;
+        struct wl_list points;
+    } touch;
 
-    bool keyboard_is_virtual;
+    struct
+    {
+        struct zwp_text_input_v3 *zwp_text_input;
+        SDL_Rect cursor_rect;
+        bool enabled;
+        bool has_preedit;
+    } text_input;
 
-    // Current SDL modifier flags
-    SDL_Keymod pressed_modifiers;
-    SDL_Keymod locked_modifiers;
-};
+    struct
+    {
+        struct zwp_tablet_seat_v2 *wl_tablet_seat;
+        struct wl_list tool_list;
+    } tablet;
+} SDL_WaylandSeat;
 
 
-extern Uint64 Wayland_GetTouchTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms);
+extern Uint64 Wayland_GetTouchTimestamp(struct SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms);
 
 extern void Wayland_PumpEvents(SDL_VideoDevice *_this);
 extern void Wayland_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window);
 extern int Wayland_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS);
 
-extern void Wayland_create_data_device(SDL_VideoData *d);
-extern void Wayland_create_primary_selection_device(SDL_VideoData *d);
-
-extern void Wayland_create_text_input_manager(SDL_VideoData *d, uint32_t id);
-
-extern void Wayland_input_initialize_seat(SDL_VideoData *d);
-extern void Wayland_display_destroy_input(SDL_VideoData *d);
-
-extern void Wayland_input_init_relative_pointer(SDL_VideoData *d);
-extern bool Wayland_input_enable_relative_pointer(struct SDL_WaylandInput *input);
-extern bool Wayland_input_disable_relative_pointer(struct SDL_WaylandInput *input);
-
-extern bool Wayland_input_lock_pointer(struct SDL_WaylandInput *input, SDL_Window *window);
-extern bool Wayland_input_unlock_pointer(struct SDL_WaylandInput *input, SDL_Window *window);
-
-extern bool Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *window);
-extern bool Wayland_input_unconfine_pointer(struct SDL_WaylandInput *input, SDL_Window *window);
+extern void Wayland_DisplayInitInputTimestampManager(SDL_VideoData *display);
+extern void Wayland_DisplayInitCursorShapeManager(SDL_VideoData *display);
+extern void Wayland_DisplayInitRelativePointerManager(SDL_VideoData *display);
+extern void Wayland_DisplayInitTabletManager(SDL_VideoData *display);
+extern void Wayland_DisplayInitDataDeviceManager(SDL_VideoData *display);
+extern void Wayland_DisplayInitPrimarySelectionDeviceManager(SDL_VideoData *display);
 
-extern bool Wayland_input_grab_keyboard(SDL_Window *window, struct SDL_WaylandInput *input);
-extern bool Wayland_input_ungrab_keyboard(SDL_Window *window);
+extern void Wayland_DisplayCreateTextInputManager(SDL_VideoData *d, uint32_t id);
 
-extern void Wayland_input_init_tablet_support(struct SDL_WaylandInput *input, struct zwp_tablet_manager_v2 *tablet_manager);
-extern void Wayland_input_quit_tablet_support(struct SDL_WaylandInput *input);
+extern void Wayland_DisplayCreateSeat(SDL_VideoData *display, struct wl_seat *wl_seat, Uint32 id);
+extern void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events);
 
-extern void Wayland_RegisterTimestampListeners(struct SDL_WaylandInput *input);
-extern void Wayland_CreateCursorShapeDevice(struct SDL_WaylandInput *input);
+extern bool Wayland_SeatHasRelativePointerFocus(SDL_WaylandSeat *seat);
+extern void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat);
+extern void Wayland_DisplayUpdatePointerGrabs(SDL_VideoData *display, SDL_WindowData *window);
+extern void Wayland_DisplayUpdateKeyboardGrabs(SDL_VideoData *display, SDL_WindowData *window);
 
 /* The implicit grab serial needs to be updated on:
  * - Keyboard key down/up
@@ -190,6 +213,6 @@ extern void Wayland_CreateCursorShapeDevice(struct SDL_WaylandInput *input);
  * - Tablet tool down
  * - Tablet tool button down/up
  */
-extern void Wayland_UpdateImplicitGrabSerial(struct SDL_WaylandInput *input, Uint32 serial);
+extern void Wayland_UpdateImplicitGrabSerial(struct SDL_WaylandSeat *seat, Uint32 serial);
 
 #endif // SDL_waylandevents_h_

+ 145 - 106
src/video/wayland/SDL_waylandkeyboard.c

@@ -51,109 +51,140 @@ void Wayland_QuitKeyboard(SDL_VideoDevice *_this)
 #endif
 }
 
-bool Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
+void Wayland_UpdateTextInput(SDL_VideoData *display)
 {
-    SDL_VideoData *internal = _this->internal;
-    struct SDL_WaylandInput *input = internal->input;
-
-    if (internal->text_input_manager) {
-        if (input && input->text_input) {
-            const SDL_Rect *rect = &input->text_input->cursor_rect;
-            enum zwp_text_input_v3_content_hint hint = ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE;
-            enum zwp_text_input_v3_content_purpose purpose;
-
-            switch (SDL_GetTextInputType(props)) {
-            default:
-            case SDL_TEXTINPUT_TYPE_TEXT:
-                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
-                break;
-            case SDL_TEXTINPUT_TYPE_TEXT_NAME:
-                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME;
-                break;
-            case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
-                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL;
-                break;
-            case SDL_TEXTINPUT_TYPE_TEXT_USERNAME:
-                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
-                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
-                break;
-            case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
-                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD;
-                hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA);
-                break;
-            case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE:
-                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD;
-                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
-                break;
-            case SDL_TEXTINPUT_TYPE_NUMBER:
-                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER;
-                break;
-            case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
-                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN;
-                hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA);
-                break;
-            case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
-                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN;
-                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
-                break;
+    SDL_WaylandSeat *seat = NULL;
+
+    if (display->text_input_manager) {
+        wl_list_for_each(seat, &display->seat_list, link) {
+            SDL_WindowData *focus = seat->keyboard.focus;
+
+            if (seat->text_input.zwp_text_input) {
+                if (focus && focus->text_input_props.active) {
+                    // Enabling will reset all state, so don't do it redundantly.
+                    if (!seat->text_input.enabled) {
+                        seat->text_input.enabled = true;
+                        zwp_text_input_v3_enable(seat->text_input.zwp_text_input);
+
+                        // Now that it's enabled, set the input properties
+                        zwp_text_input_v3_set_content_type(seat->text_input.zwp_text_input, focus->text_input_props.hint, focus->text_input_props.purpose);
+                        if (!SDL_RectEmpty(&focus->sdlwindow->text_input_rect)) {
+                            SDL_copyp(&seat->text_input.cursor_rect, &focus->sdlwindow->text_input_rect);
+
+                            // This gets reset on enable so we have to cache it
+                            zwp_text_input_v3_set_cursor_rectangle(seat->text_input.zwp_text_input,
+                                                                   focus->sdlwindow->text_input_rect.x,
+                                                                   focus->sdlwindow->text_input_rect.y,
+                                                                   focus->sdlwindow->text_input_rect.w,
+                                                                   focus->sdlwindow->text_input_rect.h);
+                        }
+                        zwp_text_input_v3_commit(seat->text_input.zwp_text_input);
+
+                        if (seat->keyboard.xkb.compose_state) {
+                            // Reset compose state so composite and dead keys don't carry over
+                            WAYLAND_xkb_compose_state_reset(seat->keyboard.xkb.compose_state);
+                        }
+                    }
+                } else {
+                    if (seat->text_input.enabled) {
+                        seat->text_input.enabled = false;
+                        SDL_zero(seat->text_input.cursor_rect);
+                        zwp_text_input_v3_disable(seat->text_input.zwp_text_input);
+                        zwp_text_input_v3_commit(seat->text_input.zwp_text_input);
+                    }
+
+                    if (seat->keyboard.xkb.compose_state) {
+                        // Reset compose state so composite and dead keys don't carry over
+                        WAYLAND_xkb_compose_state_reset(seat->keyboard.xkb.compose_state);
+                    }
+                }
             }
+        }
+    }
+}
 
-            switch (SDL_GetTextInputCapitalization(props)) {
-            default:
-            case SDL_CAPITALIZE_NONE:
-                break;
-            case SDL_CAPITALIZE_LETTERS:
-                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE;
-                break;
-            case SDL_CAPITALIZE_WORDS:
-                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE;
-                break;
-            case SDL_CAPITALIZE_SENTENCES:
-                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION;
-                break;
-            }
+bool Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
+{
+    SDL_VideoData *display = _this->internal;
+
+    if (display->text_input_manager) {
+        SDL_WindowData *wind = window->internal;
+        wind->text_input_props.hint = ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE;
+
+        switch (SDL_GetTextInputType(props)) {
+        default:
+        case SDL_TEXTINPUT_TYPE_TEXT:
+            wind->text_input_props.purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
+            break;
+        case SDL_TEXTINPUT_TYPE_TEXT_NAME:
+            wind->text_input_props.purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME;
+            break;
+        case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
+            wind->text_input_props.purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL;
+            break;
+        case SDL_TEXTINPUT_TYPE_TEXT_USERNAME:
+            wind->text_input_props.purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
+            wind->text_input_props.hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
+            break;
+        case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
+            wind->text_input_props.purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD;
+            wind->text_input_props.hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA);
+            break;
+        case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE:
+            wind->text_input_props.purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD;
+            wind->text_input_props.hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
+            break;
+        case SDL_TEXTINPUT_TYPE_NUMBER:
+            wind->text_input_props.purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER;
+            break;
+        case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
+            wind->text_input_props.purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN;
+            wind->text_input_props.hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA);
+            break;
+        case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
+            wind->text_input_props.purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN;
+            wind->text_input_props.hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
+            break;
+        }
 
-            if (SDL_GetTextInputAutocorrect(props)) {
-                hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK);
-            }
-            if (SDL_GetTextInputMultiline(props)) {
-                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE;
-            }
+        switch (SDL_GetTextInputCapitalization(props)) {
+        default:
+        case SDL_CAPITALIZE_NONE:
+            break;
+        case SDL_CAPITALIZE_LETTERS:
+            wind->text_input_props.hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE;
+            break;
+        case SDL_CAPITALIZE_WORDS:
+            wind->text_input_props.hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE;
+            break;
+        case SDL_CAPITALIZE_SENTENCES:
+            wind->text_input_props.hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION;
+            break;
+        }
 
-            zwp_text_input_v3_enable(input->text_input->text_input);
-
-            // Now that it's enabled, set the input properties
-            zwp_text_input_v3_set_content_type(input->text_input->text_input, hint, purpose);
-            if (!SDL_RectEmpty(rect)) {
-                // This gets reset on enable so we have to cache it
-                zwp_text_input_v3_set_cursor_rectangle(input->text_input->text_input,
-                                                       rect->x,
-                                                       rect->y,
-                                                       rect->w,
-                                                       rect->h);
-            }
-            zwp_text_input_v3_commit(input->text_input->text_input);
+        if (SDL_GetTextInputAutocorrect(props)) {
+            wind->text_input_props.hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK);
         }
-    }
+        if (SDL_GetTextInputMultiline(props)) {
+            wind->text_input_props.hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE;
+        }
+
+        wind->text_input_props.active = true;
+        Wayland_UpdateTextInput(display);
 
-    if (input && input->xkb.compose_state) {
-        // Reset compose state so composite and dead keys don't carry over
-        WAYLAND_xkb_compose_state_reset(input->xkb.compose_state);
+        return true;
     }
 
-    return Wayland_UpdateTextInputArea(_this, window);
+    return false;
 }
 
 bool Wayland_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window)
 {
-    SDL_VideoData *internal = _this->internal;
-    struct SDL_WaylandInput *input = internal->input;
+    SDL_VideoData *display = _this->internal;
 
-    if (internal->text_input_manager) {
-        if (input && input->text_input) {
-            zwp_text_input_v3_disable(input->text_input->text_input);
-            zwp_text_input_v3_commit(input->text_input->text_input);
-        }
+    if (display->text_input_manager) {
+        window->internal->text_input_props.active = false;
+        Wayland_UpdateTextInput(display);
     }
 #ifdef SDL_USE_IME
     else {
@@ -161,10 +192,6 @@ bool Wayland_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window)
     }
 #endif
 
-    if (input && input->xkb.compose_state) {
-        // Reset compose state so composite and dead keys don't carry over
-        WAYLAND_xkb_compose_state_reset(input->xkb.compose_state);
-    }
     return true;
 }
 
@@ -172,20 +199,22 @@ bool Wayland_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window)
 {
     SDL_VideoData *internal = _this->internal;
     if (internal->text_input_manager) {
-        struct SDL_WaylandInput *input = internal->input;
-        if (input && input->text_input) {
-            if (!SDL_RectsEqual(&window->text_input_rect, &input->text_input->cursor_rect)) {
-                SDL_copyp(&input->text_input->cursor_rect, &window->text_input_rect);
-                zwp_text_input_v3_set_cursor_rectangle(input->text_input->text_input,
-                                                       window->text_input_rect.x,
-                                                       window->text_input_rect.y,
-                                                       window->text_input_rect.w,
-                                                       window->text_input_rect.h);
-                zwp_text_input_v3_commit(input->text_input->text_input);
+        SDL_WaylandSeat *seat;
+
+        wl_list_for_each (seat, &internal->seat_list, link) {
+            if (seat->text_input.zwp_text_input && seat->keyboard.focus == window->internal) {
+                if (!SDL_RectsEqual(&window->text_input_rect, &seat->text_input.cursor_rect)) {
+                    SDL_copyp(&seat->text_input.cursor_rect, &window->text_input_rect);
+                    zwp_text_input_v3_set_cursor_rectangle(seat->text_input.zwp_text_input,
+                                                           window->text_input_rect.x,
+                                                           window->text_input_rect.y,
+                                                           window->text_input_rect.w,
+                                                           window->text_input_rect.h);
+                    zwp_text_input_v3_commit(seat->text_input.zwp_text_input);
+                }
             }
         }
     }
-
 #ifdef SDL_USE_IME
     else {
         SDL_IME_UpdateTextInputArea(window);
@@ -196,13 +225,23 @@ bool Wayland_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window)
 
 bool Wayland_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
 {
-    /* In reality we just want to return true when the screen keyboard is the
+    /* In reality, we just want to return true when the screen keyboard is the
      * _only_ way to get text input. So, in addition to checking for the text
      * input protocol, make sure we don't have any physical keyboards either.
      */
     SDL_VideoData *internal = _this->internal;
-    bool haskeyboard = (internal->input != NULL) && (internal->input->keyboard != NULL);
+    SDL_WaylandSeat *seat;
     bool hastextmanager = (internal->text_input_manager != NULL);
+    bool haskeyboard = false;
+
+    // Check for at least one keyboard object on one seat.
+    wl_list_for_each (seat, &internal->seat_list, link) {
+        if (seat->keyboard.wl_keyboard) {
+            haskeyboard = true;
+            break;
+        }
+    }
+
     return !haskeyboard && hastextmanager;
 }
 

+ 1 - 7
src/video/wayland/SDL_waylandkeyboard.h

@@ -23,18 +23,12 @@
 #ifndef SDL_waylandkeyboard_h_
 #define SDL_waylandkeyboard_h_
 
-typedef struct SDL_WaylandTextInput
-{
-    struct zwp_text_input_v3 *text_input;
-    SDL_Rect cursor_rect;
-    bool has_preedit;
-} SDL_WaylandTextInput;
-
 extern bool Wayland_InitKeyboard(SDL_VideoDevice *_this);
 extern void Wayland_QuitKeyboard(SDL_VideoDevice *_this);
 extern bool Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 extern bool Wayland_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window);
 extern bool Wayland_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window);
+extern void Wayland_UpdateTextInput(SDL_VideoData *display);
 extern bool Wayland_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
 
 #endif // SDL_waylandkeyboard_h_

+ 256 - 172
src/video/wayland/SDL_waylandmouse.c

@@ -63,18 +63,14 @@ typedef struct
 typedef struct
 {
     struct wl_buffer *wl_buffer;
-    Uint32 duration;
+    Uint64 duration_ns;
 } Wayland_SystemCursorFrame;
 
 typedef struct
 {
     Wayland_SystemCursorFrame *frames;
-    struct wl_callback *frame_callback;
-    Uint64 last_frame_callback_time_ms;
-    Uint64 current_frame_time_ms;
-    Uint32 total_duration;
+    Uint64 total_duration_ns;
     int num_frames;
-    int current_frame;
     SDL_SystemCursor id;
 } Wayland_SystemCursor;
 
@@ -86,9 +82,6 @@ struct SDL_CursorData
         Wayland_SystemCursor system;
     } cursor_data;
 
-    struct wl_surface *surface;
-    struct wp_viewport *viewport;
-
     bool is_system_cursor;
 };
 
@@ -298,40 +291,41 @@ struct wl_callback_listener cursor_frame_listener = {
 
 static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time)
 {
-    SDL_CursorData *c = (SDL_CursorData *)data;
+    SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
+    SDL_CursorData *c = (struct SDL_CursorData *)seat->pointer.current_cursor;
 
-    const Uint64 now = SDL_GetTicks();
-    const Uint64 elapsed = (now - c->cursor_data.system.last_frame_callback_time_ms) % c->cursor_data.system.total_duration;
+    const Uint64 now = SDL_GetTicksNS();
+    const Uint64 elapsed = (now - seat->pointer.cursor_state.last_frame_callback_time_ns) % c->cursor_data.system.total_duration_ns;
     Uint64 advance = 0;
-    int next = c->cursor_data.system.current_frame;
+    int next = seat->pointer.cursor_state.current_frame;
 
     wl_callback_destroy(cb);
-    c->cursor_data.system.frame_callback = wl_surface_frame(c->surface);
-    wl_callback_add_listener(c->cursor_data.system.frame_callback, &cursor_frame_listener, data);
+    seat->pointer.cursor_state.frame_callback = wl_surface_frame(seat->pointer.cursor_state.surface);
+    wl_callback_add_listener(seat->pointer.cursor_state.frame_callback, &cursor_frame_listener, data);
 
-    c->cursor_data.system.current_frame_time_ms += elapsed;
+    seat->pointer.cursor_state.current_frame_time_ns += elapsed;
 
     // Calculate the next frame based on the elapsed duration.
-    for (Uint64 t = c->cursor_data.system.frames[next].duration; t <= c->cursor_data.system.current_frame_time_ms; t += c->cursor_data.system.frames[next].duration) {
+    for (Uint64 t = c->cursor_data.system.frames[next].duration_ns; t <= seat->pointer.cursor_state.current_frame_time_ns; t += c->cursor_data.system.frames[next].duration_ns) {
         next = (next + 1) % c->cursor_data.system.num_frames;
         advance = t;
 
         // Make sure we don't end up in an infinite loop if a cursor has frame durations of 0.
-        if (!c->cursor_data.system.frames[next].duration) {
+        if (!c->cursor_data.system.frames[next].duration_ns) {
             break;
         }
     }
 
-    c->cursor_data.system.current_frame_time_ms -= advance;
-    c->cursor_data.system.last_frame_callback_time_ms = now;
-    c->cursor_data.system.current_frame = next;
-    wl_surface_attach(c->surface, c->cursor_data.system.frames[next].wl_buffer, 0, 0);
-    if (wl_surface_get_version(c->surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) {
-        wl_surface_damage_buffer(c->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
+    seat->pointer.cursor_state.current_frame_time_ns -= advance;
+    seat->pointer.cursor_state.last_frame_callback_time_ns = now;
+    seat->pointer.cursor_state.current_frame = next;
+    wl_surface_attach(seat->pointer.cursor_state.surface, c->cursor_data.system.frames[next].wl_buffer, 0, 0);
+    if (wl_surface_get_version(seat->pointer.cursor_state.surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) {
+        wl_surface_damage_buffer(seat->pointer.cursor_state.surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
     } else {
-        wl_surface_damage(c->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
+        wl_surface_damage(seat->pointer.cursor_state.surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
     }
-    wl_surface_commit(c->surface);
+    wl_surface_commit(seat->pointer.cursor_state.surface);
 }
 
 static bool Wayland_GetSystemCursor(SDL_VideoData *vdata, SDL_CursorData *cdata, int *scale, int *dst_size, int *hot_x, int *hot_y)
@@ -415,11 +409,11 @@ static bool Wayland_GetSystemCursor(SDL_VideoData *vdata, SDL_CursorData *cdata,
 
     // ... Set the cursor data, finally.
     cdata->cursor_data.system.num_frames = cursor->image_count;
-    cdata->cursor_data.system.total_duration = 0;
+    cdata->cursor_data.system.total_duration_ns = 0;
     for (int i = 0; i < cursor->image_count; ++i) {
         cdata->cursor_data.system.frames[i].wl_buffer = WAYLAND_wl_cursor_image_get_buffer(cursor->images[i]);
-        cdata->cursor_data.system.frames[i].duration = cursor->images[i]->delay;
-        cdata->cursor_data.system.total_duration += cursor->images[i]->delay;
+        cdata->cursor_data.system.frames[i].duration_ns = SDL_MS_TO_NS((Uint64)cursor->images[i]->delay);
+        cdata->cursor_data.system.total_duration_ns += cdata->cursor_data.system.frames[i].duration_ns;
     }
 
     *scale = SDL_ceil(scale_factor) == scale_factor ? (int)scale_factor : 0;
@@ -533,10 +527,8 @@ static bool Wayland_GetCustomCursor(SDL_Cursor *cursor, struct wl_buffer **buffe
 
 static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
 {
-    SDL_VideoDevice *vd = SDL_GetVideoDevice();
-    SDL_VideoData *wd = vd->internal;
-
     SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor));
+
     if (cursor) {
         SDL_CursorData *data = SDL_calloc(1, sizeof(*data));
         if (!data) {
@@ -547,7 +539,6 @@ static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot
         WAYLAND_wl_list_init(&data->cursor_data.custom.scaled_cursor_cache);
         data->cursor_data.custom.hot_x = hot_x;
         data->cursor_data.custom.hot_y = hot_y;
-        data->surface = wl_compositor_create_surface(wd->compositor);
 
         data->cursor_data.custom.sdl_cursor_surface = surface;
         ++surface->refcount;
@@ -563,8 +554,8 @@ static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot
 
 static SDL_Cursor *Wayland_CreateSystemCursor(SDL_SystemCursor id)
 {
-    SDL_VideoData *data = SDL_GetVideoDevice()->internal;
     SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor));
+
     if (cursor) {
         SDL_CursorData *cdata = SDL_calloc(1, sizeof(*cdata));
         if (!cdata) {
@@ -573,16 +564,6 @@ static SDL_Cursor *Wayland_CreateSystemCursor(SDL_SystemCursor id)
         }
         cursor->internal = cdata;
 
-        /* The surface is only necessary if the cursor shape manager is not present.
-         *
-         * Note that we can't actually set any other cursor properties, as this
-         * is window-specific. See Wayland_GetSystemCursor for the rest!
-         */
-        if (!data->cursor_shape_manager) {
-            cdata->surface = wl_compositor_create_surface(data->compositor);
-            wl_surface_set_user_data(cdata->surface, NULL);
-        }
-
         cdata->cursor_data.system.id = id;
         cdata->is_system_cursor = true;
     }
@@ -598,18 +579,28 @@ static SDL_Cursor *Wayland_CreateDefaultCursor(void)
 
 static void Wayland_FreeCursorData(SDL_CursorData *d)
 {
-    SDL_VideoDevice *vd = SDL_GetVideoDevice();
-    struct SDL_WaylandInput *input = vd->internal->input;
+    SDL_VideoDevice *video_device = SDL_GetVideoDevice();
+    SDL_VideoData *video_data = video_device->internal;
+    SDL_WaylandSeat *seat;
+
+    // Stop any frame callbacks and detach buffers associated with the cursor being destroyed.
+    wl_list_for_each (seat, &video_data->seat_list, link)
+    {
+        if (seat->pointer.current_cursor == d) {
+            if (seat->pointer.cursor_state.frame_callback) {
+                wl_callback_destroy(seat->pointer.cursor_state.frame_callback);
+                seat->pointer.cursor_state.frame_callback = NULL;
+            }
+            if (seat->pointer.cursor_state.surface) {
+                wl_surface_attach(seat->pointer.cursor_state.surface, NULL, 0, 0);
+            }
 
-    if (input->current_cursor == d) {
-        input->current_cursor = NULL;
+            seat->pointer.current_cursor = NULL;
+        }
     }
 
     // Buffers for system cursors must not be destroyed.
     if (d->is_system_cursor) {
-        if (d->cursor_data.system.frame_callback) {
-            wl_callback_destroy(d->cursor_data.system.frame_callback);
-        }
         SDL_free(d->cursor_data.system.frames);
     } else {
         Wayland_ScaledCustomCursor *c, *temp;
@@ -620,16 +611,6 @@ static void Wayland_FreeCursorData(SDL_CursorData *d)
 
         SDL_DestroySurface(d->cursor_data.custom.sdl_cursor_surface);
     }
-
-    if (d->viewport) {
-        wp_viewport_destroy(d->viewport);
-        d->viewport = NULL;
-    }
-
-    if (d->surface) {
-        wl_surface_destroy(d->surface);
-        d->surface = NULL;
-    }
 }
 
 static void Wayland_FreeCursor(SDL_Cursor *cursor)
@@ -649,7 +630,7 @@ static void Wayland_FreeCursor(SDL_Cursor *cursor)
     SDL_free(cursor);
 }
 
-static void Wayland_SetSystemCursorShape(struct SDL_WaylandInput *input, SDL_SystemCursor id)
+static void Wayland_SetSystemCursorShape(SDL_WaylandSeat *seat, SDL_SystemCursor id)
 {
     Uint32 shape;
 
@@ -719,110 +700,140 @@ static void Wayland_SetSystemCursorShape(struct SDL_WaylandInput *input, SDL_Sys
         shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;
     }
 
-    wp_cursor_shape_device_v1_set_shape(input->cursor_shape, input->pointer_enter_serial, shape);
+    wp_cursor_shape_device_v1_set_shape(seat->pointer.cursor_shape, seat->pointer.enter_serial, shape);
 }
 
-static bool Wayland_ShowCursor(SDL_Cursor *cursor)
+static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
 {
-    SDL_VideoDevice *vd = SDL_GetVideoDevice();
-    SDL_VideoData *d = vd->internal;
-    struct SDL_WaylandInput *input = d->input;
-    struct wl_pointer *pointer = d->pointer;
-    struct wl_buffer *buffer = NULL;
-    int scale = 1;
-    int dst_width = 0;
-    int dst_height = 0;
-    int hot_x;
-    int hot_y;
+    if (seat->pointer.wl_pointer) {
+        struct wl_buffer *buffer = NULL;
+        int scale = 1;
+        int dst_width = 0;
+        int dst_height = 0;
+        int hot_x;
+        int hot_y;
+        SDL_CursorData *cursor_data = cursor ? cursor->internal : NULL;
+
+        // Stop the frame callback for old animated cursors.
+        if (seat->pointer.cursor_state.frame_callback && cursor_data != seat->pointer.current_cursor) {
+            wl_callback_destroy(seat->pointer.cursor_state.frame_callback);
+            seat->pointer.cursor_state.frame_callback = NULL;
+        }
 
-    if (!pointer) {
-        return false;
-    }
+        if (cursor) {
+            if (cursor_data == seat->pointer.current_cursor) {
+                return;
+            }
 
-    // Stop the frame callback for old animated cursors.
-    if (input->current_cursor && input->current_cursor->is_system_cursor &&
-        input->current_cursor->cursor_data.system.frame_callback) {
-        wl_callback_destroy(input->current_cursor->cursor_data.system.frame_callback);
-        input->current_cursor->cursor_data.system.frame_callback = NULL;
-    }
+            if (cursor_data->is_system_cursor) {
+                // If the cursor shape protocol is supported, the compositor will draw nicely scaled cursors for us, so nothing more to do.
+                if (seat->pointer.cursor_shape) {
+                    // Don't need the surface or viewport if using the cursor shape protocol.
+                    if (seat->pointer.cursor_state.surface) {
+                        wl_pointer_set_cursor(seat->pointer.wl_pointer, seat->pointer.enter_serial, NULL, 0, 0);
+                        wl_surface_destroy(seat->pointer.cursor_state.surface);
+                        seat->pointer.cursor_state.surface = NULL;
+                    }
+                    if (seat->pointer.cursor_state.viewport) {
+                        wp_viewport_destroy(seat->pointer.cursor_state.viewport);
+                        seat->pointer.cursor_state.viewport = NULL;
+                    }
+
+                    Wayland_SetSystemCursorShape(seat, cursor_data->cursor_data.system.id);
+                    seat->pointer.current_cursor = cursor_data;
+
+                    return;
+                }
 
-    if (cursor) {
-        SDL_CursorData *data = cursor->internal;
-
-        if (data->is_system_cursor) {
-            // If the cursor shape protocol is supported, the compositor will draw nicely scaled cursors for us, so nothing more to do.
-            if (input->cursor_shape) {
-                Wayland_SetSystemCursorShape(input, data->cursor_data.system.id);
-                input->current_cursor = data;
-                return true;
-            }
+                if (!Wayland_GetSystemCursor(seat->display, cursor_data, &scale, &dst_width, &hot_x, &hot_y)) {
+                    return;
+                }
 
-            if (!Wayland_GetSystemCursor(d, data, &scale, &dst_width, &hot_x, &hot_y)) {
-                return false;
-            }
+                dst_height = dst_width;
 
-            dst_height = dst_width;
-            wl_surface_attach(data->surface, data->cursor_data.system.frames[0].wl_buffer, 0, 0);
+                if (!seat->pointer.cursor_state.surface) {
+                    seat->pointer.cursor_state.surface = wl_compositor_create_surface(seat->display->compositor);
+                }
+                wl_surface_attach(seat->pointer.cursor_state.surface, cursor_data->cursor_data.system.frames[0].wl_buffer, 0, 0);
+
+                // If more than one frame is available, create a frame callback to run the animation.
+                if (cursor_data->cursor_data.system.num_frames > 1) {
+                    seat->pointer.cursor_state.last_frame_callback_time_ns = SDL_GetTicks();
+                    seat->pointer.cursor_state.current_frame_time_ns = 0;
+                    seat->pointer.cursor_state.current_frame = 0;
+                    seat->pointer.cursor_state.frame_callback = wl_surface_frame(seat->pointer.cursor_state.surface);
+                    wl_callback_add_listener(seat->pointer.cursor_state.frame_callback, &cursor_frame_listener, seat);
+                }
+            } else {
+                if (!Wayland_GetCustomCursor(cursor, &buffer, &scale, &dst_width, &dst_height, &hot_x, &hot_y)) {
+                    return;
+                }
 
-            // If more than one frame is available, create a frame callback to run the animation.
-            if (data->cursor_data.system.num_frames > 1) {
-                data->cursor_data.system.last_frame_callback_time_ms = SDL_GetTicks();
-                data->cursor_data.system.current_frame_time_ms = 0;
-                data->cursor_data.system.current_frame = 0;
-                data->cursor_data.system.frame_callback = wl_surface_frame(data->surface);
-                wl_callback_add_listener(data->cursor_data.system.frame_callback, &cursor_frame_listener, data);
+                if (!seat->pointer.cursor_state.surface) {
+                    seat->pointer.cursor_state.surface = wl_compositor_create_surface(seat->display->compositor);
+                }
+                wl_surface_attach(seat->pointer.cursor_state.surface, buffer, 0, 0);
             }
-        } else {
-            if (!Wayland_GetCustomCursor(cursor, &buffer, &scale, &dst_width, &dst_height, &hot_x, &hot_y)) {
-                return false;
+
+            // A scale value of 0 indicates that a viewport with the returned destination size should be used.
+            if (!scale) {
+                if (!seat->pointer.cursor_state.viewport) {
+                    seat->pointer.cursor_state.viewport = wp_viewporter_get_viewport(seat->display->viewporter, seat->pointer.cursor_state.surface);
+                }
+                wl_surface_set_buffer_scale(seat->pointer.cursor_state.surface, 1);
+                wp_viewport_set_source(seat->pointer.cursor_state.viewport, wl_fixed_from_int(-1), wl_fixed_from_int(-1), wl_fixed_from_int(-1), wl_fixed_from_int(-1));
+                wp_viewport_set_destination(seat->pointer.cursor_state.viewport, dst_width, dst_height);
+            } else {
+                if (seat->pointer.cursor_state.viewport) {
+                    wp_viewport_destroy(seat->pointer.cursor_state.viewport);
+                    seat->pointer.cursor_state.viewport = NULL;
+                }
+                wl_surface_set_buffer_scale(seat->pointer.cursor_state.surface, scale);
             }
 
-            wl_surface_attach(data->surface, buffer, 0, 0);
-        }
+            wl_pointer_set_cursor(seat->pointer.wl_pointer, seat->pointer.enter_serial, seat->pointer.cursor_state.surface, hot_x, hot_y);
 
-        // A scale value of 0 indicates that a viewport with the returned destination size should be used.
-        if (!scale) {
-            if (!data->viewport) {
-                data->viewport = wp_viewporter_get_viewport(d->viewporter, data->surface);
+            if (wl_surface_get_version(seat->pointer.cursor_state.surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) {
+                wl_surface_damage_buffer(seat->pointer.cursor_state.surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
+            } else {
+                wl_surface_damage(seat->pointer.cursor_state.surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
             }
-            wl_surface_set_buffer_scale(data->surface, 1);
-            wp_viewport_set_source(data->viewport, wl_fixed_from_int(-1), wl_fixed_from_int(-1), wl_fixed_from_int(-1), wl_fixed_from_int(-1));
-            wp_viewport_set_destination(data->viewport, dst_width, dst_height);
+
+            seat->pointer.current_cursor = cursor_data;
+            wl_surface_commit(seat->pointer.cursor_state.surface);
         } else {
-            if (data->viewport) {
-                wp_viewport_destroy(data->viewport);
-                data->viewport = NULL;
-            }
-            wl_surface_set_buffer_scale(data->surface, scale);
+            seat->pointer.current_cursor = NULL;
+            wl_pointer_set_cursor(seat->pointer.wl_pointer, seat->pointer.enter_serial, NULL, 0, 0);
         }
+    }
+}
 
-        wl_pointer_set_cursor(pointer, input->pointer_enter_serial, data->surface, hot_x, hot_y);
+static bool Wayland_ShowCursor(SDL_Cursor *cursor)
+{
+    SDL_VideoDevice *vd = SDL_GetVideoDevice();
+    SDL_VideoData *d = vd->internal;
+    SDL_Mouse *mouse = SDL_GetMouse();
+    SDL_WaylandSeat *seat;
 
-        if (wl_surface_get_version(data->surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) {
-            wl_surface_damage_buffer(data->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
-        } else {
-            wl_surface_damage(data->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
+    wl_list_for_each (seat, &d->seat_list, link) {
+        if (mouse->focus && mouse->focus->internal == seat->pointer.focus) {
+            Wayland_SeatSetCursor(seat, cursor);
+        } else if (!seat->pointer.focus) {
+            Wayland_SeatSetCursor(seat, NULL);
         }
-
-        wl_surface_commit(data->surface);
-        input->current_cursor = data;
-    } else {
-        input->current_cursor = NULL;
-        wl_pointer_set_cursor(pointer, input->pointer_enter_serial, NULL, 0, 0);
     }
 
     return true;
 }
 
-static bool Wayland_WarpMouse(SDL_Window *window, float x, float y)
+void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float x, float y)
 {
     SDL_VideoDevice *vd = SDL_GetVideoDevice();
     SDL_VideoData *d = vd->internal;
-    SDL_WindowData *wind = window->internal;
-    struct SDL_WaylandInput *input = d->input;
 
-    if (d->pointer_constraints) {
-        const bool toggle_lock = !wind->locked_pointer;
+    if (seat->pointer.wl_pointer) {
+        bool toggle_lock = !seat->pointer.locked_pointer;
+        bool update_grabs = false;
 
         /* The pointer confinement protocol allows setting a hint to warp the pointer,
          * but only when the pointer is locked.
@@ -830,22 +841,51 @@ static bool Wayland_WarpMouse(SDL_Window *window, float x, float y)
          * Lock the pointer, set the position hint, unlock, and hope for the best.
          */
         if (toggle_lock) {
-            Wayland_input_lock_pointer(input, window);
-        }
-        if (wind->locked_pointer) {
-            const wl_fixed_t f_x = wl_fixed_from_double(x / wind->pointer_scale.x);
-            const wl_fixed_t f_y = wl_fixed_from_double(y / wind->pointer_scale.y);
-            zwp_locked_pointer_v1_set_cursor_position_hint(wind->locked_pointer, f_x, f_y);
-            wl_surface_commit(wind->surface);
+            if (seat->pointer.confined_pointer) {
+                zwp_confined_pointer_v1_destroy(seat->pointer.confined_pointer);
+                seat->pointer.confined_pointer = NULL;
+                update_grabs = true;
+            }
+            seat->pointer.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(d->pointer_constraints, window->surface,
+                                                                                   seat->pointer.wl_pointer, NULL,
+                                                                                   ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT);
         }
+
+        const wl_fixed_t f_x = wl_fixed_from_double(x / window->pointer_scale.x);
+        const wl_fixed_t f_y = wl_fixed_from_double(y / window->pointer_scale.y);
+        zwp_locked_pointer_v1_set_cursor_position_hint(seat->pointer.locked_pointer, f_x, f_y);
+        wl_surface_commit(window->surface);
+
         if (toggle_lock) {
-            Wayland_input_unlock_pointer(input, window);
+            zwp_locked_pointer_v1_destroy(seat->pointer.locked_pointer);
+            seat->pointer.locked_pointer = NULL;
+
+            if (update_grabs) {
+                Wayland_SeatUpdatePointerGrab(seat);
+            }
         }
 
         /* NOTE: There is a pending warp event under discussion that should replace this when available.
          * https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/340
          */
-        SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, x, y);
+        SDL_SendMouseMotion(0, window->sdlwindow, seat->pointer.sdl_id, false, x, y);
+    }
+}
+
+static bool Wayland_WarpMouseRelative(SDL_Window *window, float x, float y)
+{
+    SDL_VideoDevice *vd = SDL_GetVideoDevice();
+    SDL_VideoData *d = vd->internal;
+    SDL_WindowData *wind = window->internal;
+    SDL_WaylandSeat *seat;
+
+    if (d->pointer_constraints) {
+        wl_list_for_each (seat, &d->seat_list, link) {
+            if (wind == seat->pointer.focus ||
+                (!seat->pointer.focus && wind == seat->keyboard.focus)) {
+                Wayland_SeatWarpMouse(seat, wind, x, y);
+            }
+        }
     } else {
         return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required zwp_pointer_confinement_v1 protocol");
     }
@@ -857,16 +897,33 @@ static bool Wayland_WarpMouseGlobal(float x, float y)
 {
     SDL_VideoDevice *vd = SDL_GetVideoDevice();
     SDL_VideoData *d = vd->internal;
-    struct SDL_WaylandInput *input = d->input;
-    SDL_WindowData *wind = input->pointer_focus;
+    SDL_WaylandSeat *seat;
+
+    if (d->pointer_constraints) {
+        wl_list_for_each (seat, &d->seat_list, link) {
+            SDL_WindowData *wind = seat->pointer.focus ? seat->pointer.focus : seat->keyboard.focus;
 
-    // If the client wants the coordinates warped to within the focused window, just convert the coordinates to relative.
-    if (wind) {
-        SDL_Window *window = wind->sdlwindow;
-        return Wayland_WarpMouse(window, x - (float)window->x, y - (float)window->y);
+            // If the client wants the coordinates warped to within a focused window, just convert the coordinates to relative.
+            if (wind) {
+                SDL_Window *window = wind->sdlwindow;
+
+                int abs_x, abs_y;
+                SDL_RelativeToGlobalForWindow(window, window->x, window->y, &abs_x, &abs_y);
+
+                const SDL_FPoint p = { x, y };
+                const SDL_FRect r = { abs_x, abs_y, window->w, window->h };
+
+                // Try to warp the cursor if the point is within the seat's focused window.
+                if (SDL_PointInRectFloat(&p, &r)) {
+                    Wayland_SeatWarpMouse(seat, wind, p.x - abs_x, p.y - abs_y);
+                }
+            }
+        }
+    } else {
+        return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required zwp_pointer_confinement_v1 protocol");
     }
 
-    return SDL_SetError("wayland: can't warp the mouse when a window does not have focus");
+    return true;
 }
 
 static bool Wayland_SetRelativeMouseMode(bool enabled)
@@ -874,11 +931,17 @@ static bool Wayland_SetRelativeMouseMode(bool enabled)
     SDL_VideoDevice *vd = SDL_GetVideoDevice();
     SDL_VideoData *data = vd->internal;
 
-    if (enabled) {
-        return Wayland_input_enable_relative_pointer(data->input);
-    } else {
-        return Wayland_input_disable_relative_pointer(data->input);
+    // Relative mode requires both the relative motion and pointer confinement protocols.
+    if (!data->relative_pointer_manager) {
+        return SDL_SetError("Failed to enable relative mode: compositor lacks support for the required zwp_relative_pointer_manager_v1 protocol");
     }
+    if (!data->pointer_constraints) {
+        return SDL_SetError("Failed to enable relative mode: compositor lacks support for the required zwp_pointer_constraints_v1 protocol");
+    }
+
+    data->relative_mode_enabled = enabled;
+    Wayland_DisplayUpdatePointerGrabs(data, NULL);
+    return true;
 }
 
 /* Wayland doesn't support getting the true global cursor position, but it can
@@ -895,18 +958,19 @@ static bool Wayland_SetRelativeMouseMode(bool enabled)
  */
 static SDL_MouseButtonFlags SDLCALL Wayland_GetGlobalMouseState(float *x, float *y)
 {
-    SDL_Window *focus = SDL_GetMouseFocus();
+    SDL_Mouse *mouse = SDL_GetMouse();
     SDL_MouseButtonFlags result = 0;
 
-    if (focus) {
-        SDL_VideoData *viddata = SDL_GetVideoDevice()->internal;
+    // If there is no window with mouse focus, we have no idea what the actual position or button state is.
+    if (mouse->focus) {
         int off_x, off_y;
-
-        result = viddata->input->buttons_pressed;
-        SDL_GetMouseState(x, y);
-        SDL_RelativeToGlobalForWindow(focus, focus->x, focus->y, &off_x, &off_y);
-        *x += off_x;
-        *y += off_y;
+        SDL_RelativeToGlobalForWindow(mouse->focus, mouse->focus->x, mouse->focus->y, &off_x, &off_y);
+        result = SDL_GetMouseState(x, y);
+        *x = mouse->x + off_x;
+        *y = mouse->y + off_y;
+    } else {
+        *x = 0.f;
+        *y = 0.f;
     }
 
     return result;
@@ -978,7 +1042,7 @@ void Wayland_InitMouse(void)
     mouse->CreateSystemCursor = Wayland_CreateSystemCursor;
     mouse->ShowCursor = Wayland_ShowCursor;
     mouse->FreeCursor = Wayland_FreeCursor;
-    mouse->WarpMouse = Wayland_WarpMouse;
+    mouse->WarpMouse = Wayland_WarpMouseRelative;
     mouse->WarpMouseGlobal = Wayland_WarpMouseGlobal;
     mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode;
     mouse->GetGlobalMouseState = Wayland_GetGlobalMouseState;
@@ -1046,12 +1110,32 @@ void Wayland_FiniMouse(SDL_VideoData *data)
     }
 }
 
-void Wayland_SetHitTestCursor(SDL_HitTestResult rc)
+void Wayland_SeatUpdateCursor(SDL_WaylandSeat *seat)
 {
-    if (rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) {
-        SDL_SetCursor(NULL);
+    SDL_Mouse *mouse = SDL_GetMouse();
+    SDL_WindowData *pointer_focus = seat->pointer.focus;
+
+    if (pointer_focus) {
+        const bool has_relative_focus = Wayland_SeatHasRelativePointerFocus(seat);
+
+        if (!seat->display->relative_mode_enabled || !has_relative_focus || mouse->relative_mode_cursor_visible) {
+            const SDL_HitTestResult rc = pointer_focus->hit_test_result;
+
+            if ((seat->display->relative_mode_enabled && has_relative_focus) ||
+                rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) {
+                Wayland_SeatSetCursor(seat, mouse->cur_cursor);
+            } else {
+                Wayland_SeatSetCursor(seat, sys_cursors[rc]);
+            }
+        } else {
+            // Hide the cursor in relative mode, unless requested otherwise by the hint.
+            Wayland_SeatSetCursor(seat, NULL);
+        }
     } else {
-        Wayland_ShowCursor(sys_cursors[rc]);
+        /* The spec states "The cursor actually changes only if the input device focus is one of the
+         * requesting client's surfaces", so just clear the cursor if the seat has no pointer focus.
+         */
+        Wayland_SeatSetCursor(seat, NULL);
     }
 }
 

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

@@ -26,7 +26,8 @@
 
 extern void Wayland_InitMouse(void);
 extern void Wayland_FiniMouse(SDL_VideoData *data);
-extern void Wayland_SetHitTestCursor(SDL_HitTestResult rc);
+extern void Wayland_SeatUpdateCursor(SDL_WaylandSeat *seat);
+extern void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float x, float y);
 #if 0  // TODO RECONNECT: See waylandvideo.c for more information!
 extern void Wayland_RecreateCursors(void);
 #endif // 0

+ 30 - 34
src/video/wayland/SDL_waylandvideo.c

@@ -502,7 +502,6 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols)
 {
     SDL_VideoDevice *device;
     SDL_VideoData *data;
-    struct SDL_WaylandInput *input;
     struct wl_display *display = SDL_GetPointerProperty(SDL_GetGlobalProperties(),
                                                  SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, NULL);
     bool display_is_external = !!display;
@@ -549,32 +548,16 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols)
         return NULL;
     }
 
-    input = SDL_calloc(1, sizeof(*input));
-    if (!input) {
-        SDL_free(data);
-        if (!display_is_external) {
-            WAYLAND_wl_display_disconnect(display);
-        }
-        SDL_WAYLAND_UnloadSymbols();
-        return NULL;
-    }
-
-    input->display = data;
-    input->sx_w = wl_fixed_from_int(0);
-    input->sy_w = wl_fixed_from_int(0);
-    input->xkb.current_group = XKB_GROUP_INVALID;
-
     data->initializing = true;
     data->display = display;
-    data->input = input;
     data->display_externally_owned = display_is_external;
     data->scale_to_display_enabled = SDL_GetHintBoolean(SDL_HINT_VIDEO_WAYLAND_SCALE_TO_DISPLAY, false);
+    WAYLAND_wl_list_init(&data->seat_list);
     WAYLAND_wl_list_init(&external_window_list);
 
     // Initialize all variables that we clean on shutdown
     device = SDL_calloc(1, sizeof(SDL_VideoDevice));
     if (!device) {
-        SDL_free(input);
         SDL_free(data);
         if (!display_is_external) {
             WAYLAND_wl_display_disconnect(display);
@@ -1267,8 +1250,8 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
     } else if (SDL_strcmp(interface, "wl_output") == 0) {
         Wayland_add_display(d, id, SDL_min(version, SDL_WL_OUTPUT_VERSION));
     } else if (SDL_strcmp(interface, "wl_seat") == 0) {
-        d->input->seat = wl_registry_bind(d->registry, id, &wl_seat_interface, SDL_min(SDL_WL_SEAT_VERSION, version));
-        Wayland_input_initialize_seat(d);
+        struct wl_seat *seat = wl_registry_bind(d->registry, id, &wl_seat_interface, SDL_min(SDL_WL_SEAT_VERSION, version));
+        Wayland_DisplayCreateSeat(d, seat, id);
     } else if (SDL_strcmp(interface, "xdg_wm_base") == 0) {
         d->shell.xdg = wl_registry_bind(d->registry, id, &xdg_wm_base_interface, SDL_min(version, 6));
         xdg_wm_base_add_listener(d->shell.xdg, &shell_listener_xdg, NULL);
@@ -1276,7 +1259,7 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
         d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
     } else if (SDL_strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) {
         d->relative_pointer_manager = wl_registry_bind(d->registry, id, &zwp_relative_pointer_manager_v1_interface, 1);
-        Wayland_input_init_relative_pointer(d);
+        Wayland_DisplayInitRelativePointerManager(d);
     } else if (SDL_strcmp(interface, "zwp_pointer_constraints_v1") == 0) {
         d->pointer_constraints = wl_registry_bind(d->registry, id, &zwp_pointer_constraints_v1_interface, 1);
     } else if (SDL_strcmp(interface, "zwp_keyboard_shortcuts_inhibit_manager_v1") == 0) {
@@ -1286,18 +1269,18 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
     } else if (SDL_strcmp(interface, "xdg_activation_v1") == 0) {
         d->activation_manager = wl_registry_bind(d->registry, id, &xdg_activation_v1_interface, 1);
     } else if (SDL_strcmp(interface, "zwp_text_input_manager_v3") == 0) {
-        Wayland_create_text_input_manager(d, id);
+        Wayland_DisplayCreateTextInputManager(d, id);
     } else if (SDL_strcmp(interface, "wl_data_device_manager") == 0) {
         d->data_device_manager = wl_registry_bind(d->registry, id, &wl_data_device_manager_interface, SDL_min(3, version));
-        Wayland_create_data_device(d);
+        Wayland_DisplayInitDataDeviceManager(d);
     } else if (SDL_strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0) {
         d->primary_selection_device_manager = wl_registry_bind(d->registry, id, &zwp_primary_selection_device_manager_v1_interface, 1);
-        Wayland_create_primary_selection_device(d);
+        Wayland_DisplayInitPrimarySelectionDeviceManager(d);
     } else if (SDL_strcmp(interface, "zxdg_decoration_manager_v1") == 0) {
         d->decoration_manager = wl_registry_bind(d->registry, id, &zxdg_decoration_manager_v1_interface, 1);
     } else if (SDL_strcmp(interface, "zwp_tablet_manager_v2") == 0) {
         d->tablet_manager = wl_registry_bind(d->registry, id, &zwp_tablet_manager_v2_interface, 1);
-        Wayland_input_init_tablet_support(d->input, d->tablet_manager);
+        Wayland_DisplayInitTabletManager(d);
     } else if (SDL_strcmp(interface, "zxdg_output_manager_v1") == 0) {
         version = SDL_min(version, 3); // Versions 1 through 3 are supported.
         d->xdg_output_manager = wl_registry_bind(d->registry, id, &zxdg_output_manager_v1_interface, version);
@@ -1308,14 +1291,10 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
         d->fractional_scale_manager = wl_registry_bind(d->registry, id, &wp_fractional_scale_manager_v1_interface, 1);
     } else if (SDL_strcmp(interface, "zwp_input_timestamps_manager_v1") == 0) {
         d->input_timestamps_manager = wl_registry_bind(d->registry, id, &zwp_input_timestamps_manager_v1_interface, 1);
-        if (d->input) {
-            Wayland_RegisterTimestampListeners(d->input);
-        }
+        Wayland_DisplayInitInputTimestampManager(d);
     } else if (SDL_strcmp(interface, "wp_cursor_shape_manager_v1") == 0) {
         d->cursor_shape_manager = wl_registry_bind(d->registry, id, &wp_cursor_shape_manager_v1_interface, 1);
-        if (d->input) {
-            Wayland_CreateCursorShapeDevice(d->input);
-        }
+        Wayland_DisplayInitCursorShapeManager(d);
     } else if (SDL_strcmp(interface, "zxdg_exporter_v2") == 0) {
         d->zxdg_exporter_v2 = wl_registry_bind(d->registry, id, &zxdg_exporter_v2_interface, 1);
     } else if (SDL_strcmp(interface, "xdg_wm_dialog_v1") == 0) {
@@ -1336,7 +1315,7 @@ static void display_remove_global(void *data, struct wl_registry *registry, uint
 {
     SDL_VideoData *d = data;
 
-    // We don't get an interface, just an ID, so assume it's a wl_output :shrug:
+    // We don't get an interface, just an ID, so check outputs and seats.
     for (int i = 0; i < d->output_count; ++i) {
         SDL_DisplayData *disp = d->output_list[i];
         if (disp->registry_id == id) {
@@ -1347,7 +1326,21 @@ static void display_remove_global(void *data, struct wl_registry *registry, uint
             }
 
             d->output_count--;
-            break;
+            return;
+        }
+    }
+
+    struct SDL_WaylandSeat *seat, *temp;
+    wl_list_for_each_safe (seat, temp, &d->seat_list, link)
+    {
+        if (seat->registry_id == id) {
+            if (seat->keyboard.wl_keyboard) {
+                SDL_RemoveKeyboard(seat->keyboard.sdl_id, true);
+            }
+            if (seat->keyboard.wl_keyboard) {
+                SDL_RemoveMouse(seat->pointer.sdl_id, true);
+            }
+            Wayland_SeatDestroy(seat, true);
         }
     }
 }
@@ -1487,6 +1480,7 @@ static bool Wayland_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *d
 static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
 {
     SDL_VideoData *data = _this->internal;
+    SDL_WaylandSeat *seat, *tmp;
     int i;
 
     Wayland_FiniMouse(data);
@@ -1497,7 +1491,9 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
     }
     SDL_free(data->output_list);
 
-    Wayland_display_destroy_input(data);
+    wl_list_for_each_safe (seat, tmp, &data->seat_list, link) {
+        Wayland_SeatDestroy(seat, false);
+    }
 
     if (data->pointer_constraints) {
         zwp_pointer_constraints_v1_destroy(data->pointer_constraints);

+ 8 - 4
src/video/wayland/SDL_waylandvideo.h

@@ -32,7 +32,7 @@
 #include "../../core/linux/SDL_ime.h"
 
 struct xkb_context;
-struct SDL_WaylandInput;
+struct SDL_WaylandSeat;
 
 typedef struct
 {
@@ -56,7 +56,6 @@ struct SDL_VideoData
     struct wl_shm *shm;
     SDL_WaylandCursorTheme *cursor_themes;
     int num_cursor_themes;
-    struct wl_pointer *pointer;
     struct
     {
         struct xdg_wm_base *xdg;
@@ -87,12 +86,17 @@ struct SDL_VideoData
     struct zwp_tablet_manager_v2 *tablet_manager;
 
     struct xkb_context *xkb_context;
-    struct SDL_WaylandInput *input;
+
+    struct wl_list seat_list;
+    struct SDL_WaylandSeat *last_implicit_grab_seat;
+    struct SDL_WaylandSeat *last_incoming_data_offer_seat;
+    struct SDL_WaylandSeat *last_incoming_primary_selection_seat;
+
     SDL_DisplayData **output_list;
     int output_count;
     int output_max;
 
-    int relative_mouse_mode;
+    bool relative_mode_enabled;
     bool display_externally_owned;
 
     bool scale_to_display_enabled;

+ 31 - 29
src/video/wayland/SDL_waylandwindow.c

@@ -707,9 +707,6 @@ static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time
             }
         }
 
-        // Create the pointer confinement region, if necessary.
-        Wayland_input_confine_pointer(wind->waylandData->input, wind->sdlwindow);
-
         /* If the window was initially set to the suspended state, send the occluded event now,
          * as we don't want to mark the window as occluded until at least one frame has been submitted.
          */
@@ -2223,9 +2220,17 @@ static const struct xdg_activation_token_v1_listener activation_listener_xdg = {
  */
 static void Wayland_activate_window(SDL_VideoData *data, SDL_WindowData *target_wind, bool set_serial)
 {
-    struct SDL_WaylandInput * input = data->input;
-    SDL_Window *focus = SDL_GetKeyboardFocus();
-    struct wl_surface *requesting_surface = focus ? focus->internal->surface : NULL;
+    SDL_WaylandSeat *seat = data->last_implicit_grab_seat;
+    SDL_WindowData *focus = NULL;
+
+    if (seat) {
+        focus = seat->keyboard.focus;
+        if (!focus) {
+            focus = seat->pointer.focus;
+        }
+    }
+
+    struct wl_surface *requesting_surface = focus ? focus->surface : NULL;
 
     if (data->activation_manager) {
         if (target_wind->activation_token) {
@@ -2249,8 +2254,8 @@ static void Wayland_activate_window(SDL_VideoData *data, SDL_WindowData *target_
             // This specifies the surface from which the activation request is originating, not the activation target surface.
             xdg_activation_token_v1_set_surface(target_wind->activation_token, requesting_surface);
         }
-        if (set_serial && input && input->seat) {
-            xdg_activation_token_v1_set_serial(target_wind->activation_token, input->last_implicit_grab_serial, input->seat);
+        if (set_serial && seat && seat->wl_seat) {
+            xdg_activation_token_v1_set_serial(target_wind->activation_token, seat->last_implicit_grab_serial, seat->wl_seat);
         }
         xdg_activation_token_v1_commit(target_wind->activation_token);
     }
@@ -2505,35 +2510,31 @@ bool Wayland_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window)
      * Just know that this call lets you confine with a rect, SetWindowGrab
      * lets you confine without a rect.
      */
-    if (SDL_RectEmpty(&window->mouse_rect) && !(window->flags & SDL_WINDOW_MOUSE_GRABBED)) {
-        return Wayland_input_unconfine_pointer(data->input, window);
-    } else {
-        return Wayland_input_confine_pointer(data->input, window);
+    if (!data->pointer_constraints) {
+        return SDL_SetError("Failed to grab mouse: compositor lacks support for the required zwp_pointer_constraints_v1 protocol");
     }
+    Wayland_DisplayUpdatePointerGrabs(data, window->internal);
+    return true;
 }
 
 bool Wayland_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed)
 {
     SDL_VideoData *data = _this->internal;
-
-    if (grabbed) {
-        return Wayland_input_confine_pointer(data->input, window);
-    } else if (SDL_RectEmpty(&window->mouse_rect)) {
-        return Wayland_input_unconfine_pointer(data->input, window);
+    if (!data->pointer_constraints) {
+        return SDL_SetError("Failed to grab mouse: compositor lacks support for the required zwp_pointer_constraints_v1 protocol");
     }
-
+    Wayland_DisplayUpdatePointerGrabs(data, window->internal);
     return true;
 }
 
 bool Wayland_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed)
 {
     SDL_VideoData *data = _this->internal;
-
-    if (grabbed) {
-        return Wayland_input_grab_keyboard(window, data->input);
-    } else {
-        return Wayland_input_ungrab_keyboard(window);
+    if (!data->key_inhibitor_manager) {
+        return SDL_SetError("Failed to grab keyboard: compositor lacks support for the required zwp_keyboard_shortcuts_inhibit_manager_v1 protocol");
     }
+    Wayland_DisplayUpdateKeyboardGrabs(data, window->internal);
+    return true;
 }
 
 bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
@@ -2689,10 +2690,6 @@ bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Proper
     }
 #endif
 
-    if (c->relative_mouse_mode) {
-        Wayland_input_enable_relative_pointer(c->input);
-    }
-
     // We may need to create an idle inhibitor for this new window
     Wayland_SuspendScreenSaver(_this);
 
@@ -2994,6 +2991,11 @@ bool Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window)
 void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y)
 {
     SDL_WindowData *wind = window->internal;
+    SDL_WaylandSeat *seat = wind->waylandData->last_implicit_grab_seat;
+
+    if (!seat) {
+        return;
+    }
 
     if (wind->scale_to_display) {
         x = PixelToPoint(window, x);
@@ -3003,13 +3005,13 @@ void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y)
 #ifdef HAVE_LIBDECOR_H
     if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR) {
         if (wind->shell_surface.libdecor.frame) {
-            libdecor_frame_show_window_menu(wind->shell_surface.libdecor.frame, wind->waylandData->input->seat, wind->waylandData->input->last_implicit_grab_serial, x, y);
+            libdecor_frame_show_window_menu(wind->shell_surface.libdecor.frame, seat->wl_seat, seat->last_implicit_grab_serial, x, y);
         }
     } else
 #endif
     if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_XDG_TOPLEVEL) {
         if (wind->shell_surface.xdg.toplevel.xdg_toplevel) {
-            xdg_toplevel_show_window_menu(wind->shell_surface.xdg.toplevel.xdg_toplevel, wind->waylandData->input->seat, wind->waylandData->input->last_implicit_grab_serial, x, y);
+            xdg_toplevel_show_window_menu(wind->shell_surface.xdg.toplevel.xdg_toplevel, seat->wl_seat, seat->last_implicit_grab_serial, x, y);
         }
     }
 }

+ 11 - 6
src/video/wayland/SDL_waylandwindow.h

@@ -30,8 +30,6 @@
 #include "SDL_waylandvideo.h"
 #include "SDL_waylandshmbuffer.h"
 
-struct SDL_WaylandInput;
-
 struct SDL_WindowData
 {
     SDL_Window *sdlwindow;
@@ -99,14 +97,10 @@ struct SDL_WindowData
     } wm_caps;
 
     struct wl_egl_window *egl_window;
-    struct SDL_WaylandInput *keyboard_device;
 #ifdef SDL_VIDEO_OPENGL_EGL
     EGLSurface egl_surface;
 #endif
-    struct zwp_locked_pointer_v1 *locked_pointer;
-    struct zwp_confined_pointer_v1 *confined_pointer;
     struct zxdg_toplevel_decoration_v1 *server_decoration;
-    struct zwp_keyboard_shortcuts_inhibitor_v1 *key_inhibitor;
     struct zwp_idle_inhibitor_v1 *idle_inhibitor;
     struct xdg_activation_token_v1 *activation_token;
     struct wp_viewport *viewport;
@@ -133,6 +127,10 @@ struct SDL_WindowData
     struct Wayland_SHMBuffer *icon_buffers;
     int icon_buffer_count;
 
+    // Keyboard and pointer focus refcount.
+    int keyboard_focus_count;
+    int pointer_focus_count;
+
     struct
     {
         double x;
@@ -184,6 +182,13 @@ struct SDL_WindowData
         int height;
     } toplevel_bounds;
 
+    struct
+    {
+        int hint;
+        int purpose;
+        bool active;
+    } text_input_props;
+
     SDL_DisplayID last_displayID;
     int fullscreen_deadline_count;
     int maximized_restored_deadline_count;

+ 1 - 1
src/video/windows/SDL_windowskeyboard.c

@@ -96,7 +96,7 @@ void WIN_UpdateKeymap(bool send_event)
 
     WIN_ResetDeadKeys();
 
-    keymap = SDL_CreateKeymap();
+    keymap = SDL_CreateKeymap(true);
 
     for (int m = 0; m < SDL_arraysize(mods); ++m) {
         for (int i = 0; i < SDL_arraysize(windows_scancode_table); i++) {

+ 1 - 1
src/video/x11/SDL_x11keyboard.c

@@ -383,7 +383,7 @@ void X11_UpdateKeymap(SDL_VideoDevice *_this, bool send_event)
 
     SDL_VideoData *data = _this->internal;
     SDL_Scancode scancode;
-    SDL_Keymap *keymap = SDL_CreateKeymap();
+    SDL_Keymap *keymap = SDL_CreateKeymap(true);
 
 #ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM
     if (data->xkb.desc_ptr) {