Blame src/shell-app-system.c

Packit Service ed5168
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
Packit Service ed5168
Packit Service ed5168
#include "config.h"
Packit Service ed5168
Packit Service ed5168
#include "shell-app-system.h"
Packit Service ed5168
#include "shell-app-usage.h"
Packit Service ed5168
#include <string.h>
Packit Service ed5168
Packit Service ed5168
#include <gio/gio.h>
Packit Service ed5168
#include <glib/gi18n.h>
Packit Service ed5168
Packit Service ed5168
#include "shell-app-private.h"
Packit Service ed5168
#include "shell-window-tracker-private.h"
Packit Service ed5168
#include "shell-app-system-private.h"
Packit Service ed5168
#include "shell-global.h"
Packit Service ed5168
#include "shell-util.h"
Packit Service ed5168
Packit Service ed5168
/* Vendor prefixes are something that can be preprended to a .desktop
Packit Service ed5168
 * file name.  Undo this.
Packit Service ed5168
 */
Packit Service ed5168
static const char*const vendor_prefixes[] = { "gnome-",
Packit Service ed5168
                                              "fedora-",
Packit Service ed5168
                                              "mozilla-",
Packit Service ed5168
                                              "debian-",
Packit Service ed5168
                                              NULL };
Packit Service ed5168
Packit Service ed5168
enum {
Packit Service ed5168
   PROP_0,
Packit Service ed5168
Packit Service ed5168
};
Packit Service ed5168
Packit Service ed5168
enum {
Packit Service ed5168
  APP_STATE_CHANGED,
Packit Service ed5168
  INSTALLED_CHANGED,
Packit Service ed5168
  LAST_SIGNAL
Packit Service ed5168
};
Packit Service ed5168
Packit Service ed5168
static guint signals[LAST_SIGNAL] = { 0 };
Packit Service ed5168
Packit Service ed5168
typedef struct _ShellAppSystemPrivate ShellAppSystemPrivate;
Packit Service ed5168
Packit Service ed5168
struct _ShellAppSystem
Packit Service ed5168
{
Packit Service ed5168
  GObject parent;
Packit Service ed5168
Packit Service ed5168
  ShellAppSystemPrivate *priv;
Packit Service ed5168
};
Packit Service ed5168
Packit Service ed5168
struct _ShellAppSystemPrivate {
Packit Service ed5168
  GHashTable *running_apps;
Packit Service ed5168
  GHashTable *id_to_app;
Packit Service ed5168
  GHashTable *startup_wm_class_to_id;
Packit Service ed5168
  GList *installed_apps;
Packit Service ed5168
};
Packit Service ed5168
Packit Service ed5168
static void shell_app_system_finalize (GObject *object);
Packit Service ed5168
Packit Service ed5168
G_DEFINE_TYPE_WITH_PRIVATE (ShellAppSystem, shell_app_system, G_TYPE_OBJECT);
Packit Service ed5168
Packit Service ed5168
static void shell_app_system_class_init(ShellAppSystemClass *klass)
Packit Service ed5168
{
Packit Service ed5168
  GObjectClass *gobject_class = (GObjectClass *)klass;
Packit Service ed5168
Packit Service ed5168
  gobject_class->finalize = shell_app_system_finalize;
Packit Service ed5168
Packit Service ed5168
  signals[APP_STATE_CHANGED] = g_signal_new ("app-state-changed",
Packit Service ed5168
                                             SHELL_TYPE_APP_SYSTEM,
Packit Service ed5168
                                             G_SIGNAL_RUN_LAST,
Packit Service ed5168
                                             0,
Packit Service ed5168
                                             NULL, NULL, NULL,
Packit Service ed5168
                                             G_TYPE_NONE, 1,
Packit Service ed5168
                                             SHELL_TYPE_APP);
Packit Service ed5168
  signals[INSTALLED_CHANGED] =
Packit Service ed5168
    g_signal_new ("installed-changed",
Packit Service ed5168
		  SHELL_TYPE_APP_SYSTEM,
Packit Service ed5168
		  G_SIGNAL_RUN_LAST,
Packit Service ed5168
                  0,
Packit Service ed5168
                  NULL, NULL, NULL,
Packit Service ed5168
		  G_TYPE_NONE, 0);
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
static void
Packit Service ed5168
scan_startup_wm_class_to_id (ShellAppSystem *self)
Packit Service ed5168
{
Packit Service ed5168
  ShellAppSystemPrivate *priv = self->priv;
Packit Service eca014
  GList *l;
Packit Service ed5168
Packit Service ed5168
  g_hash_table_remove_all (priv->startup_wm_class_to_id);
Packit Service ed5168
Packit Service eca014
  g_list_free_full (priv->installed_apps, g_object_unref);
Packit Service eca014
  priv->installed_apps = g_app_info_get_all ();
Packit Service ed5168
Packit Service eca014
  for (l = priv->installed_apps; l != NULL; l = l->next)
Packit Service ed5168
    {
Packit Service ed5168
      GAppInfo *info = l->data;
Packit Service ed5168
      const char *startup_wm_class, *id, *old_id;
Packit Service ed5168
Packit Service ed5168
      id = g_app_info_get_id (info);
Packit Service ed5168
      startup_wm_class = g_desktop_app_info_get_startup_wm_class (G_DESKTOP_APP_INFO (info));
Packit Service ed5168
Packit Service ed5168
      if (startup_wm_class == NULL)
Packit Service ed5168
        continue;
Packit Service ed5168
Packit Service ed5168
      /* In case multiple .desktop files set the same StartupWMClass, prefer
Packit Service ed5168
       * the one where ID and StartupWMClass match */
Packit Service ed5168
      old_id = g_hash_table_lookup (priv->startup_wm_class_to_id, startup_wm_class);
Packit Service ed5168
      if (old_id == NULL || strcmp (id, startup_wm_class) == 0)
Packit Service ed5168
        g_hash_table_insert (priv->startup_wm_class_to_id,
Packit Service ed5168
                             g_strdup (startup_wm_class), g_strdup (id));
Packit Service ed5168
    }
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
static gboolean
Packit Service ed5168
app_is_stale (ShellApp *app)
Packit Service ed5168
{
Packit Service ed5168
  GDesktopAppInfo *info, *old;
Packit Service ed5168
  GAppInfo *old_info, *new_info;
Packit Service ed5168
  gboolean is_unchanged;
Packit Service ed5168
Packit Service ed5168
  if (shell_app_is_window_backed (app))
Packit Service ed5168
    return FALSE;
Packit Service ed5168
Packit Service eca014
  info = g_desktop_app_info_new (shell_app_get_id (app));
Packit Service ed5168
  if (!info)
Packit Service ed5168
    return TRUE;
Packit Service ed5168
Packit Service ed5168
  old = shell_app_get_app_info (app);
Packit Service ed5168
  old_info = G_APP_INFO (old);
Packit Service ed5168
  new_info = G_APP_INFO (info);
Packit Service ed5168
Packit Service ed5168
  is_unchanged =
Packit Service ed5168
    g_app_info_should_show (old_info) == g_app_info_should_show (new_info) &&
Packit Service ed5168
    strcmp (g_desktop_app_info_get_filename (old),
Packit Service ed5168
            g_desktop_app_info_get_filename (info)) == 0 &&
Packit Service ed5168
    g_strcmp0 (g_app_info_get_executable (old_info),
Packit Service ed5168
               g_app_info_get_executable (new_info)) == 0 &&
Packit Service ed5168
    g_strcmp0 (g_app_info_get_commandline (old_info),
Packit Service ed5168
               g_app_info_get_commandline (new_info)) == 0 &&
Packit Service ed5168
    strcmp (g_app_info_get_name (old_info),
Packit Service ed5168
            g_app_info_get_name (new_info)) == 0 &&
Packit Service ed5168
    g_strcmp0 (g_app_info_get_description (old_info),
Packit Service ed5168
               g_app_info_get_description (new_info)) == 0 &&
Packit Service ed5168
    strcmp (g_app_info_get_display_name (old_info),
Packit Service ed5168
            g_app_info_get_display_name (new_info)) == 0 &&
Packit Service ed5168
    g_icon_equal (g_app_info_get_icon (old_info),
Packit Service ed5168
                  g_app_info_get_icon (new_info));
Packit Service ed5168
Packit Service eca014
  g_object_unref (info);
Packit Service ed5168
  return !is_unchanged;
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
static gboolean
Packit Service ed5168
stale_app_remove_func (gpointer key,
Packit Service ed5168
                       gpointer value,
Packit Service ed5168
                       gpointer user_data)
Packit Service ed5168
{
Packit Service ed5168
  return app_is_stale (value);
Packit Service ed5168
}
Packit Service ed5168
rpm-build f03ed6
static void
Packit Service eca014
installed_changed (GAppInfoMonitor *monitor,
Packit Service eca014
                   gpointer         user_data)
rpm-build f03ed6
{
Packit Service eca014
  ShellAppSystem *self = user_data;
rpm-build f03ed6
Packit Service ed5168
  scan_startup_wm_class_to_id (self);
Packit Service ed5168
Packit Service ed5168
  g_hash_table_foreach_remove (self->priv->id_to_app, stale_app_remove_func, NULL);
Packit Service ed5168
Packit Service ed5168
  g_signal_emit (self, signals[INSTALLED_CHANGED], 0, NULL);
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
static void
Packit Service ed5168
shell_app_system_init (ShellAppSystem *self)
Packit Service ed5168
{
Packit Service ed5168
  ShellAppSystemPrivate *priv;
Packit Service eca014
  GAppInfoMonitor *monitor;
Packit Service ed5168
Packit Service ed5168
  self->priv = priv = shell_app_system_get_instance_private (self);
Packit Service ed5168
Packit Service ed5168
  priv->running_apps = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) g_object_unref, NULL);
Packit Service ed5168
  priv->id_to_app = g_hash_table_new_full (g_str_hash, g_str_equal,
Packit Service ed5168
                                           NULL,
Packit Service ed5168
                                           (GDestroyNotify)g_object_unref);
Packit Service ed5168
Packit Service ed5168
  priv->startup_wm_class_to_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
Packit Service ed5168
Packit Service eca014
  monitor = g_app_info_monitor_get ();
Packit Service eca014
  g_signal_connect (monitor, "changed", G_CALLBACK (installed_changed), self);
Packit Service eca014
  installed_changed (monitor, self);
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
static void
Packit Service ed5168
shell_app_system_finalize (GObject *object)
Packit Service ed5168
{
Packit Service ed5168
  ShellAppSystem *self = SHELL_APP_SYSTEM (object);
Packit Service ed5168
  ShellAppSystemPrivate *priv = self->priv;
Packit Service ed5168
Packit Service ed5168
  g_hash_table_destroy (priv->running_apps);
Packit Service ed5168
  g_hash_table_destroy (priv->id_to_app);
Packit Service ed5168
  g_hash_table_destroy (priv->startup_wm_class_to_id);
Packit Service ed5168
  g_list_free_full (priv->installed_apps, g_object_unref);
Packit Service ed5168
Packit Service ed5168
  G_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object);
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * shell_app_system_get_default:
Packit Service ed5168
 *
Packit Service ed5168
 * Return Value: (transfer none): The global #ShellAppSystem singleton
Packit Service ed5168
 */
Packit Service ed5168
ShellAppSystem *
Packit Service ed5168
shell_app_system_get_default (void)
Packit Service ed5168
{
Packit Service ed5168
  static ShellAppSystem *instance = NULL;
Packit Service ed5168
Packit Service ed5168
  if (instance == NULL)
Packit Service ed5168
    instance = g_object_new (SHELL_TYPE_APP_SYSTEM, NULL);
Packit Service ed5168
Packit Service ed5168
  return instance;
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * shell_app_system_lookup_app:
Packit Service ed5168
 *
Packit Service ed5168
 * Find a #ShellApp corresponding to an id.
Packit Service ed5168
 *
Packit Service ed5168
 * Return value: (transfer none): The #ShellApp for id, or %NULL if none
Packit Service ed5168
 */
Packit Service ed5168
ShellApp *
Packit Service ed5168
shell_app_system_lookup_app (ShellAppSystem   *self,
Packit Service ed5168
                             const char       *id)
Packit Service ed5168
{
Packit Service ed5168
  ShellAppSystemPrivate *priv = self->priv;
Packit Service ed5168
  ShellApp *app;
Packit Service ed5168
  GDesktopAppInfo *info;
Packit Service ed5168
Packit Service ed5168
  app = g_hash_table_lookup (priv->id_to_app, id);
Packit Service ed5168
  if (app)
Packit Service ed5168
    return app;
Packit Service ed5168
Packit Service eca014
  info = g_desktop_app_info_new (id);
Packit Service ed5168
  if (!info)
Packit Service ed5168
    return NULL;
Packit Service ed5168
Packit Service ed5168
  app = _shell_app_new (info);
Packit Service ed5168
  g_hash_table_insert (priv->id_to_app, (char *) shell_app_get_id (app), app);
Packit Service eca014
  g_object_unref (info);
Packit Service ed5168
  return app;
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * shell_app_system_lookup_heuristic_basename:
Packit Service ed5168
 * @system: a #ShellAppSystem
Packit Service ed5168
 * @id: Probable application identifier
Packit Service ed5168
 *
Packit Service ed5168
 * Find a valid application corresponding to a given
Packit Service ed5168
 * heuristically determined application identifier
Packit Service ed5168
 * string, or %NULL if none.
Packit Service ed5168
 *
Packit Service ed5168
 * Returns: (transfer none): A #ShellApp for @name
Packit Service ed5168
 */
Packit Service ed5168
ShellApp *
Packit Service ed5168
shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
Packit Service ed5168
                                            const char     *name)
Packit Service ed5168
{
Packit Service ed5168
  ShellApp *result;
Packit Service ed5168
  const char *const *prefix;
Packit Service ed5168
Packit Service ed5168
  result = shell_app_system_lookup_app (system, name);
Packit Service ed5168
  if (result != NULL)
Packit Service ed5168
    return result;
Packit Service ed5168
Packit Service ed5168
  for (prefix = vendor_prefixes; *prefix != NULL; prefix++)
Packit Service ed5168
    {
Packit Service ed5168
      char *tmpid = g_strconcat (*prefix, name, NULL);
Packit Service ed5168
      result = shell_app_system_lookup_app (system, tmpid);
Packit Service ed5168
      g_free (tmpid);
Packit Service ed5168
      if (result != NULL)
Packit Service ed5168
        return result;
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
  return NULL;
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * shell_app_system_lookup_desktop_wmclass:
Packit Service ed5168
 * @system: a #ShellAppSystem
Packit Service ed5168
 * @wmclass: (nullable): A WM_CLASS value
Packit Service ed5168
 *
Packit Service ed5168
 * Find a valid application whose .desktop file, without the extension
Packit Service ed5168
 * and properly canonicalized, matches @wmclass.
Packit Service ed5168
 *
Packit Service ed5168
 * Returns: (transfer none): A #ShellApp for @wmclass
Packit Service ed5168
 */
Packit Service ed5168
ShellApp *
Packit Service ed5168
shell_app_system_lookup_desktop_wmclass (ShellAppSystem *system,
Packit Service ed5168
                                         const char     *wmclass)
Packit Service ed5168
{
Packit Service ed5168
  char *canonicalized;
Packit Service ed5168
  char *desktop_file;
Packit Service ed5168
  ShellApp *app;
Packit Service ed5168
Packit Service ed5168
  if (wmclass == NULL)
Packit Service ed5168
    return NULL;
Packit Service ed5168
Packit Service ed5168
  /* First try without changing the case (this handles
Packit Service ed5168
     org.example.Foo.Bar.desktop applications)
Packit Service ed5168
Packit Service ed5168
     Note that is slightly wrong in that Gtk+ would set
Packit Service ed5168
     the WM_CLASS to Org.example.Foo.Bar, but it also
Packit Service ed5168
     sets the instance part to org.example.Foo.Bar, so we're ok
Packit Service ed5168
  */
Packit Service ed5168
  desktop_file = g_strconcat (wmclass, ".desktop", NULL);
Packit Service ed5168
  app = shell_app_system_lookup_heuristic_basename (system, desktop_file);
Packit Service ed5168
  g_free (desktop_file);
Packit Service ed5168
Packit Service ed5168
  if (app)
Packit Service ed5168
    return app;
Packit Service ed5168
Packit Service ed5168
  canonicalized = g_ascii_strdown (wmclass, -1);
Packit Service ed5168
Packit Service ed5168
  /* This handles "Fedora Eclipse", probably others.
Packit Service ed5168
   * Note g_strdelimit is modify-in-place. */
Packit Service ed5168
  g_strdelimit (canonicalized, " ", '-');
Packit Service ed5168
Packit Service ed5168
  desktop_file = g_strconcat (canonicalized, ".desktop", NULL);
Packit Service ed5168
Packit Service ed5168
  app = shell_app_system_lookup_heuristic_basename (system, desktop_file);
Packit Service ed5168
Packit Service ed5168
  g_free (canonicalized);
Packit Service ed5168
  g_free (desktop_file);
Packit Service ed5168
Packit Service ed5168
  return app;
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * shell_app_system_lookup_startup_wmclass:
Packit Service ed5168
 * @system: a #ShellAppSystem
Packit Service ed5168
 * @wmclass: (nullable): A WM_CLASS value
Packit Service ed5168
 *
Packit Service ed5168
 * Find a valid application whose .desktop file contains a
Packit Service ed5168
 * StartupWMClass entry matching @wmclass.
Packit Service ed5168
 *
Packit Service ed5168
 * Returns: (transfer none): A #ShellApp for @wmclass
Packit Service ed5168
 */
Packit Service ed5168
ShellApp *
Packit Service ed5168
shell_app_system_lookup_startup_wmclass (ShellAppSystem *system,
Packit Service ed5168
                                         const char     *wmclass)
Packit Service ed5168
{
Packit Service ed5168
  const char *id;
Packit Service ed5168
Packit Service ed5168
  if (wmclass == NULL)
Packit Service ed5168
    return NULL;
Packit Service ed5168
Packit Service ed5168
  id = g_hash_table_lookup (system->priv->startup_wm_class_to_id, wmclass);
Packit Service ed5168
  if (id == NULL)
Packit Service ed5168
    return NULL;
Packit Service ed5168
Packit Service ed5168
  return shell_app_system_lookup_app (system, id);
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
void
Packit Service ed5168
_shell_app_system_notify_app_state_changed (ShellAppSystem *self,
Packit Service ed5168
                                            ShellApp       *app)
Packit Service ed5168
{
Packit Service ed5168
  ShellAppState state = shell_app_get_state (app);
Packit Service ed5168
Packit Service ed5168
  switch (state)
Packit Service ed5168
    {
Packit Service ed5168
    case SHELL_APP_STATE_RUNNING:
Packit Service ed5168
      g_hash_table_insert (self->priv->running_apps, g_object_ref (app), NULL);
Packit Service ed5168
      break;
Packit Service ed5168
    case SHELL_APP_STATE_STARTING:
Packit Service ed5168
      break;
Packit Service ed5168
    case SHELL_APP_STATE_STOPPED:
Packit Service ed5168
      g_hash_table_remove (self->priv->running_apps, app);
Packit Service ed5168
      break;
Packit Service ed5168
    default:
Packit Service ed5168
      g_warn_if_reached();
Packit Service ed5168
      break;
Packit Service ed5168
    }
Packit Service ed5168
  g_signal_emit (self, signals[APP_STATE_CHANGED], 0, app);
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * shell_app_system_get_running:
Packit Service ed5168
 * @self: A #ShellAppSystem
Packit Service ed5168
 *
Packit Service ed5168
 * Returns the set of applications which currently have at least one
Packit Service ed5168
 * open window.  The returned list will be sorted by shell_app_compare().
Packit Service ed5168
 *
Packit Service ed5168
 * Returns: (element-type ShellApp) (transfer container): Active applications
Packit Service ed5168
 */
Packit Service ed5168
GSList *
Packit Service ed5168
shell_app_system_get_running (ShellAppSystem *self)
Packit Service ed5168
{
Packit Service ed5168
  gpointer key, value;
Packit Service ed5168
  GSList *ret;
Packit Service ed5168
  GHashTableIter iter;
Packit Service ed5168
Packit Service ed5168
  g_hash_table_iter_init (&iter, self->priv->running_apps);
Packit Service ed5168
Packit Service ed5168
  ret = NULL;
Packit Service ed5168
  while (g_hash_table_iter_next (&iter, &key, &value))
Packit Service ed5168
    {
Packit Service ed5168
      ShellApp *app = key;
Packit Service ed5168
Packit Service ed5168
      ret = g_slist_prepend (ret, app);
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
  ret = g_slist_sort (ret, (GCompareFunc)shell_app_compare);
Packit Service ed5168
Packit Service ed5168
  return ret;
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * shell_app_system_search:
Packit Service ed5168
 * @search_string: the search string to use
Packit Service ed5168
 *
Packit Service ed5168
 * Wrapper around g_desktop_app_info_search() that replaces results that
Packit Service ed5168
 * don't validate as UTF-8 with the empty string.
Packit Service ed5168
 *
Packit Service ed5168
 * Returns: (array zero-terminated=1) (element-type GStrv) (transfer full): a
Packit Service ed5168
 *   list of strvs.  Free each item with g_strfreev() and free the outer
Packit Service ed5168
 *   list with g_free().
Packit Service ed5168
 */
Packit Service ed5168
char ***
Packit Service ed5168
shell_app_system_search (const char *search_string)
Packit Service ed5168
{
Packit Service ed5168
  char ***results = g_desktop_app_info_search (search_string);
Packit Service ed5168
  char ***groups, **ids;
Packit Service ed5168
Packit Service ed5168
  for (groups = results; *groups; groups++)
Packit Service ed5168
    for (ids = *groups; *ids; ids++)
Packit Service ed5168
      if (!g_utf8_validate (*ids, -1, NULL))
Packit Service ed5168
        **ids = '\0';
Packit Service ed5168
Packit Service ed5168
  return results;
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * shell_app_system_get_installed:
Packit Service ed5168
 * @self: the #ShellAppSystem
Packit Service ed5168
 *
Packit Service ed5168
 * Returns all installed apps, as a list of #GAppInfo
Packit Service ed5168
 *
Packit Service ed5168
 * Returns: (transfer none) (element-type GAppInfo): a list of #GAppInfo
Packit Service ed5168
 *   describing all known applications. This memory is owned by the
Packit Service ed5168
 *   #ShellAppSystem and should not be freed.
Packit Service ed5168
 **/
Packit Service ed5168
GList *
Packit Service ed5168
shell_app_system_get_installed (ShellAppSystem *self)
Packit Service ed5168
{
Packit Service eca014
  ShellAppSystemPrivate *priv = self->priv;
Packit Service eca014
Packit Service eca014
  return priv->installed_apps;
Packit Service ed5168
}