From 91b97b0312906482c71a5b333d317f4e37e745f2 Mon Sep 17 00:00:00 2001
From: Roman Gilg <subdiff@gmail.com>
Date: Thu, 17 Jan 2019 15:02:28 +0100
Subject: [PATCH] xwayland: Multi DPI support via global factor rescaling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

To benefit from Wayland's multi DPI capabilities in XWayland a global scaling
factor is introduced, which is applied to all outputs.

The RandR size of an output is calculated by the (integer-)multiplication of
this global scaling factor with its logical size received via the xdg-output
protocol.

In other words the size of any RandR screen corresponds to the mode size of
its wl_output multiplied with the quotient of global scaling factor divided by
the compositor's internal output-dependent scaling factor.

HiDPI aware X clients can then provide Pixmaps enlarged by the global scaling
factor and the Wayland compositor is supposed to downscale these buffers on
outputs scaled by less than the global scaling factor.

A Wayland compositor needs to scale all X communication in its XWM part by the
global scaling factor, such that X windows have the correct geometry.

In summary:
* All positions in Wayland internal communication must be carried out by the
compositor in logical coordinates, i.e. in its compositor space.
* All positions in X internal communication are based on RandR sizes.
* All positions in Wayland to X communication must be multiplied by the global
scaling factor.
* All positions in X to Wayland communication must be divided by the global
scaling factor.

In order to not break compositors that do not support these transformations,
the global scaling factor is set to 1 by default, which behaves the same as
before. The compositor must explicitly set the global scaling factor via the
new XWAYLAND protocol. Changing the scaling factor on the fly on a running
X server is supported.

[Dario Nieuwenhuis: fix incorrect scaling in a few places]
[Antonin Décimo: updated xwl_surface_damage after 2beefda]
[Antonin Décimo: updated xwayland-cursor.c after 79afbd60]

Signed-off-by: Roman Gilg <subdiff@gmail.com>
Signed-off-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
Signed-off-by: Antonin Décimo <antonin.decimo@gmail.com>
---
 hw/xwayland/xwayland-cursor.c | 10 +++++----
 hw/xwayland/xwayland-input.c  | 42 ++++++++++++++++++++---------------
 hw/xwayland/xwayland-output.c | 18 ++++++++++-----
 hw/xwayland/xwayland-output.h |  4 +++-
 hw/xwayland/xwayland-screen.c | 19 +++++++++++++++-
 hw/xwayland/xwayland-screen.h |  1 +
 hw/xwayland/xwayland-window.c |  4 +++-
 7 files changed, 67 insertions(+), 31 deletions(-)

diff --git a/hw/xwayland/xwayland-cursor.c b/hw/xwayland/xwayland-cursor.c
index e3c1aaa50c..8467b8f36a 100644
--- a/hw/xwayland/xwayland-cursor.c
+++ b/hw/xwayland/xwayland-cursor.c
@@ -195,6 +195,7 @@ xwl_cursor_clear_frame_cb(struct xwl_cursor *xwl_cursor)
 void
 xwl_seat_set_cursor(struct xwl_seat *xwl_seat)
 {
+    struct xwl_screen *xwl_screen = xwl_seat->xwl_screen;
     struct xwl_cursor *xwl_cursor = &xwl_seat->cursor;
     PixmapPtr pixmap;
     CursorPtr cursor;
@@ -225,8 +226,8 @@ xwl_seat_set_cursor(struct xwl_seat *xwl_seat)
     wl_pointer_set_cursor(xwl_seat->wl_pointer,
                           xwl_seat->pointer_enter_serial,
                           xwl_cursor->surface,
-                          xwl_seat->x_cursor->bits->xhot,
-                          xwl_seat->x_cursor->bits->yhot);
+                          xwl_scale_to(xwl_screen, xwl_seat->x_cursor->bits->xhot),
+                          xwl_scale_to(xwl_screen, xwl_seat->x_cursor->bits->yhot));
 
     xwl_cursor_attach_pixmap(xwl_seat, xwl_cursor, pixmap);
 }
