From fdd71153fc0dab684e53344b9dcd879159b0de51 Mon Sep 17 00:00:00 2001 From: tigro Date: Sat, 16 Nov 2024 18:58:13 +0300 Subject: [PATCH] import mutter-40.9-22.el9_5.inferit --- SOURCES/0001-Update-Russian-translation.patch | 2 +- ...guate-output-mapped-to-tablet-with-c.patch | 106 + ...WaylandTextInput-event-forwarding-to.patch | 162 + ...native-Don-t-retry-forever-after-GBM.patch | 69 + ...isplay-Make-cgroup-constructor-local.patch | 26 + ...Add-addfb2_modifiers-to-MetaKmsDevic.patch | 51 + ...-Make-sure-created-window-has-a-prop.patch | 48 + ...tch-workspaces-if-users-from-forged-.patch | 101 + ...t-shell-interactions-has-user-intera.patch | 133 + ...set-window-cgroup-on-cgroup-creation.patch | 27 + ...le-modifiers-when-DRM_CAP_ADDFB2_MOD.patch | 29 + ...ow-Avoid-SIGFPE-on-bogus-window-size.patch | 24 + ...-Split-cgroup-out-to-separate-struct.patch | 816 ++++ ...window-Unregister-cgroup-on-unmanage.patch | 36 + ...-cgroup-workspace-if-there-already-i.patch | 30 + ...04-window-Track-workspace-per-cgroup.patch | 685 +++ ...-info-from-gnome-shell-when-possible.patch | 166 + ...id-placement-heuristcs-for-apps-that.patch | 556 +++ ...n-Add-optional-libsystemd-dependency.patch | 370 ++ SOURCES/sticky-or-on-top-dialog-fixes.patch | 1154 +++++ ...t-for-fractional-scaling-using-Randr.patch | 3784 +++++++++++++++++ SPECS/mutter.spec | 90 +- 22 files changed, 8459 insertions(+), 6 deletions(-) create mode 100644 SOURCES/0001-backends-Disambiguate-output-mapped-to-tablet-with-c.patch create mode 100644 SOURCES/0001-core-Change-MetaWaylandTextInput-event-forwarding-to.patch create mode 100644 SOURCES/0001-cursor-renderer-native-Don-t-retry-forever-after-GBM.patch create mode 100644 SOURCES/0001-display-Make-cgroup-constructor-local.patch create mode 100644 SOURCES/0001-kms-impl-device-Add-addfb2_modifiers-to-MetaKmsDevic.patch create mode 100644 SOURCES/0001-wayland-wl-shell-Make-sure-created-window-has-a-prop.patch create mode 100644 SOURCES/0001-window-Don-t-switch-workspaces-if-users-from-forged-.patch create mode 100644 SOURCES/0002-core-events-Count-shell-interactions-has-user-intera.patch create mode 100644 SOURCES/0002-display-Also-set-window-cgroup-on-cgroup-creation.patch create mode 100644 SOURCES/0002-kms-device-Disable-modifiers-when-DRM_CAP_ADDFB2_MOD.patch create mode 100644 SOURCES/0002-window-Avoid-SIGFPE-on-bogus-window-size.patch create mode 100644 SOURCES/0003-core-window-Split-cgroup-out-to-separate-struct.patch create mode 100644 SOURCES/0003-window-Unregister-cgroup-on-unmanage.patch create mode 100644 SOURCES/0004-window-Don-t-use-cgroup-workspace-if-there-already-i.patch create mode 100644 SOURCES/0004-window-Track-workspace-per-cgroup.patch create mode 100644 SOURCES/0005-cgroup-Get-app-info-from-gnome-shell-when-possible.patch create mode 100644 SOURCES/0005-core-display-Avoid-placement-heuristcs-for-apps-that.patch create mode 100644 SOURCES/0006-meson-Add-optional-libsystemd-dependency.patch create mode 100644 SOURCES/sticky-or-on-top-dialog-fixes.patch create mode 100644 SOURCES/x11-Add-support-for-fractional-scaling-using-Randr.patch diff --git a/SOURCES/0001-Update-Russian-translation.patch b/SOURCES/0001-Update-Russian-translation.patch index 11d0480..4afd774 100644 --- a/SOURCES/0001-Update-Russian-translation.patch +++ b/SOURCES/0001-Update-Russian-translation.patch @@ -1,4 +1,4 @@ -From 0a360a077e46592436bb7710cd8d41bccccfeb6d Mon Sep 17 00:00:00 2001 +From 26f93de29d8fdbfdcb1534f0bc4d2e105beea2f5 Mon Sep 17 00:00:00 2001 From: Sergey Cherevko Date: Wed, 10 May 2023 16:47:17 +0300 Subject: [PATCH] Update Russian translation diff --git a/SOURCES/0001-backends-Disambiguate-output-mapped-to-tablet-with-c.patch b/SOURCES/0001-backends-Disambiguate-output-mapped-to-tablet-with-c.patch new file mode 100644 index 0000000..ebeebda --- /dev/null +++ b/SOURCES/0001-backends-Disambiguate-output-mapped-to-tablet-with-c.patch @@ -0,0 +1,106 @@ +From 8d340beb368b3b59637c91e2e6e4adbc81ce7caf Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Wed, 7 Feb 2024 21:27:32 +0100 +Subject: [PATCH] backends: Disambiguate output mapped to tablet with connector + name + +In some circumstances, we may end up with outputs with the same +vendor/product/serial, in which case we have a hard time finding the +right one to map tablets to, since configuration only has these 3 +pieces of data. + +Add the handling of a 4th argument containing the output name +based on the connector (e.g. HDMI-1), so that it can be used to +disambiguate the output if necessary. + +This only kicks in if there actually are multiple outputs with the +same EDID data. A goal of the configuration as it was stored was to +remain useful if the user changed how the device is physically +connected to the computer, this remains true for the vast majority +of users having a single thing of each. +--- + src/backends/meta-input-mapper.c | 43 +++++++++++++++++++++++++++++--- + 1 file changed, 39 insertions(+), 4 deletions(-) + +diff --git a/src/backends/meta-input-mapper.c b/src/backends/meta-input-mapper.c +index ae4cd05..a0a4b8a 100644 +--- a/src/backends/meta-input-mapper.c ++++ b/src/backends/meta-input-mapper.c +@@ -395,9 +395,33 @@ match_builtin (MetaInputMapper *mapper, + return monitor == meta_monitor_manager_get_laptop_panel (mapper->monitor_manager); + } + ++static gboolean ++monitor_has_twin (MetaMonitor *monitor, ++ GList *monitors) ++{ ++ GList *l; ++ ++ for (l = monitors; l; l = l->next) ++ { ++ if (l->data == monitor) ++ continue; ++ ++ if (g_strcmp0 (meta_monitor_get_vendor (monitor), ++ meta_monitor_get_vendor (l->data)) == 0 && ++ g_strcmp0 (meta_monitor_get_product (monitor), ++ meta_monitor_get_product (l->data)) == 0 && ++ g_strcmp0 (meta_monitor_get_serial (monitor), ++ meta_monitor_get_serial (l->data)) == 0) ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ + static gboolean + match_config (MetaMapperInputInfo *info, +- MetaMonitor *monitor) ++ MetaMonitor *monitor, ++ GList *monitors) + { + gboolean match = FALSE; + char **edid; +@@ -406,10 +430,10 @@ match_config (MetaMapperInputInfo *info, + edid = g_settings_get_strv (info->settings, "output"); + n_values = g_strv_length (edid); + +- if (n_values != 3) ++ if (n_values < 3) + { + g_warning ("EDID configuration for device '%s' " +- "is incorrect, must have 3 values", ++ "is incorrect, must have at least 3 values", + clutter_input_device_get_device_name (info->device)); + goto out; + } +@@ -421,6 +445,17 @@ match_config (MetaMapperInputInfo *info, + g_strcmp0 (meta_monitor_get_product (monitor), edid[1]) == 0 && + g_strcmp0 (meta_monitor_get_serial (monitor), edid[2]) == 0); + ++ if (match && n_values >= 4 && monitor_has_twin (monitor, monitors)) ++ { ++ /* The 4th value if set contains the ID (e.g. HDMI-1), use it ++ * for disambiguation if multiple monitors with the same ++ * EDID data are found. ++ */ ++ MetaOutput *output; ++ output = meta_monitor_get_main_output (monitor); ++ match = g_strcmp0 (meta_output_get_name (output), edid[3]) == 0; ++ } ++ + out: + g_strfreev (edid); + +@@ -481,7 +516,7 @@ guess_candidates (MetaInputMapper *mapper, + if (builtin && match_builtin (mapper, l->data)) + match.score |= 1 << META_MATCH_IS_BUILTIN; + +- if (match_config (input, l->data)) ++ if (match_config (input, l->data, monitors)) + match.score |= 1 << META_MATCH_CONFIG; + + if (match.score > 0) +-- +2.43.0 + diff --git a/SOURCES/0001-core-Change-MetaWaylandTextInput-event-forwarding-to.patch b/SOURCES/0001-core-Change-MetaWaylandTextInput-event-forwarding-to.patch new file mode 100644 index 0000000..d60f681 --- /dev/null +++ b/SOURCES/0001-core-Change-MetaWaylandTextInput-event-forwarding-to.patch @@ -0,0 +1,162 @@ +From 88b50f5a9e4b1b87e766e929a77aafdc27e335cf Mon Sep 17 00:00:00 2001 +From: Carlos Garnacho +Date: Wed, 7 Jun 2023 11:04:15 +0200 +Subject: [PATCH] core: Change MetaWaylandTextInput event forwarding to IMs + +We need to juggle with some things here to keep key event ordering +and accounting consistent. + +The keyboard internal state changes (and maybe modifier event emission) +happening through meta_wayland_seat_update() should ideally happen +from the same key events that reach the client through wl_keyboard.key, +so that wl_keyboard.modifier events are emitted in the right relative +order to other key events. + +In order to fix this, we need to decide at an earlier point whether +the event will get processed through IM (and maybe be reinjected), +thus ignored in wait of IM-postprocessed events. + +This means we pay less attention to whether events are first-hand +hardware events for some things and go with the event that does +eventually reach to us (hardware or IM). + +Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/5890 +--- + src/core/events.c | 8 ++++++++ + src/wayland/meta-wayland-keyboard.c | 8 -------- + src/wayland/meta-wayland-seat.c | 30 ++++++++++++++++++++++------- + src/wayland/meta-wayland-seat.h | 3 +++ + src/wayland/meta-wayland.c | 7 +++++++ + src/wayland/meta-wayland.h | 3 +++ + 6 files changed, 44 insertions(+), 15 deletions(-) + +diff --git a/src/core/events.c b/src/core/events.c +index 6bb4e90..7751042 100644 +--- a/src/core/events.c ++++ b/src/core/events.c +@@ -234,6 +234,14 @@ meta_display_handle_event (MetaDisplay *display, + if (meta_is_wayland_compositor ()) + { + compositor = meta_wayland_compositor_get_default (); ++ ++ if (display->event_route == META_EVENT_ROUTE_NORMAL && ++ meta_wayland_compositor_handle_text_input_event (compositor, event)) ++ { ++ bypass_wayland = bypass_clutter = TRUE; ++ goto out; ++ } ++ + meta_wayland_compositor_update (compositor, event); + } + #endif +diff --git a/src/wayland/meta-wayland-keyboard.c b/src/wayland/meta-wayland-keyboard.c +index 836939c..460d9e9 100644 +--- a/src/wayland/meta-wayland-keyboard.c ++++ b/src/wayland/meta-wayland-keyboard.c +@@ -564,14 +564,6 @@ meta_wayland_keyboard_update (MetaWaylandKeyboard *keyboard, + { + gboolean is_press = event->type == CLUTTER_KEY_PRESS; + +- /* Only handle real, non-synthetic, events here. The IM is free to reemit +- * key events (incl. modifiers), handling those additionally will result +- * in doubly-pressed keys. +- */ +- if ((event->flags & +- (CLUTTER_EVENT_FLAG_SYNTHETIC | CLUTTER_EVENT_FLAG_INPUT_METHOD)) != 0) +- return; +- + /* If we get a key event but still have pending modifier state + * changes from a previous event that didn't get cleared, we need to + * send that state right away so that the new key event can be +diff --git a/src/wayland/meta-wayland-seat.c b/src/wayland/meta-wayland-seat.c +index 25d5074..27d8fe3 100644 +--- a/src/wayland/meta-wayland-seat.c ++++ b/src/wayland/meta-wayland-seat.c +@@ -376,6 +376,29 @@ meta_wayland_seat_update (MetaWaylandSeat *seat, + } + } + ++gboolean ++meta_wayland_seat_handle_text_input_event (MetaWaylandSeat *seat, ++ const ClutterEvent *event) ++{ ++ switch (event->type) ++ { ++ case CLUTTER_KEY_PRESS: ++ case CLUTTER_KEY_RELEASE: ++ if (meta_wayland_text_input_handle_event (seat->text_input, event)) ++ return TRUE; ++ ++ if (meta_wayland_gtk_text_input_handle_event (seat->gtk_text_input, ++ event)) ++ return TRUE; ++ ++ break; ++ default: ++ break; ++ } ++ ++ return FALSE; ++} ++ + gboolean + meta_wayland_seat_handle_event (MetaWaylandSeat *seat, + const ClutterEvent *event) +@@ -398,13 +421,6 @@ meta_wayland_seat_handle_event (MetaWaylandSeat *seat, + break; + case CLUTTER_KEY_PRESS: + case CLUTTER_KEY_RELEASE: +- if (meta_wayland_text_input_handle_event (seat->text_input, event)) +- return TRUE; +- +- if (meta_wayland_gtk_text_input_handle_event (seat->gtk_text_input, +- event)) +- return TRUE; +- + if (meta_wayland_seat_has_keyboard (seat)) + return meta_wayland_keyboard_handle_event (seat->keyboard, + (const ClutterKeyEvent *) event); +diff --git a/src/wayland/meta-wayland-seat.h b/src/wayland/meta-wayland-seat.h +index ae4e107..ab90106 100644 +--- a/src/wayland/meta-wayland-seat.h ++++ b/src/wayland/meta-wayland-seat.h +@@ -84,4 +84,7 @@ gboolean meta_wayland_seat_has_pointer (MetaWaylandSeat *seat); + + gboolean meta_wayland_seat_has_touch (MetaWaylandSeat *seat); + ++gboolean meta_wayland_seat_handle_text_input_event (MetaWaylandSeat *seat, ++ const ClutterEvent *event); ++ + #endif /* META_WAYLAND_SEAT_H */ +diff --git a/src/wayland/meta-wayland.c b/src/wayland/meta-wayland.c +index a3f0984..b548aa1 100644 +--- a/src/wayland/meta-wayland.c ++++ b/src/wayland/meta-wayland.c +@@ -721,3 +721,10 @@ meta_wayland_compositor_notify_surface_id (MetaWaylandCompositor *compositor, + meta_wayland_compositor_remove_surface_association (compositor, id); + } + } ++ ++gboolean ++meta_wayland_compositor_handle_text_input_event (MetaWaylandCompositor *compositor, ++ const ClutterEvent *event) ++{ ++ return meta_wayland_seat_handle_text_input_event (compositor->seat, event); ++} +diff --git a/src/wayland/meta-wayland.h b/src/wayland/meta-wayland.h +index 6c655e4..ad82d52 100644 +--- a/src/wayland/meta-wayland.h ++++ b/src/wayland/meta-wayland.h +@@ -92,6 +92,9 @@ void meta_wayland_compositor_schedule_surface_association (Me + int id, + MetaWindow *window); + ++gboolean meta_wayland_compositor_handle_text_input_event (MetaWaylandCompositor *compositor, ++ const ClutterEvent *event); ++ + void meta_wayland_compositor_notify_surface_id (MetaWaylandCompositor *compositor, + int id, + MetaWaylandSurface *surface); +-- +2.40.1 + diff --git a/SOURCES/0001-cursor-renderer-native-Don-t-retry-forever-after-GBM.patch b/SOURCES/0001-cursor-renderer-native-Don-t-retry-forever-after-GBM.patch new file mode 100644 index 0000000..14c5ba8 --- /dev/null +++ b/SOURCES/0001-cursor-renderer-native-Don-t-retry-forever-after-GBM.patch @@ -0,0 +1,69 @@ +From 4618af58cc9046142f348d16ab1150bfde8f49c4 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt +Date: Wed, 20 Jul 2022 10:10:21 +0000 +Subject: [PATCH] cursor-renderer-native: Don't retry forever after GBM cursor + functions fail + +This avoids flooding the log with every cursor change: +``` +(gnome-shell:19923): libmutter-WARNING **: 10:15:23.404: Realizing HW cursor failed: Failed to allocate gbm_bo: Invalid argument + +(gnome-shell:19923): libmutter-WARNING **: 10:15:23.450: Realizing HW cursor failed: Failed to allocate gbm_bo: Invalid argument + +(gnome-shell:19923): libmutter-WARNING **: 10:15:23.451: Realizing HW cursor failed: Failed to allocate gbm_bo: Invalid argument +``` + +Related: https://gitlab.gnome.org/GNOME/mutter/-/issues/2354 +Part-of: +--- + .../native/meta-cursor-renderer-native.c | 19 ++++++++++++++----- + 1 file changed, 14 insertions(+), 5 deletions(-) + +diff --git a/src/backends/native/meta-cursor-renderer-native.c b/src/backends/native/meta-cursor-renderer-native.c +index 3c63a15d68..08c9819a12 100644 +--- a/src/backends/native/meta-cursor-renderer-native.c ++++ b/src/backends/native/meta-cursor-renderer-native.c +@@ -506,12 +506,9 @@ unset_crtc_cursor (MetaCursorRendererNative *native, + } + + static void +-disable_hw_cursor_for_crtc (MetaKmsCrtc *kms_crtc, +- const GError *error) ++disable_hw_cursor_for_gpu (MetaGpuKms *gpu_kms, ++ const GError *error) + { +- MetaCrtcKms *crtc_kms = meta_crtc_kms_from_kms_crtc (kms_crtc); +- MetaCrtc *crtc = META_CRTC (crtc_kms); +- MetaGpuKms *gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc)); + MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data = + meta_cursor_renderer_native_gpu_data_from_gpu (gpu_kms); + +@@ -521,6 +518,17 @@ disable_hw_cursor_for_crtc (MetaKmsCrtc *kms_crtc, + cursor_renderer_gpu_data->hw_cursor_broken = TRUE; + } + ++static void ++disable_hw_cursor_for_crtc (MetaKmsCrtc *kms_crtc, ++ const GError *error) ++{ ++ MetaCrtcKms *crtc_kms = meta_crtc_kms_from_kms_crtc (kms_crtc); ++ MetaCrtc *crtc = META_CRTC (crtc_kms); ++ MetaGpuKms *gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc)); ++ ++ disable_hw_cursor_for_gpu (gpu_kms, error); ++} ++ + void + meta_cursor_renderer_native_prepare_frame (MetaCursorRendererNative *cursor_renderer_native, + MetaRendererView *view) +@@ -1375,6 +1383,7 @@ load_cursor_sprite_gbm_buffer_for_gpu (MetaCursorRendererNative *native, + if (!buffer) + { + g_warning ("Realizing HW cursor failed: %s", error->message); ++ disable_hw_cursor_for_gpu (gpu_kms, error); + return; + } + +-- +2.44.0.501.g19981daefd.dirty + diff --git a/SOURCES/0001-display-Make-cgroup-constructor-local.patch b/SOURCES/0001-display-Make-cgroup-constructor-local.patch new file mode 100644 index 0000000..13f8425 --- /dev/null +++ b/SOURCES/0001-display-Make-cgroup-constructor-local.patch @@ -0,0 +1,26 @@ +From 088644fb1773b64ca45dec497589517e1774eac1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 27 Aug 2024 11:33:14 +0200 +Subject: [PATCH 1/5] display: Make cgroup constructor local + +This silences a warning about a missing function declaration. +--- + src/core/display.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/core/display.c b/src/core/display.c +index 4c9038e627..97f591a876 100644 +--- a/src/core/display.c ++++ b/src/core/display.c +@@ -1619,7 +1619,7 @@ extract_app_id_from_cgroup (const char *cgroup) + return g_steal_pointer (&app_id); + } + +-MetaCGroup* ++static MetaCGroup* + meta_cgroup_new (const char *path) + { + MetaCGroup *cgroup; +-- +2.44.0.501.g19981daefd.dirty + diff --git a/SOURCES/0001-kms-impl-device-Add-addfb2_modifiers-to-MetaKmsDevic.patch b/SOURCES/0001-kms-impl-device-Add-addfb2_modifiers-to-MetaKmsDevic.patch new file mode 100644 index 0000000..f4c3ab6 --- /dev/null +++ b/SOURCES/0001-kms-impl-device-Add-addfb2_modifiers-to-MetaKmsDevic.patch @@ -0,0 +1,51 @@ +From 11e6100226006b5371de30310357582db64c9309 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt +Date: Tue, 5 Apr 2022 17:05:17 +0800 +Subject: [PATCH 1/2] kms/impl-device: Add addfb2_modifiers to + MetaKmsDeviceCaps + +Part-of: +--- + src/backends/native/meta-kms-impl-device.c | 6 ++++++ + src/backends/native/meta-kms-impl-device.h | 1 + + 2 files changed, 7 insertions(+) + +diff --git a/src/backends/native/meta-kms-impl-device.c b/src/backends/native/meta-kms-impl-device.c +index 75920fe6b..d2e821338 100644 +--- a/src/backends/native/meta-kms-impl-device.c ++++ b/src/backends/native/meta-kms-impl-device.c +@@ -288,6 +288,7 @@ init_caps (MetaKmsImplDevice *impl_device) + meta_kms_impl_device_get_instance_private (impl_device); + int fd = priv->fd; + uint64_t cursor_width, cursor_height; ++ uint64_t addfb2_modifiers; + + if (drmGetCap (fd, DRM_CAP_CURSOR_WIDTH, &cursor_width) == 0 && + drmGetCap (fd, DRM_CAP_CURSOR_HEIGHT, &cursor_height) == 0) +@@ -296,6 +297,11 @@ init_caps (MetaKmsImplDevice *impl_device) + priv->caps.cursor_width = cursor_width; + priv->caps.cursor_height = cursor_height; + } ++ ++ if (drmGetCap (fd, DRM_CAP_ADDFB2_MODIFIERS, &addfb2_modifiers) == 0) ++ { ++ priv->caps.addfb2_modifiers = (addfb2_modifiers != 0); ++ } + } + + static void +diff --git a/src/backends/native/meta-kms-impl-device.h b/src/backends/native/meta-kms-impl-device.h +index 913ba992f..a82ad4155 100644 +--- a/src/backends/native/meta-kms-impl-device.h ++++ b/src/backends/native/meta-kms-impl-device.h +@@ -36,6 +36,7 @@ typedef struct _MetaKmsDeviceCaps + gboolean has_cursor_size; + uint64_t cursor_width; + uint64_t cursor_height; ++ gboolean addfb2_modifiers; + } MetaKmsDeviceCaps; + + typedef struct _MetaKmsProp MetaKmsProp; +-- +2.45.2 + diff --git a/SOURCES/0001-wayland-wl-shell-Make-sure-created-window-has-a-prop.patch b/SOURCES/0001-wayland-wl-shell-Make-sure-created-window-has-a-prop.patch new file mode 100644 index 0000000..910034c --- /dev/null +++ b/SOURCES/0001-wayland-wl-shell-Make-sure-created-window-has-a-prop.patch @@ -0,0 +1,48 @@ +From 0fe26e5b6d1e6f03a99623edf6a6f4c6caa2e142 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 15 Oct 2024 14:56:33 +0200 +Subject: [PATCH 1/2] wayland/wl-shell: Make sure created window has a proper + size + +The wl_shell_window construction is a bit messy, and was not properly +resizing when a window was created after a buffer was attached. This, +when the window was the dummy window in wl-paste, caused a SIGFPE as the +window was incorrectly assumed to be 0x0, i.e. size being 0. +--- + src/wayland/meta-wayland-wl-shell.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/src/wayland/meta-wayland-wl-shell.c b/src/wayland/meta-wayland-wl-shell.c +index 964c185b23..51d88cea2b 100644 +--- a/src/wayland/meta-wayland-wl-shell.c ++++ b/src/wayland/meta-wayland-wl-shell.c +@@ -481,7 +481,7 @@ sync_wl_shell_parent_relationship (MetaWaylandSurface *surface, + } + } + +-static void ++static MetaWindow * + create_wl_shell_surface_window (MetaWaylandSurface *surface) + { + MetaWaylandWlShellSurface *wl_shell_surface = +@@ -513,6 +513,8 @@ create_wl_shell_surface_window (MetaWaylandSurface *surface) + if (meta_wayland_surface_get_window (child)) + sync_wl_shell_parent_relationship (child, surface); + } ++ ++ return window; + } + + static void +@@ -597,7 +599,7 @@ wl_shell_surface_role_apply_state (MetaWaylandSurfaceRole *surface_role, + * convenient for us. */ + if (surface->buffer_ref->buffer && !window) + { +- create_wl_shell_surface_window (surface); ++ window = create_wl_shell_surface_window (surface); + } + else if (!surface->buffer_ref->buffer && window) + { +-- +2.44.0.501.g19981daefd.dirty + diff --git a/SOURCES/0001-window-Don-t-switch-workspaces-if-users-from-forged-.patch b/SOURCES/0001-window-Don-t-switch-workspaces-if-users-from-forged-.patch new file mode 100644 index 0000000..0adcfc9 --- /dev/null +++ b/SOURCES/0001-window-Don-t-switch-workspaces-if-users-from-forged-.patch @@ -0,0 +1,101 @@ +From f1da6553e13e3c9a9ed91370dcf435fa4a19fb2b Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 6 Jun 2024 13:01:41 -0400 +Subject: [PATCH 1/6] window: Don't switch workspaces if users from forged + activation messages + +--- + src/core/window.c | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +diff --git a/src/core/window.c b/src/core/window.c +index 7d86adece..e787dbce0 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -3687,74 +3687,81 @@ meta_window_unshade (MetaWindow *window, + "Focusing window %s after unshading it", + window->desc); + meta_window_focus (window, timestamp); + + set_net_wm_state (window); + } + } + + static gboolean + unminimize_func (MetaWindow *window, + void *data) + { + meta_window_unminimize (window); + return TRUE; + } + + static void + unminimize_window_and_all_transient_parents (MetaWindow *window) + { + meta_window_unminimize (window); + meta_window_foreach_ancestor (window, unminimize_func, NULL); + } + + void + meta_window_activate_full (MetaWindow *window, + guint32 timestamp, + MetaClientType source_indication, + MetaWorkspace *workspace) + { + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; +- gboolean allow_workspace_switch; ++ gboolean allow_workspace_switch = FALSE; + + if (window->unmanaging) + { + g_warning ("Trying to activate unmanaged window '%s'", window->desc); + return; + } + + meta_topic (META_DEBUG_FOCUS, + "_NET_ACTIVE_WINDOW message sent for %s at time %u " + "by client type %u.", + window->desc, timestamp, source_indication); + +- allow_workspace_switch = (timestamp != 0); ++ if (window->display->last_user_time == timestamp) ++ { ++ /* Only allow workspace switches if this activation message uses the same ++ * timestamp as the last user interaction ++ */ ++ allow_workspace_switch = TRUE; ++ } ++ + if (timestamp != 0 && + XSERVER_TIME_IS_BEFORE (timestamp, window->display->last_user_time)) + { + meta_topic (META_DEBUG_FOCUS, + "last_user_time (%u) is more recent; ignoring " + " _NET_ACTIVE_WINDOW message.", + window->display->last_user_time); + meta_window_set_demands_attention(window); + return; + } + + if (timestamp == 0) + timestamp = meta_display_get_current_time_roundtrip (window->display); + + meta_window_set_user_time (window, timestamp); + + /* disable show desktop mode unless we're a desktop component */ + maybe_leave_show_desktop_mode (window); + + /* Get window on current or given workspace */ + if (workspace == NULL) + workspace = workspace_manager->active_workspace; + + /* For non-transient windows, we just set up a pulsing indicator, + rather than move windows or workspaces. + See http://bugzilla.gnome.org/show_bug.cgi?id=482354 */ + if (window->transient_for == NULL && + !allow_workspace_switch && + !meta_window_located_on_workspace (window, workspace)) + { +-- +2.44.0 + diff --git a/SOURCES/0002-core-events-Count-shell-interactions-has-user-intera.patch b/SOURCES/0002-core-events-Count-shell-interactions-has-user-intera.patch new file mode 100644 index 0000000..8e93f66 --- /dev/null +++ b/SOURCES/0002-core-events-Count-shell-interactions-has-user-intera.patch @@ -0,0 +1,133 @@ +From b2cf9836373a446d674ecce251e3e42bb863dc75 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 6 Jun 2024 13:32:03 -0400 +Subject: [PATCH 2/6] core/events: Count shell interactions has user + interactions too + +mutter keeps track of the last time the user used the system to +decide whether or not to prevent focus stealing. + +Right now, it only considers user interactions with application +windows, not interactions with the compositor chrome. + +That means a user could start loading an application, +switch workspaces, and get forcefully pulled back when the +application finishes loading. + +This commit fixes that problem by updating the user time on shell +interactions as well. +--- + src/core/events.c | 38 ++++++++++++++++++++++++-------------- + 1 file changed, 24 insertions(+), 14 deletions(-) + +diff --git a/src/core/events.c b/src/core/events.c +index 775104229..4d25b6dc0 100644 +--- a/src/core/events.c ++++ b/src/core/events.c +@@ -288,79 +288,89 @@ meta_display_handle_event (MetaDisplay *display, + if (source) + meta_backend_update_last_device (backend, source); + } + + #ifdef HAVE_WAYLAND + if (meta_is_wayland_compositor () && event->type == CLUTTER_MOTION) + { + MetaCursorRenderer *cursor_renderer; + ClutterInputDevice *device; + + device = clutter_event_get_device (event); + cursor_renderer = meta_backend_get_cursor_renderer_for_device (backend, + device); + if (cursor_renderer) + meta_cursor_renderer_update_position (cursor_renderer); + + if (device == clutter_seat_get_pointer (clutter_input_device_get_seat (device))) + { + MetaCursorTracker *cursor_tracker = + meta_backend_get_cursor_tracker (backend); + + meta_cursor_tracker_invalidate_position (cursor_tracker); + } + } + #endif + + window = get_window_for_event (display, event); + + display->current_time = event->any.time; + +- if (window && !window->override_redirect && +- (event->type == CLUTTER_KEY_PRESS || +- event->type == CLUTTER_BUTTON_PRESS || +- event->type == CLUTTER_TOUCH_BEGIN)) ++ if (event->type == CLUTTER_KEY_PRESS || ++ event->type == CLUTTER_BUTTON_PRESS || ++ event->type == CLUTTER_TOUCH_BEGIN) + { +- if (META_CURRENT_TIME == display->current_time) ++ if (window && !window->override_redirect) + { +- /* We can't use missing (i.e. invalid) timestamps to set user time, +- * nor do we want to use them to sanity check other timestamps. +- * See bug 313490 for more details. +- */ +- meta_warning ("Event has no timestamp! You may be using a broken " +- "program such as xse. Please ask the authors of that " +- "program to fix it."); ++ if (META_CURRENT_TIME == display->current_time) ++ { ++ /* We can't use missing (i.e. invalid) timestamps to set user time, ++ * nor do we want to use them to sanity check other timestamps. ++ * See bug 313490 for more details. ++ */ ++ meta_warning ("Event has no timestamp! You may be using a broken " ++ "program such as xse. Please ask the authors of that " ++ "program to fix it."); ++ } ++ else ++ { ++ meta_window_set_user_time (window, display->current_time); ++ meta_display_sanity_check_timestamps (display, display->current_time); ++ } + } + else + { +- meta_window_set_user_time (window, display->current_time); +- meta_display_sanity_check_timestamps (display, display->current_time); ++ /* Always update user time to the last time the user did an event, even ++ * if it was to shell chrome or a notification or something. ++ */ ++ if (XSERVER_TIME_IS_BEFORE (display->last_user_time, display->current_time)) ++ display->last_user_time = display->current_time; + } + } + + gesture_tracker = meta_display_get_gesture_tracker (display); + + if (meta_gesture_tracker_handle_event (gesture_tracker, event)) + { + bypass_wayland = bypass_clutter = TRUE; + goto out; + } + + if (display->event_route == META_EVENT_ROUTE_WINDOW_OP) + { + if (meta_window_handle_mouse_grab_op_event (window, event)) + { + bypass_clutter = TRUE; + bypass_wayland = TRUE; + goto out; + } + } + + /* For key events, it's important to enforce single-handling, or + * we can get into a confused state. So if a keybinding is + * handled (because it's one of our hot-keys, or because we are + * in a keyboard-grabbed mode like moving a window, we don't + * want to pass the key event to the compositor or Wayland at all. + */ + if (meta_keybindings_process_event (display, window, event)) + { + bypass_clutter = TRUE; +-- +2.44.0 + diff --git a/SOURCES/0002-display-Also-set-window-cgroup-on-cgroup-creation.patch b/SOURCES/0002-display-Also-set-window-cgroup-on-cgroup-creation.patch new file mode 100644 index 0000000..d05c281 --- /dev/null +++ b/SOURCES/0002-display-Also-set-window-cgroup-on-cgroup-creation.patch @@ -0,0 +1,27 @@ +From 37b4b8dd63851e97b507008fe1028a259c0419c7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 27 Aug 2024 11:33:22 +0200 +Subject: [PATCH 2/5] display: Also set window cgroup on cgroup creation + +We'd register the cgroup for a window twice, because the firs time +didn't update the MetaWindow::cgroup pointer. This meant the cgroups +were never removed. +--- + src/core/display.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/core/display.c b/src/core/display.c +index 97f591a876..e99e787fbe 100644 +--- a/src/core/display.c ++++ b/src/core/display.c +@@ -1704,6 +1704,7 @@ meta_display_register_cgroup (MetaDisplay *display, + + cgroup = meta_cgroup_new (path); + g_hash_table_insert (display->cgroups, g_file_get_path (cgroup->path), cgroup); ++ window->cgroup = cgroup; + } + + void +-- +2.44.0.501.g19981daefd.dirty + diff --git a/SOURCES/0002-kms-device-Disable-modifiers-when-DRM_CAP_ADDFB2_MOD.patch b/SOURCES/0002-kms-device-Disable-modifiers-when-DRM_CAP_ADDFB2_MOD.patch new file mode 100644 index 0000000..aa0f370 --- /dev/null +++ b/SOURCES/0002-kms-device-Disable-modifiers-when-DRM_CAP_ADDFB2_MOD.patch @@ -0,0 +1,29 @@ +From dd94c448e94b1033b90749d77c5dc587c3b8f9f4 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt +Date: Tue, 5 Apr 2022 17:06:21 +0800 +Subject: [PATCH 2/2] kms/device: Disable modifiers when + !DRM_CAP_ADDFB2_MODIFIERS + +Fixes: https://gitlab.gnome.org/GNOME/mutter/-/issues/2210 +Part-of: +--- + src/backends/native/meta-kms-device.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/backends/native/meta-kms-device.c b/src/backends/native/meta-kms-device.c +index bef1e2065..7c84f14f5 100644 +--- a/src/backends/native/meta-kms-device.c ++++ b/src/backends/native/meta-kms-device.c +@@ -490,6 +490,9 @@ meta_kms_device_new (MetaKms *kms, + free (device->path); + device->path = data.out_path; + ++ if (!device->caps.addfb2_modifiers) ++ device->flags |= META_KMS_DEVICE_FLAG_DISABLE_MODIFIERS; ++ + return device; + } + +-- +2.45.2 + diff --git a/SOURCES/0002-window-Avoid-SIGFPE-on-bogus-window-size.patch b/SOURCES/0002-window-Avoid-SIGFPE-on-bogus-window-size.patch new file mode 100644 index 0000000..b951c38 --- /dev/null +++ b/SOURCES/0002-window-Avoid-SIGFPE-on-bogus-window-size.patch @@ -0,0 +1,24 @@ +From e434615ed1d4ba506e0282ad5cdc94303310c682 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 16 Oct 2024 14:26:28 +0200 +Subject: [PATCH 2/2] window: Avoid SIGFPE on bogus window size + +--- + src/core/window.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/core/window.c b/src/core/window.c +index 512ef9312f..142aa0eca1 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -2393,6 +2393,7 @@ window_would_mostly_be_covered_by_always_above_window (MetaWindow *window) + } + + window_area = window->rect.width * window->rect.height; ++ g_return_val_if_fail (window_area > 0, FALSE); + + cairo_region_intersect_rectangle (region, &window->rect); + intersection_area = calculate_region_area (region); +-- +2.44.0.501.g19981daefd.dirty + diff --git a/SOURCES/0003-core-window-Split-cgroup-out-to-separate-struct.patch b/SOURCES/0003-core-window-Split-cgroup-out-to-separate-struct.patch new file mode 100644 index 0000000..70a52de --- /dev/null +++ b/SOURCES/0003-core-window-Split-cgroup-out-to-separate-struct.patch @@ -0,0 +1,816 @@ +From 0c104d85654318978d10d0fadf33ceea92e29c0a Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 13 Jun 2024 12:58:21 -0400 +Subject: [PATCH 3/6] core/window: Split cgroup out to separate struct + +We're going to need to attach other information to the cgroup, +aside from the cgroup path, so this commit separates it out +to its own struct, shared with other windows using the same +cgroup. +--- + src/core/display-private.h | 17 +++++++++ + src/core/display.c | 70 ++++++++++++++++++++++++++++++++++++++ + src/core/window-private.h | 2 ++ + src/core/window.c | 44 ++++++++++++++++++++++++ + 4 files changed, 133 insertions(+) + +diff --git a/src/core/display-private.h b/src/core/display-private.h +index 3d690fcb6..fb17d20a6 100644 +--- a/src/core/display-private.h ++++ b/src/core/display-private.h +@@ -78,98 +78,106 @@ typedef enum + META_TILE_LEFT, + META_TILE_RIGHT, + META_TILE_MAXIMIZED + } MetaTileMode; + + typedef enum + { + /* Normal interaction where you're interacting with windows. + * Events go to windows normally. */ + META_EVENT_ROUTE_NORMAL, + + /* In a window operation like moving or resizing. All events + * goes to MetaWindow, but not to the actual client window. */ + META_EVENT_ROUTE_WINDOW_OP, + + /* In a compositor grab operation. All events go to the + * compositor plugin. */ + META_EVENT_ROUTE_COMPOSITOR_GRAB, + + /* A Wayland application has a popup open. All events go to + * the Wayland application. */ + META_EVENT_ROUTE_WAYLAND_POPUP, + + /* The user is clicking on a window button. */ + META_EVENT_ROUTE_FRAME_BUTTON, + } MetaEventRoute; + + typedef void (* MetaDisplayWindowFunc) (MetaWindow *window, + gpointer user_data); + ++typedef struct _MetaCGroup MetaCGroup; ++struct _MetaCGroup { ++ GFile *path; ++ ++ grefcount ref_count; ++}; ++ + struct _MetaDisplay + { + GObject parent_instance; + + MetaX11Display *x11_display; + + int clutter_event_filter; + + /* Our best guess as to the "currently" focused window (that is, the + * window that we expect will be focused at the point when the X + * server processes our next request), and the serial of the request + * or event that caused this. + */ + MetaWindow *focus_window; + + /* last timestamp passed to XSetInputFocus */ + guint32 last_focus_time; + + /* last user interaction time in any app */ + guint32 last_user_time; + + /* whether we're using mousenav (only relevant for sloppy&mouse focus modes; + * !mouse_mode means "keynav mode") + */ + guint mouse_mode : 1; + + /* Helper var used when focus_new_windows setting is 'strict'; only + * relevant in 'strict' mode and if the focus window is a terminal. + * In that case, we don't allow new windows to take focus away from + * a terminal, but if the user explicitly did something that should + * allow a different window to gain focus (e.g. global keybinding or + * clicking on a dock), then we will allow the transfer. + */ + guint allow_terminal_deactivation : 1; + + /*< private-ish >*/ + GHashTable *stamps; + GHashTable *wayland_windows; ++ GHashTable *cgroups; + + /* serials of leave/unmap events that may + * correspond to an enter event we should + * ignore + */ + unsigned long ignored_crossing_serials[N_IGNORED_CROSSING_SERIALS]; + + guint32 current_time; + + /* We maintain a sequence counter, incremented for each #MetaWindow + * created. This is exposed by meta_window_get_stable_sequence() + * but is otherwise not used inside mutter. + * + * It can be useful to plugins which want to sort windows in a + * stable fashion. + */ + guint32 window_sequence_counter; + + /* Pings which we're waiting for a reply from */ + GSList *pending_pings; + + /* Pending focus change */ + guint focus_timeout_id; + + /* Pending autoraise */ + guint autoraise_timeout_id; + MetaWindow* autoraise_window; + + /* Event routing */ + MetaEventRoute event_route; +@@ -254,60 +262,69 @@ struct _MetaDisplayClass + * + * See the docs for meta_display_xserver_time_is_before(). + */ + #define XSERVER_TIME_IS_BEFORE(time1, time2) \ + ( (time1) == 0 || \ + (XSERVER_TIME_IS_BEFORE_ASSUMING_REAL_TIMESTAMPS(time1, time2) && \ + (time2) != 0) \ + ) + + gboolean meta_display_open (void); + + void meta_display_manage_all_xwindows (MetaDisplay *display); + void meta_display_unmanage_windows (MetaDisplay *display, + guint32 timestamp); + + /* Utility function to compare the stacking of two windows */ + int meta_display_stack_cmp (const void *a, + const void *b); + + /* Each MetaWindow is uniquely identified by a 64-bit "stamp"; unlike a + * a MetaWindow *, a stamp will never be recycled + */ + MetaWindow* meta_display_lookup_stamp (MetaDisplay *display, + guint64 stamp); + void meta_display_register_stamp (MetaDisplay *display, + guint64 *stampp, + MetaWindow *window); + void meta_display_unregister_stamp (MetaDisplay *display, + guint64 stamp); + ++void meta_display_register_cgroup (MetaDisplay *display, ++ MetaWindow *window, ++ const char *path); ++void meta_display_unregister_cgroup (MetaDisplay *display, ++ MetaWindow *window); ++ ++MetaCGroup* meta_cgroup_ref (MetaCGroup *cgroup); ++gboolean meta_cgroup_unref (MetaCGroup *cgroup); ++ + /* A "stack id" is a XID or a stamp */ + #define META_STACK_ID_IS_X11(id) ((id) < G_GUINT64_CONSTANT(0x100000000)) + + META_EXPORT_TEST + MetaWindow* meta_display_lookup_stack_id (MetaDisplay *display, + guint64 stack_id); + + /* for debug logging only; returns a human-description of the stack + * ID - a small number of buffers are recycled, so the result must + * be used immediately or copied */ + const char *meta_display_describe_stack_id (MetaDisplay *display, + guint64 stack_id); + + void meta_display_register_wayland_window (MetaDisplay *display, + MetaWindow *window); + void meta_display_unregister_wayland_window (MetaDisplay *display, + MetaWindow *window); + + void meta_display_notify_window_created (MetaDisplay *display, + MetaWindow *window); + + META_EXPORT_TEST + GSList* meta_display_list_windows (MetaDisplay *display, + MetaListWindowsFlags flags); + + MetaDisplay* meta_display_for_x_display (Display *xdisplay); + + META_EXPORT_TEST + MetaDisplay* meta_get_display (void); + +diff --git a/src/core/display.c b/src/core/display.c +index 4b58a5d2f..937defd2c 100644 +--- a/src/core/display.c ++++ b/src/core/display.c +@@ -833,60 +833,61 @@ meta_display_open (void) + + i = 0; + while (i < N_IGNORED_CROSSING_SERIALS) + { + display->ignored_crossing_serials[i] = 0; + ++i; + } + + display->current_time = META_CURRENT_TIME; + + display->grab_resize_timeout_id = 0; + display->grab_have_keyboard = FALSE; + + display->grab_op = META_GRAB_OP_NONE; + display->grab_window = NULL; + display->grab_tile_mode = META_TILE_NONE; + display->grab_tile_monitor_number = -1; + + meta_display_cleanup_edges (display); + + meta_display_init_keys (display); + + meta_prefs_add_listener (prefs_changed_callback, display); + + /* Get events */ + meta_display_init_events (display); + + display->stamps = g_hash_table_new (g_int64_hash, + g_int64_equal); + display->wayland_windows = g_hash_table_new (NULL, NULL); ++ display->cgroups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + monitor_manager = meta_backend_get_monitor_manager (backend); + g_signal_connect (monitor_manager, "monitors-changed-internal", + G_CALLBACK (on_monitors_changed_internal), display); + + display->pad_action_mapper = meta_pad_action_mapper_new (monitor_manager); + + settings = meta_backend_get_settings (backend); + g_signal_connect (settings, "ui-scaling-factor-changed", + G_CALLBACK (on_ui_scaling_factor_changed), display); + + display->compositor = create_compositor (display); + + meta_display_set_cursor (display, META_CURSOR_DEFAULT); + + display->stack = meta_stack_new (display); + display->stack_tracker = meta_stack_tracker_new (display); + + display->workspace_manager = meta_workspace_manager_new (display); + + display->startup_notification = meta_startup_notification_new (display); + + display->bell = meta_bell_new (display); + + display->selection = meta_selection_new (display); + meta_clipboard_manager_init (display); + + #ifdef HAVE_WAYLAND + if (meta_is_wayland_compositor ()) + { +@@ -1095,60 +1096,61 @@ meta_display_close (MetaDisplay *display, + meta_prefs_remove_listener (prefs_changed_callback, display); + + meta_display_remove_autoraise_callback (display); + + g_clear_object (&display->gesture_tracker); + + g_clear_handle_id (&display->focus_timeout_id, g_source_remove); + g_clear_handle_id (&display->tile_preview_timeout_id, g_source_remove); + + if (display->work_area_later != 0) + meta_later_remove (display->work_area_later); + if (display->check_fullscreen_later != 0) + meta_later_remove (display->check_fullscreen_later); + + /* Stop caring about events */ + meta_display_free_events (display); + + g_clear_pointer (&display->compositor, meta_compositor_destroy); + + meta_display_shutdown_x11 (display); + + g_clear_object (&display->stack); + g_clear_pointer (&display->stack_tracker, + meta_stack_tracker_free); + + /* Must be after all calls to meta_window_unmanage() since they + * unregister windows + */ + g_hash_table_destroy (display->wayland_windows); + g_hash_table_destroy (display->stamps); ++ g_hash_table_destroy (display->cgroups); + + meta_display_shutdown_keys (display); + + g_clear_object (&display->bell); + g_clear_object (&display->startup_notification); + g_clear_object (&display->workspace_manager); + g_clear_object (&display->sound_player); + + meta_clipboard_manager_shutdown (display); + g_clear_object (&display->selection); + g_clear_object (&display->pad_action_mapper); + + g_object_unref (display); + the_display = NULL; + + meta_quit (META_EXIT_SUCCESS); + } + + /** + * meta_display_for_x_display: + * @xdisplay: An X display + * + * Returns the singleton MetaDisplay if @xdisplay matches the X display it's + * managing; otherwise gives a warning and returns %NULL. When we were claiming + * to be able to manage multiple displays, this was supposed to find the + * display out of the list which matched that display. Now it's merely an + * extra sanity check. + * + * Returns: The singleton X display, or %NULL if @xdisplay isn't the one + * we're managing. +@@ -1491,60 +1493,128 @@ meta_display_set_input_focus (MetaDisplay *display, + + meta_display_update_focus_window (display, window); + + display->last_focus_time = timestamp; + + if (window == NULL || window != display->autoraise_window) + meta_display_remove_autoraise_callback (display); + } + + void + meta_display_unset_input_focus (MetaDisplay *display, + guint32 timestamp) + { + meta_display_set_input_focus (display, NULL, FALSE, timestamp); + } + + void + meta_display_register_wayland_window (MetaDisplay *display, + MetaWindow *window) + { + g_hash_table_add (display->wayland_windows, window); + } + + void + meta_display_unregister_wayland_window (MetaDisplay *display, + MetaWindow *window) + { + g_hash_table_remove (display->wayland_windows, window); + } + ++MetaCGroup* ++meta_cgroup_new (const char *path) ++{ ++ MetaCGroup *cgroup; ++ ++ cgroup = g_new0 (MetaCGroup, 1); ++ cgroup->path = g_file_new_for_path (path); ++ g_ref_count_init (&cgroup->ref_count); ++ ++ return cgroup; ++} ++ ++MetaCGroup* ++meta_cgroup_ref (MetaCGroup *cgroup) ++{ ++ g_ref_count_inc (&cgroup->ref_count); ++ return cgroup; ++} ++ ++gboolean ++meta_cgroup_unref (MetaCGroup *cgroup) ++{ ++ if (!g_ref_count_dec (&cgroup->ref_count)) ++ return FALSE; ++ ++ g_clear_object (&cgroup->path); ++ g_free (cgroup); ++ ++ return TRUE; ++} ++ ++void ++meta_display_register_cgroup (MetaDisplay *display, ++ MetaWindow *window, ++ const char *path) ++{ ++ MetaCGroup *cgroup; ++ ++ cgroup = g_hash_table_lookup (display->cgroups, path); ++ ++ if (cgroup) ++ { ++ window->cgroup = meta_cgroup_ref (cgroup); ++ return; ++ } ++ ++ cgroup = meta_cgroup_new (path); ++ g_hash_table_insert (display->cgroups, g_file_get_path (cgroup->path), cgroup); ++} ++ ++void ++meta_display_unregister_cgroup (MetaDisplay *display, ++ MetaWindow *window) ++{ ++ g_autofree const char *path = NULL; ++ MetaCGroup *cgroup = g_steal_pointer (&window->cgroup); ++ ++ if (!cgroup) ++ return; ++ ++ path = g_file_get_path (cgroup->path); ++ ++ if (!meta_cgroup_unref (cgroup)) ++ return; ++ ++ g_hash_table_remove (display->cgroups, path); ++} ++ + MetaWindow* + meta_display_lookup_stamp (MetaDisplay *display, + guint64 stamp) + { + return g_hash_table_lookup (display->stamps, &stamp); + } + + void + meta_display_register_stamp (MetaDisplay *display, + guint64 *stampp, + MetaWindow *window) + { + g_return_if_fail (g_hash_table_lookup (display->stamps, stampp) == NULL); + + g_hash_table_insert (display->stamps, stampp, window); + } + + void + meta_display_unregister_stamp (MetaDisplay *display, + guint64 stamp) + { + g_return_if_fail (g_hash_table_lookup (display->stamps, &stamp) != NULL); + + g_hash_table_remove (display->stamps, &stamp); + } + + MetaWindow* + meta_display_lookup_stack_id (MetaDisplay *display, + guint64 stack_id) + { +diff --git a/src/core/window-private.h b/src/core/window-private.h +index d1730c988..8a0aebb38 100644 +--- a/src/core/window-private.h ++++ b/src/core/window-private.h +@@ -193,60 +193,62 @@ struct _MetaWindow + * binary data + */ + char *res_class; + char *res_name; + char *role; + char *sm_client_id; + char *wm_client_machine; + + char *startup_id; + char *mutter_hints; + char *sandboxed_app_id; + char *gtk_theme_variant; + char *gtk_application_id; + char *gtk_unique_bus_name; + char *gtk_application_object_path; + char *gtk_window_object_path; + char *gtk_app_menu_object_path; + char *gtk_menubar_object_path; + + Window xtransient_for; + Window xgroup_leader; + Window xclient_leader; + MetaWindow *transient_for; + + /* Initial workspace property */ + int initial_workspace; + + /* Initial timestamp property */ + guint32 initial_timestamp; + ++ MetaCGroup *cgroup; ++ + /* Whether this is an override redirect window or not */ + guint override_redirect : 1; + + /* Whether we're maximized */ + guint maximized_horizontally : 1; + guint maximized_vertically : 1; + + /* Whether we have to maximize/minimize after placement */ + guint maximize_horizontally_after_placement : 1; + guint maximize_vertically_after_placement : 1; + guint minimize_after_placement : 1; + + /* The current tile mode */ + MetaTileMode tile_mode; + + /* The last "full" maximized/unmaximized state. We need to keep track of + * that to toggle between normal/tiled or maximized/tiled states. */ + guint saved_maximize : 1; + int tile_monitor_number; + + struct { + MetaEdgeConstraint top; + MetaEdgeConstraint right; + MetaEdgeConstraint bottom; + MetaEdgeConstraint left; + } edge_constraints; + + double tile_hfraction; + + uint64_t preferred_output_winsys_id; +diff --git a/src/core/window.c b/src/core/window.c +index e787dbce0..f5ecd6438 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -70,60 +70,65 @@ + #include "cogl/cogl.h" + #include "core/boxes-private.h" + #include "core/constraints.h" + #include "core/edge-resistance.h" + #include "core/frame.h" + #include "core/keybindings-private.h" + #include "core/meta-workspace-manager-private.h" + #include "core/place.h" + #include "core/stack.h" + #include "core/util-private.h" + #include "core/workspace-private.h" + #include "meta/compositor-mutter.h" + #include "meta/group.h" + #include "meta/meta-cursor-tracker.h" + #include "meta/meta-enum-types.h" + #include "meta/meta-x11-errors.h" + #include "meta/prefs.h" + #include "ui/ui.h" + #include "x11/meta-x11-display-private.h" + #include "x11/window-props.h" + #include "x11/window-x11.h" + #include "x11/xprops.h" + + #ifdef HAVE_WAYLAND + #include "wayland/meta-wayland-private.h" + #include "wayland/meta-wayland-surface.h" + #include "wayland/meta-window-wayland.h" + #include "wayland/meta-window-xwayland.h" + #endif + ++#ifdef HAVE_LIBSYSTEMD ++#include ++#endif ++ ++ + /* Windows that unmaximize to a size bigger than that fraction of the workarea + * will be scaled down to that size (while maintaining aspect ratio). + * Windows that cover an area greater then this size are automaximized on map. + */ + #define MAX_UNMAXIMIZED_WINDOW_AREA .8 + + #define SNAP_SECURITY_LABEL_PREFIX "snap." + + static int destroying_windows_disallowed = 0; + + /* Each window has a "stamp" which is a non-recycled 64-bit ID. They + * start after the end of the XID space so that, for stacking + * we can keep a guint64 that represents one or the other + */ + static guint64 next_window_stamp = G_GUINT64_CONSTANT(0x100000000); + + static void invalidate_work_areas (MetaWindow *window); + static void set_wm_state (MetaWindow *window); + static void set_net_wm_state (MetaWindow *window); + static void meta_window_set_above (MetaWindow *window, + gboolean new_value); + + static void meta_window_show (MetaWindow *window); + static void meta_window_hide (MetaWindow *window); + + static void meta_window_save_rect (MetaWindow *window); + + static void ensure_mru_position_after (MetaWindow *window, + MetaWindow *after_this_one); + +@@ -303,60 +308,63 @@ meta_window_real_get_client_pid (MetaWindow *window) + } + + static void + meta_window_finalize (GObject *object) + { + MetaWindow *window = META_WINDOW (object); + + if (window->icon) + cairo_surface_destroy (window->icon); + + if (window->mini_icon) + cairo_surface_destroy (window->mini_icon); + + if (window->frame_bounds) + cairo_region_destroy (window->frame_bounds); + + if (window->shape_region) + cairo_region_destroy (window->shape_region); + + if (window->opaque_region) + cairo_region_destroy (window->opaque_region); + + if (window->input_region) + cairo_region_destroy (window->input_region); + + if (window->transient_for) + g_object_unref (window->transient_for); + + g_free (window->sm_client_id); + g_free (window->wm_client_machine); ++ ++ meta_display_unregister_cgroup (window->display, window); ++ + g_free (window->startup_id); + g_free (window->role); + g_free (window->res_class); + g_free (window->res_name); + g_free (window->title); + g_free (window->desc); + g_free (window->sandboxed_app_id); + g_free (window->gtk_theme_variant); + g_free (window->gtk_application_id); + g_free (window->gtk_unique_bus_name); + g_free (window->gtk_application_object_path); + g_free (window->gtk_window_object_path); + g_free (window->gtk_app_menu_object_path); + g_free (window->gtk_menubar_object_path); + g_free (window->placement.rule); + + G_OBJECT_CLASS (meta_window_parent_class)->finalize (object); + } + + static void + meta_window_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) + { + MetaWindow *win = META_WINDOW (object); + + switch (prop_id) + { + case PROP_TITLE: +@@ -1129,60 +1137,61 @@ _meta_window_shared_new (MetaDisplay *display, + window->has_minimize_func = TRUE; + window->has_maximize_func = TRUE; + window->has_move_func = TRUE; + window->has_resize_func = TRUE; + + window->has_shade_func = TRUE; + + window->has_fullscreen_func = TRUE; + + window->always_sticky = FALSE; + + window->skip_taskbar = FALSE; + window->skip_pager = FALSE; + window->skip_from_window_list = FALSE; + window->wm_state_above = FALSE; + window->wm_state_below = FALSE; + window->wm_state_demands_attention = FALSE; + + window->res_class = NULL; + window->res_name = NULL; + window->role = NULL; + window->sm_client_id = NULL; + window->wm_client_machine = NULL; + window->is_remote = FALSE; + window->startup_id = NULL; + + window->client_pid = 0; + + window->xtransient_for = None; + window->xclient_leader = None; ++ window->cgroup = NULL; + + window->type = META_WINDOW_NORMAL; + + window->struts = NULL; + + window->layer = META_LAYER_LAST; /* invalid value */ + window->stack_position = -1; + window->initial_workspace = 0; /* not used */ + window->initial_timestamp = 0; /* not used */ + + window->compositor_private = NULL; + + window->monitor = meta_window_calculate_main_logical_monitor (window); + if (window->monitor) + window->preferred_output_winsys_id = window->monitor->winsys_id; + else + window->preferred_output_winsys_id = UINT_MAX; + + window->tile_match = NULL; + + /* Assign this #MetaWindow a sequence number which can be used + * for sorting. + */ + window->stable_sequence = ++display->window_sequence_counter; + + window->opacity = 0xFF; + + if (window->override_redirect) + { + window->decorated = FALSE; +@@ -7640,60 +7649,95 @@ meta_window_get_transient_for (MetaWindow *window) + else if (window->xtransient_for) + return meta_x11_display_lookup_x_window (window->display->x11_display, + window->xtransient_for); + else + return NULL; + } + + /** + * meta_window_get_pid: + * @window: a #MetaWindow + * + * Returns the pid of the process that created this window, if available + * to the windowing system. + * + * Note that the value returned by this is vulnerable to spoofing attacks + * by the client. + * + * Return value: the pid, or 0 if not known. + */ + pid_t + meta_window_get_pid (MetaWindow *window) + { + g_return_val_if_fail (META_IS_WINDOW (window), 0); + + if (window->client_pid == 0) + window->client_pid = META_WINDOW_GET_CLASS (window)->get_client_pid (window); + + return window->client_pid; + } + ++static void ++meta_window_read_cgroup (MetaWindow *window) ++{ ++#ifdef HAVE_LIBSYSTEMD ++ g_autofree char *contents = NULL; ++ g_autofree char *complete_path = NULL; ++ g_autofree char *unit_name = NULL; ++ g_autofree char *unit_path = NULL; ++ char *unit_end; ++ pid_t pid; ++ ++ if (window->cgroup) ++ return; ++ ++ pid = meta_window_get_pid (window); ++ if (pid < 1) ++ return; ++ ++ if (sd_pid_get_cgroup (pid, &contents) < 0) ++ return; ++ g_strstrip (contents); ++ ++ complete_path = g_strdup_printf ("%s%s", "/sys/fs/cgroup", contents); ++ ++ if (sd_pid_get_user_unit (pid, &unit_name) < 0) ++ return; ++ g_strstrip (unit_name); ++ ++ unit_end = strstr (complete_path, unit_name) + strlen (unit_name); ++ *unit_end = '\0'; ++ ++ meta_display_register_cgroup (window->display, window, complete_path); ++#endif ++} ++ + /** + * meta_window_get_client_machine: + * @window: a #MetaWindow + * + * Returns name of the client machine from which this windows was created, + * if known (obtained from the WM_CLIENT_MACHINE property). + * + * Return value: (transfer none): the machine name, or NULL; the string is + * owned by the window manager and should not be freed or modified by the + * caller. + */ + const char * + meta_window_get_client_machine (MetaWindow *window) + { + g_return_val_if_fail (META_IS_WINDOW (window), NULL); + + return window->wm_client_machine; + } + + /** + * meta_window_is_remote: + * @window: a #MetaWindow + * + * Returns: %TRUE if this window originates from a host + * different from the one running mutter. + */ + gboolean + meta_window_is_remote (MetaWindow *window) + { + return window->is_remote; +-- +2.44.0 + diff --git a/SOURCES/0003-window-Unregister-cgroup-on-unmanage.patch b/SOURCES/0003-window-Unregister-cgroup-on-unmanage.patch new file mode 100644 index 0000000..aa8427e --- /dev/null +++ b/SOURCES/0003-window-Unregister-cgroup-on-unmanage.patch @@ -0,0 +1,36 @@ +From dd887dcf4770309fca127217660c5142a463e2c1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 27 Aug 2024 11:33:23 +0200 +Subject: [PATCH 3/5] window: Unregister cgroup on unmanage() + +This means any potential reference held by gjs will not hold the cgroup +alive longer than necessary. +--- + src/core/window.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/core/window.c b/src/core/window.c +index 142aa0eca1..272d664965 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -339,8 +339,6 @@ meta_window_finalize (GObject *object) + g_free (window->sm_client_id); + g_free (window->wm_client_machine); + +- meta_display_unregister_cgroup (window->display, window); +- + g_free (window->startup_id); + g_free (window->role); + g_free (window->res_class); +@@ -1467,6 +1465,8 @@ meta_window_unmanage (MetaWindow *window, + meta_verbose ("Unmanaging %s", window->desc); + window->unmanaging = TRUE; + ++ meta_display_unregister_cgroup (window->display, window); ++ + g_clear_handle_id (&window->unmanage_idle_id, g_source_remove); + + g_signal_emit (window, window_signals[UNMANAGING], 0); +-- +2.44.0.501.g19981daefd.dirty + diff --git a/SOURCES/0004-window-Don-t-use-cgroup-workspace-if-there-already-i.patch b/SOURCES/0004-window-Don-t-use-cgroup-workspace-if-there-already-i.patch new file mode 100644 index 0000000..dce3eb7 --- /dev/null +++ b/SOURCES/0004-window-Don-t-use-cgroup-workspace-if-there-already-i.patch @@ -0,0 +1,30 @@ +From 6cdfc8abe25fccfd06cac99ae82c0f2acbbdb7c2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 17 Oct 2024 11:49:26 +0200 +Subject: [PATCH 4/5] window: Don't use cgroup workspace if there already is + one + +This fixes a re-entry issue where calling 'meta_window_activate()' +assumed it'd activate on the current workspace. +--- + src/core/window.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/core/window.c b/src/core/window.c +index 272d664965..8ad8e5c4c4 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -3812,7 +3812,9 @@ meta_window_activate_full (MetaWindow *window, + if (workspace == NULL) + { + meta_window_read_cgroup (window); +- if (window->cgroup && ++ if (!window->workspace && ++ !window->on_all_workspaces && ++ window->cgroup && + window->cgroup->last_active_workspace != NULL && + !window->cgroup->has_startup_sequence && + (!window->cgroup->app_info || +-- +2.44.0.501.g19981daefd.dirty + diff --git a/SOURCES/0004-window-Track-workspace-per-cgroup.patch b/SOURCES/0004-window-Track-workspace-per-cgroup.patch new file mode 100644 index 0000000..684b2b2 --- /dev/null +++ b/SOURCES/0004-window-Track-workspace-per-cgroup.patch @@ -0,0 +1,685 @@ +From 9befece38b28581958d87d9393ff9c788213d2fb Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 13 Jun 2024 13:47:01 -0400 +Subject: [PATCH 4/6] window: Track workspace per-cgroup + +If an application doesn't tell us what workspace to start on we +need to guess. Currently our guess is "currently active workspace", +but that's not really always the best choice. + +This commit adds code to track the workspace other windows in the +same cgroup (~application) are running on and uses that to decide +which workspace to start on instead. +--- + src/core/display-private.h | 6 ++++++ + src/core/display.c | 31 +++++++++++++++++++++++++++++++ + src/core/window-private.h | 1 + + src/core/window.c | 36 ++++++++++++++++++++++++++++++++---- + 4 files changed, 70 insertions(+), 4 deletions(-) + +diff --git a/src/core/display-private.h b/src/core/display-private.h +index fb17d20a6..2b8d9e0d0 100644 +--- a/src/core/display-private.h ++++ b/src/core/display-private.h +@@ -81,60 +81,63 @@ typedef enum + } MetaTileMode; + + typedef enum + { + /* Normal interaction where you're interacting with windows. + * Events go to windows normally. */ + META_EVENT_ROUTE_NORMAL, + + /* In a window operation like moving or resizing. All events + * goes to MetaWindow, but not to the actual client window. */ + META_EVENT_ROUTE_WINDOW_OP, + + /* In a compositor grab operation. All events go to the + * compositor plugin. */ + META_EVENT_ROUTE_COMPOSITOR_GRAB, + + /* A Wayland application has a popup open. All events go to + * the Wayland application. */ + META_EVENT_ROUTE_WAYLAND_POPUP, + + /* The user is clicking on a window button. */ + META_EVENT_ROUTE_FRAME_BUTTON, + } MetaEventRoute; + + typedef void (* MetaDisplayWindowFunc) (MetaWindow *window, + gpointer user_data); + + typedef struct _MetaCGroup MetaCGroup; + struct _MetaCGroup { + GFile *path; ++ guint32 user_time; ++ MetaWorkspace *last_active_workspace; ++ gboolean has_startup_sequence; + + grefcount ref_count; + }; + + struct _MetaDisplay + { + GObject parent_instance; + + MetaX11Display *x11_display; + + int clutter_event_filter; + + /* Our best guess as to the "currently" focused window (that is, the + * window that we expect will be focused at the point when the X + * server processes our next request), and the serial of the request + * or event that caused this. + */ + MetaWindow *focus_window; + + /* last timestamp passed to XSetInputFocus */ + guint32 last_focus_time; + + /* last user interaction time in any app */ + guint32 last_user_time; + + /* whether we're using mousenav (only relevant for sloppy&mouse focus modes; + * !mouse_mode means "keynav mode") + */ + guint mouse_mode : 1; + +@@ -270,60 +273,63 @@ struct _MetaDisplayClass + + gboolean meta_display_open (void); + + void meta_display_manage_all_xwindows (MetaDisplay *display); + void meta_display_unmanage_windows (MetaDisplay *display, + guint32 timestamp); + + /* Utility function to compare the stacking of two windows */ + int meta_display_stack_cmp (const void *a, + const void *b); + + /* Each MetaWindow is uniquely identified by a 64-bit "stamp"; unlike a + * a MetaWindow *, a stamp will never be recycled + */ + MetaWindow* meta_display_lookup_stamp (MetaDisplay *display, + guint64 stamp); + void meta_display_register_stamp (MetaDisplay *display, + guint64 *stampp, + MetaWindow *window); + void meta_display_unregister_stamp (MetaDisplay *display, + guint64 stamp); + + void meta_display_register_cgroup (MetaDisplay *display, + MetaWindow *window, + const char *path); + void meta_display_unregister_cgroup (MetaDisplay *display, + MetaWindow *window); + + MetaCGroup* meta_cgroup_ref (MetaCGroup *cgroup); + gboolean meta_cgroup_unref (MetaCGroup *cgroup); ++void meta_cgroup_update_workspace (MetaCGroup *cgroup, ++ MetaWorkspace *workspace, ++ guint32 timestamp); + + /* A "stack id" is a XID or a stamp */ + #define META_STACK_ID_IS_X11(id) ((id) < G_GUINT64_CONSTANT(0x100000000)) + + META_EXPORT_TEST + MetaWindow* meta_display_lookup_stack_id (MetaDisplay *display, + guint64 stack_id); + + /* for debug logging only; returns a human-description of the stack + * ID - a small number of buffers are recycled, so the result must + * be used immediately or copied */ + const char *meta_display_describe_stack_id (MetaDisplay *display, + guint64 stack_id); + + void meta_display_register_wayland_window (MetaDisplay *display, + MetaWindow *window); + void meta_display_unregister_wayland_window (MetaDisplay *display, + MetaWindow *window); + + void meta_display_notify_window_created (MetaDisplay *display, + MetaWindow *window); + + META_EXPORT_TEST + GSList* meta_display_list_windows (MetaDisplay *display, + MetaListWindowsFlags flags); + + MetaDisplay* meta_display_for_x_display (Display *xdisplay); + + META_EXPORT_TEST + MetaDisplay* meta_get_display (void); +diff --git a/src/core/display.c b/src/core/display.c +index 937defd2c..f30f9a268 100644 +--- a/src/core/display.c ++++ b/src/core/display.c +@@ -1524,94 +1524,121 @@ MetaCGroup* + meta_cgroup_new (const char *path) + { + MetaCGroup *cgroup; + + cgroup = g_new0 (MetaCGroup, 1); + cgroup->path = g_file_new_for_path (path); + g_ref_count_init (&cgroup->ref_count); + + return cgroup; + } + + MetaCGroup* + meta_cgroup_ref (MetaCGroup *cgroup) + { + g_ref_count_inc (&cgroup->ref_count); + return cgroup; + } + + gboolean + meta_cgroup_unref (MetaCGroup *cgroup) + { + if (!g_ref_count_dec (&cgroup->ref_count)) + return FALSE; + + g_clear_object (&cgroup->path); + g_free (cgroup); + + return TRUE; + } + ++void ++meta_cgroup_update_workspace (MetaCGroup *cgroup, ++ MetaWorkspace *workspace, ++ guint32 timestamp) ++{ ++ if (!cgroup) ++ return; ++ ++ if (!XSERVER_TIME_IS_BEFORE (cgroup->user_time, timestamp)) ++ return; ++ ++ cgroup->user_time = timestamp; ++ ++ if (cgroup->last_active_workspace) ++ g_object_remove_weak_pointer (G_OBJECT (cgroup->last_active_workspace), ++ (gpointer *) &cgroup->last_active_workspace); ++ ++ cgroup->last_active_workspace = workspace; ++ ++ g_object_add_weak_pointer (G_OBJECT (workspace), ++ (gpointer *) &cgroup->last_active_workspace); ++} ++ + void + meta_display_register_cgroup (MetaDisplay *display, + MetaWindow *window, + const char *path) + { + MetaCGroup *cgroup; + + cgroup = g_hash_table_lookup (display->cgroups, path); + + if (cgroup) + { + window->cgroup = meta_cgroup_ref (cgroup); + return; + } + + cgroup = meta_cgroup_new (path); + g_hash_table_insert (display->cgroups, g_file_get_path (cgroup->path), cgroup); + } + + void + meta_display_unregister_cgroup (MetaDisplay *display, + MetaWindow *window) + { + g_autofree const char *path = NULL; + MetaCGroup *cgroup = g_steal_pointer (&window->cgroup); + + if (!cgroup) + return; + + path = g_file_get_path (cgroup->path); + + if (!meta_cgroup_unref (cgroup)) + return; + ++ if (cgroup->last_active_workspace) ++ g_object_remove_weak_pointer (G_OBJECT (cgroup->last_active_workspace), ++ (gpointer *) &cgroup->last_active_workspace); ++ + g_hash_table_remove (display->cgroups, path); + } + + MetaWindow* + meta_display_lookup_stamp (MetaDisplay *display, + guint64 stamp) + { + return g_hash_table_lookup (display->stamps, &stamp); + } + + void + meta_display_register_stamp (MetaDisplay *display, + guint64 *stampp, + MetaWindow *window) + { + g_return_if_fail (g_hash_table_lookup (display->stamps, stampp) == NULL); + + g_hash_table_insert (display->stamps, stampp, window); + } + + void + meta_display_unregister_stamp (MetaDisplay *display, + guint64 stamp) + { + g_return_if_fail (g_hash_table_lookup (display->stamps, &stamp) != NULL); + + g_hash_table_remove (display->stamps, &stamp); + } + + MetaWindow* +@@ -3374,60 +3401,64 @@ meta_display_apply_startup_properties (MetaDisplay *display, + meta_startup_sequence_get_id (sequence), + window->desc); + + meta_startup_sequence_complete (sequence); + } + } + + /* Still no startup ID? Bail. */ + if (!startup_id) + return FALSE; + + /* We might get this far and not know the sequence ID (if the window + * already had a startup ID stored), so let's look for one if we don't + * already know it. + */ + if (sequence == NULL) + { + sequence = + meta_startup_notification_lookup_sequence (display->startup_notification, + startup_id); + } + + if (sequence != NULL) + { + gboolean changed_something = FALSE; + + meta_topic (META_DEBUG_STARTUP, + "Found startup sequence for window %s ID \"%s\"", + window->desc, startup_id); + ++ meta_window_read_cgroup (window); ++ if (window->cgroup) ++ window->cgroup->has_startup_sequence = TRUE; ++ + if (!window->initial_workspace_set) + { + int space = meta_startup_sequence_get_workspace (sequence); + if (space >= 0) + { + meta_topic (META_DEBUG_STARTUP, + "Setting initial window workspace to %d based on startup info", + space); + + window->initial_workspace_set = TRUE; + window->initial_workspace = space; + changed_something = TRUE; + } + } + + if (!window->initial_timestamp_set) + { + guint32 timestamp = meta_startup_sequence_get_timestamp (sequence); + meta_topic (META_DEBUG_STARTUP, + "Setting initial window timestamp to %u based on startup info", + timestamp); + + window->initial_timestamp_set = TRUE; + window->initial_timestamp = timestamp; + changed_something = TRUE; + } + + return changed_something; + } + else +diff --git a/src/core/window-private.h b/src/core/window-private.h +index 8a0aebb38..41782aa99 100644 +--- a/src/core/window-private.h ++++ b/src/core/window-private.h +@@ -860,31 +860,32 @@ void meta_window_update_resize (MetaWindow *window, + MetaEdgeResistanceFlags flags, + int x, int y, + gboolean force); + + void meta_window_move_resize_internal (MetaWindow *window, + MetaMoveResizeFlags flags, + MetaGravity gravity, + MetaRectangle frame_rect); + + void meta_window_grab_op_began (MetaWindow *window, MetaGrabOp op); + void meta_window_grab_op_ended (MetaWindow *window, MetaGrabOp op); + + void meta_window_set_alive (MetaWindow *window, gboolean is_alive); + + gboolean meta_window_has_pointer (MetaWindow *window); + + void meta_window_emit_size_changed (MetaWindow *window); + + MetaPlacementRule *meta_window_get_placement_rule (MetaWindow *window); + + void meta_window_force_placement (MetaWindow *window, + gboolean force_move); + + void meta_window_force_restore_shortcuts (MetaWindow *window, + ClutterInputDevice *source); + + gboolean meta_window_shortcuts_inhibited (MetaWindow *window, + ClutterInputDevice *source); + gboolean meta_window_is_stackable (MetaWindow *window); + gboolean meta_window_is_focus_async (MetaWindow *window); ++void meta_window_read_cgroup (MetaWindow *window); + #endif +diff --git a/src/core/window.c b/src/core/window.c +index f5ecd6438..5c7b2e8cf 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -1296,79 +1296,88 @@ _meta_window_shared_new (MetaDisplay *display, + * added to all the MRU lists + */ + window->on_all_workspaces_requested = TRUE; + + on_all_workspaces = TRUE; + } + else if (!on_all_workspaces) + { + meta_topic (META_DEBUG_PLACEMENT, + "Window %s is initially on space %d", + window->desc, window->initial_workspace); + + workspace = meta_workspace_manager_get_workspace_by_index (workspace_manager, + window->initial_workspace); + } + + /* Ignore when a window requests to be placed on a non-existent workspace + */ + if (on_all_workspaces || workspace != NULL) + set_workspace_state (window, on_all_workspaces, workspace); + } + + /* override-redirect windows are subtly different from other windows + * with window->on_all_workspaces == TRUE. Other windows are part of + * some workspace (so they can return to that if the flag is turned off), + * but appear on other workspaces. override-redirect windows are part + * of no workspace. + */ + if (!window->override_redirect && window->workspace == NULL) + { ++ meta_window_read_cgroup (window); + if (window->transient_for != NULL) + { + meta_topic (META_DEBUG_PLACEMENT, + "Putting window %s on same workspace as parent %s", + window->desc, window->transient_for->desc); + + g_warn_if_fail (!window->transient_for->override_redirect); + set_workspace_state (window, + window->transient_for->on_all_workspaces, + window->transient_for->workspace); + } + else if (window->on_all_workspaces) + { + meta_topic (META_DEBUG_PLACEMENT, + "Putting window %s on all workspaces", + window->desc); + + set_workspace_state (window, TRUE, NULL); + } ++ else if (window->cgroup && window->cgroup->last_active_workspace != NULL && ++ !window->cgroup->has_startup_sequence) ++ { ++ meta_topic (META_DEBUG_PLACEMENT, ++ "Putting window %s on active workspace", ++ window->desc); ++ set_workspace_state (window, FALSE, window->cgroup->last_active_workspace); ++ } + else + { + meta_topic (META_DEBUG_PLACEMENT, + "Putting window %s on active workspace", + window->desc); + + set_workspace_state (window, FALSE, workspace_manager->active_workspace); + } + + meta_window_update_struts (window); + } + + meta_window_main_monitor_changed (window, NULL); + + /* Must add window to stack before doing move/resize, since the + * window might have fullscreen size (i.e. should have been + * fullscreen'd; acrobat is one such braindead case; it withdraws + * and remaps its window whenever trying to become fullscreen...) + * and thus constraints may try to auto-fullscreen it which also + * means restacking it. + */ + if (meta_window_is_stackable (window)) + meta_stack_add (window->display->stack, + window); + else if (window->override_redirect) + window->layer = META_LAYER_OVERRIDE_REDIRECT; /* otherwise set by MetaStack */ + + if (!window->override_redirect) + { + /* FIXME we have a tendency to set this then immediately +@@ -3736,63 +3745,71 @@ meta_window_activate_full (MetaWindow *window, + "by client type %u.", + window->desc, timestamp, source_indication); + + if (window->display->last_user_time == timestamp) + { + /* Only allow workspace switches if this activation message uses the same + * timestamp as the last user interaction + */ + allow_workspace_switch = TRUE; + } + + if (timestamp != 0 && + XSERVER_TIME_IS_BEFORE (timestamp, window->display->last_user_time)) + { + meta_topic (META_DEBUG_FOCUS, + "last_user_time (%u) is more recent; ignoring " + " _NET_ACTIVE_WINDOW message.", + window->display->last_user_time); + meta_window_set_demands_attention(window); + return; + } + + if (timestamp == 0) + timestamp = meta_display_get_current_time_roundtrip (window->display); + + meta_window_set_user_time (window, timestamp); + + /* disable show desktop mode unless we're a desktop component */ + maybe_leave_show_desktop_mode (window); + +- /* Get window on current or given workspace */ +- if (workspace == NULL) +- workspace = workspace_manager->active_workspace; ++ /* Get window on last active, current, or given workspace */ ++ if (workspace == NULL) ++ { ++ meta_window_read_cgroup (window); ++ if (window->cgroup && ++ window->cgroup->last_active_workspace != NULL && ++ !window->cgroup->has_startup_sequence) ++ workspace = window->cgroup->last_active_workspace; ++ else ++ workspace = workspace_manager->active_workspace; ++ } + + /* For non-transient windows, we just set up a pulsing indicator, + rather than move windows or workspaces. + See http://bugzilla.gnome.org/show_bug.cgi?id=482354 */ + if (window->transient_for == NULL && + !allow_workspace_switch && + !meta_window_located_on_workspace (window, workspace)) + { + meta_window_set_demands_attention (window); + /* We've marked it as demanding, don't need to do anything else. */ + return; + } + else if (window->transient_for != NULL) + { + /* Move transients to current workspace - preference dialogs should appear over + the source window. */ + meta_window_change_workspace (window, workspace); + } + + if (window->shaded) + meta_window_unshade (window, timestamp); + + unminimize_window_and_all_transient_parents (window); + + if (meta_prefs_get_raise_on_click () || + source_indication == META_CLIENT_TYPE_PAGER) + meta_window_raise (window); + + meta_topic (META_DEBUG_FOCUS, + "Focusing window %s due to activation", +@@ -7165,60 +7182,71 @@ meta_window_set_user_time (MetaWindow *window, + g_return_if_fail (!window->override_redirect); + + /* Only update the time if this timestamp is newer... */ + if (window->net_wm_user_time_set && + XSERVER_TIME_IS_BEFORE (timestamp, window->net_wm_user_time)) + { + meta_topic (META_DEBUG_STARTUP, + "Window %s _NET_WM_USER_TIME not updated to %u, because it " + "is less than %u", + window->desc, timestamp, window->net_wm_user_time); + } + else + { + meta_topic (META_DEBUG_STARTUP, + "Window %s has _NET_WM_USER_TIME of %u", + window->desc, timestamp); + window->net_wm_user_time_set = TRUE; + window->net_wm_user_time = timestamp; + if (XSERVER_TIME_IS_BEFORE (window->display->last_user_time, timestamp)) + window->display->last_user_time = timestamp; + + /* If this is a terminal, user interaction with it means the user likely + * doesn't want to have focus transferred for now due to new windows. + */ + if (meta_prefs_get_focus_new_windows () == G_DESKTOP_FOCUS_NEW_WINDOWS_STRICT && + window_is_terminal (window)) + window->display->allow_terminal_deactivation = FALSE; + + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_USER_TIME]); + } ++ ++ if (!window->cgroup) ++ meta_window_read_cgroup (window); ++ ++ if (window->cgroup) ++ { ++ MetaWorkspace *workspace = meta_window_get_workspace (window); ++ ++ if (workspace) ++ meta_cgroup_update_workspace (window->cgroup, workspace, timestamp); ++ } + } + + /** + * meta_window_get_stable_sequence: + * @window: A #MetaWindow + * + * The stable sequence number is a monotonicially increasing + * unique integer assigned to each #MetaWindow upon creation. + * + * This number can be useful for sorting windows in a stable + * fashion. + * + * Returns: Internal sequence number for this window + */ + guint32 + meta_window_get_stable_sequence (MetaWindow *window) + { + g_return_val_if_fail (META_IS_WINDOW (window), 0); + + return window->stable_sequence; + } + + /* Sets the demands_attention hint on a window, but only + * if it's at least partially obscured (see #305882). + */ + void + meta_window_set_demands_attention (MetaWindow *window) + { + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + MetaRectangle candidate_rect, other_rect; +@@ -7649,61 +7677,61 @@ meta_window_get_transient_for (MetaWindow *window) + else if (window->xtransient_for) + return meta_x11_display_lookup_x_window (window->display->x11_display, + window->xtransient_for); + else + return NULL; + } + + /** + * meta_window_get_pid: + * @window: a #MetaWindow + * + * Returns the pid of the process that created this window, if available + * to the windowing system. + * + * Note that the value returned by this is vulnerable to spoofing attacks + * by the client. + * + * Return value: the pid, or 0 if not known. + */ + pid_t + meta_window_get_pid (MetaWindow *window) + { + g_return_val_if_fail (META_IS_WINDOW (window), 0); + + if (window->client_pid == 0) + window->client_pid = META_WINDOW_GET_CLASS (window)->get_client_pid (window); + + return window->client_pid; + } + +-static void ++void + meta_window_read_cgroup (MetaWindow *window) + { + #ifdef HAVE_LIBSYSTEMD + g_autofree char *contents = NULL; + g_autofree char *complete_path = NULL; + g_autofree char *unit_name = NULL; + g_autofree char *unit_path = NULL; + char *unit_end; + pid_t pid; + + if (window->cgroup) + return; + + pid = meta_window_get_pid (window); + if (pid < 1) + return; + + if (sd_pid_get_cgroup (pid, &contents) < 0) + return; + g_strstrip (contents); + + complete_path = g_strdup_printf ("%s%s", "/sys/fs/cgroup", contents); + + if (sd_pid_get_user_unit (pid, &unit_name) < 0) + return; + g_strstrip (unit_name); + + unit_end = strstr (complete_path, unit_name) + strlen (unit_name); + *unit_end = '\0'; + +-- +2.44.0 + diff --git a/SOURCES/0005-cgroup-Get-app-info-from-gnome-shell-when-possible.patch b/SOURCES/0005-cgroup-Get-app-info-from-gnome-shell-when-possible.patch new file mode 100644 index 0000000..c760791 --- /dev/null +++ b/SOURCES/0005-cgroup-Get-app-info-from-gnome-shell-when-possible.patch @@ -0,0 +1,166 @@ +From 50d0355bda637a2b214e14c23e767e80066c1084 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Fri, 18 Oct 2024 13:23:01 +0200 +Subject: [PATCH 5/5] cgroup: Get app info from gnome-shell when possible + +Using cgroups alone for getting the app-id is flawed, as there are many +situations where the cgroup isn't set up properly, e.g. opening via +xdg-open or equivalent. gnome-shell already has a system for associating +windows with apps, so reuse this for the cgroup app info. +--- + src/core/display-private.h | 2 ++ + src/core/display.c | 56 +++++++++++++++++++++++++++++++------- + src/core/window.c | 6 ++++ + 3 files changed, 54 insertions(+), 10 deletions(-) + +diff --git a/src/core/display-private.h b/src/core/display-private.h +index 3c7e0898bf..9836e560b3 100644 +--- a/src/core/display-private.h ++++ b/src/core/display-private.h +@@ -304,6 +304,8 @@ gboolean meta_cgroup_unref (MetaCGroup *cgroup); + void meta_cgroup_update_workspace (MetaCGroup *cgroup, + MetaWorkspace *workspace, + guint32 timestamp); ++void meta_cgroup_update_app_info (MetaCGroup *cgroup, ++ MetaWindow *window); + + /* A "stack id" is a XID or a stamp */ + #define META_STACK_ID_IS_X11(id) ((id) < G_GUINT64_CONSTANT(0x100000000)) +diff --git a/src/core/display.c b/src/core/display.c +index e99e787fbe..637bc006d8 100644 +--- a/src/core/display.c ++++ b/src/core/display.c +@@ -162,6 +162,7 @@ enum + WORKAREAS_CHANGED, + CLOSING, + INIT_XSERVER, ++ FIND_APP_INFO, + LAST_SIGNAL + }; + +@@ -515,6 +516,12 @@ meta_display_class_init (MetaDisplayClass *klass) + 0, g_signal_accumulator_first_wins, + NULL, NULL, + G_TYPE_BOOLEAN, 1, G_TYPE_TASK); ++ display_signals[FIND_APP_INFO] = ++ g_signal_new ("find-app-info", ++ G_TYPE_FROM_CLASS (klass), ++ G_SIGNAL_RUN_LAST, ++ 0, NULL, NULL, NULL, ++ G_TYPE_DESKTOP_APP_INFO, 1, META_TYPE_WINDOW); + + g_object_class_install_property (object_class, + PROP_COMPOSITOR_MODIFIERS, +@@ -1620,25 +1627,32 @@ extract_app_id_from_cgroup (const char *cgroup) + } + + static MetaCGroup* +-meta_cgroup_new (const char *path) ++meta_cgroup_new (const char *path, ++ GAppInfo *app_info) + { + MetaCGroup *cgroup; +- g_autofree char *app_id = NULL; + + cgroup = g_new0 (MetaCGroup, 1); + cgroup->path = g_file_new_for_path (path); + g_ref_count_init (&cgroup->ref_count); + +- app_id = extract_app_id_from_cgroup (path); +- +- if (app_id) ++ if (!app_info) + { +- g_autoptr (GDesktopAppInfo) app_info = NULL; ++ g_autofree char *app_id = NULL; + +- app_info = g_desktop_app_info_new (app_id); ++ app_id = extract_app_id_from_cgroup (path); ++ if (app_id) ++ { ++ GDesktopAppInfo *desktop_app_info; + +- if (app_info) +- cgroup->app_info = G_APP_INFO (g_steal_pointer (&app_info)); ++ desktop_app_info = g_desktop_app_info_new (app_id); ++ if (desktop_app_info) ++ cgroup->app_info = G_APP_INFO (desktop_app_info); ++ } ++ } ++ else if (app_info) ++ { ++ cgroup->app_info = g_object_ref (app_info); + } + + return cgroup; +@@ -1687,12 +1701,30 @@ meta_cgroup_update_workspace (MetaCGroup *cgroup, + (gpointer *) &cgroup->last_active_workspace); + } + ++void ++meta_cgroup_update_app_info (MetaCGroup *cgroup, ++ MetaWindow *window) ++{ ++ g_autoptr (GDesktopAppInfo) app_info = NULL; ++ ++ g_signal_emit (window->display, ++ display_signals[FIND_APP_INFO], 0, ++ window, &app_info); ++ ++ if (app_info) ++ { ++ g_clear_object (&cgroup->app_info); ++ cgroup->app_info = G_APP_INFO (g_steal_pointer (&app_info)); ++ } ++} ++ + void + meta_display_register_cgroup (MetaDisplay *display, + MetaWindow *window, + const char *path) + { + MetaCGroup *cgroup; ++ g_autoptr (GDesktopAppInfo) app_info = NULL; + + cgroup = g_hash_table_lookup (display->cgroups, path); + +@@ -1702,7 +1734,11 @@ meta_display_register_cgroup (MetaDisplay *display, + return; + } + +- cgroup = meta_cgroup_new (path); ++ g_signal_emit (display, ++ display_signals[FIND_APP_INFO], 0, ++ window, &app_info); ++ ++ cgroup = meta_cgroup_new (path, app_info ? G_APP_INFO (app_info) : NULL); + g_hash_table_insert (display->cgroups, g_file_get_path (cgroup->path), cgroup); + window->cgroup = cgroup; + } +diff --git a/src/core/window.c b/src/core/window.c +index 8ad8e5c4c4..6de815839f 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -8131,6 +8131,9 @@ meta_window_set_wm_class (MetaWindow *window, + window->res_class = g_strdup (wm_class); + + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_WM_CLASS]); ++ ++ if (window->cgroup) ++ meta_cgroup_update_app_info (window->cgroup, window); + } + + void +@@ -8169,6 +8172,9 @@ meta_window_set_gtk_dbus_properties (MetaWindow *window, + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_GTK_WINDOW_OBJECT_PATH]); + + g_object_thaw_notify (G_OBJECT (window)); ++ ++ if (window->cgroup) ++ meta_cgroup_update_app_info (window->cgroup, window); + } + + static gboolean +-- +2.44.0.501.g19981daefd.dirty + diff --git a/SOURCES/0005-core-display-Avoid-placement-heuristcs-for-apps-that.patch b/SOURCES/0005-core-display-Avoid-placement-heuristcs-for-apps-that.patch new file mode 100644 index 0000000..91ab085 --- /dev/null +++ b/SOURCES/0005-core-display-Avoid-placement-heuristcs-for-apps-that.patch @@ -0,0 +1,556 @@ +From 62e4f7153738b404b05b1ee59d088bc52fd86bc0 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 8 Jul 2024 09:54:01 -0400 +Subject: [PATCH 5/6] core/display: Avoid placement heuristcs for apps that can + do startup notification + +We recently introduced heuristics to decide which workspace to put an an +application window on. Those heuristics are only used if an application +doesn't support startup notification (since startup notification +provides a means to give that information without heuristics). + +Unfortunately, sometimes applications that support startup notification +aren't started with it. In those cases, it's wrong to fall back to +heuristics, because those heuristics are a big change in behavior, and +cause applications to break (like terminals launched from the desktop +starting on the wrong workspace) + +This commit adds code to try to deduce the application from its cgroup, +and then check if it supports startup notification, even if it's not +started with it in any given instance. +--- + src/core/display-private.h | 1 + + src/core/display.c | 113 +++++++++++++++++++++++++++++++++++++ + src/core/window.c | 10 +++- + 3 files changed, 122 insertions(+), 2 deletions(-) + +diff --git a/src/core/display-private.h b/src/core/display-private.h +index 2b8d9e0d0..3c7e0898b 100644 +--- a/src/core/display-private.h ++++ b/src/core/display-private.h +@@ -81,60 +81,61 @@ typedef enum + } MetaTileMode; + + typedef enum + { + /* Normal interaction where you're interacting with windows. + * Events go to windows normally. */ + META_EVENT_ROUTE_NORMAL, + + /* In a window operation like moving or resizing. All events + * goes to MetaWindow, but not to the actual client window. */ + META_EVENT_ROUTE_WINDOW_OP, + + /* In a compositor grab operation. All events go to the + * compositor plugin. */ + META_EVENT_ROUTE_COMPOSITOR_GRAB, + + /* A Wayland application has a popup open. All events go to + * the Wayland application. */ + META_EVENT_ROUTE_WAYLAND_POPUP, + + /* The user is clicking on a window button. */ + META_EVENT_ROUTE_FRAME_BUTTON, + } MetaEventRoute; + + typedef void (* MetaDisplayWindowFunc) (MetaWindow *window, + gpointer user_data); + + typedef struct _MetaCGroup MetaCGroup; + struct _MetaCGroup { + GFile *path; ++ GAppInfo *app_info; + guint32 user_time; + MetaWorkspace *last_active_workspace; + gboolean has_startup_sequence; + + grefcount ref_count; + }; + + struct _MetaDisplay + { + GObject parent_instance; + + MetaX11Display *x11_display; + + int clutter_event_filter; + + /* Our best guess as to the "currently" focused window (that is, the + * window that we expect will be focused at the point when the X + * server processes our next request), and the serial of the request + * or event that caused this. + */ + MetaWindow *focus_window; + + /* last timestamp passed to XSetInputFocus */ + guint32 last_focus_time; + + /* last user interaction time in any app */ + guint32 last_user_time; + + /* whether we're using mousenav (only relevant for sloppy&mouse focus modes; + * !mouse_mode means "keynav mode") +diff --git a/src/core/display.c b/src/core/display.c +index f30f9a268..4c9038e62 100644 +--- a/src/core/display.c ++++ b/src/core/display.c +@@ -65,60 +65,62 @@ + #include "core/meta-clipboard-manager.h" + #include "core/meta-workspace-manager-private.h" + #include "core/util-private.h" + #include "core/window-private.h" + #include "core/workspace-private.h" + #include "meta/compositor-mutter.h" + #include "meta/compositor.h" + #include "meta/main.h" + #include "meta/meta-backend.h" + #include "meta/meta-enum-types.h" + #include "meta/meta-sound-player.h" + #include "meta/meta-x11-errors.h" + #include "meta/prefs.h" + #include "x11/meta-startup-notification-x11.h" + #include "x11/meta-x11-display-private.h" + #include "x11/window-x11.h" + #include "x11/xprops.h" + + #ifdef HAVE_WAYLAND + #include "compositor/meta-compositor-native.h" + #include "compositor/meta-compositor-server.h" + #include "wayland/meta-xwayland-private.h" + #include "wayland/meta-wayland-tablet-seat.h" + #include "wayland/meta-wayland-tablet-pad.h" + #endif + + #ifdef HAVE_NATIVE_BACKEND + #include "backends/native/meta-backend-native.h" + #endif + ++#include ++ + /* + * SECTION:pings + * + * Sometimes we want to see whether a window is responding, + * so we send it a "ping" message and see whether it sends us back a "pong" + * message within a reasonable time. Here we have a system which lets us + * nominate one function to be called if we get the pong in time and another + * function if we don't. The system is rather more complicated than it needs + * to be, since we only ever use it to destroy windows which are asked to + * close themselves and don't do so within a reasonable amount of time, and + * therefore we always use the same callbacks. It's possible that we might + * use it for other things in future, or on the other hand we might decide + * that we're never going to do so and simplify it a bit. + */ + + /** + * MetaPingData: + * + * Describes a ping on a window. When we send a ping to a window, we build + * one of these structs, and it eventually gets passed to the timeout function + * or to the function which handles the response from the window. If the window + * does or doesn't respond to the ping, we use this information to deal with + * these facts; we have a handler function for each. + */ + typedef struct + { + MetaWindow *window; + guint32 serial; + guint ping_timeout_id; + } MetaPingData; +@@ -1493,85 +1495,196 @@ meta_display_set_input_focus (MetaDisplay *display, + + meta_display_update_focus_window (display, window); + + display->last_focus_time = timestamp; + + if (window == NULL || window != display->autoraise_window) + meta_display_remove_autoraise_callback (display); + } + + void + meta_display_unset_input_focus (MetaDisplay *display, + guint32 timestamp) + { + meta_display_set_input_focus (display, NULL, FALSE, timestamp); + } + + void + meta_display_register_wayland_window (MetaDisplay *display, + MetaWindow *window) + { + g_hash_table_add (display->wayland_windows, window); + } + + void + meta_display_unregister_wayland_window (MetaDisplay *display, + MetaWindow *window) + { + g_hash_table_remove (display->wayland_windows, window); + } + ++static void ++unescape_app_id (char **app_id) ++{ ++ char *p = *app_id; ++ char *q = *app_id; ++ ++ while (*p != '\0') ++ { ++ if (*p == '\\' && ++ p[1] == 'x' && ++ g_ascii_isxdigit (p[2]) && ++ g_ascii_isxdigit (p[3])) ++ { ++ char escape_code[3] = { p[2], p[3], '\0' }; ++ *q = (char) g_ascii_strtoll (escape_code, NULL, 16); ++ p += strlen ("\\xAA"); ++ } ++ else ++ { ++ *q = *p; ++ p++; ++ } ++ q++; ++ } ++ *q = '\0'; ++} ++ ++/* The possible formats are: ++ * ++ * /sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/app.slice/app-dbus\x2d:1.2\x2dorg.gnome.Totem.slice/dbus-:1.2-org.gnome.Totem@0.service ++ * /sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/app.slice/app-org.gnome.Terminal.slice/gnome-terminal-server.service ++ * /sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/app.slice/app-gnome-org.gnome.Evince-12345.scope ++ */ ++static char * ++extract_app_id_from_cgroup (const char *cgroup) ++{ ++ g_auto (GStrv) path_components = NULL; ++ const char *unit_name = NULL; ++ size_t i; ++ g_autofree char *app_id = NULL; ++ char *start_delimiter = NULL; ++ char *end_delimiter = NULL; ++ int app_id_length; ++ ++ path_components = g_strsplit (cgroup, G_DIR_SEPARATOR_S, -1); ++ ++ for (i = 0; path_components[i]; i++) ++ { ++ if (!g_str_equal (path_components[i], "app.slice")) ++ continue; ++ ++ unit_name = path_components[i + 1]; ++ break; ++ } ++ ++ if (!unit_name) ++ return NULL; ++ ++ if (!g_str_has_prefix (unit_name, "app-")) ++ return NULL; ++ ++ end_delimiter = g_strrstr (unit_name, ".slice"); ++ ++ if (!end_delimiter) ++ end_delimiter = strrchr (unit_name, '-'); ++ ++ if (end_delimiter == NULL || end_delimiter == unit_name) ++ return NULL; ++ ++ start_delimiter = end_delimiter - 1; ++ while (start_delimiter > cgroup && *start_delimiter != '-') ++ start_delimiter--; ++ ++ if (start_delimiter == NULL || start_delimiter == unit_name) ++ return NULL; ++ ++ app_id_length = end_delimiter - (start_delimiter + 1); ++ app_id = g_strdup_printf ("%.*s.desktop", app_id_length, start_delimiter + 1); ++ ++ unescape_app_id (&app_id); ++ ++ if (g_str_has_prefix (app_id, "dbus-")) ++ { ++ const char *dbus_prefix; ++ dbus_prefix = strchr (app_id + strlen ("dbus-") + 1, '-'); ++ ++ if (dbus_prefix) ++ { ++ char *stripped_app_id = strdup (dbus_prefix + 1); ++ g_clear_pointer (&app_id, g_free); ++ app_id = g_steal_pointer (&stripped_app_id); ++ } ++ } ++ ++ return g_steal_pointer (&app_id); ++} ++ + MetaCGroup* + meta_cgroup_new (const char *path) + { + MetaCGroup *cgroup; ++ g_autofree char *app_id = NULL; + + cgroup = g_new0 (MetaCGroup, 1); + cgroup->path = g_file_new_for_path (path); + g_ref_count_init (&cgroup->ref_count); + ++ app_id = extract_app_id_from_cgroup (path); ++ ++ if (app_id) ++ { ++ g_autoptr (GDesktopAppInfo) app_info = NULL; ++ ++ app_info = g_desktop_app_info_new (app_id); ++ ++ if (app_info) ++ cgroup->app_info = G_APP_INFO (g_steal_pointer (&app_info)); ++ } ++ + return cgroup; + } + + MetaCGroup* + meta_cgroup_ref (MetaCGroup *cgroup) + { + g_ref_count_inc (&cgroup->ref_count); + return cgroup; + } + + gboolean + meta_cgroup_unref (MetaCGroup *cgroup) + { + if (!g_ref_count_dec (&cgroup->ref_count)) + return FALSE; + ++ g_clear_object (&cgroup->app_info); + g_clear_object (&cgroup->path); + g_free (cgroup); + + return TRUE; + } + + void + meta_cgroup_update_workspace (MetaCGroup *cgroup, + MetaWorkspace *workspace, + guint32 timestamp) + { + if (!cgroup) + return; + + if (!XSERVER_TIME_IS_BEFORE (cgroup->user_time, timestamp)) + return; + + cgroup->user_time = timestamp; + + if (cgroup->last_active_workspace) + g_object_remove_weak_pointer (G_OBJECT (cgroup->last_active_workspace), + (gpointer *) &cgroup->last_active_workspace); + + cgroup->last_active_workspace = workspace; + + g_object_add_weak_pointer (G_OBJECT (workspace), + (gpointer *) &cgroup->last_active_workspace); + } + + void +diff --git a/src/core/window.c b/src/core/window.c +index 5c7b2e8cf..d36e45992 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -74,60 +74,62 @@ + #include "core/frame.h" + #include "core/keybindings-private.h" + #include "core/meta-workspace-manager-private.h" + #include "core/place.h" + #include "core/stack.h" + #include "core/util-private.h" + #include "core/workspace-private.h" + #include "meta/compositor-mutter.h" + #include "meta/group.h" + #include "meta/meta-cursor-tracker.h" + #include "meta/meta-enum-types.h" + #include "meta/meta-x11-errors.h" + #include "meta/prefs.h" + #include "ui/ui.h" + #include "x11/meta-x11-display-private.h" + #include "x11/window-props.h" + #include "x11/window-x11.h" + #include "x11/xprops.h" + + #ifdef HAVE_WAYLAND + #include "wayland/meta-wayland-private.h" + #include "wayland/meta-wayland-surface.h" + #include "wayland/meta-window-wayland.h" + #include "wayland/meta-window-xwayland.h" + #endif + + #ifdef HAVE_LIBSYSTEMD + #include + #endif + ++#include ++ + + /* Windows that unmaximize to a size bigger than that fraction of the workarea + * will be scaled down to that size (while maintaining aspect ratio). + * Windows that cover an area greater then this size are automaximized on map. + */ + #define MAX_UNMAXIMIZED_WINDOW_AREA .8 + + #define SNAP_SECURITY_LABEL_PREFIX "snap." + + static int destroying_windows_disallowed = 0; + + /* Each window has a "stamp" which is a non-recycled 64-bit ID. They + * start after the end of the XID space so that, for stacking + * we can keep a guint64 that represents one or the other + */ + static guint64 next_window_stamp = G_GUINT64_CONSTANT(0x100000000); + + static void invalidate_work_areas (MetaWindow *window); + static void set_wm_state (MetaWindow *window); + static void set_net_wm_state (MetaWindow *window); + static void meta_window_set_above (MetaWindow *window, + gboolean new_value); + + static void meta_window_show (MetaWindow *window); + static void meta_window_hide (MetaWindow *window); + + static void meta_window_save_rect (MetaWindow *window); + + static void ensure_mru_position_after (MetaWindow *window, + MetaWindow *after_this_one); +@@ -1317,61 +1319,63 @@ _meta_window_shared_new (MetaDisplay *display, + + /* override-redirect windows are subtly different from other windows + * with window->on_all_workspaces == TRUE. Other windows are part of + * some workspace (so they can return to that if the flag is turned off), + * but appear on other workspaces. override-redirect windows are part + * of no workspace. + */ + if (!window->override_redirect && window->workspace == NULL) + { + meta_window_read_cgroup (window); + if (window->transient_for != NULL) + { + meta_topic (META_DEBUG_PLACEMENT, + "Putting window %s on same workspace as parent %s", + window->desc, window->transient_for->desc); + + g_warn_if_fail (!window->transient_for->override_redirect); + set_workspace_state (window, + window->transient_for->on_all_workspaces, + window->transient_for->workspace); + } + else if (window->on_all_workspaces) + { + meta_topic (META_DEBUG_PLACEMENT, + "Putting window %s on all workspaces", + window->desc); + + set_workspace_state (window, TRUE, NULL); + } + else if (window->cgroup && window->cgroup->last_active_workspace != NULL && +- !window->cgroup->has_startup_sequence) ++ !window->cgroup->has_startup_sequence && ++ (!window->cgroup->app_info || ++ !g_desktop_app_info_get_boolean (G_DESKTOP_APP_INFO (window->cgroup->app_info), "StartupNotify"))) + { + meta_topic (META_DEBUG_PLACEMENT, + "Putting window %s on active workspace", + window->desc); + set_workspace_state (window, FALSE, window->cgroup->last_active_workspace); + } + else + { + meta_topic (META_DEBUG_PLACEMENT, + "Putting window %s on active workspace", + window->desc); + + set_workspace_state (window, FALSE, workspace_manager->active_workspace); + } + + meta_window_update_struts (window); + } + + meta_window_main_monitor_changed (window, NULL); + + /* Must add window to stack before doing move/resize, since the + * window might have fullscreen size (i.e. should have been + * fullscreen'd; acrobat is one such braindead case; it withdraws + * and remaps its window whenever trying to become fullscreen...) + * and thus constraints may try to auto-fullscreen it which also + * means restacking it. + */ + if (meta_window_is_stackable (window)) + meta_stack_add (window->display->stack, + window); +@@ -3751,61 +3755,63 @@ meta_window_activate_full (MetaWindow *window, + * timestamp as the last user interaction + */ + allow_workspace_switch = TRUE; + } + + if (timestamp != 0 && + XSERVER_TIME_IS_BEFORE (timestamp, window->display->last_user_time)) + { + meta_topic (META_DEBUG_FOCUS, + "last_user_time (%u) is more recent; ignoring " + " _NET_ACTIVE_WINDOW message.", + window->display->last_user_time); + meta_window_set_demands_attention(window); + return; + } + + if (timestamp == 0) + timestamp = meta_display_get_current_time_roundtrip (window->display); + + meta_window_set_user_time (window, timestamp); + + /* disable show desktop mode unless we're a desktop component */ + maybe_leave_show_desktop_mode (window); + + /* Get window on last active, current, or given workspace */ + if (workspace == NULL) + { + meta_window_read_cgroup (window); + if (window->cgroup && + window->cgroup->last_active_workspace != NULL && +- !window->cgroup->has_startup_sequence) ++ !window->cgroup->has_startup_sequence && ++ (!window->cgroup->app_info || ++ !g_desktop_app_info_get_boolean (G_DESKTOP_APP_INFO (window->cgroup->app_info), "StartupNotify"))) + workspace = window->cgroup->last_active_workspace; + else + workspace = workspace_manager->active_workspace; + } + + /* For non-transient windows, we just set up a pulsing indicator, + rather than move windows or workspaces. + See http://bugzilla.gnome.org/show_bug.cgi?id=482354 */ + if (window->transient_for == NULL && + !allow_workspace_switch && + !meta_window_located_on_workspace (window, workspace)) + { + meta_window_set_demands_attention (window); + /* We've marked it as demanding, don't need to do anything else. */ + return; + } + else if (window->transient_for != NULL) + { + /* Move transients to current workspace - preference dialogs should appear over + the source window. */ + meta_window_change_workspace (window, workspace); + } + + if (window->shaded) + meta_window_unshade (window, timestamp); + + unminimize_window_and_all_transient_parents (window); + + if (meta_prefs_get_raise_on_click () || + source_indication == META_CLIENT_TYPE_PAGER) +-- +2.44.0 + diff --git a/SOURCES/0006-meson-Add-optional-libsystemd-dependency.patch b/SOURCES/0006-meson-Add-optional-libsystemd-dependency.patch new file mode 100644 index 0000000..5694cef --- /dev/null +++ b/SOURCES/0006-meson-Add-optional-libsystemd-dependency.patch @@ -0,0 +1,370 @@ +From 63c529fc5a8db1a82fd6b988aa44e3e6a1e417bf Mon Sep 17 00:00:00 2001 +From: Nishal Kulkarni +Date: Thu, 5 Aug 2021 19:37:28 +0530 +Subject: [PATCH 6/6] meson: Add optional libsystemd dependency + +To utilize the API provided by libsystemd it would be better to +create a separate HAVE_LIBSYSTEMD configuration option instead of +having to rely on HAVE_NATIVE_BACKEND. + +For now this will be utilized for getting the control group of a +MetaWindow. + +Part-of: +--- + config.h.meson | 3 +++ + meson.build | 5 ++++- + meson_options.txt | 6 ++++++ + src/meson.build | 6 ++++++ + 4 files changed, 19 insertions(+), 1 deletion(-) + +diff --git a/config.h.meson b/config.h.meson +index 26e13b9ca..b7ca736df 100644 +--- a/config.h.meson ++++ b/config.h.meson +@@ -7,60 +7,63 @@ + /* Version number of package */ + #mesondefine PACKAGE_VERSION + + /* Search path for plugins */ + #mesondefine MUTTER_PLUGIN_DIR + + /* */ + #mesondefine MUTTER_LOCALEDIR + + /* */ + #mesondefine MUTTER_LIBEXECDIR + + /* */ + #mesondefine MUTTER_PKGDATADIR + + /* Defined if EGL support is enabled */ + #mesondefine HAVE_EGL + + /* Defined if EGLDevice support is enabled */ + #mesondefine HAVE_EGL_DEVICE + + /* Defined if EGLStream support is enabled */ + #mesondefine HAVE_WAYLAND_EGLSTREAM + + /* Building with gudev for device type detection */ + #mesondefine HAVE_LIBGUDEV + + /* Building with libwacom for advanced tablet management */ + #mesondefine HAVE_LIBWACOM + ++/* Building with libsystemd */ ++#mesondefine HAVE_LIBSYSTEMD ++ + /* Define if you want to enable the native (KMS) backend based on systemd */ + #mesondefine HAVE_NATIVE_BACKEND + + /* Define if you want to enable Wayland support */ + #mesondefine HAVE_WAYLAND + + /* Defined if screen cast and remote desktop support is enabled */ + #mesondefine HAVE_REMOTE_DESKTOP + + /* Building with SM support */ + #mesondefine HAVE_SM + + /* Building with startup notification support */ + #mesondefine HAVE_STARTUP_NOTIFICATION + + /* Building with Sysprof profiling support */ + #mesondefine HAVE_PROFILER + + /* Path to Xwayland executable */ + #mesondefine XWAYLAND_PATH + + /* Xwayland applications allowed to issue keyboard grabs */ + #mesondefine XWAYLAND_GRAB_DEFAULT_ACCESS_RULES + + /* XKB base prefix */ + #mesondefine XKB_BASE + + /* Whether exists and it defines prctl() */ + #mesondefine HAVE_SYS_PRCTL + +diff --git a/meson.build b/meson.build +index 39ad5bcd1..613aa6779 100644 +--- a/meson.build ++++ b/meson.build +@@ -161,67 +161,69 @@ if have_gles2 + if not have_egl + error('GLESv2 support requires EGL to be enabled') + endif + endif + + have_wayland = get_option('wayland') + if have_wayland + wayland_server_dep = dependency('wayland-server', version: wayland_server_req) + wayland_client_dep = dependency('wayland-client', version: wayland_server_req) + wayland_protocols_dep = dependency('wayland-protocols', + version: wayland_protocols_req) + wayland_egl_dep = dependency('wayland-egl') + + if not have_egl + error('Wayland support requires EGL to be enabled') + endif + endif + + have_libgudev = get_option('udev') + if have_libgudev + libudev_dep = dependency('libudev', version: udev_req) + gudev_dep = dependency('gudev-1.0', version: gudev_req) + udev_dep = dependency('udev') + + udev_dir = get_option('udev_dir') + if udev_dir == '' + udev_dir = udev_dep.get_pkgconfig_variable('udevdir') + endif + endif + ++have_libsystemd = get_option('systemd') ++libsystemd_dep = dependency('libsystemd', required: have_libsystemd) ++ + have_native_backend = get_option('native_backend') + if have_native_backend + libdrm_dep = dependency('libdrm') + libgbm_dep = dependency('gbm', version: gbm_req) + libinput_dep = dependency('libinput', version: libinput_req) + +- libsystemd_dep = dependency('libsystemd', required: false) + if libsystemd_dep.found() + logind_provider_dep = libsystemd_dep + else + logind_provider_dep = dependency('libelogind') + endif + + if not have_egl + error('The native backend requires EGL to be enabled') + endif + + if not have_gles2 + error('The native backend requires GLESv2 to be enabled') + endif + + if not have_libgudev + error('The native backend requires udev to be enabled') + endif + endif + + have_egl_device = get_option('egl_device') + + have_wayland_eglstream = get_option('wayland_eglstream') + if have_wayland_eglstream + wayland_eglstream_protocols_dep = dependency('wayland-eglstream-protocols') + dl_dep = cc.find_library('dl', required: true) + + if not have_wayland + error('Wayland EGLStream support requires Wayland to be enabled') + endif + endif +@@ -359,60 +361,61 @@ if buildtype != 'plain' + '-Werror=array-bounds', + '-Werror=write-strings', + '-Werror=address', + '-Werror=int-to-pointer-cast', + '-Werror=pointer-to-int-cast', + '-Werror=empty-body', + '-Werror=write-strings', + ] + supported_warnings = cc.get_supported_arguments(all_warnings) + add_project_arguments(supported_warnings, language: 'c') + endif + + if get_option('debug') + debug_c_args = [ + '-DG_ENABLE_DEBUG', + '-fno-omit-frame-pointer' + ] + supported_debug_c_args = cc.get_supported_arguments(debug_c_args) + add_project_arguments(supported_debug_c_args, language: 'c') + endif + + cc.compiles('void main (void) { __builtin_ffsl (0); __builtin_popcountl (0); }') + + cdata = configuration_data() + cdata.set_quoted('GETTEXT_PACKAGE', gettext_package) + cdata.set_quoted('VERSION', meson.project_version()) + cdata.set_quoted('PACKAGE_VERSION', meson.project_version()) + + cdata.set('HAVE_EGL', have_egl) + cdata.set('HAVE_WAYLAND', have_wayland) ++cdata.set('HAVE_LIBSYSTEMD', have_libsystemd) + cdata.set('HAVE_NATIVE_BACKEND', have_native_backend) + cdata.set('HAVE_REMOTE_DESKTOP', have_remote_desktop) + cdata.set('HAVE_EGL_DEVICE', have_egl_device) + cdata.set('HAVE_WAYLAND_EGLSTREAM', have_wayland_eglstream) + cdata.set('HAVE_LIBGUDEV', have_libgudev) + cdata.set('HAVE_LIBWACOM', have_libwacom) + cdata.set('HAVE_SM', have_sm) + cdata.set('HAVE_STARTUP_NOTIFICATION', have_startup_notification) + cdata.set('HAVE_INTROSPECTION', have_introspection) + cdata.set('HAVE_PROFILER', have_profiler) + + xkb_base = xkeyboard_config_dep.get_pkgconfig_variable('xkb_base') + cdata.set_quoted('XKB_BASE', xkb_base) + + if cc.has_header_symbol('sys/prctl.h', 'prctl') + cdata.set('HAVE_SYS_PRCTL', 1) + endif + + have_xwayland_initfd = false + have_xwayland_listenfd = false + if have_wayland + xwayland_dep = dependency('xwayland', required: false) + + xwayland_path = get_option('xwayland_path') + if xwayland_path == '' + if xwayland_dep.found() + xwayland_path = xwayland_dep.get_pkgconfig_variable('xwayland') + else + xwayland_path = find_program('Xwayland').path() + endif +diff --git a/meson_options.txt b/meson_options.txt +index 61d9cb48d..6609e4332 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -12,60 +12,66 @@ option('opengl_libname', + + option('gles2_libname', + type: 'string', + value: 'libGLESv2.so.2', + description: 'GLESv2 library file name' + ) + + option('gles2', + type: 'boolean', + value: true, + description: 'Enable GLES2 support' + ) + + option('egl', + type: 'boolean', + value: true, + description: 'Enable EGL support' + ) + option('glx', + type: 'boolean', + value: true, + description: 'Enable GLX support' + ) + + option('wayland', + type: 'boolean', + value: true, + description: 'Enable Wayland support' + ) + ++option('systemd', ++ type: 'boolean', ++ value: true, ++ description: 'Enable systemd support' ++) ++ + option('native_backend', + type: 'boolean', + value: true, + description: 'Enable the native backend' + ) + + option('remote_desktop', + type: 'boolean', + value: true, + description: 'Enable remote desktop and screen cast support' + ) + + option('egl_device', + type: 'boolean', + value: false, + description: 'Enable EGLDevice and EGLStream renderer support' + ) + + option('wayland_eglstream', + type: 'boolean', + value: false, + description: 'Enable Wayland EGLStream support client support' + ) + + option('udev', + type: 'boolean', + value: true, + description: 'Enable udev support when using the X11 backend' + ) + +diff --git a/src/meson.build b/src/meson.build +index 47633498e..9ac6afa1a 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -91,60 +91,66 @@ if have_x11 + xinerama_dep, + xext_dep, + ice_dep, + xcomposite_dep, + xcursor_dep, + xdamage_dep, + xkbfile_dep, + xkeyboard_config_dep, + xkbcommon_x11_dep, + xrender_dep, + x11_xcb_dep, + xcb_randr_dep, + xcb_res_dep, + xau_dep, + xtst_dep, + ] + + if have_sm + mutter_pkg_private_deps += [ + sm_dep, + ] + endif + endif + + if have_wayland + mutter_pkg_deps += [ + wayland_server_dep, + ] + endif + ++if have_libsystemd ++ mutter_pkg_private_deps += [ ++ libsystemd_dep, ++ ] ++endif ++ + if have_native_backend + mutter_pkg_private_deps += [ + libdrm_dep, + libinput_dep, + gudev_dep, + libgbm_dep, + logind_provider_dep, + libudev_dep, + xkbcommon_dep, + ] + endif + + if have_wayland_eglstream + mutter_lib_deps += [ + dl_dep, + ] + mutter_pkg_private_deps += [ + wayland_eglstream_protocols_dep, + ] + endif + + mutter_deps = [ + mutter_pkg_deps, + mutter_pkg_private_deps, + mutter_lib_deps, + ] + + mutter_c_args = [ + '-DCLUTTER_ENABLE_COMPOSITOR_API', + '-DCOGL_ENABLE_EXPERIMENTAL_API', +-- +2.44.0 + diff --git a/SOURCES/sticky-or-on-top-dialog-fixes.patch b/SOURCES/sticky-or-on-top-dialog-fixes.patch new file mode 100644 index 0000000..cd45854 --- /dev/null +++ b/SOURCES/sticky-or-on-top-dialog-fixes.patch @@ -0,0 +1,1154 @@ +From a93862f377dc77207ade317a2b2b284402e496e7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 3 Jul 2024 14:13:59 +0200 +Subject: [PATCH 01/13] window: Don't switch workspace on sticky transient when + activating + +If a transient window is sticky (visible on all workspaces) and it gets +activated, we'd call move_worskpace() which would effectively unstick +it, which is rather unexpected. It'd also effectively unstick its parent +as well, due to moving a transient window also moves its descendants and +ascendants. +--- + src/core/window.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/core/window.c b/src/core/window.c +index 7d86adece4..ee4ea90354 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -3762,7 +3762,7 @@ meta_window_activate_full (MetaWindow *window, + /* We've marked it as demanding, don't need to do anything else. */ + return; + } +- else if (window->transient_for != NULL) ++ else if (window->transient_for != NULL && !window->on_all_workspaces) + { + /* Move transients to current workspace - preference dialogs should appear over + the source window. */ +-- +2.44.0.501.g19981daefd.dirty + + +From 5d2e7e48a055cf422f1ef2b8d99bbe5346f704b0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 3 Jul 2024 14:17:48 +0200 +Subject: [PATCH 02/13] window: Inherit stickyness from parent when becoming + transient + +When a transient window becomes transient, check if the parent is +sticky, and if it is, make the transient sticky as well. This handles +situations where e.g. a utility dialog (such as search and replace) is +opened on a sticky window, also making the utility dialog sharing the +same stickyness state. + +This is also more in line with the semantics of making a window sticky, +where transient would implicitly become sticky as a side effect. +--- + src/core/window.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/core/window.c b/src/core/window.c +index ee4ea90354..6da7ae7c03 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -8105,6 +8105,9 @@ meta_window_set_transient_for (MetaWindow *window, + + if (meta_window_appears_focused (window) && window->transient_for != NULL) + meta_window_propagate_focus_appearance (window, TRUE); ++ ++ if (parent && parent->on_all_workspaces) ++ meta_window_stick (window); + } + + void +-- +2.44.0.501.g19981daefd.dirty + + +From fe04ff1ff3b63a6943d7a192c494f6fc99eea088 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 16 Jul 2024 11:32:37 +0200 +Subject: [PATCH 03/13] window: Ignoring unmanaging ancestor when finding root + +This avoids the following critical warning happening sometimes when a +Wayland client exits taking all its window with it in an arbitrary +order: + + CRITICAL: meta_window_set_stack_position_no_sync: assertion 'window->stack_position >= 0' failed +--- + src/core/window.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/core/window.c b/src/core/window.c +index 6da7ae7c03..8d21e3419a 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -5096,7 +5096,8 @@ find_root_ancestor (MetaWindow *window, + MetaWindow **ancestor = data; + + /* Overwrite the previously "most-root" ancestor with the new one found */ +- *ancestor = window; ++ if (!window->unmanaging) ++ *ancestor = window; + + /* We want this to continue until meta_window_foreach_ancestor quits because + * there are no more valid ancestors. +-- +2.44.0.501.g19981daefd.dirty + + +From 02ec655ea9356aefdef4a07896287358903c417b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 16 Jul 2024 11:44:05 +0200 +Subject: [PATCH 04/13] tests/test-runner: Add (un)set_modal + +Ends up calling gtk_window_(un)set_modal() in the client. +--- + src/tests/test-client.c | 32 ++++++++++++++++++++++++++++++++ + src/tests/test-runner.c | 2 ++ + 2 files changed, 34 insertions(+) + +diff --git a/src/tests/test-client.c b/src/tests/test-client.c +index 73931375e9..63fdf5818b 100644 +--- a/src/tests/test-client.c ++++ b/src/tests/test-client.c +@@ -738,6 +738,38 @@ process_line (const char *line) + + gtk_window_unmaximize (GTK_WINDOW (window)); + } ++ else if (strcmp (argv[0], "set_modal") == 0) ++ { ++ GtkWidget *window; ++ ++ if (argc != 2) ++ { ++ g_print ("usage: set_modal \n"); ++ goto out; ++ } ++ ++ window = lookup_window (argv[1]); ++ if (!window) ++ goto out; ++ ++ gtk_window_set_modal (GTK_WINDOW (window), TRUE); ++ } ++ else if (strcmp (argv[0], "unset_modal") == 0) ++ { ++ GtkWidget *window; ++ ++ if (argc != 2) ++ { ++ g_print ("usage: unset_modal \n"); ++ goto out; ++ } ++ ++ window = lookup_window (argv[1]); ++ if (!window) ++ goto out; ++ ++ gtk_window_set_modal (GTK_WINDOW (window), FALSE); ++ } + else if (strcmp (argv[0], "fullscreen") == 0) + { + if (argc != 2) +diff --git a/src/tests/test-runner.c b/src/tests/test-runner.c +index d94e0e1c2b..88aeac30d8 100644 +--- a/src/tests/test-runner.c ++++ b/src/tests/test-runner.c +@@ -703,6 +703,8 @@ test_case_do (TestCase *test, + strcmp (argv[0], "unmaximize") == 0 || + strcmp (argv[0], "fullscreen") == 0 || + strcmp (argv[0], "unfullscreen") == 0 || ++ strcmp (argv[0], "set_modal") == 0 || ++ strcmp (argv[0], "unset_modal") == 0 || + strcmp (argv[0], "freeze") == 0 || + strcmp (argv[0], "thaw") == 0 || + strcmp (argv[0], "destroy") == 0) +-- +2.44.0.501.g19981daefd.dirty + + +From d893cb65583d451e718ae6f5b470a873ea7e0735 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 16 Jul 2024 11:44:58 +0200 +Subject: [PATCH 05/13] window: Propagate stickyness across modal dialog chains + +While marking a parent window as sticky or non-sticky always propagates +to the children, also propagate to the parents if the dialog in question +is modal. +--- + src/core/window.c | 23 +++++++++++++++++++++++ + 1 file changed, 23 insertions(+) + +diff --git a/src/core/window.c b/src/core/window.c +index 8d21e3419a..de9efb8a7b 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -5057,6 +5057,27 @@ stick_foreach_func (MetaWindow *window, + return TRUE; + } + ++static void ++foreach_modal_ancestor (MetaWindow *window, ++ void (*func) (MetaWindow *window)) ++{ ++ MetaWindow *parent; ++ ++ if (window->type != META_WINDOW_MODAL_DIALOG) ++ return; ++ ++ parent = window->transient_for; ++ while (parent) ++ { ++ func (parent); ++ ++ if (parent->type != META_WINDOW_MODAL_DIALOG) ++ break; ++ ++ parent = parent->transient_for; ++ } ++} ++ + void + meta_window_stick (MetaWindow *window) + { +@@ -5068,6 +5089,7 @@ meta_window_stick (MetaWindow *window) + meta_window_foreach_transient (window, + stick_foreach_func, + &stick); ++ foreach_modal_ancestor (window, window_stick_impl); + } + + void +@@ -5081,6 +5103,7 @@ meta_window_unstick (MetaWindow *window) + meta_window_foreach_transient (window, + stick_foreach_func, + &stick); ++ foreach_modal_ancestor (window, window_unstick_impl); + } + + void +-- +2.44.0.501.g19981daefd.dirty + + +From 4ab883d5a509db954f09c410f88f5661ed17351d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 4 Jul 2024 11:50:24 +0200 +Subject: [PATCH 06/13] window: Clean up formatting and naming of stacking + adjustment condition + +The function checking whether a 'always-on-top' window covers the +showing window now has that in the name, to make it more obvious. That +function was also changed to use the more common way of iterating a +list, and now uses auto cleanup pointers for the list. + +The condition itself was updated to follow the current coding style. +--- + src/core/window.c | 37 +++++++++++++++---------------------- + 1 file changed, 15 insertions(+), 22 deletions(-) + +diff --git a/src/core/window.c b/src/core/window.c +index de9efb8a7b..8b7b33b29e 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -2338,33 +2338,25 @@ windows_overlap (const MetaWindow *w1, const MetaWindow *w2) + * (say) ninety per cent and almost indistinguishable from total. + */ + static gboolean +-window_would_be_covered (const MetaWindow *newbie) ++window_would_be_covered_by_always_above_window (MetaWindow *window) + { +- MetaWorkspace *workspace = meta_window_get_workspace ((MetaWindow *)newbie); +- GList *tmp, *windows; ++ MetaWorkspace *workspace = meta_window_get_workspace (window); ++ g_autoptr (GList) windows = NULL; ++ GList *l; + + windows = meta_workspace_list_windows (workspace); +- +- tmp = windows; +- while (tmp != NULL) ++ for (l = windows; l; l = l->next) + { +- MetaWindow *w = tmp->data; ++ MetaWindow *other_window = l->data; + +- if (w->wm_state_above && w != newbie) ++ if (other_window->wm_state_above && other_window != window) + { +- /* We have found a window that is "above". Perhaps it overlaps. */ +- if (windows_overlap (w, newbie)) +- { +- g_list_free (windows); /* clean up... */ +- return TRUE; /* yes, it does */ +- } ++ if (windows_overlap (other_window, window)) ++ return TRUE; + } +- +- tmp = tmp->next; + } + +- g_list_free (windows); +- return FALSE; /* none found */ ++ return FALSE; + } + + void +@@ -2445,10 +2437,11 @@ meta_window_show (MetaWindow *window) + * probably rather be a term in the "if" condition below. + */ + +- if ( focus_window != NULL && window->showing_for_first_time && +- ( (!place_on_top_on_map && !takes_focus_on_map) || +- window_would_be_covered (window) ) +- ) { ++ if (focus_window && ++ window->showing_for_first_time && ++ ((!place_on_top_on_map && !takes_focus_on_map) || ++ window_would_be_covered_by_always_above_window (window))) ++ { + if (!meta_window_is_ancestor_of_transient (focus_window, window)) + { + needs_stacking_adjustment = TRUE; +-- +2.44.0.501.g19981daefd.dirty + + +From 7b201110be2990073518fe859c55bf0a0b3b220b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 4 Jul 2024 13:50:53 +0200 +Subject: [PATCH 07/13] place: Remove a couple of comments about X11 roundtrips + +It's not relevant in the context where the comment is, and don't need to +be reminded of X11 quirks here. +--- + src/core/place.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/src/core/place.c b/src/core/place.c +index 1075fe20d5..b03f746121 100644 +--- a/src/core/place.c ++++ b/src/core/place.c +@@ -807,7 +807,6 @@ meta_window_place (MetaWindow *window, + MetaRectangle work_area; + MetaRectangle frame_rect; + +- /* Warning, this function is a round trip! */ + logical_monitor = meta_backend_get_current_logical_monitor (backend); + + meta_window_get_work_area_for_logical_monitor (window, +@@ -851,7 +850,6 @@ meta_window_place (MetaWindow *window, + g_slist_free (all_windows); + } + +- /* Warning, on X11 this might be a round trip! */ + logical_monitor = meta_backend_get_current_logical_monitor (backend); + + /* Maximize windows if they are too big for their work area (bit of +-- +2.44.0.501.g19981daefd.dirty + + +From 508ca92d4e01b4b79906528d25898a6ef1decee4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 11 Jul 2024 14:23:26 +0200 +Subject: [PATCH 08/13] window: Add place flags + +Replace a boolean argument and a temporary MetaWindow struct field with +a `MetaPlaceFlag` passed where relevant. This includes +`meta_window_move_resize_internal()` and `meta_window_constrain()`, as +placement may happen during constraining, and also +`meta_window_force_placement()`. + +The struct field (denied_focus_and_not_transient) was only ever set in +meta_window_show(), before meta_window_force_placement(), and +immediately unset as a side effect of that. In .._show() we'll always +force placement if the window wasn't already placed, and in +meta_window_constrain(), we'd only ever call meta_window_place() if the +window wasn't already placed, meaning the variable would only ever be +relevant during `meta_window_show()`. Having it as a flag makes that +relationship and temporary state clearer. +--- + src/core/constraints.c | 12 +++++--- + src/core/constraints.h | 1 + + src/core/place.c | 16 +++++----- + src/core/place.h | 11 +++---- + src/core/window-private.h | 15 ++++++---- + src/core/window.c | 45 +++++++++++++++++++--------- + src/wayland/meta-wayland-xdg-shell.c | 2 +- + src/wayland/meta-window-wayland.c | 7 ++++- + src/x11/window-x11.c | 18 +++++++++-- + 9 files changed, 87 insertions(+), 40 deletions(-) + +diff --git a/src/core/constraints.c b/src/core/constraints.c +index a140c62458..e0df50f1a2 100644 +--- a/src/core/constraints.c ++++ b/src/core/constraints.c +@@ -217,6 +217,7 @@ static void setup_constraint_info (ConstraintInfo *info, + const MetaRectangle *orig, + MetaRectangle *new); + static void place_window_if_needed (MetaWindow *window, ++ MetaPlaceFlag place_flags, + ConstraintInfo *info); + static void update_onscreen_requirements (MetaWindow *window, + ConstraintInfo *info); +@@ -290,6 +291,7 @@ do_all_constraints (MetaWindow *window, + void + meta_window_constrain (MetaWindow *window, + MetaMoveResizeFlags flags, ++ MetaPlaceFlag place_flags, + MetaGravity resize_gravity, + const MetaRectangle *orig, + MetaRectangle *new, +@@ -313,7 +315,7 @@ meta_window_constrain (MetaWindow *window, + resize_gravity, + orig, + new); +- place_window_if_needed (window, &info); ++ place_window_if_needed (window, place_flags, &info); + + while (!satisfied && priority <= PRIORITY_MAXIMUM) { + gboolean check_only = TRUE; +@@ -519,8 +521,9 @@ get_start_rect_for_resize (MetaWindow *window, + } + + static void +-place_window_if_needed(MetaWindow *window, +- ConstraintInfo *info) ++place_window_if_needed (MetaWindow *window, ++ MetaPlaceFlag place_flags, ++ ConstraintInfo *info) + { + gboolean did_placement; + +@@ -564,7 +567,8 @@ place_window_if_needed(MetaWindow *window, + } + else + { +- meta_window_place (window, orig_rect.x, orig_rect.y, ++ meta_window_place (window, place_flags, ++ orig_rect.x, orig_rect.y, + &placed_rect.x, &placed_rect.y); + + /* placing the window may have changed the monitor. Find the +diff --git a/src/core/constraints.h b/src/core/constraints.h +index eaa4e45940..0bbcf7146a 100644 +--- a/src/core/constraints.h ++++ b/src/core/constraints.h +@@ -29,6 +29,7 @@ + + void meta_window_constrain (MetaWindow *window, + MetaMoveResizeFlags flags, ++ MetaPlaceFlag place_flags, + MetaGravity resize_gravity, + const MetaRectangle *orig, + MetaRectangle *new, +diff --git a/src/core/place.c b/src/core/place.c +index b03f746121..350e1ed0f1 100644 +--- a/src/core/place.c ++++ b/src/core/place.c +@@ -327,9 +327,10 @@ window_place_centered (MetaWindow *window) + } + + static void +-avoid_being_obscured_as_second_modal_dialog (MetaWindow *window, +- int *x, +- int *y) ++avoid_being_obscured_as_second_modal_dialog (MetaWindow *window, ++ MetaPlaceFlag flags, ++ int *x, ++ int *y) + { + /* We can't center this dialog if it was denied focus and it + * overlaps with the focus window and this dialog is modal and this +@@ -351,7 +352,7 @@ avoid_being_obscured_as_second_modal_dialog (MetaWindow *window, + + /* denied_focus_and_not_transient is only set when focus_window != NULL */ + +- if (window->denied_focus_and_not_transient && ++ if (flags & META_PLACE_FLAG_DENIED_FOCUS_AND_NOT_TRANSIENT && + window->type == META_WINDOW_MODAL_DIALOG && + meta_window_same_application (window, focus_window) && + window_overlaps_focus_window (window)) +@@ -660,6 +661,7 @@ meta_window_process_placement (MetaWindow *window, + + void + meta_window_place (MetaWindow *window, ++ MetaPlaceFlag flags, + int x, + int y, + int *new_x, +@@ -758,7 +760,7 @@ meta_window_place (MetaWindow *window, + { + meta_topic (META_DEBUG_PLACEMENT, + "Not placing window with PPosition or USPosition set"); +- avoid_being_obscured_as_second_modal_dialog (window, &x, &y); ++ avoid_being_obscured_as_second_modal_dialog (window, flags, &x, &y); + goto done; + } + } +@@ -791,7 +793,7 @@ meta_window_place (MetaWindow *window, + "Centered window %s over transient parent", + window->desc); + +- avoid_being_obscured_as_second_modal_dialog (window, &x, &y); ++ avoid_being_obscured_as_second_modal_dialog (window, flags, &x, &y); + + goto done; + } +@@ -895,7 +897,7 @@ meta_window_place (MetaWindow *window, + * if at all possible. This is guaranteed to only be called if the + * focus_window is non-NULL, and we try to avoid that window. + */ +- if (window->denied_focus_and_not_transient) ++ if (flags & META_PLACE_FLAG_DENIED_FOCUS_AND_NOT_TRANSIENT) + { + MetaWindow *focus_window; + gboolean found_fit; +diff --git a/src/core/place.h b/src/core/place.h +index 2e2c811413..0c95ad6329 100644 +--- a/src/core/place.h ++++ b/src/core/place.h +@@ -30,10 +30,11 @@ void meta_window_process_placement (MetaWindow *window, + int *rel_x, + int *rel_y); + +-void meta_window_place (MetaWindow *window, +- int x, +- int y, +- int *new_x, +- int *new_y); ++void meta_window_place (MetaWindow *window, ++ MetaPlaceFlag place_flags, ++ int x, ++ int y, ++ int *new_x, ++ int *new_y); + + #endif +diff --git a/src/core/window-private.h b/src/core/window-private.h +index d1730c9880..cacc45e964 100644 +--- a/src/core/window-private.h ++++ b/src/core/window-private.h +@@ -81,6 +81,13 @@ typedef enum + META_MOVE_RESIZE_PLACEMENT_CHANGED = 1 << 11, + } MetaMoveResizeFlags; + ++typedef enum _MetaPlaceFlag ++{ ++ META_PLACE_FLAG_NONE = 0, ++ META_PLACE_FLAG_FORCE_MOVE = 1 << 0, ++ META_PLACE_FLAG_DENIED_FOCUS_AND_NOT_TRANSIENT = 1 << 1, ++} MetaPlaceFlag; ++ + typedef enum + { + META_MOVE_RESIZE_RESULT_MOVED = 1 << 0, +@@ -385,9 +392,6 @@ struct _MetaWindow + /* Have we placed this window? */ + guint placed : 1; + +- /* Is this not a transient of the focus window which is being denied focus? */ +- guint denied_focus_and_not_transient : 1; +- + /* Has this window not ever been shown yet? */ + guint showing_for_first_time : 1; + +@@ -861,6 +865,7 @@ void meta_window_update_resize (MetaWindow *window, + + void meta_window_move_resize_internal (MetaWindow *window, + MetaMoveResizeFlags flags, ++ MetaPlaceFlag place_flags, + MetaGravity gravity, + MetaRectangle frame_rect); + +@@ -875,8 +880,8 @@ void meta_window_emit_size_changed (MetaWindow *window); + + MetaPlacementRule *meta_window_get_placement_rule (MetaWindow *window); + +-void meta_window_force_placement (MetaWindow *window, +- gboolean force_move); ++void meta_window_force_placement (MetaWindow *window, ++ MetaPlaceFlag flags); + + void meta_window_force_restore_shortcuts (MetaWindow *window, + ClutterInputDevice *source); +diff --git a/src/core/window.c b/src/core/window.c +index 8b7b33b29e..f1c81c644b 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -1085,7 +1085,6 @@ _meta_window_shared_new (MetaDisplay *display, + /* if already mapped we don't want to do the placement thing; + * override-redirect windows are placed by the app */ + window->placed = ((window->mapped && !window->hidden) || window->override_redirect); +- window->denied_focus_and_not_transient = FALSE; + window->unmanaging = FALSE; + window->is_in_queues = 0; + window->keys_grabbed = FALSE; +@@ -2360,8 +2359,8 @@ window_would_be_covered_by_always_above_window (MetaWindow *window) + } + + void +-meta_window_force_placement (MetaWindow *window, +- gboolean force_move) ++meta_window_force_placement (MetaWindow *window, ++ MetaPlaceFlag place_flags) + { + MetaMoveResizeFlags flags; + +@@ -2379,11 +2378,12 @@ meta_window_force_placement (MetaWindow *window, + window->calc_placement = TRUE; + + flags = META_MOVE_RESIZE_MOVE_ACTION | META_MOVE_RESIZE_RESIZE_ACTION; +- if (force_move) ++ if (place_flags & META_PLACE_FLAG_FORCE_MOVE) + flags |= META_MOVE_RESIZE_FORCE_MOVE; + + meta_window_move_resize_internal (window, + flags, ++ place_flags, + META_GRAVITY_NORTH_WEST, + window->unconstrained_rect); + window->calc_placement = FALSE; +@@ -2393,11 +2393,6 @@ meta_window_force_placement (MetaWindow *window, + * still get placed when they are ultimately shown. + */ + window->placed = TRUE; +- +- /* Don't want to accidentally reuse the fact that we had been denied +- * focus in any future constraints unless we're denied focus again. +- */ +- window->denied_focus_and_not_transient = FALSE; + } + + static void +@@ -2410,6 +2405,7 @@ meta_window_show (MetaWindow *window) + MetaWindow *focus_window; + gboolean notify_demands_attention = FALSE; + MetaDisplay *display = window->display; ++ MetaPlaceFlag place_flags = META_PLACE_FLAG_NONE; + + meta_topic (META_DEBUG_WINDOW_STATE, + "Showing window %s, shaded: %d iconic: %d placed: %d", +@@ -2446,7 +2442,7 @@ meta_window_show (MetaWindow *window) + { + needs_stacking_adjustment = TRUE; + if (!window->placed) +- window->denied_focus_and_not_transient = TRUE; ++ place_flags |= META_PLACE_FLAG_DENIED_FOCUS_AND_NOT_TRANSIENT; + } + } + +@@ -2466,7 +2462,7 @@ meta_window_show (MetaWindow *window) + window->maximize_vertically_after_placement = TRUE; + } + } +- meta_window_force_placement (window, FALSE); ++ meta_window_force_placement (window, place_flags); + } + + if (needs_stacking_adjustment) +@@ -2934,6 +2930,7 @@ meta_window_maximize (MetaWindow *window, + (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_STATE_CHANGED), ++ META_PLACE_FLAG_NONE, + META_GRAVITY_NORTH_WEST, + window->unconstrained_rect); + } +@@ -3202,6 +3199,7 @@ meta_window_tile (MetaWindow *window, + (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_STATE_CHANGED), ++ META_PLACE_FLAG_NONE, + META_GRAVITY_NORTH_WEST, + window->unconstrained_rect); + +@@ -3411,6 +3409,7 @@ meta_window_unmaximize (MetaWindow *window, + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_STATE_CHANGED | + META_MOVE_RESIZE_UNMAXIMIZE), ++ META_PLACE_FLAG_NONE, + META_GRAVITY_NORTH_WEST, + target_rect); + +@@ -3528,6 +3527,7 @@ meta_window_make_fullscreen (MetaWindow *window) + (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_STATE_CHANGED), ++ META_PLACE_FLAG_NONE, + META_GRAVITY_NORTH_WEST, + window->unconstrained_rect); + } +@@ -3575,6 +3575,7 @@ meta_window_unmake_fullscreen (MetaWindow *window) + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_STATE_CHANGED | + META_MOVE_RESIZE_UNFULLSCREEN), ++ META_PLACE_FLAG_NONE, + META_GRAVITY_NORTH_WEST, + target_rect); + +@@ -3834,6 +3835,7 @@ meta_window_reposition (MetaWindow *window) + meta_window_move_resize_internal (window, + (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION), ++ META_PLACE_FLAG_NONE, + META_GRAVITY_NORTH_WEST, + window->rect); + } +@@ -4005,6 +4007,7 @@ meta_window_update_monitor (MetaWindow *window, + void + meta_window_move_resize_internal (MetaWindow *window, + MetaMoveResizeFlags flags, ++ MetaPlaceFlag place_flags, + MetaGravity gravity, + MetaRectangle frame_rect) + { +@@ -4099,6 +4102,7 @@ meta_window_move_resize_internal (MetaWindow *window, + + meta_window_constrain (window, + flags, ++ place_flags, + gravity, + &old_rect, + &constrained_rect, +@@ -4216,7 +4220,11 @@ meta_window_move_frame (MetaWindow *window, + g_return_if_fail (!window->override_redirect); + + flags = (user_op ? META_MOVE_RESIZE_USER_ACTION : 0) | META_MOVE_RESIZE_MOVE_ACTION; +- meta_window_move_resize_internal (window, flags, META_GRAVITY_NORTH_WEST, rect); ++ meta_window_move_resize_internal (window, ++ flags, ++ META_PLACE_FLAG_NONE, ++ META_GRAVITY_NORTH_WEST, ++ rect); + } + + static void +@@ -4249,6 +4257,7 @@ meta_window_move_between_rects (MetaWindow *window, + move_resize_flags | + META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION, ++ META_PLACE_FLAG_NONE, + META_GRAVITY_NORTH_WEST, + window->unconstrained_rect); + } +@@ -4280,7 +4289,11 @@ meta_window_move_resize_frame (MetaWindow *window, + + flags = (user_op ? META_MOVE_RESIZE_USER_ACTION : 0) | META_MOVE_RESIZE_MOVE_ACTION | META_MOVE_RESIZE_RESIZE_ACTION; + +- meta_window_move_resize_internal (window, flags, META_GRAVITY_NORTH_WEST, rect); ++ meta_window_move_resize_internal (window, ++ flags, ++ META_PLACE_FLAG_NONE, ++ META_GRAVITY_NORTH_WEST, ++ rect); + } + + /** +@@ -4379,7 +4392,11 @@ meta_window_resize_frame_with_gravity (MetaWindow *window, + } + + flags = (user_op ? META_MOVE_RESIZE_USER_ACTION : 0) | META_MOVE_RESIZE_RESIZE_ACTION; +- meta_window_move_resize_internal (window, flags, gravity, rect); ++ meta_window_move_resize_internal (window, ++ flags, ++ META_PLACE_FLAG_NONE, ++ gravity, ++ rect); + } + + static void +diff --git a/src/wayland/meta-wayland-xdg-shell.c b/src/wayland/meta-wayland-xdg-shell.c +index c5f0d0b913..16c7cac60e 100644 +--- a/src/wayland/meta-wayland-xdg-shell.c ++++ b/src/wayland/meta-wayland-xdg-shell.c +@@ -429,7 +429,7 @@ xdg_toplevel_set_maximized (struct wl_client *client, + if (!window->has_maximize_func) + return; + +- meta_window_force_placement (window, TRUE); ++ meta_window_force_placement (window, META_PLACE_FLAG_FORCE_MOVE); + meta_window_maximize (window, META_MAXIMIZE_BOTH); + } + +diff --git a/src/wayland/meta-window-wayland.c b/src/wayland/meta-window-wayland.c +index 12e9567d9c..92cb684bb2 100644 +--- a/src/wayland/meta-window-wayland.c ++++ b/src/wayland/meta-window-wayland.c +@@ -1000,7 +1000,11 @@ meta_window_wayland_finish_move_resize (MetaWindow *window, + gravity = meta_resize_gravity_from_grab_op (window->display->grab_op); + else + gravity = META_GRAVITY_STATIC; +- meta_window_move_resize_internal (window, flags, gravity, rect); ++ meta_window_move_resize_internal (window, ++ flags, ++ META_PLACE_FLAG_NONE, ++ gravity, ++ rect); + + g_clear_pointer (&acked_configuration, meta_wayland_window_configuration_free); + } +@@ -1046,6 +1050,7 @@ meta_window_place_with_placement_rule (MetaWindow *window, + (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_PLACEMENT_CHANGED), ++ META_PLACE_FLAG_NONE, + META_GRAVITY_NORTH_WEST, + window->unconstrained_rect); + window->calc_placement = FALSE; +diff --git a/src/x11/window-x11.c b/src/x11/window-x11.c +index 204b49e93e..a4bd06bf0e 100644 +--- a/src/x11/window-x11.c ++++ b/src/x11/window-x11.c +@@ -509,7 +509,11 @@ meta_window_apply_session_info (MetaWindow *window, + + adjust_for_gravity (window, FALSE, gravity, &rect); + meta_window_client_rect_to_frame_rect (window, &rect, &rect); +- meta_window_move_resize_internal (window, flags, gravity, rect); ++ meta_window_move_resize_internal (window, ++ flags, ++ META_PLACE_FLAG_NONE, ++ gravity, ++ rect); + } + } + +@@ -577,7 +581,11 @@ meta_window_x11_manage (MetaWindow *window) + + adjust_for_gravity (window, TRUE, gravity, &rect); + meta_window_client_rect_to_frame_rect (window, &rect, &rect); +- meta_window_move_resize_internal (window, flags, gravity, rect); ++ meta_window_move_resize_internal (window, ++ flags, ++ META_PLACE_FLAG_NONE, ++ gravity, ++ rect); + } + + meta_window_x11_update_shape_region (window); +@@ -2658,7 +2666,11 @@ meta_window_move_resize_request (MetaWindow *window, + + adjust_for_gravity (window, TRUE, gravity, &rect); + meta_window_client_rect_to_frame_rect (window, &rect, &rect); +- meta_window_move_resize_internal (window, flags, gravity, rect); ++ meta_window_move_resize_internal (window, ++ flags, ++ META_PLACE_FLAG_NONE, ++ gravity, ++ rect); + } + } + +-- +2.44.0.501.g19981daefd.dirty + + +From e7b243bd6aee654b94812a402ad1a1f3103fdde3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 11 Jul 2024 14:33:01 +0200 +Subject: [PATCH 09/13] window: Move required condition into main if statement + in show() + +No logical changes. +--- + src/core/window.c | 10 ++++------ + 1 file changed, 4 insertions(+), 6 deletions(-) + +diff --git a/src/core/window.c b/src/core/window.c +index f1c81c644b..aa5623682b 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -2435,15 +2435,13 @@ meta_window_show (MetaWindow *window) + + if (focus_window && + window->showing_for_first_time && ++ !meta_window_is_ancestor_of_transient (focus_window, window) && + ((!place_on_top_on_map && !takes_focus_on_map) || + window_would_be_covered_by_always_above_window (window))) + { +- if (!meta_window_is_ancestor_of_transient (focus_window, window)) +- { +- needs_stacking_adjustment = TRUE; +- if (!window->placed) +- place_flags |= META_PLACE_FLAG_DENIED_FOCUS_AND_NOT_TRANSIENT; +- } ++ needs_stacking_adjustment = TRUE; ++ if (!window->placed) ++ place_flags |= META_PLACE_FLAG_DENIED_FOCUS_AND_NOT_TRANSIENT; + } + + if (!window->placed) +-- +2.44.0.501.g19981daefd.dirty + + +From b92ae1e8d11719bdf2aaa00a80259ca8d7661a13 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 11 Jul 2024 14:46:04 +0200 +Subject: [PATCH 10/13] window: Clarify expression for deciding whether to + auto-maximize + +Calculate areas and store them in descriptively named variables, and +then compare them, instead of doing it all in one go. +--- + src/core/window.c | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/src/core/window.c b/src/core/window.c +index aa5623682b..8ab86933ef 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -2452,9 +2452,15 @@ meta_window_show (MetaWindow *window) + window->has_maximize_func) + { + MetaRectangle work_area; +- meta_window_get_work_area_for_monitor (window, window->monitor->number, &work_area); +- /* Automaximize windows that map with a size > MAX_UNMAXIMIZED_WINDOW_AREA of the work area */ +- if (window->rect.width * window->rect.height > work_area.width * work_area.height * MAX_UNMAXIMIZED_WINDOW_AREA) ++ int window_area; ++ int work_area_area; ++ ++ window_area = window->rect.width * window->rect.height; ++ meta_window_get_work_area_for_monitor (window, window->monitor->number, ++ &work_area); ++ work_area_area = work_area.width * work_area.height; ++ ++ if (window_area > work_area_area * MAX_UNMAXIMIZED_WINDOW_AREA) + { + window->maximize_horizontally_after_placement = TRUE; + window->maximize_vertically_after_placement = TRUE; +-- +2.44.0.501.g19981daefd.dirty + + +From b7d43ff70a9188b64e42d5cfd1d1be6c9ea3b768 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 11 Jul 2024 14:47:07 +0200 +Subject: [PATCH 11/13] window: Fix minor coding style issue + +--- + src/core/window.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/core/window.c b/src/core/window.c +index 8ab86933ef..6a693a7a9d 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -4102,8 +4102,8 @@ meta_window_move_resize_internal (MetaWindow *window, + window->monitor) + { + MetaRectangle old_rect; +- meta_window_get_frame_rect (window, &old_rect); + ++ meta_window_get_frame_rect (window, &old_rect); + meta_window_constrain (window, + flags, + place_flags, +-- +2.44.0.501.g19981daefd.dirty + + +From 6397f60c9b5f06ece742bcaa2936b6501498ede7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 11 Jul 2024 14:57:15 +0200 +Subject: [PATCH 12/13] window: Don't check always-on-top overlap before + placing + +When we show a window, we'll check if it overlaps with an existing +always-on-top window with the intention to deny focus. However, we did +this potentially before having placed the window, meaning we effectively +checked as if it was placed at (0, 0), which created unexpected results. + +Instead check the overlap state after placing. A window placement test +case is added to verify this works as expected. +--- + src/core/place.c | 11 ++++++++--- + src/core/window.c | 10 ++++++++-- + 2 files changed, 16 insertions(+), 5 deletions(-) + +diff --git a/src/core/place.c b/src/core/place.c +index 350e1ed0f1..e89aa4887b 100644 +--- a/src/core/place.c ++++ b/src/core/place.c +@@ -296,7 +296,9 @@ find_most_freespace (MetaWindow *window, + } + + static gboolean +-window_overlaps_focus_window (MetaWindow *window) ++window_overlaps_focus_window (MetaWindow *window, ++ int new_x, ++ int new_y) + { + MetaWindow *focus_window; + MetaRectangle window_frame, focus_frame, overlap; +@@ -306,6 +308,9 @@ window_overlaps_focus_window (MetaWindow *window) + return FALSE; + + meta_window_get_frame_rect (window, &window_frame); ++ window_frame.x = new_x; ++ window_frame.y = new_y; ++ + meta_window_get_frame_rect (focus_window, &focus_frame); + + return meta_rectangle_intersect (&window_frame, +@@ -355,7 +360,7 @@ avoid_being_obscured_as_second_modal_dialog (MetaWindow *window, + if (flags & META_PLACE_FLAG_DENIED_FOCUS_AND_NOT_TRANSIENT && + window->type == META_WINDOW_MODAL_DIALOG && + meta_window_same_application (window, focus_window) && +- window_overlaps_focus_window (window)) ++ window_overlaps_focus_window (window, *x, *y)) + { + find_most_freespace (window, focus_window, *x, *y, x, y); + meta_topic (META_DEBUG_PLACEMENT, +@@ -906,7 +911,7 @@ meta_window_place (MetaWindow *window, + g_assert (focus_window != NULL); + + /* No need to do anything if the window doesn't overlap at all */ +- found_fit = !window_overlaps_focus_window (window); ++ found_fit = !window_overlaps_focus_window (window, x, y); + + /* Try to do a first fit again, this time only taking into account the + * focus window. +diff --git a/src/core/window.c b/src/core/window.c +index 6a693a7a9d..6082a158a7 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -2436,8 +2436,8 @@ meta_window_show (MetaWindow *window) + if (focus_window && + window->showing_for_first_time && + !meta_window_is_ancestor_of_transient (focus_window, window) && +- ((!place_on_top_on_map && !takes_focus_on_map) || +- window_would_be_covered_by_always_above_window (window))) ++ !place_on_top_on_map && ++ !takes_focus_on_map) + { + needs_stacking_adjustment = TRUE; + if (!window->placed) +@@ -2469,6 +2469,12 @@ meta_window_show (MetaWindow *window) + meta_window_force_placement (window, place_flags); + } + ++ if (focus_window && ++ window->showing_for_first_time && ++ !meta_window_is_ancestor_of_transient (focus_window, window) && ++ window_would_be_covered_by_always_above_window (window)) ++ needs_stacking_adjustment = TRUE; ++ + if (needs_stacking_adjustment) + { + gboolean overlap; +-- +2.44.0.501.g19981daefd.dirty + + +From aefa1b34670124c0c95c741328c4095b55ec6333 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 11 Jul 2024 15:11:13 +0200 +Subject: [PATCH 13/13] window: Only deny focus if mostly overlapped with + always-on-top window + +Having an always-on-top window affects focus granting logic if the +to be showing window overlaps with any of them. Instead of triggering +the focus denying logic if a new window ever so slightly touches an +always-on-top window to only triggering if it's covered more than 60% by +always-on-top windows. + +This is intended to make using always-on-top windows a bit less annoying +and not cause as many unintended focus-on-map denials. +--- + src/core/window.c | 41 ++++++++++++++++++++++++++++++++++------- + 1 file changed, 34 insertions(+), 7 deletions(-) + +diff --git a/src/core/window.c b/src/core/window.c +index 6082a158a7..fe34023673 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -68,6 +68,7 @@ + #include "backends/meta-backend-private.h" + #include "backends/meta-logical-monitor.h" + #include "cogl/cogl.h" ++#include "compositor/region-utils.h" + #include "core/boxes-private.h" + #include "core/constraints.h" + #include "core/edge-resistance.h" +@@ -2325,6 +2326,20 @@ windows_overlap (const MetaWindow *w1, const MetaWindow *w2) + return meta_rectangle_overlap (&w1rect, &w2rect); + } + ++static int ++calculate_region_area (cairo_region_t *region) ++{ ++ MetaRegionIterator iter; ++ int area = 0; ++ ++ for (meta_region_iterator_init (&iter, region); ++ !meta_region_iterator_at_end (&iter); ++ meta_region_iterator_next (&iter)) ++ area += iter.rectangle.width * iter.rectangle.height; ++ ++ return area; ++} ++ + /* Returns whether a new window would be covered by any + * existing window on the same workspace that is set + * to be "above" ("always on top"). A window that is not +@@ -2337,25 +2352,37 @@ windows_overlap (const MetaWindow *w1, const MetaWindow *w2) + * (say) ninety per cent and almost indistinguishable from total. + */ + static gboolean +-window_would_be_covered_by_always_above_window (MetaWindow *window) ++window_would_mostly_be_covered_by_always_above_window (MetaWindow *window) + { + MetaWorkspace *workspace = meta_window_get_workspace (window); + g_autoptr (GList) windows = NULL; + GList *l; ++ cairo_region_t *region; ++ int window_area, intersection_area, visible_area; + ++ region = cairo_region_create (); + windows = meta_workspace_list_windows (workspace); + for (l = windows; l; l = l->next) + { + MetaWindow *other_window = l->data; + + if (other_window->wm_state_above && other_window != window) +- { +- if (windows_overlap (other_window, window)) +- return TRUE; +- } ++ cairo_region_union_rectangle (region, &other_window->rect); + } + +- return FALSE; ++ window_area = window->rect.width * window->rect.height; ++ ++ cairo_region_intersect_rectangle (region, &window->rect); ++ intersection_area = calculate_region_area (region); ++ visible_area = window_area - intersection_area; ++ ++ cairo_region_destroy (region); ++ ++#define REQUIRED_VISIBLE_AREA_PERCENT 40 ++ if ((100 * visible_area) / window_area > REQUIRED_VISIBLE_AREA_PERCENT) ++ return FALSE; ++ else ++ return TRUE; + } + + void +@@ -2472,7 +2499,7 @@ meta_window_show (MetaWindow *window) + if (focus_window && + window->showing_for_first_time && + !meta_window_is_ancestor_of_transient (focus_window, window) && +- window_would_be_covered_by_always_above_window (window)) ++ window_would_mostly_be_covered_by_always_above_window (window)) + needs_stacking_adjustment = TRUE; + + if (needs_stacking_adjustment) +-- +2.44.0.501.g19981daefd.dirty + diff --git a/SOURCES/x11-Add-support-for-fractional-scaling-using-Randr.patch b/SOURCES/x11-Add-support-for-fractional-scaling-using-Randr.patch new file mode 100644 index 0000000..7a47344 --- /dev/null +++ b/SOURCES/x11-Add-support-for-fractional-scaling-using-Randr.patch @@ -0,0 +1,3784 @@ +From: =?utf-8?b?Ik1hcmNvIFRyZXZpc2FuIChUcmV2acOxbyki?= +Date: Thu, 4 Apr 2019 01:18:03 +0200 +Subject: x11-Add-support-for-fractional-scaling-using-Randr + +Add scaling support using randr under x11. + +Origin: https://gitlab.gnome.org/3v1n0/mutter/commits/xrandr-scaling +Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/mutter/+bug/1820850 +Forwarded: No, forwarding is in progress and planned though +--- + data/meson.build | 7 + + data/org.gnome.mutter.gschema.xml.in | 5 +- + data/org.gnome.mutter.x11.gschema.xml.in | 30 ++ + src/backends/meta-crtc.c | 21 + + src/backends/meta-crtc.h | 6 + + src/backends/meta-monitor-config-manager.c | 180 ++++++- + src/backends/meta-monitor-config-manager.h | 5 + + src/backends/meta-monitor-config-migration.c | 15 +- + src/backends/meta-monitor-config-store.c | 17 + + src/backends/meta-monitor-manager-dummy.c | 24 +- + src/backends/meta-monitor-manager-private.h | 34 +- + src/backends/meta-monitor-manager.c | 511 +++++++++++++++++-- + src/backends/meta-monitor.c | 60 ++- + src/backends/meta-monitor.h | 6 +- + src/backends/meta-settings-private.h | 13 + + src/backends/meta-settings.c | 148 +++++- + src/backends/native/meta-monitor-manager-native.c | 41 +- + src/backends/x11/meta-crtc-xrandr.c | 100 +++- + src/backends/x11/meta-crtc-xrandr.h | 14 +- + src/backends/x11/meta-gpu-xrandr.c | 112 ++++- + src/backends/x11/meta-gpu-xrandr.h | 4 + + src/backends/x11/meta-monitor-manager-xrandr.c | 588 +++++++++++++++++----- + src/backends/x11/meta-monitor-manager-xrandr.h | 4 +- + src/backends/x11/meta-output-xrandr.c | 3 +- + src/compositor/meta-compositor-x11.c | 99 +++- + src/core/boxes-private.h | 4 + + src/core/boxes.c | 21 + + src/core/window.c | 19 + + src/org.gnome.Mutter.DisplayConfig.xml | 5 + + src/tests/meta-monitor-manager-test.c | 13 +- + 30 files changed, 1839 insertions(+), 270 deletions(-) + create mode 100644 data/org.gnome.mutter.x11.gschema.xml.in + +diff --git a/data/meson.build b/data/meson.build +index b1e81d1..977b340 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -55,6 +55,13 @@ configure_file( + install_dir: schemadir + ) + ++configure_file( ++ input: 'org.gnome.mutter.x11.gschema.xml.in', ++ output: 'org.gnome.mutter.x11.gschema.xml', ++ configuration: gschema_config, ++ install_dir: schemadir ++) ++ + install_data(['mutter-schemas.convert'], + install_dir: join_paths(datadir, 'GConf/gsettings'), + ) +diff --git a/data/org.gnome.mutter.gschema.xml.in b/data/org.gnome.mutter.gschema.xml.in +index 23fa9f3..e64691d 100644 +--- a/data/org.gnome.mutter.gschema.xml.in ++++ b/data/org.gnome.mutter.gschema.xml.in +@@ -134,7 +134,10 @@ + • “autoclose-xwayland” — automatically terminates Xwayland if all + relevant X11 clients are gone. Does not + require a restart. +- ++ • “x11-randr-fractional-scaling” — enable fractional scaling under X11 ++ using xrandr scaling. It might reduce ++ performances. ++ Does not require a restart. + + + +diff --git a/data/org.gnome.mutter.x11.gschema.xml.in b/data/org.gnome.mutter.x11.gschema.xml.in +new file mode 100644 +index 0000000..3696659 +--- /dev/null ++++ b/data/org.gnome.mutter.x11.gschema.xml.in +@@ -0,0 +1,30 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ "scale-ui-down" ++ ++ Choose the scaling mode to be used under X11 via Randr extension. ++ ++ Supported methods are: ++ ++ • “scale-up” — Scale everything up to the requested scale, shrinking ++ the UI. The applications will look blurry when scaling ++ at higher values and the resolution will be lowered. ++ • “scale-ui-down — Scale up the UI toolkits to the closest integer ++ scaling value upwards, while scale down the display ++ to match the requested scaling level. ++ It increases the resolution of the logical display. ++ ++ ++ ++ ++ ++ +diff --git a/src/backends/meta-crtc.c b/src/backends/meta-crtc.c +index 09f9199..df4033d 100644 +--- a/src/backends/meta-crtc.c ++++ b/src/backends/meta-crtc.c +@@ -117,6 +117,7 @@ meta_crtc_set_config (MetaCrtc *crtc, + config->layout = *layout; + config->mode = mode; + config->transform = transform; ++ config->scale = 1.0f; + + priv->config = config; + } +@@ -137,6 +138,26 @@ meta_crtc_get_config (MetaCrtc *crtc) + return priv->config; + } + ++void ++meta_crtc_set_config_scale (MetaCrtc *crtc, ++ float scale) ++{ ++ MetaCrtcPrivate *priv = meta_crtc_get_instance_private (crtc); ++ ++ g_return_if_fail (scale > 0); ++ ++ if (priv->config) ++ priv->config->scale = scale; ++} ++ ++float ++meta_crtc_get_config_scale (MetaCrtc *crtc) ++{ ++ MetaCrtcPrivate *priv = meta_crtc_get_instance_private (crtc); ++ ++ return priv->config ? priv->config->scale : 1.0f; ++} ++ + static void + meta_crtc_set_property (GObject *object, + guint prop_id, +diff --git a/src/backends/meta-crtc.h b/src/backends/meta-crtc.h +index f6a3bc1..acdc2b7 100644 +--- a/src/backends/meta-crtc.h ++++ b/src/backends/meta-crtc.h +@@ -33,6 +33,7 @@ typedef struct _MetaCrtcConfig + graphene_rect_t layout; + MetaMonitorTransform transform; + MetaCrtcMode *mode; ++ float scale; + } MetaCrtcConfig; + + #define META_TYPE_CRTC (meta_crtc_get_type ()) +@@ -68,6 +69,11 @@ void meta_crtc_set_config (MetaCrtc *crtc, + MetaCrtcMode *mode, + MetaMonitorTransform transform); + ++void meta_crtc_set_config_scale (MetaCrtc *crtc, ++ float scale); ++ ++float meta_crtc_get_config_scale (MetaCrtc *crtc); ++ + META_EXPORT_TEST + void meta_crtc_unset_config (MetaCrtc *crtc); + +diff --git a/src/backends/meta-monitor-config-manager.c b/src/backends/meta-monitor-config-manager.c +index 0253e07..406e247 100644 +--- a/src/backends/meta-monitor-config-manager.c ++++ b/src/backends/meta-monitor-config-manager.c +@@ -217,6 +217,18 @@ assign_monitor_crtc (MetaMonitor *monitor, + else + crtc_hw_transform = META_MONITOR_TRANSFORM_NORMAL; + ++ scale = data->logical_monitor_config->scale; ++ if (!meta_monitor_manager_is_scale_supported (data->monitor_manager, ++ data->config->layout_mode, ++ monitor, mode, scale)) ++ { ++ scale = roundf (scale); ++ if (!meta_monitor_manager_is_scale_supported (data->monitor_manager, ++ data->config->layout_mode, ++ monitor, mode, scale)) ++ scale = 1.0f; ++ } ++ + meta_monitor_calculate_crtc_pos (monitor, mode, output, crtc_transform, + &crtc_x, &crtc_y); + +@@ -231,6 +243,8 @@ assign_monitor_crtc (MetaMonitor *monitor, + case META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL: + scale = 1.0; + break; ++ case META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL: ++ break; + } + + crtc_mode = monitor_crtc_mode->crtc_mode; +@@ -258,6 +272,7 @@ assign_monitor_crtc (MetaMonitor *monitor, + .mode = crtc_mode, + .layout = crtc_layout, + .transform = crtc_hw_transform, ++ .scale = scale, + .outputs = g_ptr_array_new () + }; + g_ptr_array_add (crtc_assignment->outputs, output); +@@ -542,7 +557,11 @@ meta_monitor_config_manager_get_stored (MetaMonitorConfigManager *config_manager + typedef enum _MonitorMatchRule + { + MONITOR_MATCH_ALL = 0, +- MONITOR_MATCH_EXTERNAL = (1 << 0) ++ MONITOR_MATCH_EXTERNAL = (1 << 0), ++ MONITOR_MATCH_BUILTIN = (1 << 1), ++ MONITOR_MATCH_PRIMARY = (1 << 2), ++ MONITOR_MATCH_VISIBLE = (1 << 3), ++ MONITOR_MATCH_WITH_POSITION = (1 << 4), + } MonitorMatchRule; + + static MetaMonitor * +@@ -696,12 +696,69 @@ get_monitor_transform (MetaMonitorManager *monitor_manager, + } + } + ++static float ++get_preferred_preferred_max_scale (MetaMonitorManager *monitor_manager, ++ MetaLogicalMonitorLayoutMode layout_mode, ++ MonitorMatchRule match_rule) ++{ ++ float scale = 1.0f; ++ GList *monitors, *l; ++ ++ monitors = meta_monitor_manager_get_monitors (monitor_manager); ++ ++ for (l = monitors; l; l = l->next) ++ { ++ float s; ++ MetaMonitor *monitor = l->data; ++ MetaMonitorMode *mode = meta_monitor_get_preferred_mode (monitor); ++ ++ if (match_rule & MONITOR_MATCH_PRIMARY) ++ { ++ if (!meta_monitor_is_primary (monitor)) ++ continue; ++ } ++ ++ if (match_rule & MONITOR_MATCH_BUILTIN) ++ { ++ if (!meta_monitor_is_laptop_panel (monitor)) ++ continue; ++ } ++ else if (match_rule & MONITOR_MATCH_EXTERNAL) ++ { ++ if (meta_monitor_is_laptop_panel (monitor)) ++ continue; ++ } ++ ++ if (match_rule & MONITOR_MATCH_VISIBLE) ++ { ++ if (meta_monitor_is_laptop_panel (monitor) && ++ is_lid_closed (monitor_manager)) ++ continue; ++ } ++ ++ if (match_rule & MONITOR_MATCH_WITH_POSITION) ++ { ++ if (!meta_monitor_get_suggested_position (monitor, NULL, NULL)) ++ continue; ++ } ++ ++ s = meta_monitor_manager_calculate_monitor_mode_scale (monitor_manager, ++ layout_mode, ++ monitor, ++ mode); ++ scale = MAX (scale, s); ++ } ++ ++ return scale; ++} ++ + static MetaLogicalMonitorConfig * + create_logical_monitor_config (MetaMonitorManager *monitor_manager, + MetaMonitor *monitor, + MetaMonitorMode *mode, + int x, + int y, ++ float max_scale, + MetaLogicalMonitorConfig *primary_logical_monitor_config, + MetaLogicalMonitorLayoutMode layout_mode) + { +@@ -700,6 +776,7 @@ create_preferred_logical_monitor_config (MetaMonitorManager *monitor_ma + scale = primary_logical_monitor_config->scale; + else + scale = meta_monitor_manager_calculate_monitor_mode_scale (monitor_manager, ++ monitor_manager->layout_mode, + monitor, + mode); + +@@ -709,6 +786,13 @@ create_preferred_logical_monitor_config (MetaMonitorManager *monitor_ma + width = (int) roundf (width / scale); + height = (int) roundf (height / scale); + break; ++ case META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL: ++ { ++ float ui_scale = scale / ceilf (max_scale); ++ width = (int) roundf (width / ui_scale); ++ height = (int) roundf (height / ui_scale); ++ } ++ break; + case META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL: + break; + } +@@ -747,6 +831,7 @@ meta_monitor_config_manager_create_linear (MetaMonitorConfigManager *config_mana + MetaMonitor *primary_monitor; + MetaLogicalMonitorLayoutMode layout_mode; + MetaLogicalMonitorConfig *primary_logical_monitor_config; ++ float max_scale = 1.0f; + int x; + GList *monitors; + GList *l; +@@ -758,10 +843,16 @@ meta_monitor_config_manager_create_linear (MetaMonitorConfigManager *config_mana + + layout_mode = meta_monitor_manager_get_default_layout_mode (monitor_manager); + ++ if (layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ max_scale = get_preferred_preferred_max_scale (monitor_manager, ++ layout_mode, ++ MONITOR_MATCH_VISIBLE); ++ + primary_logical_monitor_config = + create_preferred_logical_monitor_config (monitor_manager, + primary_monitor, + 0, 0, ++ max_scale, + NULL, + layout_mode); + primary_logical_monitor_config->is_primary = TRUE; +@@ -786,6 +877,7 @@ meta_monitor_config_manager_create_linear (MetaMonitorConfigManager *config_mana + create_preferred_logical_monitor_config (monitor_manager, + monitor, + x, 0, ++ max_scale, + primary_logical_monitor_config, + layout_mode); + logical_monitor_configs = g_list_append (logical_monitor_configs, +@@ -813,6 +905,7 @@ meta_monitor_config_manager_create_fallback (MetaMonitorConfigManager *config_ma + GList *logical_monitor_configs; + MetaLogicalMonitorLayoutMode layout_mode; + MetaLogicalMonitorConfig *primary_logical_monitor_config; ++ float max_scale = 1.0f; + + primary_monitor = find_primary_monitor (monitor_manager); + if (!primary_monitor) +@@ -820,10 +913,16 @@ meta_monitor_config_manager_create_fallback (MetaMonitorConfigManager *config_ma + + layout_mode = meta_monitor_manager_get_default_layout_mode (monitor_manager); + ++ if (layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ max_scale = get_preferred_preferred_max_scale (monitor_manager, ++ layout_mode, ++ MONITOR_MATCH_PRIMARY); ++ + primary_logical_monitor_config = + create_preferred_logical_monitor_config (monitor_manager, + primary_monitor, + 0, 0, ++ max_scale, + NULL, + layout_mode); + primary_logical_monitor_config->is_primary = TRUE; +@@ -828,6 +828,7 @@ create_preferred_logical_monitor_config (MetaMonitorManager *monitor_m + MetaMonitor *monitor, + int x, + int y, ++ float max_scale, + MetaLogicalMonitorConfig *primary_logical_monitor_config, + MetaLogicalMonitorLayoutMode layout_mode) + { +@@ -835,6 +836,7 @@ create_preferred_logical_monitor_config (MetaMonitorManager *monitor_m + monitor, + meta_monitor_get_preferred_mode (monitor), + x, y, ++ max_scale, + primary_logical_monitor_config, + layout_mode); + } +@@ -846,6 +945,7 @@ meta_monitor_config_manager_create_suggested (MetaMonitorConfigManager *config_m + GList *logical_monitor_configs; + GList *region; + int x, y; ++ float max_scale = 1; + GList *monitors; + GList *l; + +@@ -849,6 +849,7 @@ create_logical_monitor_config_from_monitor (MetaMonitorManager *monito + { + MetaRectangle monitor_layout; + MetaMonitorMode *mode; ++ float max_scale = 1.0F; + + meta_monitor_derive_layout (monitor, &monitor_layout); + mode = meta_monitor_get_current_mode (monitor); +@@ -858,6 +859,7 @@ create_logical_monitor_config_from_monitor (MetaMonitorManager *monito + mode, + monitor_layout.x, + monitor_layout.y, ++ max_scale, + primary_logical_monitor_config, + layout_mode); + } +@@ -858,10 +958,16 @@ meta_monitor_config_manager_create_suggested (MetaMonitorConfigManager *config_m + + layout_mode = meta_monitor_manager_get_default_layout_mode (monitor_manager); + ++ if (layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ max_scale = get_preferred_preferred_max_scale (monitor_manager, ++ layout_mode, ++ MONITOR_MATCH_WITH_POSITION); ++ + primary_logical_monitor_config = + create_preferred_logical_monitor_config (monitor_manager, + primary_monitor, + x, y, ++ max_scale, + NULL, + layout_mode); + primary_logical_monitor_config->is_primary = TRUE; +@@ -885,6 +991,7 @@ meta_monitor_config_manager_create_suggested (MetaMonitorConfigManager *config_m + create_preferred_logical_monitor_config (monitor_manager, + monitor, + x, y, ++ max_scale, + primary_logical_monitor_config, + layout_mode); + logical_monitor_configs = g_list_append (logical_monitor_configs, +@@ -903,6 +1010,21 @@ meta_monitor_config_manager_create_suggested (MetaMonitorConfigManager *config_m + region = g_list_prepend (region, &logical_monitor_config->layout); + } + ++ for (l = region; region->next && l; l = l->next) ++ { ++ MetaRectangle *rect = l->data; ++ ++ if (!meta_rectangle_has_adjacent_in_region (region, rect)) ++ { ++ g_warning ("Suggested monitor config has monitors with no neighbors, " ++ "rejecting"); ++ g_list_free (region); ++ g_list_free_full (logical_monitor_configs, ++ (GDestroyNotify) meta_logical_monitor_config_free); ++ return NULL; ++ } ++ } ++ + g_list_free (region); + + if (!logical_monitor_configs) +@@ -1071,6 +1193,39 @@ meta_monitor_config_manager_create_for_rotate_monitor (MetaMonitorConfigManager + return create_for_builtin_display_rotation (config_manager, TRUE, META_MONITOR_TRANSFORM_NORMAL); + } + ++MetaMonitorsConfig * ++meta_monitor_config_manager_create_for_layout (MetaMonitorConfigManager *config_manager, ++ MetaMonitorsConfig *config, ++ MetaLogicalMonitorLayoutMode layout_mode) ++{ ++ MetaMonitorManager *monitor_manager = config_manager->monitor_manager; ++ GList *logical_monitor_configs; ++ GList *l; ++ ++ if (!config) ++ return NULL; ++ ++ if (config->layout_mode == layout_mode) ++ return g_object_ref (config); ++ ++ logical_monitor_configs = ++ clone_logical_monitor_config_list (config->logical_monitor_configs); ++ ++ if (layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL) ++ { ++ for (l = logical_monitor_configs; l; l = l->next) ++ { ++ MetaLogicalMonitorConfig *monitor_config = l->data; ++ monitor_config->scale = roundf (monitor_config->scale); ++ } ++ } ++ ++ return meta_monitors_config_new (monitor_manager, ++ logical_monitor_configs, ++ layout_mode, ++ META_MONITORS_CONFIG_FLAG_NONE); ++} ++ + static MetaMonitorsConfig * + create_for_switch_config_all_mirror (MetaMonitorConfigManager *config_manager) + { +@@ -1159,7 +1314,9 @@ create_for_switch_config_all_mirror (MetaMonitorConfigManager *config_manager) + if (!mode) + continue; + +- scale = meta_monitor_manager_calculate_monitor_mode_scale (monitor_manager, monitor, mode); ++ scale = meta_monitor_manager_calculate_monitor_mode_scale (monitor_manager, ++ monitor_manager->layout_mode, ++ monitor, mode); + best_scale = MAX (best_scale, scale); + monitor_configs = g_list_prepend (monitor_configs, create_monitor_config (monitor, mode)); + } +@@ -1195,6 +1352,7 @@ create_for_switch_config_external (MetaMonitorConfigManager *config_manager) + MetaMonitorManager *monitor_manager = config_manager->monitor_manager; + GList *logical_monitor_configs = NULL; + int x = 0; ++ float max_scale = 1.0f; + MetaLogicalMonitorLayoutMode layout_mode; + GList *monitors; + GList *l; +@@ -1202,6 +1360,11 @@ create_for_switch_config_external (MetaMonitorConfigManager *config_manager) + + layout_mode = meta_monitor_manager_get_default_layout_mode (monitor_manager); + ++ if (layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ max_scale = get_preferred_preferred_max_scale (monitor_manager, ++ layout_mode, ++ MONITOR_MATCH_EXTERNAL); ++ + monitors = meta_monitor_manager_get_monitors (monitor_manager); + for (l = monitors; l; l = l->next) + { +@@ -1215,6 +1378,7 @@ create_for_switch_config_external (MetaMonitorConfigManager *config_manager) + create_preferred_logical_monitor_config (monitor_manager, + monitor, + x, 0, ++ max_scale, + NULL, + layout_mode); + logical_monitor_configs = g_list_append (logical_monitor_configs, +@@ -1249,6 +1413,7 @@ create_for_switch_config_builtin (MetaMonitorConfigManager *config_manager) + MetaLogicalMonitorConfig *primary_logical_monitor_config; + MetaMonitor *monitor; + MetaMonitorsConfig *monitors_config; ++ float max_scale = 1.0f; + + monitor = meta_monitor_manager_get_laptop_panel (monitor_manager); + if (!monitor) +@@ -1256,10 +1421,16 @@ create_for_switch_config_builtin (MetaMonitorConfigManager *config_manager) + + layout_mode = meta_monitor_manager_get_default_layout_mode (monitor_manager); + ++ if (layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ max_scale = get_preferred_preferred_max_scale (monitor_manager, ++ layout_mode, ++ MONITOR_MATCH_BUILTIN); ++ + primary_logical_monitor_config = + create_preferred_logical_monitor_config (monitor_manager, + monitor, + 0, 0, ++ max_scale, + NULL, + layout_mode); + primary_logical_monitor_config->is_primary = TRUE; +@@ -1666,6 +1837,7 @@ gboolean + meta_verify_logical_monitor_config (MetaLogicalMonitorConfig *logical_monitor_config, + MetaLogicalMonitorLayoutMode layout_mode, + MetaMonitorManager *monitor_manager, ++ float max_scale, + GError **error) + { + GList *l; +@@ -1702,6 +1874,10 @@ meta_verify_logical_monitor_config (MetaLogicalMonitorConfig *logical_monitor + + switch (layout_mode) + { ++ case META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL: ++ expected_mode_width /= ceilf (max_scale); ++ expected_mode_height /= ceilf (max_scale); ++ /* fall through! */ + case META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL: + expected_mode_width = roundf (expected_mode_width * + logical_monitor_config->scale); +diff --git a/src/backends/meta-monitor-config-manager.h b/src/backends/meta-monitor-config-manager.h +index 86756a7..8d9fc6c 100644 +--- a/src/backends/meta-monitor-config-manager.h ++++ b/src/backends/meta-monitor-config-manager.h +@@ -110,6 +110,10 @@ MetaMonitorsConfig * meta_monitor_config_manager_create_for_orientation (MetaMon + META_EXPORT_TEST + MetaMonitorsConfig * meta_monitor_config_manager_create_for_rotate_monitor (MetaMonitorConfigManager *config_manager); + ++MetaMonitorsConfig * meta_monitor_config_manager_create_for_layout (MetaMonitorConfigManager *config_manager, ++ MetaMonitorsConfig *config, ++ MetaLogicalMonitorLayoutMode layout_mode); ++ + META_EXPORT_TEST + MetaMonitorsConfig * meta_monitor_config_manager_create_for_switch_config (MetaMonitorConfigManager *config_manager, + MetaMonitorSwitchConfigType config_type); +@@ -191,6 +195,7 @@ META_EXPORT_TEST + gboolean meta_verify_logical_monitor_config (MetaLogicalMonitorConfig *logical_monitor_config, + MetaLogicalMonitorLayoutMode layout_mode, + MetaMonitorManager *monitor_manager, ++ float max_scale, + GError **error); + + META_EXPORT_TEST +diff --git a/src/backends/meta-monitor-config-migration.c b/src/backends/meta-monitor-config-migration.c +index d619dc4..69c426c 100644 +--- a/src/backends/meta-monitor-config-migration.c ++++ b/src/backends/meta-monitor-config-migration.c +@@ -1190,6 +1190,9 @@ meta_finish_monitors_config_migration (MetaMonitorManager *monitor_manager, + MetaMonitorConfigStore *config_store = + meta_monitor_config_manager_get_store (config_manager); + GList *l; ++ MetaLogicalMonitorLayoutMode layout_mode; ++ ++ layout_mode = meta_monitor_manager_get_default_layout_mode (monitor_manager); + + for (l = config->logical_monitor_configs; l; l = l->next) + { +@@ -1199,7 +1202,6 @@ meta_finish_monitors_config_migration (MetaMonitorManager *monitor_manager, + MetaMonitor *monitor; + MetaMonitorModeSpec *monitor_mode_spec; + MetaMonitorMode *monitor_mode; +- float scale; + + monitor_config = logical_monitor_config->monitor_configs->data; + monitor_spec = monitor_config->monitor_spec; +@@ -1215,13 +1217,14 @@ meta_finish_monitors_config_migration (MetaMonitorManager *monitor_manager, + return FALSE; + } + +- scale = meta_monitor_calculate_mode_scale (monitor, monitor_mode); +- +- logical_monitor_config->scale = scale; ++ logical_monitor_config->scale = ++ meta_monitor_manager_calculate_monitor_mode_scale (monitor_manager, ++ layout_mode, ++ monitor, ++ monitor_mode); + } + +- config->layout_mode = +- meta_monitor_manager_get_default_layout_mode (monitor_manager); ++ config->layout_mode = layout_mode; + config->flags &= ~META_MONITORS_CONFIG_FLAG_MIGRATED; + + if (!meta_verify_monitors_config (config, monitor_manager, error)) +diff --git a/src/backends/meta-monitor-config-store.c b/src/backends/meta-monitor-config-store.c +index 4dd357a..326723e 100644 +--- a/src/backends/meta-monitor-config-store.c ++++ b/src/backends/meta-monitor-config-store.c +@@ -496,6 +496,7 @@ handle_start_element (GMarkupParseContext *context, + static gboolean + derive_logical_monitor_layout (MetaLogicalMonitorConfig *logical_monitor_config, + MetaLogicalMonitorLayoutMode layout_mode, ++ float max_scale, + GError **error) + { + MetaMonitorConfig *monitor_config; +@@ -533,6 +534,10 @@ derive_logical_monitor_layout (MetaLogicalMonitorConfig *logical_monitor_conf + + switch (layout_mode) + { ++ case META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL: ++ width *= ceilf (max_scale); ++ height *= ceilf (max_scale); ++ /* fall through! */ + case META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL: + width = roundf (width / logical_monitor_config->scale); + height = roundf (height / logical_monitor_config->scale); +@@ -740,6 +745,7 @@ handle_end_element (GMarkupParseContext *context, + GList *l; + MetaLogicalMonitorLayoutMode layout_mode; + MetaMonitorsConfigFlag config_flags = META_MONITORS_CONFIG_FLAG_NONE; ++ float max_scale = 1.0f; + + g_assert (g_str_equal (element_name, "configuration")); + +@@ -749,18 +755,29 @@ handle_end_element (GMarkupParseContext *context, + layout_mode = + meta_monitor_manager_get_default_layout_mode (store->monitor_manager); + ++ if (layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ { ++ for (l = parser->current_logical_monitor_configs; l; l = l->next) ++ { ++ MetaLogicalMonitorConfig *logical_monitor_config = l->data; ++ max_scale = MAX (max_scale, logical_monitor_config->scale); ++ } ++ } ++ + for (l = parser->current_logical_monitor_configs; l; l = l->next) + { + MetaLogicalMonitorConfig *logical_monitor_config = l->data; + + if (!derive_logical_monitor_layout (logical_monitor_config, + layout_mode, ++ max_scale, + error)) + return; + + if (!meta_verify_logical_monitor_config (logical_monitor_config, + layout_mode, + store->monitor_manager, ++ max_scale, + error)) + return; + } +diff --git a/src/backends/meta-monitor-manager-dummy.c b/src/backends/meta-monitor-manager-dummy.c +index d08fb02..32f3b92 100644 +--- a/src/backends/meta-monitor-manager-dummy.c ++++ b/src/backends/meta-monitor-manager-dummy.c +@@ -372,6 +372,15 @@ append_tiled_monitor (MetaMonitorManager *manager, + } + } + ++static gboolean ++has_tiled_monitors (void) ++{ ++ const char *tiled_monitors_str; ++ ++ tiled_monitors_str = g_getenv ("MUTTER_DEBUG_TILED_DUMMY_MONITORS"); ++ return g_strcmp0 (tiled_monitors_str, "1") == 0; ++} ++ + static void + meta_monitor_manager_dummy_read_current (MetaMonitorManager *manager) + { +@@ -380,7 +389,6 @@ meta_monitor_manager_dummy_read_current (MetaMonitorManager *manager) + float *monitor_scales = NULL; + const char *num_monitors_str; + const char *monitor_scales_str; +- const char *tiled_monitors_str; + gboolean tiled_monitors; + unsigned int i; + GList *outputs; +@@ -458,8 +466,7 @@ meta_monitor_manager_dummy_read_current (MetaMonitorManager *manager) + g_strfreev (scales_str_list); + } + +- tiled_monitors_str = g_getenv ("MUTTER_DEBUG_TILED_DUMMY_MONITORS"); +- tiled_monitors = g_strcmp0 (tiled_monitors_str, "1") == 0; ++ tiled_monitors = has_tiled_monitors (); + + modes = NULL; + crtcs = NULL; +@@ -638,9 +645,10 @@ meta_monitor_manager_dummy_is_transform_handled (MetaMonitorManager *manager, + } + + static float +-meta_monitor_manager_dummy_calculate_monitor_mode_scale (MetaMonitorManager *manager, +- MetaMonitor *monitor, +- MetaMonitorMode *monitor_mode) ++meta_monitor_manager_dummy_calculate_monitor_mode_scale (MetaMonitorManager *manager, ++ MetaLogicalMonitorLayoutMode layout_mode, ++ MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode) + { + MetaOutput *output; + MetaOutputDummy *output_dummy; +@@ -664,6 +672,7 @@ meta_monitor_manager_dummy_calculate_supported_scales (MetaMonitorManager + switch (layout_mode) + { + case META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL: ++ case META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL: + break; + case META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL: + constraints |= META_MONITOR_SCALES_CONSTRAINT_NO_FRAC; +@@ -694,6 +703,9 @@ meta_monitor_manager_dummy_get_capabilities (MetaMonitorManager *manager) + MetaMonitorManagerCapability capabilities = + META_MONITOR_MANAGER_CAPABILITY_NONE; + ++ if (has_tiled_monitors ()) ++ capabilities |= META_MONITOR_MANAGER_CAPABILITY_TILING; ++ + if (meta_settings_is_experimental_feature_enabled ( + settings, + META_EXPERIMENTAL_FEATURE_SCALE_MONITOR_FRAMEBUFFER)) +diff --git a/src/backends/meta-monitor-manager-private.h b/src/backends/meta-monitor-manager-private.h +index 60c1e90..4cc666d 100644 +--- a/src/backends/meta-monitor-manager-private.h ++++ b/src/backends/meta-monitor-manager-private.h +@@ -45,7 +45,8 @@ typedef enum _MetaMonitorManagerCapability + META_MONITOR_MANAGER_CAPABILITY_NONE = 0, + META_MONITOR_MANAGER_CAPABILITY_LAYOUT_MODE = (1 << 0), + META_MONITOR_MANAGER_CAPABILITY_GLOBAL_SCALE_REQUIRED = (1 << 1), +- META_MONITOR_MANAGER_CAPABILITY_CAN_DERIVE_CURRENT = (1 << 2), ++ META_MONITOR_MANAGER_CAPABILITY_TILING = (1 << 2), ++ META_MONITOR_MANAGER_CAPABILITY_NATIVE_OUTPUT_SCALING = (1 << 3), + } MetaMonitorManagerCapability; + + /* Equivalent to the 'method' enum in org.gnome.Mutter.DisplayConfig */ +@@ -59,7 +61,8 @@ typedef enum _MetaMonitorsConfigMethod + typedef enum _MetaLogicalMonitorLayoutMode + { + META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL = 1, +- META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL = 2 ++ META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL = 2, ++ META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL = 3 + } MetaLogicalMonitorLayoutMode; + + /* +@@ -73,6 +76,7 @@ struct _MetaCrtcAssignment + MetaCrtc *crtc; + MetaCrtcMode *mode; + graphene_rect_t layout; ++ float scale; + MetaMonitorTransform transform; + GPtrArray *outputs; + }; +@@ -134,6 +138,7 @@ struct _MetaMonitorManager + int screen_height; + + GList *monitors; ++ GList *scale_override_monitors; + + GList *logical_monitors; + MetaLogicalMonitor *primary_logical_monitor; +@@ -165,6 +170,9 @@ struct _MetaMonitorManager + * @apply_monitors_config: Tries to apply the given config using the given + * method. Throws an error if something went wrong. + * ++ * @update_screen_size_derived: Computes the screen size for derived ++ * configuration. ++ * + * @set_power_save_mode: Sets the #MetaPowerSave mode (for all displays). + * + * @change_backlight: Changes the backlight intensity to the given value (in +@@ -231,6 +239,9 @@ struct _MetaMonitorManagerClass + unsigned short *green, + unsigned short *blue); + ++ void (*update_screen_size_derived) (MetaMonitorManager *, ++ MetaMonitorsConfig *); ++ + void (* tiled_monitor_added) (MetaMonitorManager *manager, + MetaMonitor *monitor); + +@@ -241,9 +252,10 @@ struct _MetaMonitorManagerClass + MetaCrtc *crtc, + MetaMonitorTransform transform); + +- float (* calculate_monitor_mode_scale) (MetaMonitorManager *manager, +- MetaMonitor *monitor, +- MetaMonitorMode *monitor_mode); ++ float (* calculate_monitor_mode_scale) (MetaMonitorManager *, ++ MetaLogicalMonitorLayoutMode , ++ MetaMonitor *, ++ MetaMonitorMode *); + + float * (* calculate_supported_scales) (MetaMonitorManager *manager, + MetaLogicalMonitorLayoutMode layout_mode, +@@ -366,9 +378,10 @@ void meta_monitor_manager_lid_is_closed_changed (MetaMonitorManage + + gboolean meta_monitor_manager_is_headless (MetaMonitorManager *manager); + +-float meta_monitor_manager_calculate_monitor_mode_scale (MetaMonitorManager *manager, +- MetaMonitor *monitor, +- MetaMonitorMode *monitor_mode); ++float meta_monitor_manager_calculate_monitor_mode_scale (MetaMonitorManager *manager, ++ MetaLogicalMonitorLayoutMode layout_mode, ++ MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode); + + float * meta_monitor_manager_calculate_supported_scales (MetaMonitorManager *, + MetaLogicalMonitorLayoutMode , +@@ -382,6 +395,11 @@ gboolean meta_monitor_manager_is_scale_supported (MetaMonitorManager + MetaMonitorMode *monitor_mode, + float scale); + ++float meta_monitor_manager_get_maximum_crtc_scale (MetaMonitorManager *manager); ++ ++gboolean meta_monitor_manager_disable_scale_for_monitor (MetaMonitorManager *manager, ++ MetaLogicalMonitor *monitor); ++ + MetaMonitorManagerCapability + meta_monitor_manager_get_capabilities (MetaMonitorManager *manager); + +diff --git a/src/backends/meta-monitor-manager.c b/src/backends/meta-monitor-manager.c +index a75da93..6cf4aad 100644 +--- a/src/backends/meta-monitor-manager.c ++++ b/src/backends/meta-monitor-manager.c +@@ -120,8 +120,18 @@ static gboolean + meta_monitor_manager_is_config_complete (MetaMonitorManager *manager, + MetaMonitorsConfig *config); + +-static MetaMonitor * +-meta_monitor_manager_get_active_monitor (MetaMonitorManager *manager); ++static gboolean ++is_global_scale_matching_in_config (MetaMonitorsConfig *config, ++ float scale); ++ ++static gboolean ++meta_monitor_manager_is_scale_supported_with_threshold (MetaMonitorManager *manager, ++ MetaLogicalMonitorLayoutMode layout_mode, ++ MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode, ++ float scale, ++ float threshold, ++ float *out_scale); + + static void + meta_monitor_manager_real_read_current_state (MetaMonitorManager *manager); +@@ -212,15 +222,45 @@ meta_monitor_manager_rebuild_logical_monitors (MetaMonitorManager *manager, + primary_logical_monitor); + } + ++float ++meta_monitor_manager_get_maximum_crtc_scale (MetaMonitorManager *manager) ++{ ++ GList *l; ++ float scale; ++ ++ scale = 1.0f; ++ for (l = manager->monitors; l != NULL; l = l->next) ++ { ++ MetaMonitor *monitor = l->data; ++ MetaOutput *output = meta_monitor_get_main_output (monitor); ++ MetaCrtc *crtc = meta_output_get_assigned_crtc (output); ++ ++ if (crtc) ++ { ++ const MetaCrtcConfig *crtc_config = meta_crtc_get_config (crtc); ++ ++ scale = MAX (scale, crtc_config ? crtc_config->scale : 1.0f); ++ } ++ } ++ ++ return scale; ++} ++ + static float + derive_configured_global_scale (MetaMonitorManager *manager, + MetaMonitorsConfig *config) + { +- MetaLogicalMonitorConfig *logical_monitor_config; ++ GList *l; ++ ++ for (l = config->logical_monitor_configs; l; l = l->next) ++ { ++ MetaLogicalMonitorConfig *monitor_config = l->data; + +- logical_monitor_config = config->logical_monitor_configs->data; ++ if (is_global_scale_matching_in_config (config, monitor_config->scale)) ++ return monitor_config->scale; ++ } + +- return logical_monitor_config->scale; ++ return 1.0f; + } + + static float +@@ -231,24 +271,70 @@ calculate_monitor_scale (MetaMonitorManager *manager, + + monitor_mode = meta_monitor_get_current_mode (monitor); + return meta_monitor_manager_calculate_monitor_mode_scale (manager, ++ manager->layout_mode, + monitor, + monitor_mode); + } + ++static gboolean ++meta_monitor_manager_is_scale_supported_by_other_monitors (MetaMonitorManager *manager, ++ MetaMonitor *not_this_one, ++ float scale) ++{ ++ GList *l; ++ ++ for (l = manager->monitors; l; l = l->next) ++ { ++ MetaMonitor *monitor = l->data; ++ MetaMonitorMode *mode; ++ ++ if (monitor == not_this_one || !meta_monitor_is_active (monitor)) ++ continue; ++ ++ mode = meta_monitor_get_current_mode (monitor); ++ if (!meta_monitor_manager_is_scale_supported (manager, manager->layout_mode, ++ monitor, mode, scale)) ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ + static float + derive_calculated_global_scale (MetaMonitorManager *manager) + { + MetaMonitor *monitor = NULL; ++ float scale; ++ GList *l; + ++ scale = 1.0f; + monitor = meta_monitor_manager_get_primary_monitor (manager); + +- if (!monitor || !meta_monitor_is_active (monitor)) +- monitor = meta_monitor_manager_get_active_monitor (manager); ++ if (monitor && meta_monitor_is_active (monitor)) ++ { ++ scale = calculate_monitor_scale (manager, monitor); ++ if (meta_monitor_manager_is_scale_supported_by_other_monitors (manager, ++ monitor, ++ scale)) ++ return scale; ++ } + +- if (!monitor) +- return 1.0; ++ for (l = manager->monitors; l; l = l->next) ++ { ++ MetaMonitor *other_monitor = l->data; ++ float monitor_scale; ++ ++ if (other_monitor == monitor || !meta_monitor_is_active (other_monitor)) ++ continue; + +- return calculate_monitor_scale (manager, monitor); ++ monitor_scale = calculate_monitor_scale (manager, other_monitor); ++ if (meta_monitor_manager_is_scale_supported_by_other_monitors (manager, ++ other_monitor, ++ monitor_scale)) ++ scale = MAX (scale, monitor_scale); ++ } ++ ++ return scale; + } + + static float +@@ -270,6 +356,51 @@ derive_scale_from_config (MetaMonitorManager *manager, + return 1.0; + } + ++static gboolean ++derive_scale_from_crtc (MetaMonitorManager *manager, ++ MetaMonitor *monitor, ++ float *out_scale) ++{ ++ MetaMonitorManagerCapability capabilities; ++ MetaMonitorMode *monitor_mode; ++ float threshold; ++ MetaOutput *output; ++ MetaCrtc *crtc; ++ float scale; ++ ++ capabilities = meta_monitor_manager_get_capabilities (manager); ++ ++ if (!(capabilities & META_MONITOR_MANAGER_CAPABILITY_NATIVE_OUTPUT_SCALING)) ++ return FALSE; ++ ++ if (!(capabilities & META_MONITOR_MANAGER_CAPABILITY_LAYOUT_MODE)) ++ return FALSE; ++ ++ output = meta_monitor_get_main_output (monitor); ++ crtc = meta_output_get_assigned_crtc (output); ++ ++ if (!crtc) ++ return FALSE; ++ ++ /* Due to integer and possibly inverse scaling applied to the output the ++ * result could not match exactly, so we apply a more relaxed threshold ++ * in this case. */ ++ threshold = 0.001f; ++ ++ scale = meta_crtc_get_config_scale (crtc); ++ monitor_mode = meta_monitor_get_current_mode (monitor); ++ if (meta_monitor_manager_is_scale_supported_with_threshold (manager, ++ manager->layout_mode, ++ monitor, ++ monitor_mode, ++ scale, ++ threshold, ++ out_scale)) ++ return TRUE; ++ ++ return FALSE; ++} ++ + static void + meta_monitor_manager_rebuild_logical_monitors_derived (MetaMonitorManager *manager, + MetaMonitorsConfig *config) +@@ -317,11 +448,17 @@ meta_monitor_manager_rebuild_logical_monitors_derived (MetaMonitorManager *manag + float scale; + + if (use_global_scale) +- scale = global_scale; +- else if (config) +- scale = derive_scale_from_config (manager, config, &layout); ++ scale = roundf (global_scale); + else +- scale = calculate_monitor_scale (manager, monitor); ++ { ++ if (!derive_scale_from_crtc (manager, monitor, &scale)) ++ { ++ if (config) ++ scale = derive_scale_from_config (manager, config, &layout); ++ else ++ scale = calculate_monitor_scale (manager, monitor); ++ } ++ } + + g_assert (scale > 0); + +@@ -433,16 +570,24 @@ meta_monitor_manager_is_headless (MetaMonitorManager *manager) + } + + float +-meta_monitor_manager_calculate_monitor_mode_scale (MetaMonitorManager *manager, +- MetaMonitor *monitor, +- MetaMonitorMode *monitor_mode) ++meta_monitor_manager_calculate_monitor_mode_scale (MetaMonitorManager *manager, ++ MetaLogicalMonitorLayoutMode layout_mode, ++ MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode) + { ++ float scale; + MetaMonitorManagerClass *manager_class = + META_MONITOR_MANAGER_GET_CLASS (manager); + +- return manager_class->calculate_monitor_mode_scale (manager, +- monitor, +- monitor_mode); ++ scale = manager_class->calculate_monitor_mode_scale (manager, ++ layout_mode, ++ monitor, ++ monitor_mode); ++ ++ if (g_list_find (manager->scale_override_monitors, monitor)) ++ return ceilf (scale); ++ ++ return scale; + } + + float * +@@ -622,6 +768,8 @@ meta_monitor_manager_ensure_configured (MetaMonitorManager *manager) + MetaMonitorsConfigMethod method; + MetaMonitorsConfigMethod fallback_method = + META_MONITORS_CONFIG_METHOD_TEMPORARY; ++ MetaLogicalMonitorLayoutMode layout_mode = ++ meta_monitor_manager_get_default_layout_mode (manager); + + use_stored_config = should_use_stored_config (manager); + if (use_stored_config) +@@ -631,7 +779,18 @@ meta_monitor_manager_ensure_configured (MetaMonitorManager *manager) + + if (use_stored_config) + { ++ g_autoptr(MetaMonitorsConfig) new_config = NULL; ++ + config = meta_monitor_config_manager_get_stored (manager->config_manager); ++ if (config && config->layout_mode != layout_mode) ++ { ++ new_config = ++ meta_monitor_config_manager_create_for_layout (manager->config_manager, ++ config, ++ layout_mode); ++ config = new_config; ++ } ++ + if (config) + { + if (!meta_monitor_manager_apply_monitors_config (manager, +@@ -676,6 +835,16 @@ meta_monitor_manager_ensure_configured (MetaMonitorManager *manager) + { + config = g_object_ref (config); + ++ if (config && config->layout_mode != layout_mode) ++ { ++ MetaMonitorsConfig *new_config = ++ meta_monitor_config_manager_create_for_layout (manager->config_manager, ++ config, ++ layout_mode); ++ g_object_unref (config); ++ config = new_config; ++ } ++ + if (meta_monitor_manager_is_config_complete (manager, config)) + { + if (!meta_monitor_manager_apply_monitors_config (manager, +@@ -755,7 +755,9 @@ meta_monitor_manager_has_hotplug_mode_update (MetaMonitorManager *manager) + static gboolean + should_use_stored_config (MetaMonitorManager *manager) + { +- return !meta_monitor_manager_has_hotplug_mode_update (manager); ++ return (manager->in_init || ++ (!manager->scale_override_monitors && ++ !meta_monitor_manager_has_hotplug_mode_update (manager))); + } + + static gboolean +@@ -764,7 +766,7 @@ can_derive_current_config (MetaMonitorManager *manager) + MetaMonitorManagerCapability capabilities; + + capabilities = meta_monitor_manager_get_capabilities (manager); +- return !!(capabilities & META_MONITOR_MANAGER_CAPABILITY_CAN_DERIVE_CURRENT); ++ return !!(capabilities & META_MONITOR_MANAGER_CAPABILITY_LAYOUT_MODE); + } + + MetaMonitorsConfig * +@@ -859,6 +1028,66 @@ orientation_changed (MetaOrientationManager *orientation_manager, + handle_orientation_change (orientation_manager, manager); + } + ++static gboolean ++apply_x11_fractional_scaling_config (MetaMonitorManager *manager) ++{ ++ g_autoptr(GError) error = NULL; ++ g_autoptr(MetaMonitorsConfig) config = NULL; ++ MetaMonitorsConfig *applied_config; ++ MetaLogicalMonitorLayoutMode layout_mode = ++ meta_monitor_manager_get_default_layout_mode (manager); ++ ++ if (!META_IS_MONITOR_MANAGER_XRANDR (manager)) ++ return TRUE; ++ ++ applied_config = ++ meta_monitor_config_manager_get_current (manager->config_manager); ++ config = ++ meta_monitor_config_manager_create_for_layout (manager->config_manager, ++ applied_config, ++ layout_mode); ++ if (!config) ++ return FALSE; ++ ++ if (meta_monitor_manager_apply_monitors_config (manager, ++ config, ++ META_MONITORS_CONFIG_METHOD_PERSISTENT, ++ &error)) ++ { ++ if (config != applied_config && manager->persistent_timeout_id) ++ { ++ if (G_UNLIKELY (applied_config != ++ meta_monitor_config_manager_get_previous (manager->config_manager))) ++ { ++ g_warning ("The removed configuration doesn't match the " ++ "previously applied one, reverting may not work"); ++ } ++ else ++ { ++ g_autoptr(MetaMonitorsConfig) previous_config = NULL; ++ ++ /* The previous config we applied was just a temporary one that ++ * GNOME control center passed us while toggling the fractional ++ * scaling. So, in such case, once the configuration with the ++ * correct layout has been applied, we need to ignore the ++ * temporary one. */ ++ previous_config = ++ meta_monitor_config_manager_pop_previous (manager->config_manager); ++ ++ g_assert_true (applied_config == previous_config); ++ } ++ } ++ } ++ else ++ { ++ g_warning ("Impossible to apply the layout config %s\n", ++ error->message); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ + static void + experimental_features_changed (MetaSettings *settings, + MetaExperimentalFeature old_experimental_features, +@@ -866,6 +1095,8 @@ experimental_features_changed (MetaSettings *settings, + { + gboolean was_stage_views_scaled; + gboolean is_stage_views_scaled; ++ gboolean was_x11_scaling; ++ gboolean x11_scaling; + gboolean should_reconfigure = FALSE; + + was_stage_views_scaled = +@@ -875,10 +1106,23 @@ experimental_features_changed (MetaSettings *settings, + meta_settings_is_experimental_feature_enabled ( + settings, + META_EXPERIMENTAL_FEATURE_SCALE_MONITOR_FRAMEBUFFER); ++ was_x11_scaling = ++ !!(old_experimental_features & ++ META_EXPERIMENTAL_FEATURE_X11_RANDR_FRACTIONAL_SCALING); ++ x11_scaling = ++ meta_settings_is_experimental_feature_enabled ( ++ settings, ++ META_EXPERIMENTAL_FEATURE_X11_RANDR_FRACTIONAL_SCALING); + + if (is_stage_views_scaled != was_stage_views_scaled) + should_reconfigure = TRUE; + ++ if (was_x11_scaling != x11_scaling) ++ { ++ if (!apply_x11_fractional_scaling_config (manager)) ++ should_reconfigure = TRUE; ++ } ++ + if (should_reconfigure) + meta_monitor_manager_reconfigure (manager); + +@@ -1421,6 +1665,33 @@ meta_monitor_manager_handle_get_resources (MetaDBusDisplayConfig *skeleton, + return TRUE; + } + ++static void ++restore_previous_experimental_config (MetaMonitorManager *manager, ++ MetaMonitorsConfig *previous_config) ++{ ++ MetaBackend *backend = manager->backend; ++ MetaSettings *settings = meta_backend_get_settings (backend); ++ gboolean was_fractional; ++ ++ if (!META_IS_MONITOR_MANAGER_XRANDR (manager)) ++ return; ++ ++ was_fractional = ++ previous_config->layout_mode != META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL; ++ ++ if (meta_settings_is_experimental_feature_enabled (settings, ++ META_EXPERIMENTAL_FEATURE_X11_RANDR_FRACTIONAL_SCALING) == was_fractional) ++ return; ++ ++ g_signal_handler_block (settings, ++ manager->experimental_features_changed_handler_id); ++ ++ meta_settings_enable_x11_fractional_scaling (settings, was_fractional); ++ ++ g_signal_handler_unblock (settings, ++ manager->experimental_features_changed_handler_id); ++} ++ + static void + restore_previous_config (MetaMonitorManager *manager) + { +@@ -1434,6 +1705,8 @@ restore_previous_config (MetaMonitorManager *manager) + { + MetaMonitorsConfigMethod method; + ++ restore_previous_experimental_config (manager, previous_config); ++ + method = META_MONITORS_CONFIG_METHOD_TEMPORARY; + if (meta_monitor_manager_apply_monitors_config (manager, + previous_config, +@@ -1490,6 +1763,41 @@ request_persistent_confirmation (MetaMonitorManager *manager) + g_signal_emit (manager, signals[CONFIRM_DISPLAY_CHANGE], 0); + } + ++gboolean ++meta_monitor_manager_disable_scale_for_monitor (MetaMonitorManager *manager, ++ MetaLogicalMonitor *monitor) ++{ ++ switch (manager->layout_mode) ++ { ++ case META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL: ++ case META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL: ++ break; ++ default: ++ return FALSE; ++ } ++ ++ if (monitor && fmodf (monitor->scale, 1.0) != 0.0f) ++ { ++ if (manager->scale_override_monitors) ++ { ++ g_clear_pointer (&manager->scale_override_monitors, g_list_free); ++ g_object_unref (meta_monitor_config_manager_pop_previous (manager->config_manager)); ++ } ++ ++ manager->scale_override_monitors = g_list_copy (monitor->monitors); ++ meta_monitor_manager_ensure_configured (manager); ++ return TRUE; ++ } ++ ++ if (manager->scale_override_monitors) ++ { ++ g_clear_pointer (&manager->scale_override_monitors, g_list_free); ++ restore_previous_config (manager); ++ } ++ ++ return FALSE; ++} ++ + #define META_DISPLAY_CONFIG_MODE_FLAGS_PREFERRED (1 << 0) + #define META_DISPLAY_CONFIG_MODE_FLAGS_CURRENT (1 << 1) + +@@ -1517,6 +1825,7 @@ meta_monitor_manager_handle_get_current_state (MetaDBusDisplayConfig *skeleton, + MetaMonitorManagerCapability capabilities; + int ui_scaling_factor; + int max_screen_width, max_screen_height; ++ char *renderer; + + g_variant_builder_init (&monitors_builder, + G_VARIANT_TYPE (MONITORS_FORMAT)); +@@ -1563,6 +1872,7 @@ meta_monitor_manager_handle_get_current_state (MetaDBusDisplayConfig *skeleton, + + preferred_scale = + meta_monitor_manager_calculate_monitor_mode_scale (manager, ++ manager->layout_mode, + monitor, + monitor_mode); + +@@ -1670,6 +1980,14 @@ meta_monitor_manager_handle_get_current_state (MetaDBusDisplayConfig *skeleton, + } + + g_variant_builder_init (&properties_builder, G_VARIANT_TYPE ("a{sv}")); ++ ++ renderer = g_ascii_strdown (G_OBJECT_TYPE_NAME (manager) + ++ strlen (g_type_name (g_type_parent (G_OBJECT_TYPE (manager)))), ++ -1); ++ g_variant_builder_add (&properties_builder, "{sv}", ++ "renderer", ++ g_variant_new_take_string (renderer)); ++ + capabilities = meta_monitor_manager_get_capabilities (manager); + + g_variant_builder_add (&properties_builder, "{sv}", +@@ -1688,6 +2006,14 @@ meta_monitor_manager_handle_get_current_state (MetaDBusDisplayConfig *skeleton, + "global-scale-required", + g_variant_new_boolean (TRUE)); + } ++ else if (META_IS_MONITOR_MANAGER_XRANDR (manager) && ++ (capabilities & META_MONITOR_MANAGER_CAPABILITY_NATIVE_OUTPUT_SCALING) && ++ (capabilities & META_MONITOR_MANAGER_CAPABILITY_LAYOUT_MODE)) ++ { ++ g_variant_builder_add (&properties_builder, "{sv}", ++ "x11-fractional-scaling", ++ g_variant_new_boolean (TRUE)); ++ } + + ui_scaling_factor = meta_settings_get_ui_scaling_factor (settings); + g_variant_builder_add (&properties_builder, "{sv}", +@@ -1732,12 +2058,14 @@ meta_monitor_manager_handle_get_current_state (MetaDBusDisplayConfig *skeleton, + #undef LOGICAL_MONITOR_FORMAT + #undef LOGICAL_MONITORS_FORMAT + +-gboolean +-meta_monitor_manager_is_scale_supported (MetaMonitorManager *manager, +- MetaLogicalMonitorLayoutMode layout_mode, +- MetaMonitor *monitor, +- MetaMonitorMode *monitor_mode, +- float scale) ++static gboolean ++meta_monitor_manager_is_scale_supported_with_threshold (MetaMonitorManager *manager, ++ MetaLogicalMonitorLayoutMode layout_mode, ++ MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode, ++ float scale, ++ float threshold, ++ float *out_scale) + { + g_autofree float *supported_scales = NULL; + int n_supported_scales; +@@ -1751,8 +2079,66 @@ meta_monitor_manager_is_scale_supported (MetaMonitorManager *manager, + &n_supported_scales); + for (i = 0; i < n_supported_scales; i++) + { +- if (supported_scales[i] == scale) +- return TRUE; ++ if (fabs (supported_scales[i] - scale) < threshold) ++ { ++ if (out_scale) ++ *out_scale = supported_scales[i]; ++ ++ return TRUE; ++ } ++ } ++ ++ return FALSE; ++} ++ ++gboolean ++meta_monitor_manager_is_scale_supported (MetaMonitorManager *manager, ++ MetaLogicalMonitorLayoutMode layout_mode, ++ MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode, ++ float scale) ++{ ++ return meta_monitor_manager_is_scale_supported_with_threshold (manager, ++ layout_mode, ++ monitor, ++ monitor_mode, ++ scale, ++ FLT_EPSILON, ++ NULL); ++} ++ ++static gboolean ++is_global_scale_matching_in_config (MetaMonitorsConfig *config, ++ float scale) ++{ ++ GList *l; ++ ++ for (l = config->logical_monitor_configs; l; l = l->next) ++ { ++ MetaLogicalMonitorConfig *logical_monitor_config = l->data; ++ ++ if (fabs (logical_monitor_config->scale - scale) > FLT_EPSILON) ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static gboolean ++meta_monitor_manager_is_scale_supported_for_config (MetaMonitorManager *manager, ++ MetaMonitorsConfig *config, ++ MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode, ++ float scale) ++{ ++ if (meta_monitor_manager_is_scale_supported (manager, config->layout_mode, ++ monitor, monitor_mode, scale)) ++ { ++ if (meta_monitor_manager_get_capabilities (manager) & ++ META_MONITOR_MANAGER_CAPABILITY_GLOBAL_SCALE_REQUIRED) ++ return is_global_scale_matching_in_config (config, scale); ++ ++ return TRUE; + } + + return FALSE; +@@ -1796,11 +2182,11 @@ meta_monitor_manager_is_config_applicable (MetaMonitorManager *manager, + return FALSE; + } + +- if (!meta_monitor_manager_is_scale_supported (manager, +- config->layout_mode, +- monitor, +- monitor_mode, +- scale)) ++ if (!meta_monitor_manager_is_scale_supported_for_config (manager, ++ config, ++ monitor, ++ monitor_mode, ++ scale)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Scale not supported by backend"); +@@ -2021,6 +2407,7 @@ derive_logical_monitor_size (MetaMonitorConfig *monitor_config, + switch (layout_mode) + { + case META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL: ++ case META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL: + width = roundf (width / scale); + height = roundf (height / scale); + break; +@@ -2120,9 +2507,11 @@ create_logical_monitor_config_from_variant (MetaMonitorManager *manager + .monitor_configs = monitor_configs + }; + +- if (!meta_verify_logical_monitor_config (logical_monitor_config, ++ if (layout_mode != META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL && ++ !meta_verify_logical_monitor_config (logical_monitor_config, + layout_mode, + manager, ++ 1.0f, + error)) + { + meta_logical_monitor_config_free (logical_monitor_config); +@@ -2143,6 +2532,7 @@ is_valid_layout_mode (MetaLogicalMonitorLayoutMode layout_mode) + { + case META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL: + case META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL: ++ case META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL: + return TRUE; + } + +@@ -2165,6 +2555,7 @@ meta_monitor_manager_handle_apply_monitors_config (MetaDBusDisplayConfig *skelet + MetaMonitorsConfig *config; + GList *logical_monitor_configs = NULL; + GError *error = NULL; ++ float max_scale = 1.0f; + + if (serial != manager->serial) + { +@@ -2236,10 +2627,42 @@ meta_monitor_manager_handle_apply_monitors_config (MetaDBusDisplayConfig *skelet + return TRUE; + } + ++ max_scale = MAX (max_scale, logical_monitor_config->scale); + logical_monitor_configs = g_list_append (logical_monitor_configs, + logical_monitor_config); + } + ++ if (manager->layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ { ++ GList *l; ++ int ui_scale = ceilf (max_scale); ++ ++ for (l = logical_monitor_configs; l; l = l->next) ++ { ++ MetaLogicalMonitorConfig *logical_monitor_config = l->data; ++ ++ logical_monitor_config->layout.width = ++ roundf (logical_monitor_config->layout.width * ui_scale); ++ logical_monitor_config->layout.height = ++ roundf (logical_monitor_config->layout.height * ui_scale); ++ ++ if (!meta_verify_logical_monitor_config (logical_monitor_config, ++ manager->layout_mode, ++ manager, ++ ui_scale, ++ &error)) ++ { ++ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, ++ G_DBUS_ERROR_INVALID_ARGS, ++ "%s", error->message); ++ g_error_free (error); ++ g_list_free_full (logical_monitor_configs, ++ (GDestroyNotify) meta_logical_monitor_config_free); ++ return TRUE; ++ } ++ } ++ } ++ + config = meta_monitors_config_new (manager, + logical_monitor_configs, + layout_mode, +@@ -2746,12 +3169,6 @@ meta_monitor_manager_get_laptop_panel (MetaMonitorManager *manager) + return find_monitor (manager, meta_monitor_is_laptop_panel); + } + +-static MetaMonitor * +-meta_monitor_manager_get_active_monitor (MetaMonitorManager *manager) +-{ +- return find_monitor (manager, meta_monitor_is_active); +-} +- + MetaMonitor * + meta_monitor_manager_get_monitor_from_connector (MetaMonitorManager *manager, + const char *connector) +@@ -2934,6 +3351,10 @@ rebuild_monitors (MetaMonitorManager *manager) + { + GList *gpus; + GList *l; ++ gboolean has_tiling; ++ ++ has_tiling = meta_monitor_manager_get_capabilities (manager) & ++ META_MONITOR_MANAGER_CAPABILITY_TILING; + + if (manager->monitors) + { +@@ -2952,7 +3373,7 @@ rebuild_monitors (MetaMonitorManager *manager) + MetaOutput *output = k->data; + const MetaOutputInfo *output_info = meta_output_get_info (output); + +- if (output_info->tile_info.group_id) ++ if (has_tiling && output_info->tile_info.group_id) + { + if (is_main_tiled_monitor_output (output)) + { +@@ -3170,7 +3591,7 @@ meta_monitor_manager_update_logical_state_derived (MetaMonitorManager *manager, + else + manager->current_switch_config = META_MONITOR_SWITCH_CONFIG_UNKNOWN; + +- manager->layout_mode = META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL; ++ manager->layout_mode = meta_monitor_manager_get_default_layout_mode (manager); + + meta_monitor_manager_rebuild_logical_monitors_derived (manager, config); + } +@@ -3179,10 +3600,14 @@ void + meta_monitor_manager_rebuild_derived (MetaMonitorManager *manager, + MetaMonitorsConfig *config) + { ++ MetaMonitorManagerClass *klass = META_MONITOR_MANAGER_GET_CLASS (manager); + GList *old_logical_monitors; + + meta_monitor_manager_update_monitor_modes_derived (manager); + ++ if (klass->update_screen_size_derived) ++ klass->update_screen_size_derived (manager, config); ++ + if (manager->in_init) + return; + +diff --git a/src/backends/meta-monitor.c b/src/backends/meta-monitor.c +index e02f8ed..d3ad030 100644 +--- a/src/backends/meta-monitor.c ++++ b/src/backends/meta-monitor.c +@@ -730,8 +730,11 @@ meta_monitor_normal_get_suggested_position (MetaMonitor *monitor, + if (output_info->suggested_x < 0 && output_info->suggested_y < 0) + return FALSE; + +- *x = output_info->suggested_x; +- *y = output_info->suggested_y; ++ if (x) ++ *x = output_info->suggested_x; ++ ++ if (y) ++ *y = output_info->suggested_y; + + return TRUE; + } +@@ -1657,8 +1660,9 @@ meta_monitor_calculate_crtc_pos (MetaMonitor *monitor, + #define SMALLEST_4K_WIDTH 3656 + + static float +-calculate_scale (MetaMonitor *monitor, +- MetaMonitorMode *monitor_mode) ++calculate_scale (MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode, ++ MetaMonitorScalesConstraint constraints) + { + int resolution_width, resolution_height; + int width_mm, height_mm; +@@ -1714,8 +1718,9 @@ out: + } + + float +-meta_monitor_calculate_mode_scale (MetaMonitor *monitor, +- MetaMonitorMode *monitor_mode) ++meta_monitor_calculate_mode_scale (MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode, ++ MetaMonitorScalesConstraint constraints) + { + MetaBackend *backend = meta_get_backend (); + MetaSettings *settings = meta_backend_get_settings (backend); +@@ -1725,7 +1730,7 @@ meta_monitor_calculate_mode_scale (MetaMonitor *monitor, + &global_scaling_factor)) + return global_scaling_factor; + +- return calculate_scale (monitor, monitor_mode); ++ return calculate_scale (monitor, monitor_mode, constraints); + } + + static gboolean +@@ -1735,6 +1740,16 @@ is_logical_size_large_enough (int width, + return width * height >= MINIMUM_LOGICAL_AREA; + } + ++static gboolean ++is_scale_valid_for_size (float width, ++ float height, ++ float scale) ++{ ++ return scale >= MINIMUM_SCALE_FACTOR && ++ scale <= MAXIMUM_SCALE_FACTOR && ++ is_logical_size_large_enough (floorf (width/scale), floorf (width/scale)); ++} ++ + gboolean + meta_monitor_mode_should_be_advertised (MetaMonitorMode *monitor_mode) + { +@@ -1764,21 +1779,16 @@ get_closest_scale_factor_for_resolution (float width, + gboolean found_one; + + best_scale = 0; +- scaled_w = width / scale; +- scaled_h = height / scale; + +- if (scale < MINIMUM_SCALE_FACTOR || +- scale > MAXIMUM_SCALE_FACTOR || +- !is_logical_size_large_enough (floorf (scaled_w), floorf (scaled_h))) ++ if (!is_scale_valid_for_size (width, height, scale)) + goto out; + +- if (floorf (scaled_w) == scaled_w && floorf (scaled_h) == scaled_h) ++ if (fmodf (width, scale) == 0.0 && fmodf (height, scale) == 0.0) + return scale; + + i = 0; + found_one = FALSE; +- base_scaled_w = floorf (scaled_w); +- ++ base_scaled_w = floorf (width / scale); + do + { + for (j = 0; j < 2; j++) +@@ -1838,16 +1848,24 @@ meta_monitor_calculate_supported_scales (MetaMonitor *monitor, + float scale; + float scale_value = i + j * SCALE_FACTORS_STEPS; + +- if ((constraints & META_MONITOR_SCALES_CONSTRAINT_NO_FRAC) && +- fmodf (scale_value, 1.0) != 0.0) ++ if (constraints & META_MONITOR_SCALES_CONSTRAINT_NO_FRAC) + { +- continue; ++ if (fmodf (scale_value, 1.0) != 0.0) ++ continue; + } + +- scale = get_closest_scale_factor_for_resolution (width, +- height, +- scale_value); ++ if ((constraints & META_MONITOR_SCALES_CONSTRAINT_NO_FRAC) || ++ (constraints & META_MONITOR_SCALES_CONSTRAINT_NO_LOGICAL)) ++ { ++ if (!is_scale_valid_for_size (width, height, scale_value)) ++ continue; + ++ scale = scale_value; ++ } ++ else ++ scale = get_closest_scale_factor_for_resolution (width, ++ height, ++ scale_value); + if (scale > 0.0f) + g_array_append_val (supported_scales, scale); + } +diff --git a/src/backends/meta-monitor.h b/src/backends/meta-monitor.h +index 341657a..234a6f9 100644 +--- a/src/backends/meta-monitor.h ++++ b/src/backends/meta-monitor.h +@@ -62,6 +62,7 @@ typedef enum _MetaMonitorScalesConstraint + { + META_MONITOR_SCALES_CONSTRAINT_NONE = 0, + META_MONITOR_SCALES_CONSTRAINT_NO_FRAC = (1 << 0), ++ META_MONITOR_SCALES_CONSTRAINT_NO_LOGICAL = (1 << 1), + } MetaMonitorScalesConstraint; + + #define META_TYPE_MONITOR (meta_monitor_get_type ()) +@@ -211,8 +212,9 @@ void meta_monitor_calculate_crtc_pos (MetaMonitor *monitor, + int *out_y); + + META_EXPORT_TEST +-float meta_monitor_calculate_mode_scale (MetaMonitor *monitor, +- MetaMonitorMode *monitor_mode); ++float meta_monitor_calculate_mode_scale (MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode, ++ MetaMonitorScalesConstraint constraints); + + META_EXPORT_TEST + float * meta_monitor_calculate_supported_scales (MetaMonitor *monitor, +diff --git a/src/backends/meta-settings-private.h b/src/backends/meta-settings-private.h +index cde7e04..4b8b4c5 100644 +--- a/src/backends/meta-settings-private.h ++++ b/src/backends/meta-settings-private.h +@@ -36,6 +36,7 @@ typedef enum _MetaExperimentalFeature + META_EXPERIMENTAL_FEATURE_RT_SCHEDULER = (1 << 2), + META_EXPERIMENTAL_FEATURE_DMA_BUF_SCREEN_SHARING = (1 << 3), + META_EXPERIMENTAL_FEATURE_AUTOCLOSE_XWAYLAND = (1 << 4), ++ META_EXPERIMENTAL_FEATURE_X11_RANDR_FRACTIONAL_SCALING = (1 << 5), + } MetaExperimentalFeature; + + typedef enum _MetaXwaylandExtension +@@ -44,6 +45,13 @@ typedef enum _MetaXwaylandExtension + META_XWAYLAND_EXTENSION_XTEST = (1 << 1), + } MetaXwaylandExtension; + ++typedef enum _MetaX11ScaleMode ++{ ++ META_X11_SCALE_MODE_NONE = 0, ++ META_X11_SCALE_MODE_UP = 1, ++ META_X11_SCALE_MODE_UI_DOWN = 2, ++} MetaX11ScaleMode; ++ + #define META_TYPE_SETTINGS (meta_settings_get_type ()) + G_DECLARE_FINAL_TYPE (MetaSettings, meta_settings, + META, SETTINGS, GObject) +@@ -78,4 +86,9 @@ gboolean meta_settings_are_xwayland_grabs_allowed (MetaSettings *settings); + + int meta_settings_get_xwayland_disable_extensions (MetaSettings *settings); + ++MetaX11ScaleMode meta_settings_get_x11_scale_mode (MetaSettings *settings); ++ ++void meta_settings_enable_x11_fractional_scaling (MetaSettings *settings, ++ gboolean enabled); ++ + #endif /* META_SETTINGS_PRIVATE_H */ +diff --git a/src/backends/meta-settings.c b/src/backends/meta-settings.c +index 6a754d4..b2a6408 100644 +--- a/src/backends/meta-settings.c ++++ b/src/backends/meta-settings.c +@@ -40,6 +40,7 @@ enum + UI_SCALING_FACTOR_CHANGED, + GLOBAL_SCALING_FACTOR_CHANGED, + FONT_DPI_CHANGED, ++ X11_SCALE_MODE_CHANGED, + EXPERIMENTAL_FEATURES_CHANGED, + + N_SIGNALS +@@ -56,6 +57,7 @@ struct _MetaSettings + GSettings *interface_settings; + GSettings *mutter_settings; + GSettings *wayland_settings; ++ GSettings *x11_settings; + + int ui_scaling_factor; + int global_scaling_factor; +@@ -71,6 +73,8 @@ struct _MetaSettings + + /* A bitmask of MetaXwaylandExtension enum */ + int xwayland_disable_extensions; ++ ++ MetaX11ScaleMode x11_scale_mode; + }; + + G_DEFINE_TYPE (MetaSettings, meta_settings, G_TYPE_OBJECT) +@@ -80,14 +84,39 @@ calculate_ui_scaling_factor (MetaSettings *settings) + { + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (settings->backend); +- MetaLogicalMonitor *primary_logical_monitor; + +- primary_logical_monitor = +- meta_monitor_manager_get_primary_logical_monitor (monitor_manager); +- if (!primary_logical_monitor) +- return 1; ++ if (!meta_is_wayland_compositor () && ++ monitor_manager && ++ (meta_monitor_manager_get_capabilities (monitor_manager) & ++ META_MONITOR_MANAGER_CAPABILITY_LAYOUT_MODE)) ++ { ++ MetaLogicalMonitorLayoutMode layout_mode = ++ meta_monitor_manager_get_default_layout_mode (monitor_manager); ++ ++ if (layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ { ++ return ++ ceilf (meta_monitor_manager_get_maximum_crtc_scale (monitor_manager)); ++ } ++ else if (layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL) ++ { ++ return 1.0f; ++ } ++ } ++ ++ if (monitor_manager) ++ { ++ MetaLogicalMonitor *primary_logical_monitor; ++ ++ primary_logical_monitor = ++ meta_monitor_manager_get_primary_logical_monitor (monitor_manager); ++ if (!primary_logical_monitor) ++ return 1; ++ ++ return (int) meta_logical_monitor_get_scale (primary_logical_monitor); ++ } + +- return (int) meta_logical_monitor_get_scale (primary_logical_monitor); ++ return 1; + } + + static gboolean +@@ -235,6 +264,76 @@ meta_settings_override_experimental_features (MetaSettings *settings) + settings->experimental_features_overridden = TRUE; + } + ++static gboolean ++update_x11_scale_mode (MetaSettings *settings) ++{ ++ MetaX11ScaleMode scale_mode; ++ ++ if (!(settings->experimental_features & ++ META_EXPERIMENTAL_FEATURE_X11_RANDR_FRACTIONAL_SCALING)) ++ { ++ scale_mode = META_X11_SCALE_MODE_NONE; ++ } ++ else ++ { ++ scale_mode = ++ g_settings_get_enum (settings->x11_settings, "fractional-scale-mode"); ++ } ++ ++ if (settings->x11_scale_mode != scale_mode) ++ { ++ settings->x11_scale_mode = scale_mode; ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ ++void meta_settings_enable_x11_fractional_scaling (MetaSettings *settings, ++ gboolean enable) ++{ ++ g_auto(GStrv) existing_features = NULL; ++ gboolean have_fractional_scaling = FALSE; ++ g_autoptr(GVariantBuilder) builder = NULL; ++ MetaExperimentalFeature old_experimental_features; ++ ++ if (enable == meta_settings_is_experimental_feature_enabled (settings, ++ META_EXPERIMENTAL_FEATURE_X11_RANDR_FRACTIONAL_SCALING)) ++ return; ++ ++ /* Change the internal value now, as we don't want to wait for gsettings */ ++ old_experimental_features = settings->experimental_features; ++ settings->experimental_features |= ++ META_EXPERIMENTAL_FEATURE_X11_RANDR_FRACTIONAL_SCALING; ++ ++ update_x11_scale_mode (settings); ++ ++ g_signal_emit (settings, signals[EXPERIMENTAL_FEATURES_CHANGED], 0, ++ (unsigned int) old_experimental_features); ++ ++ /* Add or remove the fractional scaling feature from mutter */ ++ existing_features = g_settings_get_strv (settings->mutter_settings, ++ "experimental-features"); ++ builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); ++ for (int i = 0; existing_features[i] != NULL; i++) ++ { ++ if (g_strcmp0 (existing_features[i], "x11-randr-fractional-scaling") == 0) ++ { ++ if (enable) ++ have_fractional_scaling = TRUE; ++ else ++ continue; ++ } ++ ++ g_variant_builder_add (builder, "s", existing_features[i]); ++ } ++ if (enable && !have_fractional_scaling) ++ g_variant_builder_add (builder, "s", "x11-randr-fractional-scaling"); ++ ++ g_settings_set_value (settings->mutter_settings, "experimental-features", ++ g_variant_builder_end (builder)); ++} ++ + void + meta_settings_enable_experimental_feature (MetaSettings *settings, + MetaExperimentalFeature feature) +@@ -242,6 +341,9 @@ meta_settings_enable_experimental_feature (MetaSettings *settings, + g_assert (settings->experimental_features_overridden); + + settings->experimental_features |= feature; ++ ++ if (update_x11_scale_mode (settings)) ++ g_signal_emit (settings, signals[X11_SCALE_MODE_CHANGED], 0, NULL); + } + + static gboolean +@@ -275,6 +377,8 @@ experimental_features_handler (GVariant *features_variant, + feature = META_EXPERIMENTAL_FEATURE_DMA_BUF_SCREEN_SHARING; + else if (g_str_equal (feature_str, "autoclose-xwayland")) + feature = META_EXPERIMENTAL_FEATURE_AUTOCLOSE_XWAYLAND; ++ else if (g_str_equal (feature_str, "x11-randr-fractional-scaling")) ++ feature = META_EXPERIMENTAL_FEATURE_X11_RANDR_FRACTIONAL_SCALING; + + if (feature) + g_message ("Enabling experimental feature '%s'", feature_str); +@@ -287,6 +391,7 @@ experimental_features_handler (GVariant *features_variant, + if (features != settings->experimental_features) + { + settings->experimental_features = features; ++ update_x11_scale_mode (settings); + *result = GINT_TO_POINTER (TRUE); + } + else +@@ -420,6 +525,18 @@ wayland_settings_changed (GSettings *wayland_settings, + } + } + ++static void ++x11_settings_changed (GSettings *wayland_settings, ++ gchar *key, ++ MetaSettings *settings) ++{ ++ if (g_str_equal (key, "fractional-scale-mode")) ++ { ++ if (update_x11_scale_mode (settings)) ++ g_signal_emit (settings, signals[X11_SCALE_MODE_CHANGED], 0, NULL); ++ } ++} ++ + void + meta_settings_get_xwayland_grab_patterns (MetaSettings *settings, + GPtrArray **allow_list_patterns, +@@ -441,6 +558,12 @@ meta_settings_get_xwayland_disable_extensions (MetaSettings *settings) + return (settings->xwayland_disable_extensions); + } + ++MetaX11ScaleMode ++meta_settings_get_x11_scale_mode (MetaSettings *settings) ++{ ++ return settings->x11_scale_mode; ++} ++ + MetaSettings * + meta_settings_new (MetaBackend *backend) + { +@@ -460,6 +583,7 @@ meta_settings_dispose (GObject *object) + g_clear_object (&settings->mutter_settings); + g_clear_object (&settings->interface_settings); + g_clear_object (&settings->wayland_settings); ++ g_clear_object (&settings->x11_settings); + g_clear_pointer (&settings->xwayland_grab_allow_list_patterns, + g_ptr_array_unref); + g_clear_pointer (&settings->xwayland_grab_deny_list_patterns, +@@ -483,6 +607,10 @@ meta_settings_init (MetaSettings *settings) + g_signal_connect (settings->wayland_settings, "changed", + G_CALLBACK (wayland_settings_changed), + settings); ++ settings->x11_settings = g_settings_new ("org.gnome.mutter.x11"); ++ g_signal_connect (settings->x11_settings, "changed", ++ G_CALLBACK (x11_settings_changed), ++ settings); + + /* Chain up inter-dependent settings. */ + g_signal_connect (settings, "global-scaling-factor-changed", +@@ -549,6 +677,14 @@ meta_settings_class_init (MetaSettingsClass *klass) + NULL, NULL, NULL, + G_TYPE_NONE, 0); + ++ signals[X11_SCALE_MODE_CHANGED] = ++ g_signal_new ("x11-scale-mode-changed", ++ G_TYPE_FROM_CLASS (object_class), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, NULL, NULL, ++ G_TYPE_NONE, 0); ++ + signals[EXPERIMENTAL_FEATURES_CHANGED] = + g_signal_new ("experimental-features-changed", + G_TYPE_FROM_CLASS (object_class), +diff --git a/src/backends/native/meta-monitor-manager-native.c b/src/backends/native/meta-monitor-manager-native.c +index fd5e778..3864474 100644 +--- a/src/backends/native/meta-monitor-manager-native.c ++++ b/src/backends/native/meta-monitor-manager-native.c +@@ -571,20 +571,9 @@ meta_monitor_manager_native_is_transform_handled (MetaMonitorManager *manager, + transform); + } + +-static float +-meta_monitor_manager_native_calculate_monitor_mode_scale (MetaMonitorManager *manager, +- MetaMonitor *monitor, +- MetaMonitorMode *monitor_mode) +-{ +- return meta_monitor_calculate_mode_scale (monitor, monitor_mode); +-} ++static MetaMonitorScalesConstraint ++get_monitor_scale_constraints_per_layout_mode (MetaLogicalMonitorLayoutMode layout_mode) + +-static float * +-meta_monitor_manager_native_calculate_supported_scales (MetaMonitorManager *manager, +- MetaLogicalMonitorLayoutMode layout_mode, +- MetaMonitor *monitor, +- MetaMonitorMode *monitor_mode, +- int *n_supported_scales) + { + MetaMonitorScalesConstraint constraints = + META_MONITOR_SCALES_CONSTRAINT_NONE; +@@ -598,6 +587,32 @@ meta_monitor_manager_native_calculate_supported_scales (MetaMonitorManager + break; + } + ++ return constraints; ++} ++ ++static float ++meta_monitor_manager_native_calculate_monitor_mode_scale (MetaMonitorManager *manager, ++ MetaLogicalMonitorLayoutMode layout_mode, ++ MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode) ++{ ++ MetaMonitorScalesConstraint constraints = ++ get_monitor_scale_constraints_per_layout_mode (layout_mode); ++ ++ return meta_monitor_calculate_mode_scale (monitor, monitor_mode, constraints); ++} ++ ++static float * ++meta_monitor_manager_native_calculate_supported_scales (MetaMonitorManager *manager, ++ MetaLogicalMonitorLayoutMode layout_mode, ++ MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode, ++ int *n_supported_scales) ++{ ++ MetaMonitorScalesConstraint constraints = ++ get_monitor_scale_constraints_per_layout_mode (layout_mode); ++ ++ + return meta_monitor_calculate_supported_scales (monitor, monitor_mode, + constraints, + n_supported_scales); +diff --git a/src/backends/x11/meta-crtc-xrandr.c b/src/backends/x11/meta-crtc-xrandr.c +index e06448b6..e17d3ea 100644 +--- a/src/backends/x11/meta-crtc-xrandr.c ++++ b/src/backends/x11/meta-crtc-xrandr.c +@@ -36,6 +36,7 @@ + #include "backends/x11/meta-crtc-xrandr.h" + + #include ++#include + #include + #include + +@@ -46,6 +47,9 @@ + #include "backends/x11/meta-gpu-xrandr.h" + #include "backends/x11/meta-monitor-manager-xrandr.h" + ++#define ALL_TRANSFORMS ((1 << (META_MONITOR_TRANSFORM_FLIPPED_270 + 1)) - 1) ++#define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536)) ++ + struct _MetaCrtcXrandr + { + MetaCrtc parent; +@@ -110,6 +114,63 @@ meta_crtc_xrandr_set_config (MetaCrtcXrandr *crtc_xrandr, + *out_timestamp = reply->timestamp; + free (reply); + ++ ++ return TRUE; ++} ++ ++gboolean ++meta_crtc_xrandr_set_scale (MetaCrtc *crtc, ++ xcb_randr_crtc_t xrandr_crtc, ++ float scale) ++{ ++ MetaGpu *gpu = meta_crtc_get_gpu (crtc); ++ MetaBackend *backend = meta_gpu_get_backend (gpu); ++ MetaMonitorManager *monitor_manager = ++ meta_backend_get_monitor_manager (backend); ++ MetaMonitorManagerXrandr *monitor_manager_xrandr = ++ META_MONITOR_MANAGER_XRANDR (monitor_manager); ++ Display *xdisplay; ++ const char *scale_filter; ++ xcb_connection_t *xcb_conn; ++ xcb_void_cookie_t transform_cookie; ++ xcb_generic_error_t *xcb_error = NULL; ++ xcb_render_transform_t transformation = { ++ DOUBLE_TO_FIXED (1), DOUBLE_TO_FIXED (0), DOUBLE_TO_FIXED (0), ++ DOUBLE_TO_FIXED (0), DOUBLE_TO_FIXED (1), DOUBLE_TO_FIXED (0), ++ DOUBLE_TO_FIXED (0), DOUBLE_TO_FIXED (0), DOUBLE_TO_FIXED (1) ++ }; ++ ++ if (!(meta_monitor_manager_get_capabilities (monitor_manager) & ++ META_MONITOR_MANAGER_CAPABILITY_NATIVE_OUTPUT_SCALING)) ++ return FALSE; ++ ++ xdisplay = meta_monitor_manager_xrandr_get_xdisplay (monitor_manager_xrandr); ++ xcb_conn = XGetXCBConnection (xdisplay); ++ ++ if (fabsf (scale - 1.0f) > 0.001) ++ { ++ scale_filter = FilterGood; ++ transformation.matrix11 = DOUBLE_TO_FIXED (1.0 / scale); ++ transformation.matrix22 = DOUBLE_TO_FIXED (1.0 / scale); ++ } ++ else ++ scale_filter = FilterFast; ++ ++ transform_cookie = ++ xcb_randr_set_crtc_transform_checked (xcb_conn, xrandr_crtc, transformation, ++ strlen (scale_filter), scale_filter, ++ 0, NULL); ++ ++ xcb_error = xcb_request_check (xcb_conn, transform_cookie); ++ if (xcb_error) ++ { ++ g_warning ("Impossible to set scaling on crtc %u to %f, error id %u", ++ xrandr_crtc, scale, xcb_error->error_code); ++ g_clear_pointer (&xcb_error, free); ++ ++ return FALSE; ++ } ++ + return TRUE; + } + +@@ -221,11 +282,34 @@ meta_crtc_xrandr_get_current_mode (MetaCrtcXrandr *crtc_xrandr) + return crtc_xrandr->current_mode; + } + ++static float ++meta_monitor_scale_from_transformation (XRRCrtcTransformAttributes *transformation) ++{ ++ XTransform *xt; ++ float scale; ++ ++ if (!transformation) ++ return 1.0f; ++ ++ xt = &transformation->currentTransform; ++ ++ if (xt->matrix[0][0] == xt->matrix[1][1]) ++ scale = XFixedToDouble (xt->matrix[0][0]); ++ else ++ scale = XFixedToDouble (xt->matrix[0][0] + xt->matrix[1][1]) / 2.0; ++ ++ g_return_val_if_fail (scale > 0.0f, 1.0f); ++ ++ return 1.0f / scale; ++} ++ + MetaCrtcXrandr * +-meta_crtc_xrandr_new (MetaGpuXrandr *gpu_xrandr, +- XRRCrtcInfo *xrandr_crtc, +- RRCrtc crtc_id, +- XRRScreenResources *resources) ++meta_crtc_xrandr_new (MetaGpuXrandr *gpu_xrandr, ++ XRRCrtcInfo *xrandr_crtc, ++ RRCrtc crtc_id, ++ XRRScreenResources *resources, ++ XRRCrtcTransformAttributes *transform_attributes, ++ float scale_multiplier) + { + MetaGpu *gpu = META_GPU (gpu_xrandr); + MetaBackend *backend = meta_gpu_get_backend (gpu); +@@ -285,6 +369,9 @@ meta_crtc_xrandr_new (MetaGpuXrandr *gpu_xrandr, + + if (crtc_xrandr->current_mode) + { ++ float crtc_scale = ++ meta_monitor_scale_from_transformation (transform_attributes); ++ + meta_crtc_set_config (META_CRTC (crtc_xrandr), + &GRAPHENE_RECT_INIT (crtc_xrandr->rect.x, + crtc_xrandr->rect.y, +@@ -292,6 +379,11 @@ meta_crtc_xrandr_new (MetaGpuXrandr *gpu_xrandr, + crtc_xrandr->rect.height), + crtc_xrandr->current_mode, + crtc_xrandr->transform); ++ ++ if (scale_multiplier > 0.0f) ++ crtc_scale *= scale_multiplier; ++ ++ meta_crtc_set_config_scale (META_CRTC (crtc_xrandr), crtc_scale); + } + + return crtc_xrandr; +diff --git a/src/backends/x11/meta-crtc-xrandr.h b/src/backends/x11/meta-crtc-xrandr.h +index dbf5da0..dbaeb26 100644 +--- a/src/backends/x11/meta-crtc-xrandr.h ++++ b/src/backends/x11/meta-crtc-xrandr.h +@@ -44,14 +44,20 @@ gboolean meta_crtc_xrandr_set_config (MetaCrtcXrandr *crtc_xrandr, + int n_outputs, + xcb_timestamp_t *out_timestamp); + ++gboolean meta_crtc_xrandr_set_scale (MetaCrtc *crtc, ++ xcb_randr_crtc_t xrandr_crtc, ++ float scale); ++ + gboolean meta_crtc_xrandr_is_assignment_changed (MetaCrtcXrandr *crtc_xrandr, + MetaCrtcAssignment *crtc_assignment); + + MetaCrtcMode * meta_crtc_xrandr_get_current_mode (MetaCrtcXrandr *crtc_xrandr); + +-MetaCrtcXrandr * meta_crtc_xrandr_new (MetaGpuXrandr *gpu_xrandr, +- XRRCrtcInfo *xrandr_crtc, +- RRCrtc crtc_id, +- XRRScreenResources *resources); ++MetaCrtcXrandr * meta_crtc_xrandr_new (MetaGpuXrandr *gpu_xrandr, ++ XRRCrtcInfo *xrandr_crtc, ++ RRCrtc crtc_id, ++ XRRScreenResources *resources, ++ XRRCrtcTransformAttributes *transform_attributes, ++ float scale_multiplier); + + #endif /* META_CRTC_XRANDR_H */ +diff --git a/src/backends/x11/meta-gpu-xrandr.c b/src/backends/x11/meta-gpu-xrandr.c +index bc3292d..0e9d55a 100644 +--- a/src/backends/x11/meta-gpu-xrandr.c ++++ b/src/backends/x11/meta-gpu-xrandr.c +@@ -23,6 +23,7 @@ + * along with this program; if not, see . + */ + ++#include "backends/meta-crtc.h" + #include "config.h" + + #include "backends/x11/meta-gpu-xrandr.h" +@@ -45,6 +45,8 @@ struct _MetaGpuXrandr + + XRRScreenResources *resources; + ++ int min_screen_width; ++ int min_screen_height; + int max_screen_width; + int max_screen_height; + +@@ -56,6 +59,15 @@ meta_gpu_xrandr_get_resources (MetaGpuXrandr *gpu_xrandr) + return gpu_xrandr->resources; + } + ++void ++meta_gpu_xrandr_get_min_screen_size (MetaGpuXrandr *gpu_xrandr, ++ int *min_width, ++ int *min_height) ++{ ++ *min_width = gpu_xrandr->min_screen_width; ++ *min_height = gpu_xrandr->min_screen_height; ++} ++ + void + meta_gpu_xrandr_get_max_screen_size (MetaGpuXrandr *gpu_xrandr, + int *max_width, +@@ -98,6 +100,60 @@ get_xmode_name (XRRModeInfo *xmode) + return g_strdup_printf ("%dx%d", width, height); + } + ++static int ++get_current_dpi_scale (MetaMonitorManagerXrandr *manager_xrandr, ++ MetaGpuXrandr *gpu_xrandr) ++{ ++ Atom actual; ++ int result, format; ++ unsigned long n, left; ++ g_autofree unsigned char *data = NULL; ++ g_auto(GStrv) resources = NULL; ++ Display *dpy; ++ int i; ++ ++ if (gpu_xrandr->resources->timestamp == ++ meta_monitor_manager_xrandr_get_config_timestamp (manager_xrandr)) ++ { ++ MetaMonitorManager *monitor_manager = META_MONITOR_MANAGER (manager_xrandr); ++ MetaBackend *backend = meta_monitor_manager_get_backend (monitor_manager); ++ MetaSettings *settings = meta_backend_get_settings (backend); ++ ++ return meta_settings_get_ui_scaling_factor (settings); ++ } ++ ++ dpy = meta_monitor_manager_xrandr_get_xdisplay (manager_xrandr); ++ result = XGetWindowProperty (dpy, DefaultRootWindow (dpy), ++ XA_RESOURCE_MANAGER, 0L, 65536, False, ++ XA_STRING, &actual, &format, ++ &n, &left, &data); ++ ++ if (result != Success || !data || actual != XA_STRING) ++ return 1; ++ ++ resources = g_strsplit ((char *) data, "\n", -1); ++ ++ for (i = 0; resources && resources[i]; ++i) ++ { ++ if (g_str_has_prefix (resources[i], "Xft.dpi:")) ++ { ++ g_auto(GStrv) res = g_strsplit (resources[i], "\t", 2); ++ ++ if (res && res[0] && res[1]) ++ { ++ guint64 dpi; ++ dpi = g_ascii_strtoull (res[1], NULL, 10); ++ ++ if (dpi > 0 && dpi < 96 * 10) ++ return MAX (1, roundf ((float) dpi / 96.0f)); ++ } ++ } ++ } ++ ++ return 1; ++} ++ ++ + static float + calculate_xrandr_refresh_rate (XRRModeInfo *xmode) + { +@@ -135,12 +191,13 @@ update_screen_size (MetaGpuXrandr *gpu_xrandr) + META_MONITOR_MANAGER_XRANDR (monitor_manager); + Display *xdisplay = + meta_monitor_manager_xrandr_get_xdisplay (monitor_manager_xrandr); +- int min_width, min_height; ++ gboolean has_transform; ++ int dpi_scale = 1; + Screen *screen; + + XRRGetScreenSizeRange (xdisplay, DefaultRootWindow (xdisplay), +- &min_width, +- &min_height, ++ &gpu_xrandr->min_screen_width, ++ &gpu_xrandr->min_screen_height, + &gpu_xrandr->max_screen_width, + &gpu_xrandr->max_screen_height); + +@@ -162,22 +228,60 @@ meta_gpu_xrandr_read_current (MetaGpu *gpu, + } + meta_gpu_take_modes (gpu, modes); + ++ has_transform = !!(meta_monitor_manager_get_capabilities (monitor_manager) & ++ META_MONITOR_MANAGER_CAPABILITY_NATIVE_OUTPUT_SCALING); ++ ++ if (has_transform && ++ meta_monitor_manager_get_default_layout_mode (monitor_manager) == ++ META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ dpi_scale = get_current_dpi_scale (monitor_manager_xrandr, gpu_xrandr); ++ + for (i = 0; i < (unsigned)resources->ncrtc; i++) + { + XRRCrtcInfo *xrandr_crtc; ++ XRRCrtcTransformAttributes *transform_attributes; + RRCrtc crtc_id; + MetaCrtcXrandr *crtc_xrandr; + + crtc_id = resources->crtcs[i]; + xrandr_crtc = XRRGetCrtcInfo (xdisplay, + resources, crtc_id); ++ ++ if (!has_transform || ++ !XRRGetCrtcTransform (xdisplay, crtc_id, &transform_attributes)) ++ transform_attributes = NULL; ++ + crtc_xrandr = meta_crtc_xrandr_new (gpu_xrandr, +- xrandr_crtc, crtc_id, resources); ++ xrandr_crtc, crtc_id, resources, ++ transform_attributes, dpi_scale); ++ XFree (transform_attributes); + XRRFreeCrtcInfo (xrandr_crtc); + + crtcs = g_list_append (crtcs, crtc_xrandr); + } + ++ if (has_transform && dpi_scale == 1 && ++ meta_monitor_manager_get_default_layout_mode (monitor_manager) == ++ META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ { ++ dpi_scale = ++ ceilf (meta_monitor_manager_get_maximum_crtc_scale (monitor_manager)); ++ ++ if (dpi_scale > 1) ++ { ++ for (l = crtcs; l; l = l->next) ++ { ++ MetaCrtc *crtc = l->data; ++ const MetaCrtcConfig *crtc_config = meta_crtc_get_config (crtc); ++ ++ if (!crtc_config) ++ continue; ++ ++ meta_crtc_set_config_scale (crtc, crtc_config->scale * dpi_scale); ++ } ++ } ++ } ++ + meta_gpu_take_crtcs (gpu, crtcs); + + primary_output = XRRGetOutputPrimary (xdisplay, +@@ -252,6 +309,8 @@ meta_gpu_xrandr_read_current (MetaGpu *gpu, + GList *outputs = NULL; + GList *modes = NULL; + GList *crtcs = NULL; ++ gboolean has_transform; ++ int dpi_scale = 1; + + if (!meta_monitor_manager_xrandr_has_randr (monitor_manager_xrandr)) + return read_current_fallback (gpu_xrandr, monitor_manager_xrandr); +diff --git a/src/backends/x11/meta-gpu-xrandr.h b/src/backends/x11/meta-gpu-xrandr.h +index 2086f86..a1f3b48 100644 +--- a/src/backends/x11/meta-gpu-xrandr.h ++++ b/src/backends/x11/meta-gpu-xrandr.h +@@ -33,6 +33,10 @@ G_DECLARE_FINAL_TYPE (MetaGpuXrandr, meta_gpu_xrandr, META, GPU_XRANDR, MetaGpu) + + XRRScreenResources * meta_gpu_xrandr_get_resources (MetaGpuXrandr *gpu_xrandr); + ++void meta_gpu_xrandr_get_min_screen_size (MetaGpuXrandr *gpu_xrandr, ++ int *min_width, ++ int *min_height); ++ + void meta_gpu_xrandr_get_max_screen_size (MetaGpuXrandr *gpu_xrandr, + int *max_width, + int *max_height); +diff --git a/src/backends/x11/meta-monitor-manager-xrandr.c b/src/backends/x11/meta-monitor-manager-xrandr.c +index 489a9b4..f2ddbfe 100644 +--- a/src/backends/x11/meta-monitor-manager-xrandr.c ++++ b/src/backends/x11/meta-monitor-manager-xrandr.c +@@ -36,6 +36,7 @@ + * and udev. + */ + ++#include "backends/meta-backend-types.h" + #include "config.h" + + #include "backends/x11/meta-monitor-manager-xrandr.h" +@@ -64,6 +65,9 @@ + * http://git.gnome.org/browse/gnome-settings-daemon/tree/plugins/xsettings/gsd-xsettings-manager.c + * for the reasoning */ + #define DPI_FALLBACK 96.0 ++#define RANDR_VERSION_FORMAT(major, minor) ((major * 100) + minor) ++#define RANDR_TILING_MIN_VERSION RANDR_VERSION_FORMAT (1, 5) ++#define RANDR_TRANSFORM_MIN_VERSION RANDR_VERSION_FORMAT (1, 3) + + struct _MetaMonitorManagerXrandr + { +@@ -81,16 +81,16 @@ struct _MetaMonitorManagerXrandr + guint logind_signal_sub_id; + + gboolean has_randr; +- gboolean has_randr15; ++ int randr_version; + + xcb_timestamp_t last_xrandr_set_timestamp; + + GHashTable *tiled_monitor_atoms; + +- float *supported_scales; +- int n_supported_scales; + }; + ++static MetaGpu * meta_monitor_manager_xrandr_get_gpu (MetaMonitorManagerXrandr *manager_xrandr); ++ + struct _MetaMonitorManagerXrandrClass + { + MetaMonitorManagerClass parent_class; +@@ -119,10 +119,10 @@ meta_monitor_manager_xrandr_has_randr (MetaMonitorManagerXrandr *manager_xrandr) + return manager_xrandr->has_randr; + } + +-gboolean +-meta_monitor_manager_xrandr_has_randr15 (MetaMonitorManagerXrandr *manager_xrandr) ++uint32_t ++meta_monitor_manager_xrandr_get_config_timestamp (MetaMonitorManagerXrandr *manager_xrandr) + { +- return manager_xrandr->has_randr15; ++ return manager_xrandr->last_xrandr_set_timestamp; + } + + static GBytes * +@@ -187,6 +191,81 @@ meta_monitor_manager_xrandr_set_power_save_mode (MetaMonitorManager *manager, + DPMSSetTimeouts (manager_xrandr->xdisplay, 0, 0, 0); + } + ++static void ++meta_monitor_manager_xrandr_update_screen_size (MetaMonitorManagerXrandr *manager_xrandr, ++ int width, ++ int height, ++ float scale) ++{ ++ MetaMonitorManager *manager = META_MONITOR_MANAGER (manager_xrandr); ++ MetaGpu *gpu = meta_monitor_manager_xrandr_get_gpu (manager_xrandr); ++ xcb_connection_t *xcb_conn; ++ xcb_generic_error_t *xcb_error; ++ xcb_void_cookie_t xcb_cookie; ++ Screen *screen; ++ int min_width; ++ int min_height; ++ int max_width; ++ int max_height; ++ int width_mm; ++ int height_mm; ++ ++ g_assert (width > 0 && height > 0 && scale > 0); ++ ++ if (manager->screen_width == width && manager->screen_height == height) ++ return; ++ ++ screen = ScreenOfDisplay (manager_xrandr->xdisplay, ++ DefaultScreen (manager_xrandr->xdisplay)); ++ meta_gpu_xrandr_get_min_screen_size (META_GPU_XRANDR (gpu), ++ &min_width, &min_height); ++ meta_gpu_xrandr_get_max_screen_size (META_GPU_XRANDR (gpu), ++ &max_width, &max_height); ++ width = MIN (MAX (min_width, width), max_width); ++ height = MIN (MAX (min_height, height), max_height); ++ ++ /* The 'physical size' of an X screen is meaningless if that screen can ++ * consist of many monitors. So just pick a size that make the dpi 96. ++ * ++ * Firefox and Evince apparently believe what X tells them. ++ */ ++ width_mm = (width / (DPI_FALLBACK * scale)) * 25.4 + 0.5; ++ height_mm = (height / (DPI_FALLBACK * scale)) * 25.4 + 0.5; ++ ++ if (width == WidthOfScreen (screen) && height == HeightOfScreen (screen) && ++ width_mm == WidthMMOfScreen (screen) && height_mm == HeightMMOfScreen (screen)) ++ return; ++ ++ xcb_conn = XGetXCBConnection (manager_xrandr->xdisplay); ++ ++ xcb_grab_server (xcb_conn); ++ ++ /* Some drivers (nvidia I look at you!) might no advertise some CRTCs, so in ++ * such case, we may ignore X errors here */ ++ xcb_cookie = xcb_randr_set_screen_size_checked (xcb_conn, ++ DefaultRootWindow (manager_xrandr->xdisplay), ++ width, height, ++ width_mm, height_mm); ++ xcb_error = xcb_request_check (xcb_conn, xcb_cookie); ++ if (!xcb_error) ++ { ++ manager->screen_width = width; ++ manager->screen_height = height; ++ } ++ else ++ { ++ gchar buf[64]; ++ ++ XGetErrorText (manager_xrandr->xdisplay, xcb_error->error_code, buf, ++ sizeof (buf) - 1); ++ g_warning ("Impossible to resize screen at size %dx%d, error id %u: %s", ++ width, height, xcb_error->error_code, buf); ++ g_clear_pointer (&xcb_error, free); ++ } ++ ++ xcb_ungrab_server (xcb_conn); ++} ++ + static xcb_randr_rotation_t + meta_monitor_transform_to_xrandr (MetaMonitorTransform transform) + { +@@ -242,13 +321,50 @@ xrandr_set_crtc_config (MetaMonitorManagerXrandr *manager_xrandr, + return TRUE; + } + ++static float ++get_maximum_crtc_assignments_scale (MetaCrtcAssignment **crtc_assignments, ++ unsigned int n_crtc_assignments) ++{ ++ float max_scale = 1.0f; ++ unsigned int i; ++ ++ for (i = 0; i < n_crtc_assignments; i++) ++ { ++ MetaCrtcAssignment *crtc_assignment = crtc_assignments[i]; ++ ++ if (crtc_assignment->mode) ++ max_scale = MAX (max_scale, crtc_assignment->scale); ++ } ++ ++ return max_scale; ++} ++ + static gboolean +-is_crtc_assignment_changed (MetaCrtc *crtc, ++is_crtc_assignment_changed (MetaMonitorManager *monitor_manager, ++ MetaCrtc *crtc, + MetaCrtcAssignment **crtc_assignments, +- unsigned int n_crtc_assignments) ++ unsigned int n_crtc_assignments, ++ gboolean *weak_change) + { ++ MetaLogicalMonitorLayoutMode layout_mode; ++ gboolean have_scaling; ++ float max_crtc_scale = 1.0f; ++ float max_req_scale = 1.0f; + unsigned int i; + ++ layout_mode = meta_monitor_manager_get_default_layout_mode (monitor_manager); ++ have_scaling = meta_monitor_manager_get_capabilities (monitor_manager) & ++ META_MONITOR_MANAGER_CAPABILITY_NATIVE_OUTPUT_SCALING; ++ ++ if (have_scaling && ++ layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ { ++ max_crtc_scale = ++ meta_monitor_manager_get_maximum_crtc_scale (monitor_manager); ++ max_req_scale = ++ get_maximum_crtc_assignments_scale (crtc_assignments, n_crtc_assignments); ++ } ++ + for (i = 0; i < n_crtc_assignments; i++) + { + MetaCrtcAssignment *crtc_assignment = crtc_assignments[i]; +@@ -256,8 +372,44 @@ is_crtc_assignment_changed (MetaCrtc *crtc, + if (crtc_assignment->crtc != crtc) + continue; + +- return meta_crtc_xrandr_is_assignment_changed (META_CRTC_XRANDR (crtc), +- crtc_assignment); ++ if (meta_crtc_xrandr_is_assignment_changed (META_CRTC_XRANDR (crtc), ++ crtc_assignment)) ++ return TRUE; ++ ++ if (have_scaling) ++ { ++ const MetaCrtcConfig *crtc_config = meta_crtc_get_config (crtc); ++ float crtc_scale = crtc_config ? crtc_config->scale : 1.0f; ++ float req_output_scale = crtc_assignment->scale; ++ ++ if (layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL) ++ { ++ if (fmodf (crtc_scale, 1.0) == 0.0f) ++ { ++ *weak_change = fabsf (crtc_scale - req_output_scale) > 0.001; ++ return FALSE; ++ } ++ } ++ else if (layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ { ++ /* In scale ui-down mode we need to check if the actual output ++ * scale that will be applied to the crtc has actually changed ++ * from the current value, so we need to compare the current crtc ++ * scale with the scale that will be applied taking care of the ++ * UI scale (max crtc scale) and of the requested maximum scale. ++ * If we don't do this, we'd try to call randr calls which won't ++ * ever trigger a RRScreenChangeNotify, as no actual change is ++ * needed, and thus we won't ever emit a monitors-changed signal. ++ */ ++ crtc_scale /= ceilf (max_crtc_scale); ++ req_output_scale /= ceilf (max_req_scale); ++ } ++ ++ if (fabsf (crtc_scale - req_output_scale) > 0.001) ++ return TRUE; ++ } ++ ++ return FALSE; + } + + return !!meta_crtc_xrandr_get_current_mode (META_CRTC_XRANDR (crtc)); +@@ -333,7 +485,8 @@ is_assignments_changed (MetaMonitorManager *manager, + MetaCrtcAssignment **crtc_assignments, + unsigned int n_crtc_assignments, + MetaOutputAssignment **output_assignments, +- unsigned int n_output_assignments) ++ unsigned int n_output_assignments, ++ gboolean *weak_change) + { + MetaMonitorManagerXrandr *manager_xrandr = + META_MONITOR_MANAGER_XRANDR (manager); +@@ -344,7 +497,9 @@ is_assignments_changed (MetaMonitorManager *manager, + { + MetaCrtc *crtc = l->data; + +- if (is_crtc_assignment_changed (crtc, crtc_assignments, n_crtc_assignments)) ++ if (is_crtc_assignment_changed (manager, crtc, ++ crtc_assignments, n_crtc_assignments, ++ weak_change)) + return TRUE; + } + +@@ -360,6 +515,32 @@ is_assignments_changed (MetaMonitorManager *manager, + return TRUE; + } + ++ if (meta_monitor_manager_get_default_layout_mode (manager) == ++ META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ { ++ /* If nothing has changed, ensure that the crtc logical scaling matches ++ * with the requested one, as in case of global UI logical layout we might ++ * assume that it is in fact equal, while it's techincally different. ++ * Not doing this would then cause a wrong computation of the max crtc ++ * scale and thus of the UI scaling. */ ++ for (l = meta_gpu_get_crtcs (gpu); l; l = l->next) ++ { ++ MetaCrtc *crtc = l->data; ++ unsigned int i; ++ ++ for (i = 0; i < n_crtc_assignments; i++) ++ { ++ MetaCrtcAssignment *crtc_assignment = crtc_assignments[i]; ++ ++ if (crtc_assignment->crtc == crtc) ++ { ++ meta_crtc_set_config_scale (crtc, crtc_assignment->scale); ++ break; ++ } ++ } ++ } ++ } ++ + return FALSE; + } + +@@ -375,31 +556,55 @@ apply_crtc_assignments (MetaMonitorManager *manager, + MetaGpu *gpu = meta_monitor_manager_xrandr_get_gpu (manager_xrandr); + g_autoptr (GList) to_configure_outputs = NULL; + g_autoptr (GList) to_disable_crtcs = NULL; +- unsigned i; ++ MetaBackend *backend = meta_monitor_manager_get_backend (manager); ++ MetaSettings *settings = meta_backend_get_settings (backend); ++ MetaX11ScaleMode scale_mode = meta_settings_get_x11_scale_mode (settings); ++ unsigned i, valid_crtcs; + GList *l; +- int width, height, width_mm, height_mm; ++ int width, height; ++ float max_scale; ++ float avg_screen_scale; ++ gboolean have_scaling; + + to_configure_outputs = g_list_copy (meta_gpu_get_outputs (gpu)); + to_disable_crtcs = g_list_copy (meta_gpu_get_crtcs (gpu)); + + XGrabServer (manager_xrandr->xdisplay); + +- /* First compute the new size of the screen (framebuffer) */ ++ have_scaling = meta_monitor_manager_get_capabilities (manager) & ++ META_MONITOR_MANAGER_CAPABILITY_NATIVE_OUTPUT_SCALING; ++ ++ /* Compute the new size of the screen (framebuffer) */ ++ max_scale = get_maximum_crtc_assignments_scale (crtcs, n_crtcs); + width = 0; height = 0; ++ avg_screen_scale = 0; ++ valid_crtcs = 0; + for (i = 0; i < n_crtcs; i++) + { + MetaCrtcAssignment *crtc_assignment = crtcs[i]; + MetaCrtc *crtc = crtc_assignment->crtc; ++ float scale = 1.0f; + + if (crtc_assignment->mode == NULL) + continue; + + to_disable_crtcs = g_list_remove (to_disable_crtcs, crtc); + +- width = MAX (width, (int) roundf (crtc_assignment->layout.origin.x + +- crtc_assignment->layout.size.width)); +- height = MAX (height, (int) roundf (crtc_assignment->layout.origin.y + +- crtc_assignment->layout.size.height)); ++ if (have_scaling && scale_mode == META_X11_SCALE_MODE_UI_DOWN) ++ { ++ scale = (ceilf (max_scale) / crtc_assignment->scale) * ++ crtc_assignment->scale; ++ } ++ ++ width = MAX (width, ++ (int) roundf (crtc_assignment->layout.origin.x + ++ crtc_assignment->layout.size.width * scale)); ++ height = MAX (height, ++ (int) roundf (crtc_assignment->layout.origin.y + ++ crtc_assignment->layout.size.height * scale)); ++ ++ avg_screen_scale += (crtc_assignment->scale - avg_screen_scale) / ++ (float) (++valid_crtcs); + } + + /* Second disable all newly disabled CRTCs, or CRTCs that in the previous +@@ -433,6 +638,10 @@ apply_crtc_assignments (MetaMonitorManager *manager, + 0, 0, XCB_NONE, + XCB_RANDR_ROTATION_ROTATE_0, + NULL, 0); ++ if (have_scaling) ++ meta_crtc_xrandr_set_scale (crtc, ++ (xcb_randr_crtc_t) meta_crtc_get_id (crtc), ++ 1.0f); + + meta_crtc_unset_config (crtc); + } +@@ -453,6 +662,10 @@ apply_crtc_assignments (MetaMonitorManager *manager, + 0, 0, XCB_NONE, + XCB_RANDR_ROTATION_ROTATE_0, + NULL, 0); ++ if (have_scaling) ++ meta_crtc_xrandr_set_scale (crtc, ++ (xcb_randr_crtc_t) meta_crtc_get_id (crtc), ++ 1.0f); + + meta_crtc_unset_config (crtc); + } +@@ -460,17 +673,12 @@ apply_crtc_assignments (MetaMonitorManager *manager, + if (!n_crtcs) + goto out; + +- g_assert (width > 0 && height > 0); +- /* The 'physical size' of an X screen is meaningless if that screen +- * can consist of many monitors. So just pick a size that make the +- * dpi 96. +- * +- * Firefox and Evince apparently believe what X tells them. +- */ +- width_mm = (width / DPI_FALLBACK) * 25.4 + 0.5; +- height_mm = (height / DPI_FALLBACK) * 25.4 + 0.5; +- XRRSetScreenSize (manager_xrandr->xdisplay, DefaultRootWindow (manager_xrandr->xdisplay), +- width, height, width_mm, height_mm); ++ if (width > manager->screen_width || height > manager->screen_height) ++ { ++ meta_monitor_manager_xrandr_update_screen_size (manager_xrandr, ++ width, height, ++ avg_screen_scale); ++ } + + for (i = 0; i < n_crtcs; i++) + { +@@ -486,12 +694,21 @@ apply_crtc_assignments (MetaMonitorManager *manager, + int x, y; + xcb_randr_rotation_t rotation; + xcb_randr_mode_t mode; ++ float scale = 1.0f; + + crtc_mode = crtc_assignment->mode; + + n_output_ids = crtc_assignment->outputs->len; + output_ids = g_new (xcb_randr_output_t, n_output_ids); + ++ if (have_scaling && scale_mode != META_X11_SCALE_MODE_NONE) ++ { ++ scale = crtc_assignment->scale; ++ ++ if (scale_mode == META_X11_SCALE_MODE_UI_DOWN) ++ scale /= ceilf (max_scale); ++ } ++ + for (j = 0; j < n_output_ids; j++) + { + MetaOutput *output; +@@ -516,6 +733,14 @@ apply_crtc_assignments (MetaMonitorManager *manager, + rotation = + meta_monitor_transform_to_xrandr (crtc_assignment->transform); + mode = meta_crtc_mode_get_id (crtc_mode); ++ ++ if (have_scaling && ++ !meta_crtc_xrandr_set_scale (crtc, crtc_id, scale)) ++ { ++ meta_warning ("Scalig CRTC %d at %f failed\n", ++ (unsigned) crtc_id, scale); ++ } ++ + if (!xrandr_set_crtc_config (manager_xrandr, + crtc, + save_timestamp, +@@ -544,6 +769,20 @@ apply_crtc_assignments (MetaMonitorManager *manager, + &crtc_assignment->layout, + crtc_mode, + crtc_assignment->transform); ++ meta_crtc_set_config_scale (crtc, crtc_assignment->scale); ++ ++ if (have_scaling && scale_mode == META_X11_SCALE_MODE_UI_DOWN) ++ { ++ const MetaCrtcConfig *crtc_config = meta_crtc_get_config (crtc); ++ graphene_size_t *crtc_size = ++ (graphene_size_t *) &crtc_config->layout.size; ++ ++ scale = (ceilf (max_scale) / crtc_assignment->scale) * ++ crtc_assignment->scale; ++ ++ crtc_size->width = roundf (crtc_size->width * scale); ++ crtc_size->height = roundf (crtc_size->height * scale); ++ } + } + } + +@@ -559,6 +798,13 @@ apply_crtc_assignments (MetaMonitorManager *manager, + (GFunc) meta_output_unassign_crtc, + NULL); + ++ if (width > 0 && height > 0) ++ { ++ meta_monitor_manager_xrandr_update_screen_size (manager_xrandr, ++ width, height, ++ avg_screen_scale); ++ } ++ + out: + XUngrabServer (manager_xrandr->xdisplay); + XFlush (manager_xrandr->xdisplay); +@@ -585,14 +831,88 @@ meta_monitor_manager_xrandr_ensure_initial_config (MetaMonitorManager *manager) + } + + static void +-meta_monitor_manager_xrandr_rebuild_derived (MetaMonitorManager *manager, +- MetaMonitorsConfig *config) ++meta_monitor_manager_xrandr_update_screen_size_derived (MetaMonitorManager *manager, ++ MetaMonitorsConfig *config) + { + MetaMonitorManagerXrandr *manager_xrandr = + META_MONITOR_MANAGER_XRANDR (manager); ++ MetaBackend *backend = meta_monitor_manager_get_backend (manager); ++ MetaSettings *settings = meta_backend_get_settings (backend); ++ MetaX11ScaleMode scale_mode = meta_settings_get_x11_scale_mode (settings); ++ int screen_width = 0; ++ int screen_height = 0; ++ unsigned n_crtcs = 0; ++ float average_scale = 0; ++ gboolean have_scaling; ++ GList *l; ++ ++ have_scaling = meta_monitor_manager_get_capabilities (manager) & ++ META_MONITOR_MANAGER_CAPABILITY_NATIVE_OUTPUT_SCALING; ++ ++ /* Compute the new size of the screen (framebuffer) */ ++ for (l = manager->monitors; l != NULL; l = l->next) ++ { ++ MetaMonitor *monitor = l->data; ++ MetaOutput *output = meta_monitor_get_main_output (monitor); ++ MetaCrtc *crtc = meta_output_get_assigned_crtc (output); ++ const MetaCrtcConfig *crtc_config; ++ const graphene_rect_t *crtc_layout; ++ float scale = 1.0f; ++ ++ if (!crtc) ++ continue; + +- g_clear_pointer (&manager_xrandr->supported_scales, g_free); +- meta_monitor_manager_rebuild_derived (manager, config); ++ crtc_config = meta_crtc_get_config (crtc); ++ ++ if (!crtc_config) ++ continue; ++ ++ if (!have_scaling || scale_mode != META_X11_SCALE_MODE_UI_DOWN) ++ { ++ /* When scaling up we should not reduce the screen size, or X will ++ * fail miserably, while we must do it when scaling down, in order to ++ * increase the available screen area we can use. */ ++ scale = crtc_config->scale > 1.0f ? crtc_config->scale : 1.0f; ++ } ++ ++ /* When computing the screen size from the crtc rects we don't have to ++ * use inverted values when monitors are rotated, because this is already ++ * taken in account in the crtc rectangles */ ++ crtc_layout = &crtc_config->layout; ++ screen_width = MAX (screen_width, crtc_layout->origin.x + ++ roundf (crtc_layout->size.width * scale)); ++ screen_height = MAX (screen_height, crtc_layout->origin.y + ++ roundf (crtc_layout->size.height * scale)); ++ ++n_crtcs; ++ ++ /* This value isn't completely exact, since it doesn't take care of the ++ * actual crtc sizes, however, since w're going to use this only to set ++ * the MM size of the screen, and given that this value is just an ++ * estimation, we don't need to be super precise. */ ++ average_scale += (crtc_config->scale - average_scale) / (float) n_crtcs; ++ } ++ ++ if (screen_width > 0 && screen_height > 0) ++ { ++ meta_monitor_manager_xrandr_update_screen_size (manager_xrandr, ++ screen_width, ++ screen_height, ++ average_scale); ++ } ++} ++ ++static void ++maybe_update_ui_scaling_factor (MetaMonitorManager *manager, ++ MetaMonitorsConfig *config) ++{ ++ if (config->layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL || ++ manager->layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL) ++ { ++ MetaBackend *backend = meta_monitor_manager_get_backend (manager); ++ MetaSettings *settings = meta_backend_get_settings (backend); ++ ++ meta_settings_update_ui_scaling_factor (settings); ++ } + } + + static gboolean +@@ -609,7 +929,7 @@ meta_monitor_manager_xrandr_apply_monitors_config (MetaMonitorManager *mana + if (!manager->in_init) + apply_crtc_assignments (manager, TRUE, NULL, 0, NULL, 0); + +- meta_monitor_manager_xrandr_rebuild_derived (manager, NULL); ++ meta_monitor_manager_rebuild_derived (manager, NULL); + return TRUE; + } + +@@ -621,6 +941,8 @@ meta_monitor_manager_xrandr_apply_monitors_config (MetaMonitorManager *mana + + if (method != META_MONITORS_CONFIG_METHOD_VERIFY) + { ++ gboolean weak_change = FALSE; ++ + /* + * If the assignment has not changed, we won't get any notification about + * any new configuration from the X server; but we still need to update +@@ -628,12 +950,16 @@ meta_monitor_manager_xrandr_apply_monitors_config (MetaMonitorManager *mana + * have changed locally, such as the logical monitors scale. This means we + * must check that our new assignment actually changes anything, otherwise + * just update the logical state. ++ * If we record a weak change it means that only UI scaling needs to be ++ * updated and so that we don't have to reconfigure the CRTCs, but still ++ * need to update the logical state. + */ + if (is_assignments_changed (manager, + (MetaCrtcAssignment **) crtc_assignments->pdata, + crtc_assignments->len, + (MetaOutputAssignment **) output_assignments->pdata, +- output_assignments->len)) ++ output_assignments->len, ++ &weak_change)) + { + apply_crtc_assignments (manager, + TRUE, +@@ -641,10 +967,14 @@ meta_monitor_manager_xrandr_apply_monitors_config (MetaMonitorManager *mana + crtc_assignments->len, + (MetaOutputAssignment **) output_assignments->pdata, + output_assignments->len); ++ maybe_update_ui_scaling_factor (manager, config); + } + else + { +- meta_monitor_manager_xrandr_rebuild_derived (manager, config); ++ if (weak_change) ++ maybe_update_ui_scaling_factor (manager, config); ++ ++ meta_monitor_manager_rebuild_derived (manager, config); + } + } + +@@ -777,7 +1107,8 @@ meta_monitor_manager_xrandr_tiled_monitor_added (MetaMonitorManager *manager, + GList *l; + int i; + +- if (manager_xrandr->has_randr15 == FALSE) ++ if (!(meta_monitor_manager_get_capabilities (manager) & ++ META_MONITOR_MANAGER_CAPABILITY_TILING)) + return; + + product = meta_monitor_get_product (monitor); +@@ -826,7 +1157,8 @@ meta_monitor_manager_xrandr_tiled_monitor_removed (MetaMonitorManager *manager, + + int monitor_count; + +- if (manager_xrandr->has_randr15 == FALSE) ++ if (!(meta_monitor_manager_get_capabilities (manager) & ++ META_MONITOR_MANAGER_CAPABILITY_TILING)) + return; + + monitor_xrandr_data = meta_monitor_xrandr_data_from_monitor (monitor); +@@ -844,10 +1176,12 @@ meta_monitor_manager_xrandr_tiled_monitor_removed (MetaMonitorManager *manager, + static void + meta_monitor_manager_xrandr_init_monitors (MetaMonitorManagerXrandr *manager_xrandr) + { ++ MetaMonitorManager *manager = META_MONITOR_MANAGER (manager_xrandr); + XRRMonitorInfo *m; + int n, i; + +- if (manager_xrandr->has_randr15 == FALSE) ++ if (!(meta_monitor_manager_get_capabilities (manager) & ++ META_MONITOR_MANAGER_CAPABILITY_TILING)) + return; + + /* delete any tiled monitors setup, as mutter will want to recreate +@@ -879,83 +1213,26 @@ meta_monitor_manager_xrandr_is_transform_handled (MetaMonitorManager *manager, + return TRUE; + } + +-static float +-meta_monitor_manager_xrandr_calculate_monitor_mode_scale (MetaMonitorManager *manager, +- MetaMonitor *monitor, +- MetaMonitorMode *monitor_mode) +-{ +- return meta_monitor_calculate_mode_scale (monitor, monitor_mode); +-} +- +-static void +-add_supported_scale (GArray *supported_scales, +- float scale) ++static MetaMonitorScalesConstraint ++get_scale_constraints (MetaMonitorManager *manager) + { +- unsigned int i; ++ MetaMonitorScalesConstraint constraints = 0; + +- for (i = 0; i < supported_scales->len; i++) +- { +- float supported_scale = g_array_index (supported_scales, float, i); +- +- if (scale == supported_scale) +- return; +- } +- +- g_array_append_val (supported_scales, scale); +-} +- +-static int +-compare_scales (gconstpointer a, +- gconstpointer b) +-{ +- float f = *(float *) a - *(float *) b; ++ if (meta_monitor_manager_get_capabilities (manager) & ++ META_MONITOR_MANAGER_CAPABILITY_GLOBAL_SCALE_REQUIRED) ++ constraints |= META_MONITOR_SCALES_CONSTRAINT_NO_FRAC; + +- if (f < 0) +- return -1; +- if (f > 0) +- return 1; +- return 0; ++ return constraints; + } + +-static void +-ensure_supported_monitor_scales (MetaMonitorManager *manager) ++static float ++meta_monitor_manager_xrandr_calculate_monitor_mode_scale (MetaMonitorManager *manager, ++ MetaLogicalMonitorLayoutMode layout_mode, ++ MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode) + { +- MetaMonitorManagerXrandr *manager_xrandr = +- META_MONITOR_MANAGER_XRANDR (manager); +- MetaMonitorScalesConstraint constraints; +- GList *l; +- GArray *supported_scales; +- +- if (manager_xrandr->supported_scales) +- return; +- +- constraints = META_MONITOR_SCALES_CONSTRAINT_NO_FRAC; +- supported_scales = g_array_new (FALSE, FALSE, sizeof (float)); +- +- for (l = manager->monitors; l; l = l->next) +- { +- MetaMonitor *monitor = l->data; +- MetaMonitorMode *monitor_mode; +- float *monitor_scales; +- int n_monitor_scales; +- int i; +- +- monitor_mode = meta_monitor_get_preferred_mode (monitor); +- monitor_scales = +- meta_monitor_calculate_supported_scales (monitor, +- monitor_mode, +- constraints, +- &n_monitor_scales); +- +- for (i = 0; i < n_monitor_scales; i++) +- add_supported_scale (supported_scales, monitor_scales[i]); +- g_array_sort (supported_scales, compare_scales); +- g_free (monitor_scales); +- } +- +- manager_xrandr->supported_scales = (float *) supported_scales->data; +- manager_xrandr->n_supported_scales = supported_scales->len; +- g_array_free (supported_scales, FALSE); ++ return meta_monitor_calculate_mode_scale (monitor, monitor_mode, ++ get_scale_constraints (manager)); + } + + static float * +@@ -996,9 +1291,41 @@ meta_monitor_manager_xrandr_get_max_screen_size (MetaMonitorManager *manager, + return TRUE; + } + ++static void ++scale_mode_changed (MetaSettings *settings, ++ MetaMonitorManager *manager) ++{ ++ if (!(meta_monitor_manager_get_capabilities (manager) & ++ META_MONITOR_MANAGER_CAPABILITY_NATIVE_OUTPUT_SCALING)) ++ return; ++ ++ if (!meta_settings_is_experimental_feature_enabled (settings, ++ META_EXPERIMENTAL_FEATURE_X11_RANDR_FRACTIONAL_SCALING)) ++ return; ++ ++ meta_monitor_manager_reconfigure (manager); ++ meta_settings_update_ui_scaling_factor (settings); ++} ++ + static MetaLogicalMonitorLayoutMode + meta_monitor_manager_xrandr_get_default_layout_mode (MetaMonitorManager *manager) + { ++ MetaMonitorManagerCapability capabilities = ++ meta_monitor_manager_get_capabilities (manager); ++ ++ if ((capabilities & META_MONITOR_MANAGER_CAPABILITY_NATIVE_OUTPUT_SCALING) && ++ (capabilities & META_MONITOR_MANAGER_CAPABILITY_LAYOUT_MODE)) ++ { ++ MetaBackend *backend = meta_monitor_manager_get_backend (manager); ++ MetaSettings *settings = meta_backend_get_settings (backend); ++ MetaX11ScaleMode scale_mode = meta_settings_get_x11_scale_mode (settings); ++ ++ if (scale_mode == META_X11_SCALE_MODE_UI_DOWN) ++ return META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL; ++ else if (scale_mode == META_X11_SCALE_MODE_UP) ++ return META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL; ++ } ++ + return META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL; + } + +@@ -1017,6 +1344,7 @@ meta_monitor_manager_xrandr_constructed (GObject *object) + MetaMonitorManager *manager = META_MONITOR_MANAGER (manager_xrandr); + MetaBackend *backend = meta_monitor_manager_get_backend (manager); + MetaBackendX11 *backend_x11 = META_BACKEND_X11 (backend); ++ MetaSettings *settings = meta_backend_get_settings (backend); + + manager_xrandr->xdisplay = meta_backend_x11_get_xdisplay (backend_x11); + +@@ -1037,19 +1365,19 @@ meta_monitor_manager_xrandr_constructed (GObject *object) + | RRCrtcChangeNotifyMask + | RROutputPropertyNotifyMask); + +- manager_xrandr->has_randr15 = FALSE; + XRRQueryVersion (manager_xrandr->xdisplay, &major_version, + &minor_version); +- if (major_version > 1 || +- (major_version == 1 && +- minor_version >= 5)) +- { +- manager_xrandr->has_randr15 = TRUE; +- manager_xrandr->tiled_monitor_atoms = g_hash_table_new (NULL, NULL); +- } ++ manager_xrandr->randr_version = RANDR_VERSION_FORMAT (major_version, ++ minor_version); ++ if (manager_xrandr->randr_version >= RANDR_TILING_MIN_VERSION) ++ manager_xrandr->tiled_monitor_atoms = g_hash_table_new (NULL, NULL); ++ + meta_monitor_manager_xrandr_init_monitors (manager_xrandr); + } + ++ g_signal_connect_object (settings, "x11-scale-mode-changed", ++ G_CALLBACK (scale_mode_changed), manager_xrandr, 0); ++ + G_OBJECT_CLASS (meta_monitor_manager_xrandr_parent_class)->constructed (object); + } + +@@ -1082,6 +1409,7 @@ meta_monitor_manager_xrandr_class_init (MetaMonitorManagerXrandrClass *klass) + manager_class->read_current_state = meta_monitor_manager_xrandr_read_current_state; + manager_class->ensure_initial_config = meta_monitor_manager_xrandr_ensure_initial_config; + manager_class->apply_monitors_config = meta_monitor_manager_xrandr_apply_monitors_config; ++ manager_class->update_screen_size_derived = meta_monitor_manager_xrandr_update_screen_size_derived; + manager_class->set_power_save_mode = meta_monitor_manager_xrandr_set_power_save_mode; + manager_class->change_backlight = meta_monitor_manager_xrandr_change_backlight; + manager_class->get_crtc_gamma = meta_monitor_manager_xrandr_get_crtc_gamma; +@@ -1275,21 +1275,38 @@ meta_monitor_manager_xrandr_calculate_supported_scales (MetaMonitorManager + MetaMonitorMode *monitor_mode, + int *n_supported_scales) + { +- MetaMonitorManagerXrandr *manager_xrandr = +- META_MONITOR_MANAGER_XRANDR (manager); +- +- ensure_supported_monitor_scales (manager); +- +- *n_supported_scales = manager_xrandr->n_supported_scales; +- return g_memdup2 (manager_xrandr->supported_scales, +- manager_xrandr->n_supported_scales * sizeof (float)); ++ return meta_monitor_calculate_supported_scales (monitor, monitor_mode, ++ get_scale_constraints (manager), ++ n_supported_scales); + } + + static MetaMonitorManagerCapability + meta_monitor_manager_xrandr_get_capabilities (MetaMonitorManager *manager) + { +- return (META_MONITOR_MANAGER_CAPABILITY_GLOBAL_SCALE_REQUIRED | +- META_MONITOR_MANAGER_CAPABILITY_CAN_DERIVE_CURRENT); ++ MetaMonitorManagerCapability capabilities; ++ MetaMonitorManagerXrandr *xrandr_manager = META_MONITOR_MANAGER_XRANDR (manager); ++ MetaBackend *backend = meta_monitor_manager_get_backend (manager); ++ MetaSettings *settings = meta_backend_get_settings (backend); ++ ++ capabilities = META_MONITOR_MANAGER_CAPABILITY_NONE; ++ ++ if (xrandr_manager->randr_version >= RANDR_TILING_MIN_VERSION) ++ capabilities |= META_MONITOR_MANAGER_CAPABILITY_TILING; ++ ++ if (xrandr_manager->randr_version >= RANDR_TRANSFORM_MIN_VERSION) ++ capabilities |= META_MONITOR_MANAGER_CAPABILITY_NATIVE_OUTPUT_SCALING; ++ ++ if (meta_settings_is_experimental_feature_enabled (settings, ++ META_EXPERIMENTAL_FEATURE_X11_RANDR_FRACTIONAL_SCALING)) ++ { ++ capabilities |= META_MONITOR_MANAGER_CAPABILITY_LAYOUT_MODE; ++ } ++ else ++ { ++ capabilities |= META_MONITOR_MANAGER_CAPABILITY_GLOBAL_SCALE_REQUIRED; ++ } ++ ++ return capabilities; + } + + static gboolean +@@ -1465,7 +1482,7 @@ meta_monitor_manager_xrandr_finalize (GObject *object) + MetaMonitorManagerXrandr *manager_xrandr = META_MONITOR_MANAGER_XRANDR (object); + + g_hash_table_destroy (manager_xrandr->tiled_monitor_atoms); +- g_free (manager_xrandr->supported_scales); ++ + + if (manager_xrandr->logind_watch_id > 0) + g_bus_unwatch_name (manager_xrandr->logind_watch_id); +@@ -1575,7 +1592,7 @@ meta_monitor_manager_xrandr_update (MetaMonitorManagerXrandr *manager_xrandr) + config = NULL; + } + +- meta_monitor_manager_xrandr_rebuild_derived (manager, config); ++ meta_monitor_manager_rebuild_derived (manager, config); + } + } + +diff --git a/src/backends/x11/meta-monitor-manager-xrandr.h b/src/backends/x11/meta-monitor-manager-xrandr.h +index dc75134..aa385f9 100644 +--- a/src/backends/x11/meta-monitor-manager-xrandr.h ++++ b/src/backends/x11/meta-monitor-manager-xrandr.h +@@ -35,11 +35,13 @@ Display * meta_monitor_manager_xrandr_get_xdisplay (MetaMonitorManagerXrandr *ma + + gboolean meta_monitor_manager_xrandr_has_randr (MetaMonitorManagerXrandr *manager_xrandr); + +-gboolean meta_monitor_manager_xrandr_has_randr15 (MetaMonitorManagerXrandr *manager_xrandr); +- + gboolean meta_monitor_manager_xrandr_handle_xevent (MetaMonitorManagerXrandr *manager, + XEvent *event); ++ ++uint32_t meta_monitor_manager_xrandr_get_config_timestamp (MetaMonitorManagerXrandr *manager); + + void meta_monitor_manager_xrandr_update_dpms_state (MetaMonitorManagerXrandr *manager_xrandr); + ++ ++ + #endif /* META_MONITOR_MANAGER_XRANDR_H */ +diff --git a/src/backends/x11/meta-output-xrandr.c b/src/backends/x11/meta-output-xrandr.c +index 7265624..1d3da34 100644 +--- a/src/backends/x11/meta-output-xrandr.c ++++ b/src/backends/x11/meta-output-xrandr.c +@@ -915,7 +915,8 @@ meta_output_xrandr_new (MetaGpuXrandr *gpu_xrandr, + output_info->height_mm = xrandr_output->mm_height; + } + +- if (meta_monitor_manager_xrandr_has_randr15 (monitor_manager_xrandr)) ++ if ((meta_monitor_manager_get_capabilities (monitor_manager) & ++ META_MONITOR_MANAGER_CAPABILITY_TILING)) + output_info_init_tile_info (output_info, xdisplay, output_id); + output_info_init_modes (output_info, gpu, xrandr_output); + output_info_init_crtcs (output_info, gpu, xrandr_output); +diff --git a/src/compositor/meta-compositor-x11.c b/src/compositor/meta-compositor-x11.c +index 1d0ba4c..70017ca 100644 +--- a/src/compositor/meta-compositor-x11.c ++++ b/src/compositor/meta-compositor-x11.c +@@ -31,6 +31,8 @@ + #include "compositor/meta-sync-ring.h" + #include "compositor/meta-window-actor-x11.h" + #include "core/display-private.h" ++#include "core/window-private.h" ++#include "core/window-private.h" + #include "core/stack-tracker.h" + #include "core/stereo.h" + #include "x11/meta-x11-display-private.h" +@@ -52,6 +54,8 @@ struct _MetaCompositorX11 + gboolean xserver_uses_monotonic_clock; + int64_t xserver_time_query_time_us; + int64_t xserver_time_offset_us; ++ ++ gboolean randr_scale_disabled; + + int glx_opcode; + gboolean stereo_tree_ext; +@@ -267,20 +270,91 @@ shape_cow_for_window (MetaCompositorX11 *compositor_x11, + } + } + ++static void ++on_redirected_monitor_changed (MetaWindow *window, ++ int old_monitor, ++ MetaCompositorX11 *compositor_x11) ++{ ++ MetaBackend *backend = meta_get_backend (); ++ MetaMonitorManager *monitor_manager = ++ meta_backend_get_monitor_manager (backend); ++ ++ if (old_monitor >= 0 && window->monitor && ++ window->monitor->number != old_monitor) ++ { ++ g_signal_handlers_block_by_func (window, ++ on_redirected_monitor_changed, ++ compositor_x11); ++ ++ if (!compositor_x11->randr_scale_disabled) ++ { ++ compositor_x11->randr_scale_disabled = ++ meta_monitor_manager_disable_scale_for_monitor (monitor_manager, ++ window->monitor); ++ } ++ ++ g_signal_handlers_unblock_by_func (window, ++ on_redirected_monitor_changed, ++ compositor_x11); ++ } ++ else ++ shape_cow_for_window (META_COMPOSITOR_X11 (compositor_x11), window); ++} ++ ++static MetaWindow * ++get_unredirectable_window (MetaCompositorX11 *compositor_x11) ++{ ++ MetaCompositor *compositor = META_COMPOSITOR (compositor_x11); ++ MetaWindowActor *window_actor; ++ MetaWindowActorX11 *window_actor_x11; ++ ++ window_actor = meta_compositor_get_top_window_actor (compositor); ++ if (!window_actor) ++ return NULL; ++ ++ window_actor_x11 = META_WINDOW_ACTOR_X11 (window_actor); ++ if (!meta_window_actor_x11_should_unredirect (window_actor_x11)) ++ return NULL; ++ ++ return meta_window_actor_get_meta_window (window_actor); ++} ++ + static void + set_unredirected_window (MetaCompositorX11 *compositor_x11, + MetaWindow *window) + { ++ MetaBackend *backend; ++ MetaMonitorManager *monitor_manager; + MetaWindow *prev_unredirected_window = compositor_x11->unredirected_window; + + if (prev_unredirected_window == window) +- return; ++ { ++ if (!window && compositor_x11->randr_scale_disabled && ++ !get_unredirectable_window (compositor_x11)) ++ { ++ backend = meta_get_backend (); ++ monitor_manager = meta_backend_get_monitor_manager (backend); ++ ++ compositor_x11->randr_scale_disabled = ++ meta_monitor_manager_disable_scale_for_monitor (monitor_manager, ++ NULL); ++ } ++ ++ return; ++ } ++ ++ backend = meta_get_backend (); ++ monitor_manager = meta_backend_get_monitor_manager (backend); + + if (prev_unredirected_window) + { + MetaWindowActor *window_actor; + MetaWindowActorX11 *window_actor_x11; + ++ g_signal_handlers_disconnect_by_func (prev_unredirected_window, ++ on_redirected_monitor_changed, ++ compositor_x11); ++ + window_actor = meta_window_actor_from_window (prev_unredirected_window); + window_actor_x11 = META_WINDOW_ACTOR_X11 (window_actor); + meta_window_actor_x11_set_unredirected (window_actor_x11, FALSE); +@@ -294,6 +368,17 @@ set_unredirected_window (MetaCompositorX11 *compositor_x11, + MetaWindowActor *window_actor; + MetaWindowActorX11 *window_actor_x11; + ++ if (!compositor_x11->randr_scale_disabled) ++ { ++ compositor_x11->randr_scale_disabled = ++ meta_monitor_manager_disable_scale_for_monitor (monitor_manager, ++ window->monitor); ++ } ++ ++ g_signal_connect_object (window, "monitor-changed", ++ G_CALLBACK (on_redirected_monitor_changed), ++ compositor_x11, 0); ++ + window_actor = meta_window_actor_from_window (window); + window_actor_x11 = META_WINDOW_ACTOR_X11 (window_actor); + meta_window_actor_x11_set_unredirected (window_actor_x11, TRUE); +@@ -305,21 +390,11 @@ maybe_unredirect_top_window (MetaCompositorX11 *compositor_x11) + { + MetaCompositor *compositor = META_COMPOSITOR (compositor_x11); + MetaWindow *window_to_unredirect = NULL; +- MetaWindowActor *window_actor; +- MetaWindowActorX11 *window_actor_x11; + + if (meta_compositor_is_unredirect_inhibited (compositor)) + goto out; + +- window_actor = meta_compositor_get_top_window_actor (compositor); +- if (!window_actor) +- goto out; +- +- window_actor_x11 = META_WINDOW_ACTOR_X11 (window_actor); +- if (!meta_window_actor_x11_should_unredirect (window_actor_x11)) +- goto out; +- +- window_to_unredirect = meta_window_actor_get_meta_window (window_actor); ++ window_to_unredirect = get_unredirectable_window (compositor_x11); + + out: + set_unredirected_window (compositor_x11, window_to_unredirect); +diff --git a/src/core/boxes-private.h b/src/core/boxes-private.h +index d398164..482b0e5 100644 +--- a/src/core/boxes-private.h ++++ b/src/core/boxes-private.h +@@ -158,6 +158,10 @@ gboolean meta_rectangle_overlaps_with_region ( + const GList *spanning_rects, + const MetaRectangle *rect); + ++gboolean meta_rectangle_has_adjacent_in_region ( ++ const GList *spanning_rects, ++ const MetaRectangle *rect); ++ + /* Make the rectangle small enough to fit into one of the spanning_rects, + * but make it no smaller than min_size. + */ +diff --git a/src/core/boxes.c b/src/core/boxes.c +index 9a9633e..afefba7 100644 +--- a/src/core/boxes.c ++++ b/src/core/boxes.c +@@ -899,6 +899,27 @@ meta_rectangle_overlaps_with_region (const GList *spanning_rects, + return overlaps; + } + ++gboolean ++meta_rectangle_has_adjacent_in_region (const GList *spanning_rects, ++ const MetaRectangle *rect) ++{ ++ const GList *l; ++ ++ for (l = spanning_rects; l; l = l->next) ++ { ++ MetaRectangle *other = (MetaRectangle *) l->data; ++ ++ if (rect == other || meta_rectangle_equal (rect, other)) ++ continue; ++ ++ if (meta_rectangle_is_adjacent_to ((MetaRectangle *) rect, other)) ++ { ++ return TRUE; ++ } ++ } ++ ++ return FALSE; ++} + + void + meta_rectangle_clamp_to_fit_into_region (const GList *spanning_rects, +diff --git a/src/core/window.c b/src/core/window.c +index ea56f33..d2e7698 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -227,6 +227,7 @@ enum + UNMANAGED, + SIZE_CHANGED, + POSITION_CHANGED, ++ MONITOR_CHANGED, + SHOWN, + + LAST_SIGNAL +@@ -683,6 +684,21 @@ meta_window_class_init (MetaWindowClass *klass) + NULL, NULL, NULL, + G_TYPE_NONE, 0); + ++ /** ++ * MetaWindow::monitor-changed: ++ * @window: a #MetaWindow ++ * @old_monitor: the old monitor index or -1 if not known ++ * ++ * This is emitted when the window has changed monitor ++ */ ++ window_signals[MONITOR_CHANGED] = ++ g_signal_new ("monitor-changed", ++ G_TYPE_FROM_CLASS (object_class), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, NULL, NULL, ++ G_TYPE_NONE, 1, G_TYPE_INT); ++ + /** + * MetaWindow::shown: + * @window: a #MetaWindow +@@ -936,6 +952,9 @@ meta_window_main_monitor_changed (MetaWindow *window, + { + META_WINDOW_GET_CLASS (window)->main_monitor_changed (window, old); + ++ g_signal_emit (window, window_signals[MONITOR_CHANGED], 0, ++ old ? old->number : -1); ++ + if (old) + g_signal_emit_by_name (window->display, "window-left-monitor", + old->number, window); +diff --git a/src/org.gnome.Mutter.DisplayConfig.xml b/src/org.gnome.Mutter.DisplayConfig.xml +index 7522652..0843e3a 100644 +--- a/src/org.gnome.Mutter.DisplayConfig.xml ++++ b/src/org.gnome.Mutter.DisplayConfig.xml +@@ -393,6 +393,11 @@ + using the logical monitor scale. + * 2 : physical - the dimension of a logical monitor is derived from + the monitor modes associated with it. ++ * 3 : logical with ui scaling - the dimension of a logical monitor ++ is derived from the monitor modes associated with it, ++ then scaled using the logical monitor scale that is also ++ scaled by the global UI scaling (computed using the maximum ++ ceiled scaling value across the displays). + * "supports-changing-layout-mode" (b): True if the layout mode can be + changed. Absence of this means the + layout mode cannot be changed. +diff --git a/src/tests/meta-monitor-manager-test.c b/src/tests/meta-monitor-manager-test.c +index 5a672c5..31e112a 100644 +--- a/src/tests/meta-monitor-manager-test.c ++++ b/src/tests/meta-monitor-manager-test.c +@@ -290,9 +290,10 @@ meta_monitor_manager_test_is_transform_handled (MetaMonitorManager *manager, + } + + static float +-meta_monitor_manager_test_calculate_monitor_mode_scale (MetaMonitorManager *manager, +- MetaMonitor *monitor, +- MetaMonitorMode *monitor_mode) ++meta_monitor_manager_test_calculate_monitor_mode_scale (MetaMonitorManager *manager, ++ MetaLogicalMonitorLayoutMode layout_mode, ++ MetaMonitor *monitor, ++ MetaMonitorMode *monitor_mode) + { + MetaOutput *output; + MetaOutputTest *output_test; +@@ -319,6 +320,7 @@ meta_monitor_manager_test_calculate_supported_scales (MetaMonitorManager + switch (layout_mode) + { + case META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL: ++ case META_LOGICAL_MONITOR_LAYOUT_MODE_GLOBAL_UI_LOGICAL: + break; + case META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL: + constraints |= META_MONITOR_SCALES_CONSTRAINT_NO_FRAC; +@@ -344,8 +346,9 @@ is_monitor_framebuffer_scaled (void) + static MetaMonitorManagerCapability + meta_monitor_manager_test_get_capabilities (MetaMonitorManager *manager) + { +- MetaMonitorManagerCapability capabilities = +- META_MONITOR_MANAGER_CAPABILITY_NONE; ++ MetaMonitorManagerCapability capabilities; ++ ++ capabilities = META_MONITOR_MANAGER_CAPABILITY_TILING; + + if (is_monitor_framebuffer_scaled ()) + capabilities |= META_MONITOR_MANAGER_CAPABILITY_LAYOUT_MODE; diff --git a/SPECS/mutter.spec b/SPECS/mutter.spec index 36870e0..937cf1d 100644 --- a/SPECS/mutter.spec +++ b/SPECS/mutter.spec @@ -10,7 +10,7 @@ Name: mutter Version: 40.9 -Release: 14%{?dist}.inferit +Release: 22%{?dist}.inferit Summary: Window and compositing manager based on Clutter License: GPLv2+ @@ -111,9 +111,47 @@ Patch44: 0001-backends-Only-apply-EDID-based-tablet-mapping-heuris.patch Patch45: 0001-gpu-kms-Report-that-we-can-have-outputs-if-we-have-c.patch Patch46: 0001-clutter-text-Don-t-query-preferred-size-without-allo.patch +Patch47: 0001-core-Change-MetaWaylandTextInput-event-forwarding-to.patch + +Patch48: 0001-backends-Disambiguate-output-mapped-to-tablet-with-c.patch + +# Backport https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2359 +# Resolves https://issues.redhat.com/browse/RHEL-45198 +Patch49: 0001-kms-impl-device-Add-addfb2_modifiers-to-MetaKmsDevic.patch +Patch50: 0002-kms-device-Disable-modifiers-when-DRM_CAP_ADDFB2_MOD.patch + +# Focus stealing prevention fixes +# Resolves https://issues.redhat.com/browse/RHEL-29537 +Patch51: 0001-window-Don-t-switch-workspaces-if-users-from-forged-.patch +Patch52: 0002-core-events-Count-shell-interactions-has-user-intera.patch +Patch53: 0003-core-window-Split-cgroup-out-to-separate-struct.patch +Patch54: 0004-window-Track-workspace-per-cgroup.patch +Patch55: 0005-core-display-Avoid-placement-heuristcs-for-apps-that.patch +Patch56: 0006-meson-Add-optional-libsystemd-dependency.patch + +# Don't retry cursor plane if failed (RHEL-33720) +Patch57: 0001-cursor-renderer-native-Don-t-retry-forever-after-GBM.patch + +# RHEL-45998 & RHEL-45366 +Patch58: sticky-or-on-top-dialog-fixes.patch + +# RHEL-62988 +Patch59: 0001-wayland-wl-shell-Make-sure-created-window-has-a-prop.patch +Patch60: 0002-window-Avoid-SIGFPE-on-bogus-window-size.patch + +# RHEL-62997, RHEL-63000 +Patch61: 0001-display-Make-cgroup-constructor-local.patch +Patch62: 0002-display-Also-set-window-cgroup-on-cgroup-creation.patch +Patch63: 0003-window-Unregister-cgroup-on-unmanage.patch +Patch64: 0004-window-Don-t-use-cgroup-workspace-if-there-already-i.patch +Patch65: 0005-cgroup-Get-app-info-from-gnome-shell-when-possible.patch + # MSVSphere Patch100: 0001-Update-Russian-translation.patch +# Add scaling support using randr under x11. +Patch101: x11-Add-support-for-fractional-scaling-using-Randr.patch + BuildRequires: chrpath BuildRequires: pango-devel BuildRequires: startup-notification-devel @@ -245,6 +283,7 @@ desktop-file-validate %{buildroot}/%{_datadir}/applications/%{name}.desktop %{_libexecdir}/mutter-restart-helper %{_datadir}/GConf/gsettings/mutter-schemas.convert %{_datadir}/glib-2.0/schemas/org.gnome.mutter.gschema.xml +%{_datadir}/glib-2.0/schemas/org.gnome.mutter.x11.gschema.xml %{_datadir}/glib-2.0/schemas/org.gnome.mutter.wayland.gschema.xml %{_datadir}/gnome-control-center/keybindings/50-mutter-*.xml %{_mandir}/man1/mutter.1* @@ -261,9 +300,50 @@ desktop-file-validate %{buildroot}/%{_datadir}/applications/%{name}.desktop %{_datadir}/mutter-%{mutter_api_version}/tests %changelog -* Mon May 15 2023 Sergey Cherevko - 40.9-14.inferit -- Update Russian translation -- Rebuilt for MSVSphere 9.2. +* Fri Oct 18 2024 Jonas Ådahl - 40.9-22 +- Fix crash when moving window while switching workspace + Resolves: RHEL-62997 +- Improve app discovery for workspace logic + Resolves: RHEL-63000 + +* Fri Oct 18 2024 Jonas Ådahl - 40.9-21 +- Fix regression causing wl-copy to trigger a crash + Resolves: RHEL-62993 + +* Mon Sep 16 2024 Eduard Basov - 40.9-20.inferit +- Added fractional scaling support for X11 + https://gitlab.gnome.org/3v1n0/mutter/commits/xrandr-scaling + Scaling appears in the user interface ( 100%, 125%, 150%, 175%, 200% ) + +* Mon Aug 05 2024 Jonas Ådahl - 40.9-20 +- Fix positioning when using always-on-top windows + Resolves: RHEL-45998 +- Improve handling of always-on-visible-workspace windows + Resolves: RHEL-45366 + +* Mon Aug 05 2024 Jonas Ådahl - 40.9-19 +- Don't retry using cursor plane if it failed + Resolves: RHEL-32622 + +* Mon Jul 29 2024 Ray Strode - 40.9-18 +- Don't allow applications on other workspaces to steal + focus, unless their timestamps are pristine + Resolves: RHEL-29537 + +* Thu Jul 04 2024 José Expósito - 40.9-17 +- Fix Wayland session with Virtio driver + Resolves: RHEL-45198 + +* Tue Feb 06 2024 Carlos Garnacho - 40.9-16 +- Disambiguate output mapped to tablet with connector name + Resolves: RHEL-28535 + +* Mon Jul 10 2023 Carlos Garnacho - 40.9-15 +- Fix ordering of keyboard modifiers relative to other keyboard events + Resolves: #2218146 + +* Fri Apr 14 2023 MSVSphere Packaging Team - 40.9-14 +- Rebuilt for MSVSphere 9.2 beta * Wed Feb 01 2023 Jonas Ådahl ) - 40.9-14 - Allow starting headless again @@ -353,7 +433,7 @@ desktop-file-validate %{buildroot}/%{_datadir}/applications/%{name}.desktop - Fixes a race in wl_seat capabilities Resolves: #2003032 -* Wed Aug 27 2021 Florian Müllner - 40.4-2 +* Fri Aug 27 2021 Florian Müllner - 40.4-2 - Remove firstboot(windowmanager) provide Resolves: #1975355