You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mutter/SOURCES/0005-core-display-Avoid-pla...

557 lines
18 KiB

From 62e4f7153738b404b05b1ee59d088bc52fd86bc0 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
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 <gio/gdesktopappinfo.h>
+
/*
* 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 <systemd/sd-login.h>
#endif
+#include <gio/gdesktopappinfo.h>
+
/* 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