From 6ff9bf9f005ce81b587d3b4345232c73fc12da53 Mon Sep 17 00:00:00 2001
From: Hans de Goede <hdegoede@redhat.com>
Date: Wed, 26 Jun 2019 16:46:54 +0200
Subject: [PATCH xserver 04/24] xwayland: Add fake output modes to xrandr
 output mode lists
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is a preparation patch for adding support for apps which want to
change the resolution when they go fullscreen because they are hardcoded
to render at a specific resolution, e.g. 640x480.

Follow up patches will fake the mode-switch these apps want by using
WPviewport to scale there pixmap to cover the entire output.

Reviewed-by: Olivier Fourdan <ofourdan@redhat.com>
Acked-by: Michel Dänzer <mdaenzer@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
(cherry picked from commit 0d656d796071fb637e4969ea800855fe5d1c9728)
---
 hw/xwayland/xwayland-output.c | 112 ++++++++++++++++++++++++++++++++--
 hw/xwayland/xwayland.c        |  17 ++++++
 hw/xwayland/xwayland.h        |   1 +
 3 files changed, 124 insertions(+), 6 deletions(-)

diff --git a/hw/xwayland/xwayland-output.c b/hw/xwayland/xwayland-output.c
index aa6f37864..2ccc3ca60 100644
--- a/hw/xwayland/xwayland-output.c
+++ b/hw/xwayland/xwayland-output.c
@@ -245,14 +245,110 @@ update_screen_size(struct xwl_output *xwl_output, int width, int height)
     update_desktop_dimensions();
 }
 
+/* From hw/xfree86/common/xf86DefModeSet.c with some obscure modes dropped */
+const int32_t xwl_output_fake_modes[][2] = {
+    /* 4:3 (1.33) */
+    { 2048, 1536 },
+    { 1920, 1440 },
+    { 1600, 1200 },
+    { 1440, 1080 },
+    { 1400, 1050 },
+    { 1280, 1024 }, /* 5:4 (1.25) */
+    { 1280,  960 },
+    { 1152,  864 },
+    { 1024,  768 },
+    {  800,  600 },
+    {  640,  480 },
+    {  320,  240 },
+    /* 16:10 (1.6) */
+    { 2560, 1600 },
+    { 1920, 1200 },
+    { 1680, 1050 },
+    { 1440,  900 },
+    { 1280,  800 },
+    {  720,  480 }, /* 3:2 (1.5) */
+    {  640,  400 },
+    {  320,  200 },
+    /* 16:9 (1.77) */
+    { 5120, 2880 },
+    { 4096, 2304 },
+    { 3840, 2160 },
+    { 3200, 1800 },
+    { 2880, 1620 },
+    { 2560, 1440 },
+    { 2048, 1152 },
+    { 1920, 1080 },
+    { 1600,  900 },
+    { 1368,  768 },
+    { 1280,  720 },
+    { 1024,  576 },
+    {  864,  486 },
+    {  720,  400 },
+    {  640,  350 },
+};
+
+/* Build an array with RRModes the first mode is the actual output mode, the
+ * rest are fake modes from the xwl_output_fake_modes list. We do this for apps
+ * which want to change resolution when they go fullscreen.
+ * When an app requests a mode-change, we fake it using WPviewport.
+ */
+static RRModePtr *
+output_get_rr_modes(struct xwl_output *xwl_output,
+                    int32_t width, int32_t height,
+                    int *count)
+{
+    struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
+    RRModePtr *rr_modes;
+    int i;
+
+    rr_modes = xallocarray(ARRAY_SIZE(xwl_output_fake_modes) + 1, sizeof(RRModePtr));
+    if (!rr_modes)
+        goto err;
+
+    /* Add actual output mode */
+    rr_modes[0] = xwayland_cvt(width, height, xwl_output->refresh / 1000.0, 0, 0);
+    if (!rr_modes[0])
+        goto err;
+
+    *count = 1;
+
+    if (!xwl_screen_has_resolution_change_emulation(xwl_screen))
+        return rr_modes;
+
+    /* Add fake modes */
+    for (i = 0; i < ARRAY_SIZE(xwl_output_fake_modes); i++) {
+        /* Skip actual output mode, already added */
+        if (xwl_output_fake_modes[i][0] == width &&
+            xwl_output_fake_modes[i][1] == height)
+            continue;
+
+        /* Skip modes which are too big, avoid downscaling */
+        if (xwl_output_fake_modes[i][0] > width ||
+            xwl_output_fake_modes[i][1] > height)
+            continue;
+
+        rr_modes[*count] = xwayland_cvt(xwl_output_fake_modes[i][0],
+                                        xwl_output_fake_modes[i][1],
+                                        xwl_output->refresh / 1000.0, 0, 0);
+        if (!rr_modes[*count])
+            goto err;
+
+        (*count)++;
+    }
+
+    return rr_modes;
+err:
+    FatalError("Failed to allocate memory for list of RR modes");
+}
+
 static void
 apply_output_change(struct xwl_output *xwl_output)
 {
     struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
     struct xwl_output *it;
-    int mode_width, mode_height;
+    int mode_width, mode_height, count;
     int width = 0, height = 0, has_this_output = 0;
-    RRModePtr randr_mode;
+    RRModePtr *randr_modes;
     Bool need_rotate;
 
     /* Clear out the "done" received flags */
@@ -271,12 +367,16 @@ apply_output_change(struct xwl_output *xwl_output)
         mode_height = xwl_output->width;
     }
 
-    randr_mode = xwayland_cvt(mode_width, mode_height,
-                              xwl_output->refresh / 1000.0, 0, 0);
-    RROutputSetModes(xwl_output->randr_output, &randr_mode, 1, 1);
-    RRCrtcNotify(xwl_output->randr_crtc, randr_mode,
+    /* Build a fresh modes array using the current refresh rate */
+    randr_modes = output_get_rr_modes(xwl_output, mode_width, mode_height, &count);
+    RROutputSetModes(xwl_output->randr_output, randr_modes, count, 1);
+    RRCrtcNotify(xwl_output->randr_crtc, randr_modes[0],
                  xwl_output->x, xwl_output->y,
                  xwl_output->rotation, NULL, 1, &xwl_output->randr_output);
+    /* RROutputSetModes takes ownership of the passed in modes, so we only
+     * have to free the pointer array.
+     */
+    free(randr_modes);
 
     xorg_list_for_each_entry(it, &xwl_screen->output_list, link) {
         /* output done event is sent even when some property
diff --git a/hw/xwayland/xwayland.c b/hw/xwayland/xwayland.c
index 942981834..3c50396f1 100644
--- a/hw/xwayland/xwayland.c
+++ b/hw/xwayland/xwayland.c
@@ -152,6 +152,23 @@ xwl_screen_get(ScreenPtr screen)
     return dixLookupPrivate(&screen->devPrivates, &xwl_screen_private_key);
 }
 
+static Bool
+xwl_screen_has_viewport_support(struct xwl_screen *xwl_screen)
+{
+    return wl_compositor_get_version(xwl_screen->compositor) >=
+                            WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION &&
+           xwl_screen->viewporter != NULL;
+}
+
+Bool
+xwl_screen_has_resolution_change_emulation(struct xwl_screen *xwl_screen)
+{
+    /* Resolution change emulation is only supported in rootless mode and
+     * it requires viewport support.
+     */
+    return xwl_screen->rootless && xwl_screen_has_viewport_support(xwl_screen);
+}
+
 static void
 xwl_window_set_allow_commits(struct xwl_window *xwl_window, Bool allow,
                              const char *debug_msg)
diff --git a/hw/xwayland/xwayland.h b/hw/xwayland/xwayland.h
index fa78b5ab9..c66997f00 100644
--- a/hw/xwayland/xwayland.h
+++ b/hw/xwayland/xwayland.h
@@ -389,6 +389,7 @@ void xwl_surface_damage(struct xwl_screen *xwl_screen,
 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);
 
 void xwl_tablet_tool_set_cursor(struct xwl_tablet_tool *tool);
 void xwl_seat_set_cursor(struct xwl_seat *xwl_seat);
-- 
2.25.2