@@ -235,6 +236,7 @@ void
 xwl_tablet_tool_set_cursor(struct xwl_tablet_tool *xwl_tablet_tool)
 {
     struct xwl_seat *xwl_seat = xwl_tablet_tool->seat;
+    struct xwl_screen *xwl_screen = xwl_seat->xwl_screen;
     struct xwl_cursor *xwl_cursor = &xwl_tablet_tool->cursor;
     PixmapPtr pixmap;
     CursorPtr cursor;
@@ -263,8 +265,8 @@ xwl_tablet_tool_set_cursor(struct xwl_tablet_tool *xwl_tablet_tool)
     zwp_tablet_tool_v2_set_cursor(xwl_tablet_tool->tool,
                                   xwl_tablet_tool->proximity_in_serial,
                                   xwl_cursor->surface,
-                                  xwl_seat->x_cursor->bits->xhot,
-                                  xwl_seat->x_cursor->bits->yhot);
+                                  xwl_scale_to(xwl_screen, xwl_seat->x_cursor->bits->xhot),
+                                  xwl_scale_to(xwl_screen, xwl_seat->x_cursor->bits->yhot));
 
     xwl_cursor_attach_pixmap(xwl_seat, xwl_cursor, pixmap);
 }
diff --git a/hw/xwayland/xwayland-input.c b/hw/xwayland/xwayland-input.c
index 52dc4f86fb..912d1ef7fa 100644
--- a/hw/xwayland/xwayland-input.c
+++ b/hw/xwayland/xwayland-input.c
@@ -518,8 +518,8 @@ pointer_handle_enter(void *data, struct wl_pointer *pointer,
     DeviceIntPtr dev = get_pointer_device(xwl_seat);
     DeviceIntPtr master;
     int i;
-    int sx = wl_fixed_to_int(sx_w);
-    int sy = wl_fixed_to_int(sy_w);
+    int sx = wl_fixed_to_int(sx_w) * xwl_seat->xwl_screen->global_output_scale;
+    int sy = wl_fixed_to_int(sy_w) * xwl_seat->xwl_screen->global_output_scale;
     int dx, dy;
     ScreenPtr pScreen = xwl_seat->xwl_screen->screen;
     ValuatorMask mask;
@@ -686,6 +686,8 @@ dispatch_scroll_motion(struct xwl_seat *xwl_seat)
     wl_fixed_t dx = xwl_seat->pending_pointer_event.scroll_dx;
     int32_t dy_v120 = xwl_seat->pending_pointer_event.scroll_dy_v120;
     int32_t dx_v120 = xwl_seat->pending_pointer_event.scroll_dx_v120;
+    double scaled_dy = wl_fixed_to_double(dy);
+    double scaled_dx = wl_fixed_to_double(dx);
 
     valuator_mask_zero(&mask);
     if (xwl_seat->pending_pointer_event.has_vertical_scroll_v120)
@@ -693,14 +695,14 @@ dispatch_scroll_motion(struct xwl_seat *xwl_seat)
     else if (xwl_seat->pending_pointer_event.has_vertical_scroll)
         valuator_mask_set_double(&mask,
                                  SCROLL_AXIS_VERT,
-                                 wl_fixed_to_double(dy) / divisor);
+                                 scaled_dy / divisor);
 
     if (xwl_seat->pending_pointer_event.has_horizontal_scroll_v120)
         valuator_mask_set_double(&mask, SCROLL_AXIS_HORIZ, dx_v120 / 120.0);
     else if (xwl_seat->pending_pointer_event.has_horizontal_scroll)
         valuator_mask_set_double(&mask,
                                  SCROLL_AXIS_HORIZ,
-                                 wl_fixed_to_double(dx) / divisor);
+                                 scaled_dx / divisor);
 
     QueuePointerEvents(get_pointer_device(xwl_seat),
                        MotionNotify, 0, POINTER_RELATIVE, &mask);
@@ -742,13 +744,14 @@ pointer_handle_motion(void *data, struct wl_pointer *pointer,
                       uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w)
 {
     struct xwl_seat *xwl_seat = data;
+    int32_t scale = xwl_seat->xwl_screen->global_output_scale;
 
     if (!xwl_seat->focus_window)
         return;
 
     xwl_seat->pending_pointer_event.has_absolute = TRUE;
-    xwl_seat->pending_pointer_event.x = sx_w;
-    xwl_seat->pending_pointer_event.y = sy_w;
+    xwl_seat->pending_pointer_event.x = sx_w * scale;
+    xwl_seat->pending_pointer_event.y = sy_w * scale;
 
     if (wl_proxy_get_version((struct wl_proxy *) xwl_seat->wl_pointer) < 5)
         dispatch_pointer_motion_event(xwl_seat);
@@ -898,12 +901,13 @@ relative_pointer_handle_relative_motion(void *data,
                                         wl_fixed_t dy_unaccelf)
 {
     struct xwl_seat *xwl_seat = data;
+    int32_t scale = xwl_seat->xwl_screen->global_output_scale;
 
     xwl_seat->pending_pointer_event.has_relative = TRUE;
-    xwl_seat->pending_pointer_event.dx = wl_fixed_to_double(dxf);
-    xwl_seat->pending_pointer_event.dy = wl_fixed_to_double(dyf);
-    xwl_seat->pending_pointer_event.dx_unaccel = wl_fixed_to_double(dx_unaccelf);
-    xwl_seat->pending_pointer_event.dy_unaccel = wl_fixed_to_double(dy_unaccelf);
+    xwl_seat->pending_pointer_event.dx = wl_fixed_to_double(dxf) * scale;
+    xwl_seat->pending_pointer_event.dy = wl_fixed_to_double(dyf) * scale;
+    xwl_seat->pending_pointer_event.dx_unaccel = wl_fixed_to_double(dx_unaccelf) * scale;
+    xwl_seat->pending_pointer_event.dy_unaccel = wl_fixed_to_double(dy_unaccelf) * scale;
 
     if (!xwl_seat->focus_window)
         return;
@@ -1393,8 +1397,8 @@ touch_handle_down(void *data, struct wl_touch *wl_touch,
 
     xwl_touch->window = wl_surface_get_user_data(surface);
     xwl_touch->id = id;
-    xwl_touch->x = wl_fixed_to_int(sx_w);
-    xwl_touch->y = wl_fixed_to_int(sy_w);
+    xwl_touch->x = wl_fixed_to_int(sx_w) * xwl_seat->xwl_screen->global_output_scale;;
+    xwl_touch->y = wl_fixed_to_int(sy_w) * xwl_seat->xwl_screen->global_output_scale;;
     xorg_list_add(&xwl_touch->link_touch, &xwl_seat->touches);
 
     xwl_touch_send_event(xwl_touch, xwl_seat, XI_TouchBegin);
@@ -1430,8 +1434,8 @@ touch_handle_motion(void *data, struct wl_touch *wl_touch,
     if (!xwl_touch)
         return;
 
-    xwl_touch->x = wl_fixed_to_int(sx_w);
-    xwl_touch->y = wl_fixed_to_int(sy_w);
+    xwl_touch->x = wl_fixed_to_int(sx_w) * xwl_seat->xwl_screen->global_output_scale;;
+    xwl_touch->y = wl_fixed_to_int(sy_w) * xwl_seat->xwl_screen->global_output_scale;;
     xwl_touch_send_event(xwl_touch, xwl_seat, XI_TouchUpdate);
 }
 
@@ -2121,8 +2125,8 @@ tablet_tool_motion(void *data, struct zwp_tablet_tool_v2 *tool,
     struct xwl_tablet_tool *xwl_tablet_tool = data;
     struct xwl_seat *xwl_seat = xwl_tablet_tool->seat;
     int32_t dx, dy;
-    double sx = wl_fixed_to_double(x);
-    double sy = wl_fixed_to_double(y);
+    double sx = wl_fixed_to_double(x) * xwl_seat->xwl_screen->global_output_scale;
+    double sy = wl_fixed_to_double(y) * xwl_seat->xwl_screen->global_output_scale;
 
     if (!xwl_seat->tablet_focus_window)
         return;
@@ -3163,6 +3167,7 @@ xwl_pointer_warp_emulator_set_fake_pos(struct xwl_pointer_warp_emulator *warp_em
                                        int x,
                                        int y)
 {
+    struct xwl_screen *xwl_screen;
     struct zwp_locked_pointer_v1 *locked_pointer =
         warp_emulator->locked_pointer;
     WindowPtr window;
@@ -3174,6 +3179,7 @@ xwl_pointer_warp_emulator_set_fake_pos(struct xwl_pointer_warp_emulator *warp_em
     if (!warp_emulator->xwl_seat->focus_window)
         return;
 
+    xwl_screen = warp_emulator->xwl_seat->xwl_screen;
     window = warp_emulator->xwl_seat->focus_window->window;
     if (x >= window->drawable.x ||
         y >= window->drawable.y ||
@@ -3182,8 +3188,8 @@ xwl_pointer_warp_emulator_set_fake_pos(struct xwl_pointer_warp_emulator *warp_em
         sx = x - window->drawable.x;
         sy = y - window->drawable.y;
         zwp_locked_pointer_v1_set_cursor_position_hint(locked_pointer,
-                                                       wl_fixed_from_int(sx),
-                                                       wl_fixed_from_int(sy));
+                                                       wl_fixed_from_int(xwl_scale_to(xwl_screen, sx)),
+                                                       wl_fixed_from_int(xwl_scale_to(xwl_screen, sy)));
         wl_surface_commit(warp_emulator->xwl_seat->focus_window->surface);
     }
 }
diff --git a/hw/xwayland/xwayland-output.c b/hw/xwayland/xwayland-output.c
index 25559d0e13..86ed12ad95 100644
--- a/hw/xwayland/xwayland-output.c
+++ b/hw/xwayland/xwayland-output.c
@@ -189,6 +189,9 @@ update_screen_size(struct xwl_screen *xwl_screen, int width, int height)
     xwl_screen->width = width;
     xwl_screen->height = height;
 
+    width = width * xwl_screen->global_output_scale;
+    height = height * xwl_screen->global_output_scale;
+
     if (xwl_screen->root_clip_mode == ROOT_CLIP_FULL)
         SetRootClip(xwl_screen->screen, ROOT_CLIP_NONE);
 
@@ -612,14 +615,15 @@ maybe_update_fullscreen_state(struct xwl_output *xwl_output)
     }
 }
 
-static void
-apply_output_change(struct xwl_output *xwl_output)
+void
+xwl_output_apply_changes(struct xwl_output *xwl_output)
 {
     struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
     struct xwl_output *it;
     int mode_width, mode_height, count;
     int width = 0, height = 0, has_this_output = 0;
     RRModePtr *randr_modes;
+    int32_t scale = xwl_screen->global_output_scale;
 
     /* Clear out the "done" received flags */
     xwl_output->wl_output_done = FALSE;
@@ -638,10 +642,10 @@ apply_output_change(struct xwl_output *xwl_output)
     }
     if (xwl_output->randr_output) {
         /* Build a fresh modes array using the current refresh rate */
-        randr_modes = output_get_rr_modes(xwl_output, mode_width, mode_height, &count);
+        randr_modes = output_get_rr_modes(xwl_output, mode_width * scale, mode_height * scale, &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->x * scale, xwl_output->y * scale,
                      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.
@@ -714,7 +718,7 @@ output_handle_done(void *data, struct wl_output *wl_output)
      */
     if (xwl_output->xdg_output_done || !xwl_output->xdg_output ||
         zxdg_output_v1_get_version(xwl_output->xdg_output) >= 3)
-        apply_output_change(xwl_output);
+        xwl_output_apply_changes(xwl_output);
 }
 
 static void
@@ -779,7 +783,7 @@ xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
 
     if (xwl_output->wl_output_done &&
         zxdg_output_v1_get_version(xdg_output) < 3)
-        apply_output_change(xwl_output);
+        xwl_output_apply_changes(xwl_output);
 }
 
 static void
@@ -914,6 +918,8 @@ xwl_output_create(struct xwl_screen *xwl_screen, uint32_t id,
     RROutputSetConnection(xwl_output->randr_output,
                           connected ? RR_Connected : RR_Disconnected);
 
+    xwl_output->scale = 1;
+
     /* We want the output to be in the list as soon as created so we can
      * use it when binding to the xdg-output protocol...
      */
diff --git a/hw/xwayland/xwayland-output.h b/hw/xwayland/xwayland-output.h
index 24b118dfdc..c26543e3ad 100644
--- a/hw/xwayland/xwayland-output.h
+++ b/hw/xwayland/xwayland-output.h
@@ -53,7 +53,7 @@ struct xwl_output {
     struct wl_output *output;
     struct zxdg_output_v1 *xdg_output;
     uint32_t server_output_id;
-    int32_t x, y, width, height, refresh;
+    int32_t x, y, width, height, scale, refresh;
     Rotation rotation;
     Bool wl_output_done;
     Bool xdg_output_done;
@@ -108,6 +108,8 @@ void xwl_output_set_emulated_mode(struct xwl_output *xwl_output,
 void xwl_output_set_window_randr_emu_props(struct xwl_screen *xwl_screen,
                                            WindowPtr window);
 
+void xwl_output_apply_changes(struct xwl_output *xwl_output);
+
 void xwl_screen_init_xdg_output(struct xwl_screen *xwl_screen);
 
 #endif /* XWAYLAND_OUTPUT_H */
diff --git a/hw/xwayland/xwayland-screen.c b/hw/xwayland/xwayland-screen.c
index 8fe6160da1..a62d049fa6 100644
--- a/hw/xwayland/xwayland-screen.c
+++ b/hw/xwayland/xwayland-screen.c
@@ -119,6 +119,12 @@ xwl_screen_has_resolution_change_emulation(struct xwl_screen *xwl_screen)
     return xwl_screen->rootless && xwl_screen_has_viewport_support(xwl_screen);
 }
 
+int
+xwl_scale_to(struct xwl_screen *xwl_screen, int value)
+{
+    return value / (double)xwl_screen->global_output_scale + 0.5;
+}
+
 /* 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)
@@ -679,8 +685,14 @@ void xwl_surface_damage(struct xwl_screen *xwl_screen,
 {
     if (wl_surface_get_version(surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION)
         wl_surface_damage_buffer(surface, x, y, width, height);
-    else
+    else {
+        x = xwl_scale_to(xwl_screen, x);
+        y = xwl_scale_to(xwl_screen, y);
+        width = xwl_scale_to(xwl_screen, width);
+        height = xwl_scale_to(xwl_screen, height);
+
         wl_surface_damage(surface, x, y, width, height);
+    }
 }
 
 void
@@ -754,6 +766,11 @@ xwl_screen_set_global_scale( struct xwl_screen *xwl_screen, int32_t scale)
 {
     struct xwl_output *it;
     xwl_screen->global_output_scale = scale;
+
+    /* change randr resolutions and positions */
+    xorg_list_for_each_entry(it, &xwl_screen->output_list, link) {
+        xwl_output_apply_changes(it);
+    }
 }
 
 Bool
diff --git a/hw/xwayland/xwayland-screen.h b/hw/xwayland/xwayland-screen.h
index 3d4e024501..f8253fe001 100644
--- a/hw/xwayland/xwayland-screen.h
+++ b/hw/xwayland/xwayland-screen.h
@@ -174,6 +174,7 @@ void xwl_surface_damage(struct xwl_screen *xwl_screen,
                         struct wl_surface *surface,
                         int32_t x, int32_t y, int32_t width, int32_t height);
 int xwl_screen_get_next_output_serial(struct xwl_screen * xwl_screen);
+int xwl_scale_to(struct xwl_screen *xwl_screen, int value);
 void xwl_screen_set_global_scale( struct xwl_screen *xwl_screen, int32_t scale);
 
 #endif /* XWAYLAND_SCREEN_H */
diff --git a/hw/xwayland/xwayland-window.c b/hw/xwayland/xwayland-window.c
index aaeeace684..6d15495ca7 100644
--- a/hw/xwayland/xwayland-window.c
+++ b/hw/xwayland/xwayland-window.c
@@ -874,7 +874,8 @@ xwl_create_root_surface(struct xwl_window *xwl_window)
     }
 
     wl_region_add(region, 0, 0,
-                  window->drawable.width, window->drawable.height);
+                  xwl_scale_to(xwl_screen, window->drawable.width),
+                  xwl_scale_to(xwl_screen, window->drawable.height));
     wl_surface_set_opaque_region(xwl_window->surface, region);
     wl_region_destroy(region);
 
@@ -1416,6 +1417,7 @@ xwl_window_attach_buffer(struct xwl_window *xwl_window)
     }
 #endif
 
+    wl_surface_set_buffer_scale(xwl_window->surface, xwl_screen->global_output_scale);
     wl_surface_attach(xwl_window->surface, buffer, 0, 0);
 
     /* Arbitrary limit to try to avoid flooding the Wayland
