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-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-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-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-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/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/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-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/SPECS/mutter.spec b/SPECS/mutter.spec index 18d8924..03bc4b8 100644 --- a/SPECS/mutter.spec +++ b/SPECS/mutter.spec @@ -10,7 +10,7 @@ Name: mutter Version: 40.9 -Release: 15%{?dist} +Release: 20%{?dist} Summary: Window and compositing manager based on Clutter License: GPLv2+ @@ -113,6 +113,28 @@ 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 + BuildRequires: chrpath BuildRequires: pango-devel BuildRequires: startup-notification-devel @@ -260,6 +282,29 @@ desktop-file-validate %{buildroot}/%{_datadir}/applications/%{name}.desktop %{_datadir}/mutter-%{mutter_api_version}/tests %changelog +* 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