SDL: Implemented SDL_SetWindowMouseRect() on Wayland

From ae67c7d2da96ce2b2f8ef2318e6fbcc94d3b89ed Mon Sep 17 00:00:00 2001
From: Ethan Lee <[EMAIL REDACTED]>
Date: Tue, 9 Nov 2021 01:30:00 -0500
Subject: [PATCH] Implemented SDL_SetWindowMouseRect() on Wayland

---
 src/video/wayland/SDL_waylandevents.c   | 48 ++++++++++++++++---------
 src/video/wayland/SDL_waylandevents_c.h |  6 ++--
 src/video/wayland/SDL_waylandvideo.c    |  1 +
 src/video/wayland/SDL_waylandwindow.c   | 26 ++++++++++++--
 src/video/wayland/SDL_waylandwindow.h   |  2 ++
 5 files changed, 59 insertions(+), 24 deletions(-)

diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index f52b261fcf..5f20b468ba 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -1670,11 +1670,12 @@ lock_pointer_to_window(SDL_Window *window,
     w->locked_pointer = locked_pointer;
 }
 
-static void pointer_confine_destroy(struct SDL_WaylandInput *input)
+static void pointer_confine_destroy(SDL_Window *window)
 {
-    if (input->confined_pointer) {
-        zwp_confined_pointer_v1_destroy(input->confined_pointer);
-        input->confined_pointer = NULL;
+    SDL_WindowData *w = window->driverdata;
+    if (w->confined_pointer) {
+        zwp_confined_pointer_v1_destroy(w->confined_pointer);
+        w->confined_pointer = NULL;
     }
 }
 
@@ -1696,7 +1697,8 @@ int Wayland_input_lock_pointer(struct SDL_WaylandInput *input)
 
     /* If we have a pointer confine active, we must destroy it here because
      * creating a locked pointer otherwise would be a protocol error. */
-    pointer_confine_destroy(input);
+    for (window = vd->windows; window; window = window->next)
+        pointer_confine_destroy(window);
 
     if (!input->relative_pointer) {
         relative_pointer =
@@ -1736,8 +1738,8 @@ int Wayland_input_unlock_pointer(struct SDL_WaylandInput *input)
 
     d->relative_mouse_mode = 0;
 
-    if (input->confined_pointer_window)
-        Wayland_input_confine_pointer(input->confined_pointer_window, input);
+    for (window = vd->windows; window; window = window->next)
+        Wayland_input_confine_pointer(input, window);
 
     return 0;
 }
@@ -1759,11 +1761,12 @@ static const struct zwp_confined_pointer_v1_listener confined_pointer_listener =
     confined_pointer_unconfined,
 };
 
-int Wayland_input_confine_pointer(SDL_Window *window, struct SDL_WaylandInput *input)
+int Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *window)
 {
     SDL_WindowData *w = window->driverdata;
     SDL_VideoData *d = input->display;
     struct zwp_confined_pointer_v1 *confined_pointer;
+    struct wl_region *confine_rect;
 
     if (!d->pointer_constraints)
         return -1;
@@ -1773,34 +1776,45 @@ int Wayland_input_confine_pointer(SDL_Window *window, struct SDL_WaylandInput *i
 
     /* A confine may already be active, in which case we should destroy it and
      * create a new one. */
-    if (input->confined_pointer)
-        Wayland_input_unconfine_pointer(input);
-
-    input->confined_pointer_window = window;
+    pointer_confine_destroy(window);
 
     /* We cannot create a confine if the pointer is already locked. Defer until
      * the pointer is unlocked. */
     if (d->relative_mouse_mode)
         return 0;
 
+    if (SDL_RectEmpty(&window->mouse_rect)) {
+        confine_rect = NULL;
+    } else {
+        confine_rect = wl_compositor_create_region(d->compositor);
+        wl_region_add(confine_rect,
+                      window->mouse_rect.x,
+                      window->mouse_rect.y,
+                      window->mouse_rect.w,
+                      window->mouse_rect.h);
+    }
+
     confined_pointer =
         zwp_pointer_constraints_v1_confine_pointer(d->pointer_constraints,
                                                    w->surface,
                                                    input->pointer,
-                                                   NULL,
+                                                   confine_rect,
                                                    ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
     zwp_confined_pointer_v1_add_listener(confined_pointer,
                                          &confined_pointer_listener,
                                          window);
 
-    input->confined_pointer = confined_pointer;
+    if (confine_rect != NULL) {
+        wl_region_destroy(confine_rect);
+    }
+
+    w->confined_pointer = confined_pointer;
     return 0;
 }
 
-int Wayland_input_unconfine_pointer(struct SDL_WaylandInput *input)
+int Wayland_input_unconfine_pointer(struct SDL_WaylandInput *input, SDL_Window *window)
 {
-    pointer_confine_destroy(input);
-    input->confined_pointer_window = NULL;
+    pointer_confine_destroy(window);
     return 0;
 }
 
diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h
index 699d2b3779..ee905ce4db 100644
--- a/src/video/wayland/SDL_waylandevents_c.h
+++ b/src/video/wayland/SDL_waylandevents_c.h
@@ -50,8 +50,6 @@ struct SDL_WaylandInput {
     SDL_WaylandDataDevice *data_device;
     SDL_WaylandTextInput *text_input;
     struct zwp_relative_pointer_v1 *relative_pointer;
-    struct zwp_confined_pointer_v1 *confined_pointer;
-    SDL_Window *confined_pointer_window;
     SDL_WindowData *pointer_focus;
     SDL_WindowData *keyboard_focus;
     uint32_t pointer_enter_serial;
@@ -98,8 +96,8 @@ extern void Wayland_display_destroy_pointer_constraints(SDL_VideoData *d);
 extern int Wayland_input_lock_pointer(struct SDL_WaylandInput *input);
 extern int Wayland_input_unlock_pointer(struct SDL_WaylandInput *input);
 
-extern int Wayland_input_confine_pointer(SDL_Window *window, struct SDL_WaylandInput *input);
-extern int Wayland_input_unconfine_pointer(struct SDL_WaylandInput *input);
+extern int Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *window);
+extern int Wayland_input_unconfine_pointer(struct SDL_WaylandInput *input, SDL_Window *window);
 
 extern void Wayland_display_add_relative_pointer_manager(SDL_VideoData *d, uint32_t id);
 extern void Wayland_display_destroy_relative_pointer_manager(SDL_VideoData *d);
diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index 71f6fa8866..777a1fa014 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -247,6 +247,7 @@ Wayland_CreateDevice(int devindex)
     device->SetWindowFullscreen = Wayland_SetWindowFullscreen;
     device->MaximizeWindow = Wayland_MaximizeWindow;
     device->MinimizeWindow = Wayland_MinimizeWindow;
+    device->SetWindowMouseRect = Wayland_SetWindowMouseRect;
     device->SetWindowMouseGrab = Wayland_SetWindowMouseGrab;
     device->SetWindowKeyboardGrab = Wayland_SetWindowKeyboardGrab;
     device->RestoreWindow = Wayland_RestoreWindow;
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index 02443ec4e8..f4494deb2f 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -1153,15 +1153,35 @@ Wayland_MinimizeWindow(_THIS, SDL_Window * window)
     WAYLAND_wl_display_flush(viddata->display);
 }
 
+void
+Wayland_SetWindowMouseRect(_THIS, SDL_Window *window)
+{
+    SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
+
+    /* This may look suspiciously like SetWindowGrab, despite SetMouseRect not
+     * implicitly doing a grab. And you're right! Wayland doesn't let us mess
+     * around with mouse focus whatsoever, so it just happens to be that the
+     * work that we can do in these two functions ends up being the same.
+     *
+     * 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)) {
+        Wayland_input_unconfine_pointer(data->input, window);
+    } else {
+        Wayland_input_confine_pointer(data->input, window);
+    }
+}
+
 void
 Wayland_SetWindowMouseGrab(_THIS, SDL_Window *window, SDL_bool grabbed)
 {
     SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
 
     if (grabbed) {
-        Wayland_input_confine_pointer(window, data->input);
-    } else {
-        Wayland_input_unconfine_pointer(data->input);
+        Wayland_input_confine_pointer(data->input, window);
+    } else if (SDL_RectEmpty(&window->mouse_rect)) {
+        Wayland_input_unconfine_pointer(data->input, window);
     }
 }
 
diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h
index 1aaa645217..f6656997bb 100644
--- a/src/video/wayland/SDL_waylandwindow.h
+++ b/src/video/wayland/SDL_waylandwindow.h
@@ -65,6 +65,7 @@ typedef struct {
     struct SDL_WaylandInput *keyboard_device;
     EGLSurface egl_surface;
     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;
@@ -93,6 +94,7 @@ extern void Wayland_SetWindowFullscreen(_THIS, SDL_Window * window,
                                         SDL_bool fullscreen);
 extern void Wayland_MaximizeWindow(_THIS, SDL_Window * window);
 extern void Wayland_MinimizeWindow(_THIS, SDL_Window * window);
+extern void Wayland_SetWindowMouseRect(_THIS, SDL_Window * window);
 extern void Wayland_SetWindowMouseGrab(_THIS, SDL_Window * window, SDL_bool grabbed);
 extern void Wayland_SetWindowKeyboardGrab(_THIS, SDL_Window *window, SDL_bool grabbed);
 extern void Wayland_RestoreWindow(_THIS, SDL_Window * window);