f29dbd1f18
Currently our heuristics for matching a window to its app check for the application ID before the WM_CLASS, as the ID is more reliable in so far that it is outside the application's control and so it cannot use it to spoof a different application. However this also prevents applications with multiple .desktop files like LibreOffice from matching any .desktop files other than the one under the main ID. Since we now no longer allow the WM_CLASS to match a .desktop file that doesn't belong to the sandboxed application, we can fix that issue by checking the WM_CLASS first, without opening the door to spoofing. https://gitlab.gnome.org/GNOME/gnome-shell/issues/219
847 lines
24 KiB
C
847 lines
24 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <meta/display.h>
|
|
#include <meta/group.h>
|
|
#include <meta/util.h>
|
|
#include <meta/window.h>
|
|
#include <meta/meta-workspace-manager.h>
|
|
#include <meta/meta-startup-notification.h>
|
|
|
|
#include "shell-window-tracker-private.h"
|
|
#include "shell-app-private.h"
|
|
#include "shell-global.h"
|
|
#include "st.h"
|
|
|
|
/* This file includes modified code from
|
|
* desktop-data-engine/engine-dbus/hippo-application-monitor.c
|
|
* in the functions collecting application usage data.
|
|
* Written by Owen Taylor, originally licensed under LGPL 2.1.
|
|
* Copyright Red Hat, Inc. 2006-2008
|
|
*/
|
|
|
|
/**
|
|
* SECTION:shell-window-tracker
|
|
* @short_description: Associate windows with applications
|
|
*
|
|
* Maintains a mapping from windows to applications (.desktop file ids).
|
|
* It currently implements this with some heuristics on the WM_CLASS X11
|
|
* property (and some static override regexps); in the future, we want to
|
|
* have it also track through startup-notification.
|
|
*/
|
|
|
|
struct _ShellWindowTracker
|
|
{
|
|
GObject parent;
|
|
|
|
ShellApp *focus_app;
|
|
|
|
/* <MetaWindow * window, ShellApp *app> */
|
|
GHashTable *window_to_app;
|
|
};
|
|
|
|
G_DEFINE_TYPE (ShellWindowTracker, shell_window_tracker, G_TYPE_OBJECT);
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_FOCUS_APP
|
|
};
|
|
|
|
enum {
|
|
STARTUP_SEQUENCE_CHANGED,
|
|
TRACKED_WINDOWS_CHANGED,
|
|
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static void shell_window_tracker_finalize (GObject *object);
|
|
static void set_focus_app (ShellWindowTracker *tracker,
|
|
ShellApp *new_focus_app);
|
|
static void on_focus_window_changed (MetaDisplay *display, GParamSpec *spec, ShellWindowTracker *tracker);
|
|
|
|
static void track_window (ShellWindowTracker *tracker, MetaWindow *window);
|
|
static void disassociate_window (ShellWindowTracker *tracker, MetaWindow *window);
|
|
|
|
static ShellApp * shell_startup_sequence_get_app (MetaStartupSequence *sequence);
|
|
|
|
static void
|
|
shell_window_tracker_get_property (GObject *gobject,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ShellWindowTracker *tracker = SHELL_WINDOW_TRACKER (gobject);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_FOCUS_APP:
|
|
g_value_set_object (value, tracker->focus_app);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
shell_window_tracker_class_init (ShellWindowTrackerClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->get_property = shell_window_tracker_get_property;
|
|
gobject_class->finalize = shell_window_tracker_finalize;
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_FOCUS_APP,
|
|
g_param_spec_object ("focus-app",
|
|
"Focus App",
|
|
"Focused application",
|
|
SHELL_TYPE_APP,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
signals[STARTUP_SEQUENCE_CHANGED] = g_signal_new ("startup-sequence-changed",
|
|
SHELL_TYPE_WINDOW_TRACKER,
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 1, META_TYPE_STARTUP_SEQUENCE);
|
|
signals[TRACKED_WINDOWS_CHANGED] = g_signal_new ("tracked-windows-changed",
|
|
SHELL_TYPE_WINDOW_TRACKER,
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
}
|
|
|
|
static gboolean
|
|
check_app_id_prefix (ShellApp *app,
|
|
const char *prefix)
|
|
{
|
|
if (prefix == NULL)
|
|
return TRUE;
|
|
|
|
return g_str_has_prefix (shell_app_get_id (app), prefix);
|
|
}
|
|
|
|
/*
|
|
* get_app_from_window_wmclass:
|
|
*
|
|
* Looks only at the given window, and attempts to determine
|
|
* an application based on WM_CLASS. If one can't be determined,
|
|
* return %NULL.
|
|
*
|
|
* Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
|
|
*/
|
|
static ShellApp *
|
|
get_app_from_window_wmclass (MetaWindow *window)
|
|
{
|
|
ShellApp *app;
|
|
ShellAppSystem *appsys;
|
|
const char *wm_class;
|
|
const char *wm_instance;
|
|
const char *sandbox_id;
|
|
|
|
appsys = shell_app_system_get_default ();
|
|
sandbox_id = meta_window_get_sandboxed_app_id (window);
|
|
|
|
/* Notes on the heuristics used here:
|
|
much of the complexity here comes from the desire to support
|
|
Chrome apps.
|
|
|
|
From https://bugzilla.gnome.org/show_bug.cgi?id=673657#c13
|
|
|
|
Currently chrome sets WM_CLASS as follows (the first string is the 'instance',
|
|
the second one is the 'class':
|
|
|
|
For the normal browser:
|
|
WM_CLASS(STRING) = "chromium", "Chromium"
|
|
|
|
For a bookmarked page (through 'Tools -> Create application shortcuts')
|
|
WM_CLASS(STRING) = "wiki.gnome.org__GnomeShell_ApplicationBased", "Chromium"
|
|
|
|
For an application from the chrome store (with a .desktop file created through
|
|
right click, "Create shortcuts" from Chrome's apps overview)
|
|
WM_CLASS(STRING) = "crx_blpcfgokakmgnkcojhhkbfbldkacnbeo", "Chromium"
|
|
|
|
The .desktop file has a matching StartupWMClass, but the name differs, e.g. for
|
|
the store app (youtube) there is
|
|
|
|
.local/share/applications/chrome-blpcfgokakmgnkcojhhkbfbldkacnbeo-Default.desktop
|
|
|
|
with
|
|
|
|
StartupWMClass=crx_blpcfgokakmgnkcojhhkbfbldkacnbeo
|
|
|
|
Note that chromium (but not google-chrome!) includes a StartupWMClass=chromium
|
|
in their .desktop file, so we must match the instance first.
|
|
|
|
Also note that in the good case (regular gtk+ app without hacks), instance and
|
|
class are the same except for case and there is no StartupWMClass at all.
|
|
*/
|
|
|
|
/* first try a match from WM_CLASS (instance part) to StartupWMClass */
|
|
wm_instance = meta_window_get_wm_class_instance (window);
|
|
app = shell_app_system_lookup_startup_wmclass (appsys, wm_instance);
|
|
if (app != NULL && check_app_id_prefix (app, sandbox_id))
|
|
return g_object_ref (app);
|
|
|
|
/* then try a match from WM_CLASS to StartupWMClass */
|
|
wm_class = meta_window_get_wm_class (window);
|
|
app = shell_app_system_lookup_startup_wmclass (appsys, wm_class);
|
|
if (app != NULL && check_app_id_prefix (app, sandbox_id))
|
|
return g_object_ref (app);
|
|
|
|
/* then try a match from WM_CLASS (instance part) to .desktop */
|
|
app = shell_app_system_lookup_desktop_wmclass (appsys, wm_instance);
|
|
if (app != NULL && check_app_id_prefix (app, sandbox_id))
|
|
return g_object_ref (app);
|
|
|
|
/* finally, try a match from WM_CLASS to .desktop */
|
|
app = shell_app_system_lookup_desktop_wmclass (appsys, wm_class);
|
|
if (app != NULL && check_app_id_prefix (app, sandbox_id))
|
|
return g_object_ref (app);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* get_app_from_id:
|
|
* @window: a #MetaWindow
|
|
*
|
|
* Looks only at the given window, and attempts to determine
|
|
* an application based on %id. If one can't be determined,
|
|
* return %NULL.
|
|
*
|
|
* Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
|
|
*/
|
|
static ShellApp *
|
|
get_app_from_id (MetaWindow *window,
|
|
const char *id)
|
|
{
|
|
ShellApp *app;
|
|
ShellAppSystem *appsys;
|
|
g_autofree char *desktop_file = NULL;
|
|
|
|
g_return_val_if_fail (id != NULL, NULL);
|
|
|
|
appsys = shell_app_system_get_default ();
|
|
|
|
desktop_file = g_strconcat (id, ".desktop", NULL);
|
|
app = shell_app_system_lookup_app (appsys, desktop_file);
|
|
if (app)
|
|
return g_object_ref (app);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* get_app_from_gapplication_id:
|
|
* @window: a #MetaWindow
|
|
*
|
|
* Looks only at the given window, and attempts to determine
|
|
* an application based on _GTK_APPLICATION_ID. If one can't be determined,
|
|
* return %NULL.
|
|
*
|
|
* Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
|
|
*/
|
|
static ShellApp *
|
|
get_app_from_gapplication_id (MetaWindow *window)
|
|
{
|
|
const char *id;
|
|
|
|
id = meta_window_get_gtk_application_id (window);
|
|
if (!id)
|
|
return NULL;
|
|
|
|
return get_app_from_id (window, id);
|
|
}
|
|
|
|
/*
|
|
* get_app_from_sandboxed_app_id:
|
|
* @window: a #MetaWindow
|
|
*
|
|
* Looks only at the given window, and attempts to determine
|
|
* an application based on its Flatpak or Snap ID. If one can't be determined,
|
|
* return %NULL.
|
|
*
|
|
* Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
|
|
*/
|
|
static ShellApp *
|
|
get_app_from_sandboxed_app_id (MetaWindow *window)
|
|
{
|
|
const char *id;
|
|
|
|
id = meta_window_get_sandboxed_app_id (window);
|
|
if (!id)
|
|
return NULL;
|
|
|
|
return get_app_from_id (window, id);
|
|
}
|
|
|
|
/*
|
|
* get_app_from_window_group:
|
|
* @monitor: a #ShellWindowTracker
|
|
* @window: a #MetaWindow
|
|
*
|
|
* Check other windows in the group for @window to see if we have
|
|
* an application for one of them.
|
|
*
|
|
* Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
|
|
*/
|
|
static ShellApp*
|
|
get_app_from_window_group (ShellWindowTracker *tracker,
|
|
MetaWindow *window)
|
|
{
|
|
ShellApp *result;
|
|
GSList *group_windows;
|
|
MetaGroup *group;
|
|
GSList *iter;
|
|
|
|
group = meta_window_get_group (window);
|
|
if (group == NULL)
|
|
return NULL;
|
|
|
|
group_windows = meta_group_list_windows (group);
|
|
|
|
result = NULL;
|
|
/* Try finding a window in the group of type NORMAL; if we
|
|
* succeed, use that as our source. */
|
|
for (iter = group_windows; iter; iter = iter->next)
|
|
{
|
|
MetaWindow *group_window = iter->data;
|
|
|
|
if (meta_window_get_window_type (group_window) != META_WINDOW_NORMAL)
|
|
continue;
|
|
|
|
result = g_hash_table_lookup (tracker->window_to_app, group_window);
|
|
if (result)
|
|
break;
|
|
}
|
|
|
|
g_slist_free (group_windows);
|
|
|
|
if (result)
|
|
g_object_ref (result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* get_app_from_window_pid:
|
|
* @tracker: a #ShellWindowTracker
|
|
* @window: a #MetaWindow
|
|
*
|
|
* Check if the pid associated with @window corresponds to an
|
|
* application.
|
|
*
|
|
* Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
|
|
*/
|
|
static ShellApp *
|
|
get_app_from_window_pid (ShellWindowTracker *tracker,
|
|
MetaWindow *window)
|
|
{
|
|
ShellApp *result;
|
|
pid_t pid;
|
|
|
|
if (meta_window_is_remote (window))
|
|
return NULL;
|
|
|
|
pid = meta_window_get_pid (window);
|
|
|
|
if (pid < 1)
|
|
return NULL;
|
|
|
|
result = shell_window_tracker_get_app_from_pid (tracker, pid);
|
|
if (result != NULL)
|
|
g_object_ref (result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* get_app_for_window:
|
|
*
|
|
* Determines the application associated with a window, using
|
|
* all available information such as the window's MetaGroup,
|
|
* and what we know about other windows.
|
|
*
|
|
* Returns: (transfer full): a #ShellApp, or NULL if none is found
|
|
*/
|
|
static ShellApp *
|
|
get_app_for_window (ShellWindowTracker *tracker,
|
|
MetaWindow *window)
|
|
{
|
|
ShellApp *result = NULL;
|
|
MetaWindow *transient_for;
|
|
const char *startup_id;
|
|
|
|
transient_for = meta_window_get_transient_for (window);
|
|
if (transient_for != NULL)
|
|
return get_app_for_window (tracker, transient_for);
|
|
|
|
/* First, we check whether we already know about this window,
|
|
* if so, just return that.
|
|
*/
|
|
if (meta_window_get_window_type (window) == META_WINDOW_NORMAL
|
|
|| meta_window_is_remote (window))
|
|
{
|
|
result = g_hash_table_lookup (tracker->window_to_app, window);
|
|
if (result != NULL)
|
|
{
|
|
g_object_ref (result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (meta_window_is_remote (window))
|
|
return _shell_app_new_for_window (window);
|
|
|
|
/* Check if the app's WM_CLASS specifies an app; this is
|
|
* canonical if it does.
|
|
*/
|
|
result = get_app_from_window_wmclass (window);
|
|
if (result != NULL)
|
|
return result;
|
|
|
|
/* Check if the window was opened from within a sandbox; if this
|
|
* is the case, a corresponding .desktop file is guaranteed to match;
|
|
*/
|
|
result = get_app_from_sandboxed_app_id (window);
|
|
if (result != NULL)
|
|
return result;
|
|
|
|
/* Check if the window has a GApplication ID attached; this is
|
|
* canonical if it does
|
|
*/
|
|
result = get_app_from_gapplication_id (window);
|
|
if (result != NULL)
|
|
return result;
|
|
|
|
result = get_app_from_window_pid (tracker, window);
|
|
if (result != NULL)
|
|
return result;
|
|
|
|
/* Now we check whether we have a match through startup-notification */
|
|
startup_id = meta_window_get_startup_id (window);
|
|
if (startup_id)
|
|
{
|
|
GSList *iter, *sequences;
|
|
|
|
sequences = shell_window_tracker_get_startup_sequences (tracker);
|
|
for (iter = sequences; iter; iter = iter->next)
|
|
{
|
|
MetaStartupSequence *sequence = iter->data;
|
|
const char *id = meta_startup_sequence_get_id (sequence);
|
|
if (strcmp (id, startup_id) != 0)
|
|
continue;
|
|
|
|
result = shell_startup_sequence_get_app (sequence);
|
|
if (result)
|
|
{
|
|
result = g_object_ref (result);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If we didn't get a startup-notification match, see if we matched
|
|
* any other windows in the group.
|
|
*/
|
|
if (result == NULL)
|
|
result = get_app_from_window_group (tracker, window);
|
|
|
|
/* Our last resort - we create a fake app from the window */
|
|
if (result == NULL)
|
|
result = _shell_app_new_for_window (window);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
update_focus_app (ShellWindowTracker *self)
|
|
{
|
|
MetaWindow *new_focus_win;
|
|
ShellApp *new_focus_app;
|
|
|
|
new_focus_win = meta_display_get_focus_window (shell_global_get_display (shell_global_get ()));
|
|
|
|
/* we only consider an app focused if the focus window can be clearly
|
|
* associated with a running app; this is the case if the focus window
|
|
* or one of its parents is visible in the taskbar, e.g.
|
|
* - 'nautilus' should appear focused when its about dialog has focus
|
|
* - 'nautilus' should not appear focused when the DESKTOP has focus
|
|
*/
|
|
while (new_focus_win && meta_window_is_skip_taskbar (new_focus_win))
|
|
new_focus_win = meta_window_get_transient_for (new_focus_win);
|
|
|
|
new_focus_app = new_focus_win ? shell_window_tracker_get_window_app (self, new_focus_win) : NULL;
|
|
|
|
if (new_focus_app)
|
|
{
|
|
shell_app_update_window_actions (new_focus_app, new_focus_win);
|
|
shell_app_update_app_actions (new_focus_app, new_focus_win);
|
|
}
|
|
|
|
set_focus_app (self, new_focus_app);
|
|
|
|
g_clear_object (&new_focus_app);
|
|
}
|
|
|
|
static void
|
|
tracked_window_changed (ShellWindowTracker *self,
|
|
MetaWindow *window)
|
|
{
|
|
/* It's simplest to just treat this as a remove + add. */
|
|
disassociate_window (self, window);
|
|
track_window (self, window);
|
|
/* also just recalculate the focused app, in case it was the focused
|
|
window that changed */
|
|
update_focus_app (self);
|
|
}
|
|
|
|
static void
|
|
on_wm_class_changed (MetaWindow *window,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
|
|
tracked_window_changed (self, window);
|
|
}
|
|
|
|
static void
|
|
on_gtk_application_id_changed (MetaWindow *window,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
|
|
tracked_window_changed (self, window);
|
|
}
|
|
|
|
static void
|
|
track_window (ShellWindowTracker *self,
|
|
MetaWindow *window)
|
|
{
|
|
ShellApp *app;
|
|
|
|
app = get_app_for_window (self, window);
|
|
if (!app)
|
|
return;
|
|
|
|
/* At this point we've stored the association from window -> application */
|
|
g_hash_table_insert (self->window_to_app, window, app);
|
|
|
|
g_signal_connect (window, "notify::wm-class", G_CALLBACK (on_wm_class_changed), self);
|
|
g_signal_connect (window, "notify::gtk-application-id", G_CALLBACK (on_gtk_application_id_changed), self);
|
|
|
|
_shell_app_add_window (app, window);
|
|
|
|
g_signal_emit (self, signals[TRACKED_WINDOWS_CHANGED], 0);
|
|
}
|
|
|
|
static void
|
|
shell_window_tracker_on_window_added (MetaWorkspace *workspace,
|
|
MetaWindow *window,
|
|
gpointer user_data)
|
|
{
|
|
ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
|
|
MetaWindowType window_type = meta_window_get_window_type (window);
|
|
|
|
if (window_type == META_WINDOW_NORMAL ||
|
|
window_type == META_WINDOW_DIALOG ||
|
|
window_type == META_WINDOW_UTILITY ||
|
|
window_type == META_WINDOW_MODAL_DIALOG)
|
|
track_window (self, window);
|
|
}
|
|
|
|
static void
|
|
disassociate_window (ShellWindowTracker *self,
|
|
MetaWindow *window)
|
|
{
|
|
ShellApp *app;
|
|
|
|
app = g_hash_table_lookup (self->window_to_app, window);
|
|
if (!app)
|
|
return;
|
|
|
|
g_object_ref (app);
|
|
|
|
g_hash_table_remove (self->window_to_app, window);
|
|
|
|
_shell_app_remove_window (app, window);
|
|
g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_wm_class_changed), self);
|
|
g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_gtk_application_id_changed), self);
|
|
|
|
g_signal_emit (self, signals[TRACKED_WINDOWS_CHANGED], 0);
|
|
|
|
g_object_unref (app);
|
|
}
|
|
|
|
static void
|
|
shell_window_tracker_on_window_removed (MetaWorkspace *workspace,
|
|
MetaWindow *window,
|
|
gpointer user_data)
|
|
{
|
|
disassociate_window (SHELL_WINDOW_TRACKER (user_data), window);
|
|
}
|
|
|
|
static void
|
|
load_initial_windows (ShellWindowTracker *tracker)
|
|
{
|
|
MetaDisplay *display = shell_global_get_display (shell_global_get ());
|
|
MetaWorkspaceManager *workspace_manager =
|
|
meta_display_get_workspace_manager (display);
|
|
GList *workspaces;
|
|
GList *l;
|
|
|
|
workspaces = meta_workspace_manager_get_workspaces (workspace_manager);
|
|
for (l = workspaces; l; l = l->next)
|
|
{
|
|
MetaWorkspace *workspace = l->data;
|
|
GList *windows = meta_workspace_list_windows (workspace);
|
|
GList *window_iter;
|
|
|
|
for (window_iter = windows; window_iter; window_iter = window_iter->next)
|
|
{
|
|
MetaWindow *window = window_iter->data;
|
|
track_window (tracker, window);
|
|
}
|
|
|
|
g_list_free (windows);
|
|
}
|
|
}
|
|
|
|
static void
|
|
shell_window_tracker_on_n_workspaces_changed (MetaWorkspaceManager *workspace_manager,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
|
|
GList *workspaces;
|
|
GList *l;
|
|
|
|
workspaces = meta_workspace_manager_get_workspaces (workspace_manager);
|
|
for (l = workspaces; l; l = l->next)
|
|
{
|
|
MetaWorkspace *workspace = l->data;
|
|
|
|
/* This pair of disconnect/connect is idempotent if we were
|
|
* already connected, while ensuring we get connected for
|
|
* new workspaces.
|
|
*/
|
|
g_signal_handlers_disconnect_by_func (workspace,
|
|
shell_window_tracker_on_window_added,
|
|
self);
|
|
g_signal_handlers_disconnect_by_func (workspace,
|
|
shell_window_tracker_on_window_removed,
|
|
self);
|
|
|
|
g_signal_connect (workspace, "window-added",
|
|
G_CALLBACK (shell_window_tracker_on_window_added), self);
|
|
g_signal_connect (workspace, "window-removed",
|
|
G_CALLBACK (shell_window_tracker_on_window_removed), self);
|
|
}
|
|
}
|
|
|
|
static void
|
|
init_window_tracking (ShellWindowTracker *self)
|
|
{
|
|
MetaDisplay *display = shell_global_get_display (shell_global_get ());
|
|
MetaWorkspaceManager *workspace_manager =
|
|
meta_display_get_workspace_manager (display);
|
|
|
|
g_signal_connect (workspace_manager, "notify::n-workspaces",
|
|
G_CALLBACK (shell_window_tracker_on_n_workspaces_changed), self);
|
|
g_signal_connect (display, "notify::focus-window",
|
|
G_CALLBACK (on_focus_window_changed), self);
|
|
|
|
shell_window_tracker_on_n_workspaces_changed (workspace_manager, NULL, self);
|
|
}
|
|
|
|
static void
|
|
on_startup_sequence_changed (MetaStartupNotification *sn,
|
|
MetaStartupSequence *sequence,
|
|
ShellWindowTracker *self)
|
|
{
|
|
ShellApp *app;
|
|
|
|
app = shell_startup_sequence_get_app (sequence);
|
|
if (app)
|
|
_shell_app_handle_startup_sequence (app, sequence);
|
|
|
|
g_signal_emit (G_OBJECT (self), signals[STARTUP_SEQUENCE_CHANGED], 0, sequence);
|
|
}
|
|
|
|
static void
|
|
shell_window_tracker_init (ShellWindowTracker *self)
|
|
{
|
|
MetaDisplay *display = shell_global_get_display (shell_global_get ());
|
|
MetaStartupNotification *sn = meta_display_get_startup_notification (display);
|
|
|
|
self->window_to_app = g_hash_table_new_full (g_direct_hash, g_direct_equal,
|
|
NULL, (GDestroyNotify) g_object_unref);
|
|
|
|
|
|
g_signal_connect (sn, "changed",
|
|
G_CALLBACK (on_startup_sequence_changed), self);
|
|
|
|
load_initial_windows (self);
|
|
init_window_tracking (self);
|
|
}
|
|
|
|
static void
|
|
shell_window_tracker_finalize (GObject *object)
|
|
{
|
|
ShellWindowTracker *self = SHELL_WINDOW_TRACKER (object);
|
|
|
|
g_hash_table_destroy (self->window_to_app);
|
|
|
|
G_OBJECT_CLASS (shell_window_tracker_parent_class)->finalize(object);
|
|
}
|
|
|
|
/**
|
|
* shell_window_tracker_get_window_app:
|
|
* @tracker: An app monitor instance
|
|
* @metawin: A #MetaWindow
|
|
*
|
|
* Returns: (transfer full): Application associated with window
|
|
*/
|
|
ShellApp *
|
|
shell_window_tracker_get_window_app (ShellWindowTracker *tracker,
|
|
MetaWindow *metawin)
|
|
{
|
|
ShellApp *app;
|
|
|
|
app = g_hash_table_lookup (tracker->window_to_app, metawin);
|
|
if (app)
|
|
g_object_ref (app);
|
|
|
|
return app;
|
|
}
|
|
|
|
|
|
/**
|
|
* shell_window_tracker_get_app_from_pid:
|
|
* @tracker: A #ShellAppSystem
|
|
* @pid: A Unix process identifier
|
|
*
|
|
* Look up the application corresponding to a process.
|
|
*
|
|
* Returns: (transfer none): A #ShellApp, or %NULL if none
|
|
*/
|
|
ShellApp *
|
|
shell_window_tracker_get_app_from_pid (ShellWindowTracker *tracker,
|
|
int pid)
|
|
{
|
|
GSList *running = shell_app_system_get_running (shell_app_system_get_default());
|
|
GSList *iter;
|
|
ShellApp *result = NULL;
|
|
|
|
for (iter = running; iter; iter = iter->next)
|
|
{
|
|
ShellApp *app = iter->data;
|
|
GSList *pids = shell_app_get_pids (app);
|
|
GSList *pids_iter;
|
|
|
|
for (pids_iter = pids; pids_iter; pids_iter = pids_iter->next)
|
|
{
|
|
int app_pid = GPOINTER_TO_INT (pids_iter->data);
|
|
if (app_pid == pid)
|
|
{
|
|
result = app;
|
|
break;
|
|
}
|
|
}
|
|
g_slist_free (pids);
|
|
|
|
if (result != NULL)
|
|
break;
|
|
}
|
|
|
|
g_slist_free (running);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
set_focus_app (ShellWindowTracker *tracker,
|
|
ShellApp *new_focus_app)
|
|
{
|
|
if (new_focus_app == tracker->focus_app)
|
|
return;
|
|
|
|
if (tracker->focus_app != NULL)
|
|
g_object_unref (tracker->focus_app);
|
|
|
|
tracker->focus_app = new_focus_app;
|
|
|
|
if (tracker->focus_app != NULL)
|
|
g_object_ref (tracker->focus_app);
|
|
|
|
g_object_notify (G_OBJECT (tracker), "focus-app");
|
|
}
|
|
|
|
static void
|
|
on_focus_window_changed (MetaDisplay *display,
|
|
GParamSpec *spec,
|
|
ShellWindowTracker *tracker)
|
|
{
|
|
update_focus_app (tracker);
|
|
}
|
|
|
|
/**
|
|
* shell_window_tracker_get_startup_sequences:
|
|
* @tracker:
|
|
*
|
|
* Returns: (transfer none) (element-type MetaStartupSequence): Currently active startup sequences
|
|
*/
|
|
GSList *
|
|
shell_window_tracker_get_startup_sequences (ShellWindowTracker *self)
|
|
{
|
|
ShellGlobal *global = shell_global_get ();
|
|
MetaDisplay *display = shell_global_get_display (global);
|
|
MetaStartupNotification *sn = meta_display_get_startup_notification (display);
|
|
|
|
return meta_startup_notification_get_sequences (sn);
|
|
}
|
|
|
|
static ShellApp *
|
|
shell_startup_sequence_get_app (MetaStartupSequence *sequence)
|
|
{
|
|
const char *appid;
|
|
char *basename;
|
|
ShellAppSystem *appsys;
|
|
ShellApp *app;
|
|
|
|
appid = meta_startup_sequence_get_application_id (sequence);
|
|
if (!appid)
|
|
return NULL;
|
|
|
|
basename = g_path_get_basename (appid);
|
|
appsys = shell_app_system_get_default ();
|
|
app = shell_app_system_lookup_app (appsys, basename);
|
|
g_free (basename);
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* shell_window_tracker_get_default:
|
|
*
|
|
* Return Value: (transfer none): The global #ShellWindowTracker instance
|
|
*/
|
|
ShellWindowTracker *
|
|
shell_window_tracker_get_default (void)
|
|
{
|
|
static ShellWindowTracker *instance;
|
|
|
|
if (instance == NULL)
|
|
instance = g_object_new (SHELL_TYPE_WINDOW_TRACKER, NULL);
|
|
|
|
return instance;
|
|
}
|