aboutsummaryrefslogtreecommitdiffhomepage
path: root/nix
diff options
context:
space:
mode:
authorMihai Fufezan <[email protected]>2022-08-29 16:51:39 +0300
committerGitHub <[email protected]>2022-08-29 16:51:39 +0300
commitbdd20c401d5269b6d665c5c3f0de1ab333d07d07 (patch)
treecb0b3ce6ed7f297cf8127ed72a201f32306b8fe4 /nix
parent6865660e51a3e26ac4dc8a308b8e43781e30a841 (diff)
downloadHyprland-bdd20c401d5269b6d665c5c3f0de1ab333d07d07.tar.gz
Hyprland-bdd20c401d5269b6d665c5c3f0de1ab333d07d07.zip
Add HiDPI xwayland + wlroots patches (#591)
Diffstat (limited to 'nix')
-rw-r--r--nix/default.nix137
-rw-r--r--nix/hidpi_wlroots.nix31
-rw-r--r--nix/hm-module.nix42
-rw-r--r--nix/xwayland-hidpi.patch498
-rw-r--r--nix/xwayland-vsync.patch12
5 files changed, 645 insertions, 75 deletions
diff --git a/nix/default.nix b/nix/default.nix
index 3bec375d..f840da1a 100644
--- a/nix/default.nix
+++ b/nix/default.nix
@@ -2,6 +2,7 @@
lib,
stdenv,
fetchFromGitHub,
+ fetchpatch,
pkg-config,
meson,
ninja,
@@ -21,78 +22,88 @@
xwayland,
debug ? false,
enableXWayland ? true,
+ hidpiXWayland ? true,
legacyRenderer ? false,
version ? "git",
-}:
-stdenv.mkDerivation {
- pname = "hyprland" + lib.optionalString debug "-debug";
- inherit version;
+}: let
+ assertXWayland = lib.assertMsg (hidpiXWayland -> enableXWayland) ''
+ Hyprland: cannot have hidpiXWayland when enableXWayland is false.
+ '';
+in
+ assert assertXWayland;
+ stdenv.mkDerivation {
+ pname = "hyprland" + lib.optionalString debug "-debug";
+ inherit version;
- src = lib.cleanSourceWith {
- filter = name: type: let
- baseName = baseNameOf (toString name);
- in
- ! (
- lib.hasSuffix ".nix" baseName
- );
- src = lib.cleanSource ../.;
- };
+ src = lib.cleanSourceWith {
+ filter = name: type: let
+ baseName = baseNameOf (toString name);
+ in
+ ! (
+ lib.hasSuffix ".nix" baseName
+ );
+ src = lib.cleanSource ../.;
+ };
- nativeBuildInputs = [
- meson
- ninja
- pkg-config
- ];
+ nativeBuildInputs = [
+ meson
+ ninja
+ pkg-config
+ ];
- outputs = [
- "out"
- "man"
- ];
+ outputs = [
+ "out"
+ "man"
+ ];
- buildInputs =
- [
- git
- libdrm
- libinput
- libxcb
- libxkbcommon
- mesa
- pango
- wayland
- wayland-protocols
- wayland-scanner
- (wlroots.override {inherit enableXWayland;})
- xcbutilwm
- ]
- ++ lib.optional enableXWayland xwayland;
+ buildInputs =
+ [
+ git
+ libdrm
+ libinput
+ libxcb
+ libxkbcommon
+ mesa
+ pango
+ wayland
+ wayland-protocols
+ wayland-scanner
+ (
+ if hidpiXWayland
+ then (import ./hidpi_wlroots.nix {inherit wlroots xwayland fetchpatch;})
+ else wlroots.override {inherit enableXWayland;}
+ )
+ xcbutilwm
+ ]
+ ++ lib.optional enableXWayland xwayland;
- mesonBuildType =
- if debug
- then "debug"
- else "release";
+ mesonBuildType =
+ if debug
+ then "debug"
+ else "release";
- mesonFlags = builtins.concatLists [
- (lib.optional (!enableXWayland) "-Dxwayland=disabled")
- (lib.optional legacyRenderer "-DLEGACY_RENDERER:STRING=true")
- ];
+ mesonFlags = builtins.concatLists [
+ (lib.optional (!enableXWayland) "-Dxwayland=disabled")
+ (lib.optional legacyRenderer "-DLEGACY_RENDERER:STRING=true")
+ ];
- patches = [
- # make meson use the provided wlroots instead of the git submodule
- ./meson-build.patch
- ];
+ patches = [
+ # make meson use the provided wlroots instead of the git submodule
+ ./meson-build.patch
+ ];
- # Fix hardcoded paths to /usr installation
- postPatch = ''
- sed -i "s#/usr#$out#" src/render/OpenGL.cpp
- '';
+ # Fix hardcoded paths to /usr installation
+ postPatch = ''
+ sed -i "s#/usr#$out#" src/render/OpenGL.cpp
+ '';
- passthru.providedSessions = ["hyprland"];
+ passthru.providedSessions = ["hyprland"];
- meta = with lib; {
- homepage = "https://github.com/vaxerski/Hyprland";
- description = "A dynamic tiling Wayland compositor that doesn't sacrifice on its looks";
- license = licenses.bsd3;
- platforms = platforms.linux;
- mainProgram = "Hyprland";
- };
-}
+ meta = with lib; {
+ homepage = "https://github.com/vaxerski/Hyprland";
+ description = "A dynamic tiling Wayland compositor that doesn't sacrifice on its looks";
+ license = licenses.bsd3;
+ platforms = platforms.linux;
+ mainProgram = "Hyprland";
+ };
+ }
diff --git a/nix/hidpi_wlroots.nix b/nix/hidpi_wlroots.nix
new file mode 100644
index 00000000..c0f7db55
--- /dev/null
+++ b/nix/hidpi_wlroots.nix
@@ -0,0 +1,31 @@
+{
+ wlroots,
+ xwayland,
+ fetchpatch,
+}:
+(wlroots.overrideAttrs
+ (old: {
+ patches =
+ (old.patches or [])
+ ++ [
+ (fetchpatch {
+ url = "https://gitlab.freedesktop.org/lilydjwg/wlroots/-/commit/6c5ffcd1fee9e44780a6a8792f74ecfbe24a1ca7.diff";
+ sha256 = "sha256-Eo1pTa/PIiJsRZwIUnHGTIFFIedzODVf0ZeuXb0a3TQ=";
+ })
+ (fetchpatch {
+ url = "https://gitlab.freedesktop.org/wlroots/wlroots/-/commit/18595000f3a21502fd60bf213122859cc348f9af.diff";
+ sha256 = "sha256-jvfkAMh3gzkfuoRhB4E9T5X1Hu62wgUjj4tZkJm0mrI=";
+ revert = true;
+ })
+ ];
+ }))
+.override {
+ xwayland = xwayland.overrideAttrs (old: {
+ patches =
+ (old.patches or [])
+ ++ [
+ ./xwayland-vsync.patch
+ ./xwayland-hidpi.patch
+ ];
+ });
+}
diff --git a/nix/hm-module.nix b/nix/hm-module.nix
index 04641c2b..44316412 100644
--- a/nix/hm-module.nix
+++ b/nix/hm-module.nix
@@ -6,7 +6,8 @@ self: {
}: let
cfg = config.wayland.windowManager.hyprland;
defaultHyprlandPackage = self.packages.${pkgs.system}.default.override {
- enableXWayland = cfg.xwayland;
+ enableXWayland = cfg.xwayland.enable;
+ hidpiXWayland = cfg.xwayland.hidpi;
};
in {
options.wayland.windowManager.hyprland = {
@@ -41,12 +42,21 @@ in {
</itemizedlist>
'';
};
- xwayland = lib.mkOption {
- type = lib.types.bool;
- default = true;
- description = ''
- Enable xwayland.
- '';
+ xwayland = {
+ enable = lib.mkOption {
+ type = lib.types.bool;
+ default = true;
+ description = ''
+ Enable XWayland.
+ '';
+ };
+ hidpi = lib.mkOption {
+ type = lib.types.bool;
+ default = true;
+ description = ''
+ Enable HiDPI XWayland.
+ '';
+ };
};
extraConfig = lib.mkOption {
@@ -56,20 +66,28 @@ in {
Extra configuration lines to add to ~/.config/hypr/hyprland.conf.
'';
};
+
+ imports = [
+ (
+ lib.mkRenamedOptionModule
+ ["wayland" "windowManager" "hyprland" "xwayland"]
+ ["wayland" "windowManager" "hyprland" "xwayland" "enable"]
+ )
+ ];
};
config = lib.mkIf cfg.enable {
home.packages =
lib.optional (cfg.package != null) cfg.package
- ++ lib.optional cfg.xwayland pkgs.xwayland;
+ ++ lib.optional cfg.xwayland.enable pkgs.xwayland;
xdg.configFile."hypr/hyprland.conf" = {
text =
(lib.optionalString cfg.systemdIntegration ''
- exec-once=export XDG_SESSION_TYPE=wayland
- exec-once=${pkgs.dbus}/bin/dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP
- exec-once=systemctl --user start hyprland-session.target
- '')
+ exec-once=export XDG_SESSION_TYPE=wayland
+ exec-once=${pkgs.dbus}/bin/dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP
+ exec-once=systemctl --user start hyprland-session.target
+ '')
+ cfg.extraConfig;
onChange = let
diff --git a/nix/xwayland-hidpi.patch b/nix/xwayland-hidpi.patch
new file mode 100644
index 00000000..85f56e02
--- /dev/null
+++ b/nix/xwayland-hidpi.patch
@@ -0,0 +1,498 @@
+diff --git a/hw/xwayland/xwayland-cursor.c b/hw/xwayland/xwayland-cursor.c
+index c4457cc2a61b2103b47f996b51dbbe9eb87bd715..4a33e1f33e73c35c1691564ef4852e7501b02245 100644
+--- a/hw/xwayland/xwayland-cursor.c
++++ b/hw/xwayland/xwayland-cursor.c
+@@ -171,6 +171,8 @@ xwl_cursor_attach_pixmap(struct xwl_seat *xwl_seat,
+ }
+
+ wl_surface_attach(xwl_cursor->surface, buffer, 0, 0);
++ wl_surface_set_buffer_scale(xwl_cursor->surface,
++ xwl_seat->xwl_screen->global_output_scale);
+ xwl_surface_damage(xwl_seat->xwl_screen, xwl_cursor->surface, 0, 0,
+ xwl_seat->x_cursor->bits->width,
+ xwl_seat->x_cursor->bits->height);
+@@ -190,6 +192,7 @@ xwl_cursor_attach_pixmap(struct xwl_seat *xwl_seat,
+ 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;
+@@ -220,8 +223,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);
+ }
+@@ -230,6 +233,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;
+@@ -258,9 +262,9 @@ 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));
++ wl_surface_set_buffer_scale(xwl_cursor->surface, xwl_screen->global_output_scale);
+ xwl_cursor_attach_pixmap(xwl_seat, xwl_cursor, pixmap);
+ }
+
+diff --git a/hw/xwayland/xwayland-input.c b/hw/xwayland/xwayland-input.c
+index 26b3630c73b62514fe3ba7824371f79868e953f3..55cd8d466a55db03948abe93ffa03bf129b5e17a 100644
+--- a/hw/xwayland/xwayland-input.c
++++ b/hw/xwayland/xwayland-input.c
+@@ -412,8 +412,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;
+@@ -592,13 +592,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);
+@@ -672,7 +673,8 @@ pointer_handle_axis(void *data, struct wl_pointer *pointer,
+ xorg_list_del(&pending->l);
+ free(pending);
+ } else {
+- valuator_mask_set_double(&mask, index, wl_fixed_to_double(value) / divisor);
++ double scaled_value = wl_fixed_to_double(value);
++ valuator_mask_set_double(&mask, index, scaled_value / divisor);
+ }
+
+ QueuePointerEvents(get_pointer_device(xwl_seat),
+@@ -740,12 +742,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;
+@@ -1057,8 +1060,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);
+@@ -1094,8 +1097,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);
+ }
+
+@@ -1726,8 +1729,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;
+@@ -2714,6 +2717,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;
+@@ -2725,6 +2729,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 ||
+@@ -2733,8 +2738,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 ef705bc01bf8c2d2f170cda9ba21ed8293f50559..b8f6cd51bd240ed5e16271eb4749db18868bea7b 100644
+--- a/hw/xwayland/xwayland-output.c
++++ b/hw/xwayland/xwayland-output.c
+@@ -191,6 +191,9 @@ update_screen_size(struct xwl_output *xwl_output, int width, int height)
+ {
+ struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
+
++ width *= xwl_screen->global_output_scale;
++ height *= xwl_screen->global_output_scale;
++
+ if (xwl_screen->root_clip_mode == ROOT_CLIP_FULL)
+ SetRootClip(xwl_screen->screen, ROOT_CLIP_NONE);
+
+@@ -497,14 +500,15 @@ xwl_output_set_emulated_mode(struct xwl_output *xwl_output, ClientPtr client,
+ xwl_output_set_randr_emu_props(xwl_output->xwl_screen, client);
+ }
+
+-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;
+@@ -523,10 +527,10 @@ apply_output_change(struct xwl_output *xwl_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.
+@@ -567,7 +571,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
+@@ -610,7 +614,7 @@ xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
+ xwl_output->xdg_output_done = TRUE;
+ 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
+@@ -678,6 +682,8 @@ xwl_output_create(struct xwl_screen *xwl_screen, uint32_t id)
+ RROutputSetConnection(xwl_output->randr_output, RR_Connected);
+ RRTellChanged(xwl_screen->screen);
+
++ 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 02b9831083e82a33d85d4404e39d00f06f6c56fd..ec089757f44178dcd7f9c48907f790ce1b2a2729 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, refresh, scale;
+ Rotation rotation;
+ Bool wl_output_done;
+ Bool xdg_output_done;
+@@ -100,6 +100,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-present.c b/hw/xwayland/xwayland-present.c
+index c9cf8c2f569a319034e0789e7587414e50237065..5be0c208ca46b1a53a136885fdc8ab44251fe7ff 100644
+--- a/hw/xwayland/xwayland-present.c
++++ b/hw/xwayland/xwayland-present.c
+@@ -680,6 +680,8 @@ xwl_present_flip(WindowPtr present_window,
+
+ /* We can flip directly to the main surface (full screen window without clips) */
+ wl_surface_attach(xwl_window->surface, buffer, 0, 0);
++ wl_surface_set_buffer_scale(xwl_window->surface,
++ xwl_window->xwl_screen->global_output_scale);
+
+ if (!xwl_window->frame_callback)
+ xwl_window_create_frame_callback(xwl_window);
+diff --git a/hw/xwayland/xwayland-screen.c b/hw/xwayland/xwayland-screen.c
+index bb18e5c94fbc7134c801e4e1979e8184079d352e..4ec2de7d123dd36315df07a1e95b1f417925f0f8 100644
+--- a/hw/xwayland/xwayland-screen.c
++++ b/hw/xwayland/xwayland-screen.c
+@@ -51,6 +51,7 @@
+ #include "xwayland-pixmap.h"
+ #include "xwayland-present.h"
+ #include "xwayland-shm.h"
++#include "xwayland-window-buffers.h"
+
+ #ifdef MITSHM
+ #include "shmint.h"
+@@ -110,6 +111,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)
+@@ -127,6 +134,37 @@ xwl_screen_get_first_output(struct xwl_screen *xwl_screen)
+ return xorg_list_first_entry(&xwl_screen->output_list, struct xwl_output, link);
+ }
+
++static void
++xwl_screen_set_global_scale_from_property(struct xwl_screen *screen,
++ PropertyPtr prop)
++{
++ CARD32 *propdata;
++
++ if (prop->type != XA_CARDINAL || prop->format != 32 || prop->size != 1) {
++ // TODO: handle warnings more cleanly.
++ LogMessageVerb(X_WARNING, 0, "Bad value for property %s.\n",
++ NameForAtom(prop->propertyName));
++ return;
++ }
++
++ propdata = prop->data;
++ xwl_screen_set_global_scale(screen, propdata[0]);
++}
++
++static void
++xwl_screen_update_property(struct xwl_screen *screen,
++ PropertyStateRec *propstate)
++{
++ switch (propstate->state) {
++ case PropertyNewValue:
++ xwl_screen_set_global_scale_from_property(screen, propstate->prop);
++ break;
++ case PropertyDelete:
++ xwl_screen_set_global_scale(screen, 1);
++ break;
++ }
++}
++
+ static void
+ xwl_property_callback(CallbackListPtr *pcbl, void *closure,
+ void *calldata)
+@@ -134,19 +172,24 @@ xwl_property_callback(CallbackListPtr *pcbl, void *closure,
+ ScreenPtr screen = closure;
+ PropertyStateRec *rec = calldata;
+ struct xwl_screen *xwl_screen;
+- struct xwl_window *xwl_window;
+
+ if (rec->win->drawable.pScreen != screen)
+ return;
+
+- xwl_window = xwl_window_get(rec->win);
+- if (!xwl_window)
+- return;
+-
+ xwl_screen = xwl_screen_get(screen);
+
+- if (rec->prop->propertyName == xwl_screen->allow_commits_prop)
++ if (rec->prop->propertyName == xwl_screen->allow_commits_prop) {
++ struct xwl_window *xwl_window;
++
++ xwl_window = xwl_window_get(rec->win);
++ if (!xwl_window)
++ return;
++
+ xwl_window_update_property(xwl_window, rec);
++ }
++ else if (rec->prop->propertyName == xwl_screen->global_output_scale_prop) {
++ xwl_screen_update_property(xwl_screen, rec);
++ }
+ }
+
+ Bool
+@@ -521,8 +564,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
+@@ -538,10 +587,34 @@ xwl_screen_roundtrip(struct xwl_screen *xwl_screen)
+ xwl_give_up("could not connect to wayland server\n");
+ }
+
++void
++xwl_screen_set_global_scale(struct xwl_screen *xwl_screen, int32_t scale)
++{
++ struct xwl_output *it;
++ struct xwl_window *xwl_window;
++
++ 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);
++ }
++
++ if (!xwl_screen->rootless && xwl_screen->screen->root) {
++ /* Clear all the buffers, so that they'll be remade with the new sizes
++ * (this doesn't occur automatically because as far as Xorg is
++ * concerned, the window's size is the same) */
++ xorg_list_for_each_entry(xwl_window, &xwl_screen->window_list, link_window) {
++ xwl_window_buffers_recycle(xwl_window);
++ }
++ }
++}
++
+ Bool
+ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
+ {
+ static const char allow_commits[] = "_XWAYLAND_ALLOW_COMMITS";
++ static const char global_output_scale[] = "_XWAYLAND_GLOBAL_OUTPUT_SCALE";
+ struct xwl_screen *xwl_screen;
+ Pixel red_mask, blue_mask, green_mask;
+ int ret, bpc, green_bpc, i;
+@@ -573,6 +646,7 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
+ #ifdef XWL_HAS_GLAMOR
+ xwl_screen->glamor = 1;
+ #endif
++ xwl_screen->global_output_scale = 1;
+
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-rootless") == 0) {
+@@ -743,6 +817,12 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
+ if (xwl_screen->allow_commits_prop == BAD_RESOURCE)
+ return FALSE;
+
++ xwl_screen->global_output_scale_prop = MakeAtom(global_output_scale,
++ strlen(global_output_scale),
++ TRUE);
++ if (xwl_screen->global_output_scale_prop == BAD_RESOURCE)
++ return FALSE;
++
+ AddCallback(&PropertyStateCallback, xwl_property_callback, pScreen);
+
+ xwl_screen_roundtrip(xwl_screen);
+diff --git a/hw/xwayland/xwayland-screen.h b/hw/xwayland/xwayland-screen.h
+index b965dddd7f964b1d100bbb9d10da1c35ab39810e..7446829d098fbe235e605084a016daff1a8eaea2 100644
+--- a/hw/xwayland/xwayland-screen.h
++++ b/hw/xwayland/xwayland-screen.h
+@@ -72,6 +72,8 @@ struct xwl_screen {
+ struct xorg_list damage_window_list;
+ struct xorg_list window_list;
+
++ int32_t global_output_scale;
++
+ int wayland_fd;
+ struct wl_display *display;
+ struct wl_registry *registry;
+@@ -107,6 +109,7 @@ struct xwl_screen {
+ struct glamor_context *glamor_ctx;
+
+ Atom allow_commits_prop;
++ Atom global_output_scale_prop;
+
+ /* The preferred GLVND vendor. If NULL, "mesa" is assumed. */
+ const char *glvnd_vendor;
+@@ -134,5 +137,7 @@ void xwl_screen_roundtrip (struct xwl_screen *xwl_screen);
+ 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_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 00f161eda084e335ac07471a2198176d75d9fcf0..ed3903853f0dab1dad390cd8429639541546157d 100644
+--- a/hw/xwayland/xwayland-window.c
++++ b/hw/xwayland/xwayland-window.c
+@@ -470,7 +470,8 @@ ensure_surface_for_window(WindowPtr 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);
+ }
+@@ -820,6 +821,7 @@ xwl_window_post_damage(struct xwl_window *xwl_window)
+ #endif
+
+ wl_surface_attach(xwl_window->surface, buffer, 0, 0);
++ wl_surface_set_buffer_scale(xwl_window->surface, xwl_screen->global_output_scale);
+
+ /* Arbitrary limit to try to avoid flooding the Wayland
+ * connection. If we flood it too much anyway, this could
+
diff --git a/nix/xwayland-vsync.patch b/nix/xwayland-vsync.patch
new file mode 100644
index 00000000..375db880
--- /dev/null
+++ b/nix/xwayland-vsync.patch
@@ -0,0 +1,12 @@
+--- a/hw/xwayland/xwayland-present.c
++++ b/hw/xwayland/xwayland-present.c
+@@ -824,7 +824,8 @@
+ dixDestroyPixmap(vblank->pixmap, vblank->pixmap->drawable.id);
+ vblank->pixmap = NULL;
+
+- if (xwl_present_queue_vblank(screen, window, vblank->crtc,
++ if (vblank->target_msc > crtc_msc &&
++ xwl_present_queue_vblank(screen, window, vblank->crtc,
+ vblank->event_id, crtc_msc + 1)
+ == Success)
+ return;