Browse 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 6 tháng trước cách đây
mục cha
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;

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 296 - 269
src/video/wayland/SDL_waylandevents.c


+ 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) {

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác