From e997273b14a23b0ffe89f542de783adbec6e8a1b Mon Sep 17 00:00:00 2001
From: Hans de Goede <hdegoede@redhat.com>
Date: Tue, 2 Jul 2019 11:55:26 +0200
Subject: [PATCH xserver 08/14] xwayland: Add support for randr-resolution
 change emulation using viewport
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add support for per client randr-resolution change emulation using viewport,
for apps which want to change the resolution when going fullscreen.

Partly based on earlier work on this by Robert Mader <robert.mader@posteo.de>

Note SDL2 and SFML do not restore randr resolution when going from
fullscreen -> windowed, I believe this is caused by us still reporting the
desktop resolution when they query the resolution.  This is not a problem
because when windowed the toplevel window size includes the window-decorations
so it never matches the emulated resolution.

One exception would be the window being resizable in Windowed mode and the
user resizing the window so that including decorations it matches the
emulated resolution *and* the window being at pos 0x0. But this is an
extreme corner case. Still I will submit patches upstream to SDL2
and SFML to always restore the desktop resolution under Xwayland,
disabling resolution emulation all together when going windowed.

Reviewed-by: Olivier Fourdan <ofourdan@redhat.com>
Acked-by: Michel Dänzer <mdaenzer@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 hw/xwayland/xwayland-input.c  |   5 +
 hw/xwayland/xwayland-output.c |  63 ++++++++++-
 hw/xwayland/xwayland.c        | 199 ++++++++++++++++++++++++++++++++++
 hw/xwayland/xwayland.h        |  15 +++
 4 files changed, 276 insertions(+), 6 deletions(-)

diff --git a/hw/xwayland/xwayland-input.c b/hw/xwayland/xwayland-input.c
index fa46ac3e7..97b8cd132 100644
--- a/hw/xwayland/xwayland-input.c
+++ b/hw/xwayland/xwayland-input.c
@@ -486,6 +486,11 @@ dispatch_pointer_motion_event(struct xwl_seat *xwl_seat)
             int dx = xwl_seat->focus_window->window->drawable.x;
             int dy = xwl_seat->focus_window->window->drawable.y;
 
+            if (xwl_window_has_viewport_enabled(xwl_seat->focus_window)) {
+                sx *= xwl_seat->focus_window->scale_x;
+                sy *= xwl_seat->focus_window->scale_y;
+            }
+
             x = dx + sx;
             y = dy + sy;
         } else {
diff --git a/hw/xwayland/xwayland-output.c b/hw/xwayland/xwayland-output.c
index 82ff5db70..99ab1b288 100644
--- a/hw/xwayland/xwayland-output.c
+++ b/hw/xwayland/xwayland-output.c
@@ -408,6 +408,42 @@ err:
     FatalError("Failed to allocate memory for list of RR modes");
 }
 
+RRModePtr
+xwl_output_find_mode(struct xwl_output *xwl_output,
+                     int32_t width, int32_t height)
+{
+    RROutputPtr output = xwl_output->randr_output;
+    int i;
+
+    /* width & height -1 means we want the actual output mode, which is idx 0 */
+    if (width == -1 && height == -1 && output->modes)
+        return output->modes[0];
+
+    for (i = 0; i < output->numModes; i++) {
+        if (output->modes[i]->mode.width == width && output->modes[i]->mode.height == height)
+            return output->modes[i];
+    }
+
+    ErrorF("XWAYLAND: mode %dx%d is not available\n", width, height);
+    return NULL;
+}
+
+void
+xwl_output_set_emulated_mode(struct xwl_output *xwl_output, ClientPtr client,
+                             RRModePtr mode, Bool from_vidmode)
+{
+    DebugF("XWAYLAND: xwl_output_set_emulated_mode from %s: %dx%d\n",
+           from_vidmode ? "vidmode" : "randr",
+           mode->mode.width, mode->mode.height);
+
+    if (mode->mode.width == xwl_output->width && mode->mode.height == xwl_output->height)
+        xwl_output_remove_emulated_mode_for_client(xwl_output, client);
+    else
+        xwl_output_add_emulated_mode_for_client(xwl_output, client, mode, from_vidmode);
+
+    xwl_screen_check_resolution_change_emulation(xwl_output->xwl_screen);
+}
+
 static void
 apply_output_change(struct xwl_output *xwl_output)
 {
@@ -650,21 +686,36 @@ xwl_randr_screen_set_size(ScreenPtr pScreen,
 static Bool
 xwl_randr_crtc_set(ScreenPtr pScreen,
                    RRCrtcPtr crtc,
-                   RRModePtr mode,
+                   RRModePtr new_mode,
                    int x,
                    int y,
                    Rotation rotation,
                    int numOutputs, RROutputPtr * outputs)
 {
     struct xwl_output *xwl_output = crtc->devPrivate;
+    RRModePtr mode;
 
-    if (!mode || (mode->mode.width  == xwl_output->width &&
-                  mode->mode.height == xwl_output->height)) {
-        RRCrtcChanged(crtc, TRUE);
-        return TRUE;
+    if (new_mode) {
+        mode = xwl_output_find_mode(xwl_output,
+                                    new_mode->mode.width,
+                                    new_mode->mode.height);
+    } else {
+        mode = xwl_output_find_mode(xwl_output, -1, -1);
     }
+    if (!mode)
+        return FALSE;
 
-    return FALSE;
+    xwl_output_set_emulated_mode(xwl_output, GetCurrentClient(), mode, FALSE);
+
+    /* A real randr implementation would call:
+     * RRCrtcNotify(xwl_output->randr_crtc, mode, xwl_output->x, xwl_output->y,
+     *              xwl_output->rotation, NULL, 1, &xwl_output->randr_output);
+     * here to update the mode reported to clients querying the randr settings
+     * but that influences *all* clients and we do randr mode change emulation
+     * on a per client basis. So we just return success here.
+     */
+
+    return TRUE;
 }
 
 static Bool
diff --git a/hw/xwayland/xwayland.c b/hw/xwayland/xwayland.c
index 2b6065f1a..ec1b7eee4 100644
--- a/hw/xwayland/xwayland.c
+++ b/hw/xwayland/xwayland.c
@@ -163,6 +163,23 @@ xwl_screen_has_resolution_change_emulation(struct xwl_screen *xwl_screen)
     return xwl_screen->rootless && xwl_screen_has_viewport_support(xwl_screen);
 }
 
+/* Return the output @ 0x0, falling back to the first output in the list */
+struct xwl_output *
+xwl_screen_get_first_output(struct xwl_screen *xwl_screen)
+{
+    struct xwl_output *xwl_output;
+
+    xorg_list_for_each_entry(xwl_output, &xwl_screen->output_list, link) {
+        if (xwl_output->x == 0 && xwl_output->y == 0)
+            return xwl_output;
+    }
+
+    if (xorg_list_is_empty(&xwl_screen->output_list))
+        return NULL;
+
+    return xorg_list_first_entry(&xwl_screen->output_list, struct xwl_output, link);
+}
+
 static void
 xwl_window_set_allow_commits(struct xwl_window *xwl_window, Bool allow,
                              const char *debug_msg)
@@ -499,6 +516,150 @@ xwl_pixmap_get(PixmapPtr pixmap)
     return dixLookupPrivate(&pixmap->devPrivates, &xwl_pixmap_private_key);
 }
 
+Bool
+xwl_window_has_viewport_enabled(struct xwl_window *xwl_window)
+{
+    return (xwl_window->viewport != NULL);
+}
+
+static void
+xwl_window_disable_viewport(struct xwl_window *xwl_window)
+{
+    assert (xwl_window->viewport);
+
+    DebugF("XWAYLAND: disabling viewport\n");
+    wp_viewport_destroy(xwl_window->viewport);
+    xwl_window->viewport = NULL;
+}
+
+static void
+xwl_window_enable_viewport(struct xwl_window *xwl_window,
+                           struct xwl_output *xwl_output,
+                           struct xwl_emulated_mode *emulated_mode)
+{
+    /* If necessary disable old viewport to apply new settings */
+    if (xwl_window_has_viewport_enabled(xwl_window))
+        xwl_window_disable_viewport(xwl_window);
+
+    DebugF("XWAYLAND: enabling viewport %dx%d -> %dx%d\n",
+           emulated_mode->width, emulated_mode->height,
+           xwl_output->width, xwl_output->height);
+
+    xwl_window->viewport =
+        wp_viewporter_get_viewport(xwl_window->xwl_screen->viewporter,
+                                   xwl_window->surface);
+
+    wp_viewport_set_source(xwl_window->viewport,
+                           wl_fixed_from_int(0),
+                           wl_fixed_from_int(0),
+                           wl_fixed_from_int(emulated_mode->width),
+                           wl_fixed_from_int(emulated_mode->height));
+    wp_viewport_set_destination(xwl_window->viewport,
+                                xwl_output->width,
+                                xwl_output->height);
+
+    xwl_window->scale_x = (float)emulated_mode->width  / xwl_output->width;
+    xwl_window->scale_y = (float)emulated_mode->height / xwl_output->height;
+}
+
+static Bool
+xwl_screen_client_is_window_manager(struct xwl_screen *xwl_screen,
+                                    ClientPtr client)
+{
+    WindowPtr root = xwl_screen->screen->root;
+    OtherClients *others;
+
+    for (others = wOtherClients(root); others; others = others->next) {
+        if (SameClient(others, client)) {
+            if (others->mask & (SubstructureRedirectMask | ResizeRedirectMask))
+                return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+static ClientPtr
+xwl_window_get_owner(struct xwl_window *xwl_window)
+{
+    WindowPtr window = xwl_window->window;
+    ClientPtr client = wClient(window);
+
+    /* If the toplevel window is owned by the window-manager, then the
+     * actual client toplevel window has been reparented to a window-manager
+     * decoration window. In that case return the client of the
+     * first *and only* child of the toplevel (decoration) window.
+     */
+    if (xwl_screen_client_is_window_manager(xwl_window->xwl_screen, client)) {
+        if (window->firstChild && window->firstChild == window->lastChild)
+            return wClient(window->firstChild);
+        else
+            return NULL; /* Should never happen, skip resolution emulation */
+    }
+
+    return client;
+}
+
+static Bool
+xwl_window_should_enable_viewport(struct xwl_window *xwl_window,
+                                  struct xwl_output **xwl_output_ret,
+                                  struct xwl_emulated_mode **emulated_mode_ret)
+{
+    struct xwl_screen *xwl_screen = xwl_window->xwl_screen;
+    struct xwl_emulated_mode *emulated_mode;
+    struct xwl_output *xwl_output;
+    ClientPtr owner;
+
+    if (!xwl_screen_has_resolution_change_emulation(xwl_screen))
+        return FALSE;
+
+    owner = xwl_window_get_owner(xwl_window);
+    if (!owner)
+        return FALSE;
+
+    /* 1. Test if the window matches the emulated mode on one of the outputs
+     * This path gets hit by most games / libs (e.g. SDL, SFML, OGRE)
+     */
+    xorg_list_for_each_entry(xwl_output, &xwl_screen->output_list, link) {
+        emulated_mode = xwl_output_get_emulated_mode_for_client(xwl_output, owner);
+        if (!emulated_mode)
+            continue;
+
+        if (xwl_window->x == xwl_output->x &&
+            xwl_window->y == xwl_output->y &&
+            xwl_window->width  == emulated_mode->width &&
+            xwl_window->height == emulated_mode->height) {
+
+            *emulated_mode_ret = emulated_mode;
+            *xwl_output_ret = xwl_output;
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+static void
+xwl_window_check_resolution_change_emulation(struct xwl_window *xwl_window)
+{
+    struct xwl_emulated_mode *emulated_mode;
+    struct xwl_output *xwl_output;
+
+    if (xwl_window_should_enable_viewport(xwl_window, &xwl_output, &emulated_mode))
+        xwl_window_enable_viewport(xwl_window, xwl_output, emulated_mode);
+    else if (xwl_window_has_viewport_enabled(xwl_window))
+        xwl_window_disable_viewport(xwl_window);
+}
+
+void
+xwl_screen_check_resolution_change_emulation(struct xwl_screen *xwl_screen)
+{
+    struct xwl_window *xwl_window;
+
+    xorg_list_for_each_entry(xwl_window, &xwl_screen->window_list, link_window)
+        xwl_window_check_resolution_change_emulation(xwl_window);
+}
+
 static void
 xwl_window_init_allow_commits(struct xwl_window *xwl_window)
 {
@@ -569,6 +730,8 @@ ensure_surface_for_window(WindowPtr window)
 
     xwl_window->xwl_screen = xwl_screen;
     xwl_window->window = window;
+    xwl_window->width = window->drawable.width;
+    xwl_window->height = window->drawable.height;
     xwl_window->surface = wl_compositor_create_surface(xwl_screen->compositor);
     if (xwl_window->surface == NULL) {
         ErrorF("wl_display_create_surface failed\n");
@@ -610,6 +773,7 @@ ensure_surface_for_window(WindowPtr window)
 
     dixSetPrivate(&window->devPrivates, &xwl_window_private_key, xwl_window);
     xorg_list_init(&xwl_window->link_damage);
+    xorg_list_add(&xwl_window->link_window, &xwl_screen->window_list);
 
     xwl_window_init_allow_commits(xwl_window);
 
@@ -704,8 +868,12 @@ xwl_unrealize_window(WindowPtr window)
     if (!xwl_window)
         return ret;
 
+    if (xwl_window_has_viewport_enabled(xwl_window))
+        xwl_window_disable_viewport(xwl_window);
+
     wl_surface_destroy(xwl_window->surface);
     xorg_list_del(&xwl_window->link_damage);
+    xorg_list_del(&xwl_window->link_window);
     unregister_damage(window);
 
     if (xwl_window->frame_callback)
@@ -743,6 +911,33 @@ xwl_set_window_pixmap(WindowPtr window,
     ensure_surface_for_window(window);
 }
 
+static void
+xwl_resize_window(WindowPtr window,
+                  int x, int y,
+                  unsigned int width, unsigned int height,
+                  WindowPtr sib)
+{
+    ScreenPtr screen = window->drawable.pScreen;
+    struct xwl_screen *xwl_screen;
+    struct xwl_window *xwl_window;
+
+    xwl_screen = xwl_screen_get(screen);
+    xwl_window = xwl_window_get(window);
+
+    screen->ResizeWindow = xwl_screen->ResizeWindow;
+    (*screen->ResizeWindow) (window, x, y, width, height, sib);
+    xwl_screen->ResizeWindow = screen->ResizeWindow;
+    screen->ResizeWindow = xwl_resize_window;
+
+    if (xwl_window) {
+        xwl_window->x = x;
+        xwl_window->y = y;
+        xwl_window->width = width;
+        xwl_window->height = height;
+        xwl_window_check_resolution_change_emulation(xwl_window);
+    }
+}
+
 static void
 frame_callback(void *data,
                struct wl_callback *callback,
@@ -1158,6 +1353,7 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
     xorg_list_init(&xwl_screen->output_list);
     xorg_list_init(&xwl_screen->seat_list);
     xorg_list_init(&xwl_screen->damage_window_list);
+    xorg_list_init(&xwl_screen->window_list);
     xwl_screen->depth = 24;
 
     xwl_screen->display = wl_display_connect(NULL);
@@ -1256,6 +1452,9 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
     xwl_screen->CloseScreen = pScreen->CloseScreen;
     pScreen->CloseScreen = xwl_close_screen;
 
+    xwl_screen->ResizeWindow = pScreen->ResizeWindow;
+    pScreen->ResizeWindow = xwl_resize_window;
+
     if (xwl_screen->rootless) {
         xwl_screen->SetWindowPixmap = pScreen->SetWindowPixmap;
         pScreen->SetWindowPixmap = xwl_set_window_pixmap;
diff --git a/hw/xwayland/xwayland.h b/hw/xwayland/xwayland.h
index d0e87a89d..b52de11d2 100644
--- a/hw/xwayland/xwayland.h
+++ b/hw/xwayland/xwayland.h
@@ -135,10 +135,12 @@ struct xwl_screen {
     DestroyWindowProcPtr DestroyWindow;
     XYToWindowProcPtr XYToWindow;
     SetWindowPixmapProcPtr SetWindowPixmap;
+    ResizeWindowProcPtr ResizeWindow;
 
     struct xorg_list output_list;
     struct xorg_list seat_list;
     struct xorg_list damage_window_list;
+    struct xorg_list window_list;
 
     int wayland_fd;
     struct wl_display *display;
@@ -179,9 +181,13 @@ struct xwl_screen {
 struct xwl_window {
     struct xwl_screen *xwl_screen;
     struct wl_surface *surface;
+    struct wp_viewport *viewport;
+    int32_t x, y, width, height;
+    float scale_x, scale_y;
     struct wl_shell_surface *shell_surface;
     WindowPtr window;
     struct xorg_list link_damage;
+    struct xorg_list link_window;
     struct wl_callback *frame_callback;
     Bool allow_commits;
 #ifdef GLAMOR_HAS_GBM
@@ -408,6 +414,9 @@ Bool xwl_screen_init_cursor(struct xwl_screen *xwl_screen);
 
 struct xwl_screen *xwl_screen_get(ScreenPtr screen);
 Bool xwl_screen_has_resolution_change_emulation(struct xwl_screen *xwl_screen);
+struct xwl_output *xwl_screen_get_first_output(struct xwl_screen *xwl_screen);
+void xwl_screen_check_resolution_change_emulation(struct xwl_screen *xwl_screen);
+Bool xwl_window_has_viewport_enabled(struct xwl_window *xwl_window);
 
 void xwl_tablet_tool_set_cursor(struct xwl_tablet_tool *tool);
 void xwl_seat_set_cursor(struct xwl_seat *xwl_seat);
@@ -441,6 +450,12 @@ void xwl_output_remove(struct xwl_output *xwl_output);
 struct xwl_emulated_mode *xwl_output_get_emulated_mode_for_client(
                             struct xwl_output *xwl_output, ClientPtr client);
 
+RRModePtr xwl_output_find_mode(struct xwl_output *xwl_output,
+                               int32_t width, int32_t height);
+void xwl_output_set_emulated_mode(struct xwl_output *xwl_output,
+                                  ClientPtr client, RRModePtr mode,
+                                  Bool from_vidmode);
+
 RRModePtr xwayland_cvt(int HDisplay, int VDisplay,
                        float VRefresh, Bool Reduced, Bool Interlaced);
 
-- 
2.23.0

