546ae00854
Using a separate property to show when the application is busy rather than cramming it into the state property makes the code clearer. In most places we only care if an app is running or not, not whether it is actually busy. https://bugzilla.gnome.org/show_bug.cgi?id=736492
380 lines
9.8 KiB
C
380 lines
9.8 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
|
|
#include "config.h"
|
|
|
|
#include "shell-app-system.h"
|
|
#include "shell-app-usage.h"
|
|
#include <string.h>
|
|
|
|
#include <gio/gio.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "shell-app-private.h"
|
|
#include "shell-window-tracker-private.h"
|
|
#include "shell-app-system-private.h"
|
|
#include "shell-global.h"
|
|
#include "shell-util.h"
|
|
|
|
/* Vendor prefixes are something that can be preprended to a .desktop
|
|
* file name. Undo this.
|
|
*/
|
|
static const char*const vendor_prefixes[] = { "gnome-",
|
|
"fedora-",
|
|
"mozilla-",
|
|
"debian-",
|
|
NULL };
|
|
|
|
enum {
|
|
PROP_0,
|
|
|
|
};
|
|
|
|
enum {
|
|
APP_STATE_CHANGED,
|
|
INSTALLED_CHANGED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
struct _ShellAppSystemPrivate {
|
|
GHashTable *running_apps;
|
|
GHashTable *id_to_app;
|
|
GHashTable *startup_wm_class_to_id;
|
|
};
|
|
|
|
static void shell_app_system_finalize (GObject *object);
|
|
|
|
G_DEFINE_TYPE(ShellAppSystem, shell_app_system, G_TYPE_OBJECT);
|
|
|
|
static void shell_app_system_class_init(ShellAppSystemClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *)klass;
|
|
|
|
gobject_class->finalize = shell_app_system_finalize;
|
|
|
|
signals[APP_STATE_CHANGED] = g_signal_new ("app-state-changed",
|
|
SHELL_TYPE_APP_SYSTEM,
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 1,
|
|
SHELL_TYPE_APP);
|
|
signals[INSTALLED_CHANGED] =
|
|
g_signal_new ("installed-changed",
|
|
SHELL_TYPE_APP_SYSTEM,
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ShellAppSystemClass, installed_changed),
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
|
|
g_type_class_add_private (gobject_class, sizeof (ShellAppSystemPrivate));
|
|
}
|
|
|
|
static void
|
|
scan_startup_wm_class_to_id (ShellAppSystem *self)
|
|
{
|
|
ShellAppSystemPrivate *priv = self->priv;
|
|
GList *apps, *l;
|
|
|
|
g_hash_table_remove_all (priv->startup_wm_class_to_id);
|
|
|
|
apps = g_app_info_get_all ();
|
|
for (l = apps; l != NULL; l = l->next)
|
|
{
|
|
GAppInfo *info = l->data;
|
|
const char *startup_wm_class, *id;
|
|
|
|
id = g_app_info_get_id (info);
|
|
startup_wm_class = g_desktop_app_info_get_startup_wm_class (G_DESKTOP_APP_INFO (info));
|
|
|
|
if (startup_wm_class != NULL)
|
|
g_hash_table_insert (priv->startup_wm_class_to_id,
|
|
g_strdup (startup_wm_class), g_strdup (id));
|
|
}
|
|
|
|
g_list_free_full (apps, g_object_unref);
|
|
}
|
|
|
|
static gboolean
|
|
app_is_stale (ShellApp *app)
|
|
{
|
|
GDesktopAppInfo *info;
|
|
gboolean is_stale;
|
|
|
|
if (shell_app_is_window_backed (app))
|
|
return FALSE;
|
|
|
|
info = g_desktop_app_info_new (shell_app_get_id (app));
|
|
is_stale = (info == NULL);
|
|
|
|
if (info)
|
|
g_object_unref (info);
|
|
|
|
return is_stale;
|
|
}
|
|
|
|
static gboolean
|
|
stale_app_remove_func (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
return app_is_stale (value);
|
|
}
|
|
|
|
static void
|
|
installed_changed (GAppInfoMonitor *monitor,
|
|
gpointer user_data)
|
|
{
|
|
ShellAppSystem *self = user_data;
|
|
|
|
scan_startup_wm_class_to_id (self);
|
|
|
|
g_hash_table_foreach_remove (self->priv->id_to_app, stale_app_remove_func, NULL);
|
|
|
|
g_signal_emit (self, signals[INSTALLED_CHANGED], 0, NULL);
|
|
}
|
|
|
|
static void
|
|
shell_app_system_init (ShellAppSystem *self)
|
|
{
|
|
ShellAppSystemPrivate *priv;
|
|
GAppInfoMonitor *monitor;
|
|
|
|
self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
|
|
SHELL_TYPE_APP_SYSTEM,
|
|
ShellAppSystemPrivate);
|
|
|
|
priv->running_apps = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) g_object_unref, NULL);
|
|
priv->id_to_app = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
NULL,
|
|
(GDestroyNotify)g_object_unref);
|
|
|
|
priv->startup_wm_class_to_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
|
|
|
monitor = g_app_info_monitor_get ();
|
|
g_signal_connect (monitor, "changed", G_CALLBACK (installed_changed), self);
|
|
installed_changed (monitor, self);
|
|
}
|
|
|
|
static void
|
|
shell_app_system_finalize (GObject *object)
|
|
{
|
|
ShellAppSystem *self = SHELL_APP_SYSTEM (object);
|
|
ShellAppSystemPrivate *priv = self->priv;
|
|
|
|
g_hash_table_destroy (priv->running_apps);
|
|
g_hash_table_destroy (priv->id_to_app);
|
|
g_hash_table_destroy (priv->startup_wm_class_to_id);
|
|
|
|
G_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object);
|
|
}
|
|
|
|
/**
|
|
* shell_app_system_get_default:
|
|
*
|
|
* Return Value: (transfer none): The global #ShellAppSystem singleton
|
|
*/
|
|
ShellAppSystem *
|
|
shell_app_system_get_default ()
|
|
{
|
|
static ShellAppSystem *instance = NULL;
|
|
|
|
if (instance == NULL)
|
|
instance = g_object_new (SHELL_TYPE_APP_SYSTEM, NULL);
|
|
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
* shell_app_system_lookup_app:
|
|
*
|
|
* Find a #ShellApp corresponding to an id.
|
|
*
|
|
* Return value: (transfer none): The #ShellApp for id, or %NULL if none
|
|
*/
|
|
ShellApp *
|
|
shell_app_system_lookup_app (ShellAppSystem *self,
|
|
const char *id)
|
|
{
|
|
ShellAppSystemPrivate *priv = self->priv;
|
|
ShellApp *app;
|
|
GDesktopAppInfo *info;
|
|
|
|
app = g_hash_table_lookup (priv->id_to_app, id);
|
|
if (app)
|
|
return app;
|
|
|
|
info = g_desktop_app_info_new (id);
|
|
if (!info)
|
|
return NULL;
|
|
|
|
app = _shell_app_new (info);
|
|
g_hash_table_insert (priv->id_to_app, (char *) shell_app_get_id (app), app);
|
|
g_object_unref (info);
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* shell_app_system_lookup_heuristic_basename:
|
|
* @system: a #ShellAppSystem
|
|
* @id: Probable application identifier
|
|
*
|
|
* Find a valid application corresponding to a given
|
|
* heuristically determined application identifier
|
|
* string, or %NULL if none.
|
|
*
|
|
* Returns: (transfer none): A #ShellApp for @name
|
|
*/
|
|
ShellApp *
|
|
shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
|
|
const char *name)
|
|
{
|
|
ShellApp *result;
|
|
const char *const *prefix;
|
|
|
|
result = shell_app_system_lookup_app (system, name);
|
|
if (result != NULL)
|
|
return result;
|
|
|
|
for (prefix = vendor_prefixes; *prefix != NULL; prefix++)
|
|
{
|
|
char *tmpid = g_strconcat (*prefix, name, NULL);
|
|
result = shell_app_system_lookup_app (system, tmpid);
|
|
g_free (tmpid);
|
|
if (result != NULL)
|
|
return result;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* shell_app_system_lookup_desktop_wmclass:
|
|
* @system: a #ShellAppSystem
|
|
* @wmclass: (nullable): A WM_CLASS value
|
|
*
|
|
* Find a valid application whose .desktop file, without the extension
|
|
* and properly canonicalized, matches @wmclass.
|
|
*
|
|
* Returns: (transfer none): A #ShellApp for @wmclass
|
|
*/
|
|
ShellApp *
|
|
shell_app_system_lookup_desktop_wmclass (ShellAppSystem *system,
|
|
const char *wmclass)
|
|
{
|
|
char *canonicalized;
|
|
char *desktop_file;
|
|
ShellApp *app;
|
|
|
|
if (wmclass == NULL)
|
|
return NULL;
|
|
|
|
/* First try without changing the case (this handles
|
|
org.example.Foo.Bar.desktop applications)
|
|
|
|
Note that is slightly wrong in that Gtk+ would set
|
|
the WM_CLASS to Org.example.Foo.Bar, but it also
|
|
sets the instance part to org.example.Foo.Bar, so we're ok
|
|
*/
|
|
desktop_file = g_strconcat (wmclass, ".desktop", NULL);
|
|
app = shell_app_system_lookup_heuristic_basename (system, desktop_file);
|
|
g_free (desktop_file);
|
|
|
|
if (app)
|
|
return app;
|
|
|
|
canonicalized = g_ascii_strdown (wmclass, -1);
|
|
|
|
/* This handles "Fedora Eclipse", probably others.
|
|
* Note g_strdelimit is modify-in-place. */
|
|
g_strdelimit (canonicalized, " ", '-');
|
|
|
|
desktop_file = g_strconcat (canonicalized, ".desktop", NULL);
|
|
|
|
app = shell_app_system_lookup_heuristic_basename (system, desktop_file);
|
|
|
|
g_free (canonicalized);
|
|
g_free (desktop_file);
|
|
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* shell_app_system_lookup_startup_wmclass:
|
|
* @system: a #ShellAppSystem
|
|
* @wmclass: (nullable): A WM_CLASS value
|
|
*
|
|
* Find a valid application whose .desktop file contains a
|
|
* StartupWMClass entry matching @wmclass.
|
|
*
|
|
* Returns: (transfer none): A #ShellApp for @wmclass
|
|
*/
|
|
ShellApp *
|
|
shell_app_system_lookup_startup_wmclass (ShellAppSystem *system,
|
|
const char *wmclass)
|
|
{
|
|
const char *id;
|
|
|
|
if (wmclass == NULL)
|
|
return NULL;
|
|
|
|
id = g_hash_table_lookup (system->priv->startup_wm_class_to_id, wmclass);
|
|
if (id == NULL)
|
|
return NULL;
|
|
|
|
return shell_app_system_lookup_app (system, id);
|
|
}
|
|
|
|
void
|
|
_shell_app_system_notify_app_state_changed (ShellAppSystem *self,
|
|
ShellApp *app)
|
|
{
|
|
ShellAppState state = shell_app_get_state (app);
|
|
|
|
switch (state)
|
|
{
|
|
case SHELL_APP_STATE_RUNNING:
|
|
g_hash_table_insert (self->priv->running_apps, g_object_ref (app), NULL);
|
|
break;
|
|
case SHELL_APP_STATE_STARTING:
|
|
break;
|
|
case SHELL_APP_STATE_STOPPED:
|
|
g_hash_table_remove (self->priv->running_apps, app);
|
|
break;
|
|
}
|
|
g_signal_emit (self, signals[APP_STATE_CHANGED], 0, app);
|
|
}
|
|
|
|
/**
|
|
* shell_app_system_get_running:
|
|
* @self: A #ShellAppSystem
|
|
*
|
|
* Returns the set of applications which currently have at least one
|
|
* open window in the given context. The returned list will be sorted
|
|
* by shell_app_compare().
|
|
*
|
|
* Returns: (element-type ShellApp) (transfer container): Active applications
|
|
*/
|
|
GSList *
|
|
shell_app_system_get_running (ShellAppSystem *self)
|
|
{
|
|
gpointer key, value;
|
|
GSList *ret;
|
|
GHashTableIter iter;
|
|
|
|
g_hash_table_iter_init (&iter, self->priv->running_apps);
|
|
|
|
ret = NULL;
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
{
|
|
ShellApp *app = key;
|
|
|
|
ret = g_slist_prepend (ret, app);
|
|
}
|
|
|
|
ret = g_slist_sort (ret, (GCompareFunc)shell_app_compare);
|
|
|
|
return ret;
|
|
}
|