cleanup
[platform/upstream/glib.git] / gio / gdesktopappinfo.c
index 0b72803..7bfc904 100644 (file)
@@ -132,20 +132,19 @@ typedef enum {
 G_DEFINE_TYPE_WITH_CODE (GDesktopAppInfo, g_desktop_app_info, G_TYPE_OBJECT,
                          G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO, g_desktop_app_info_iface_init))
 
-G_LOCK_DEFINE_STATIC (g_desktop_env);
-static gchar *g_desktop_env = NULL;
-
 /* DesktopFileDir implementation {{{1 */
 
 typedef struct
 {
   gchar                      *path;
+  gchar                      *alternatively_watching;
   gboolean                    is_config;
   gboolean                    is_setup;
   GLocalDirectoryMonitor     *monitor;
   GHashTable                 *app_names;
   GHashTable                 *mime_tweaks;
   GHashTable                 *memory_index;
+  GHashTable                 *memory_implementations;
 } DesktopFileDir;
 
 static DesktopFileDir *desktop_file_dirs;
@@ -157,6 +156,52 @@ static GMutex          desktop_file_dir_lock;
 /* Monitor 'changed' signal handler {{{2 */
 static void desktop_file_dir_reset (DesktopFileDir *dir);
 
+/*< internal >
+ * desktop_file_dir_get_alternative_dir:
+ * @dir: a #DesktopFileDir
+ *
+ * Gets the "alternative" directory to monitor in case the path
+ * doesn't exist.
+ *
+ * If the path exists this will return NULL, otherwise it will return a
+ * parent directory of the path.
+ *
+ * This is used to avoid inotify on a non-existent directory (which
+ * results in polling).
+ *
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=522314 for more info.
+ */
+static gchar *
+desktop_file_dir_get_alternative_dir (DesktopFileDir *dir)
+{
+  gchar *parent;
+
+  /* If the directory itself exists then we need no alternative. */
+  if (g_access (dir->path, R_OK | X_OK) == 0)
+    return NULL;
+
+  /* Otherwise, try the parent directories until we find one. */
+  parent = g_path_get_dirname (dir->path);
+
+  while (g_access (parent, R_OK | X_OK) != 0)
+    {
+      gchar *tmp = parent;
+
+      parent = g_path_get_dirname (tmp);
+
+      /* If somehow we get to '/' or '.' then just stop... */
+      if (g_str_equal (parent, tmp))
+        {
+          g_free (tmp);
+          break;
+        }
+
+      g_free (tmp);
+    }
+
+  return parent;
+}
+
 static void
 desktop_file_dir_changed (GFileMonitor      *monitor,
                           GFile             *file,
@@ -165,6 +210,7 @@ desktop_file_dir_changed (GFileMonitor      *monitor,
                           gpointer           user_data)
 {
   DesktopFileDir *dir = user_data;
+  gboolean do_nothing = FALSE;
 
   /* We are not interested in receiving notifications forever just
    * because someone asked about one desktop file once.
@@ -172,15 +218,30 @@ desktop_file_dir_changed (GFileMonitor      *monitor,
    * After we receive the first notification, reset the dir, destroying
    * the monitor.  We will take this as a hint, next time that we are
    * asked, that we need to check if everything is up to date.
+   *
+   * If this is a notification for a parent directory (because the
+   * desktop directory didn't exist) then we shouldn't fire the signal
+   * unless something actually changed.
    */
   g_mutex_lock (&desktop_file_dir_lock);
 
-  desktop_file_dir_reset (dir);
+  if (dir->alternatively_watching)
+    {
+      gchar *alternative_dir;
+
+      alternative_dir = desktop_file_dir_get_alternative_dir (dir);
+      do_nothing = alternative_dir && g_str_equal (dir->alternatively_watching, alternative_dir);
+      g_free (alternative_dir);
+    }
+
+  if (!do_nothing)
+    desktop_file_dir_reset (dir);
 
   g_mutex_unlock (&desktop_file_dir_lock);
 
   /* Notify anyone else who may be interested */
-  g_app_info_monitor_fire ();
+  if (!do_nothing)
+    g_app_info_monitor_fire ();
 }
 
 /* Internal utility functions {{{2 */
@@ -242,6 +303,29 @@ get_lowercase_current_desktops (void)
   return (const gchar **) result;
 }
 
+static const gchar * const *
+get_current_desktops (const gchar *value)
+{
+  static gchar **result;
+
+  if (g_once_init_enter (&result))
+    {
+      gchar **tmp;
+
+      if (!value)
+        value = g_getenv ("XDG_CURRENT_DESKTOP");
+
+      if (!value)
+        value = "";
+
+      tmp = g_strsplit (value, ":", 0);
+
+      g_once_init_leave (&result, tmp);
+    }
+
+  return (const gchar **) result;
+}
+
 /*< internal >
  * add_to_table_if_appropriate:
  * @apps: a string to GDesktopAppInfo hash table
@@ -310,13 +394,13 @@ desktop_key_get_name (guint key_id)
     case DESKTOP_KEY_Exec:
       return "Exec";
     case DESKTOP_KEY_GenericName:
-      return "GenericName";
+      return GENERIC_NAME_KEY;
     case DESKTOP_KEY_Keywords:
-      return "Keywords";
+      return KEYWORDS_KEY;
     case DESKTOP_KEY_Name:
       return "Name";
     case DESKTOP_KEY_X_GNOME_FullName:
-      return "X-GNOME-FullName";
+      return FULL_NAME_KEY;
     default:
       g_assert_not_reached ();
     }
@@ -620,7 +704,7 @@ desktop_file_dir_unindexed_get_tweaks (DesktopFileDir *dir,
   gchar *unaliased_type;
 
   unaliased_type = _g_unix_content_type_unalias (mime_type);
-  tweaks = g_hash_table_lookup (dir->mime_tweaks, mime_type);
+  tweaks = g_hash_table_lookup (dir->mime_tweaks, unaliased_type);
 
   if (tweaks == NULL)
     {
@@ -686,8 +770,6 @@ desktop_file_dir_unindexed_read_mimeapps_list (DesktopFileDir *dir,
                                                const gchar    *added_group,
                                                gboolean        tweaks_permitted)
 {
-  const gchar default_group[] = "Default Applications";
-  const gchar removed_group[] = "Removed Assocations";
   UnindexedMimeTweaks *tweaks;
   char **desktop_file_ids;
   GKeyFile *key_file;
@@ -727,12 +809,12 @@ desktop_file_dir_unindexed_read_mimeapps_list (DesktopFileDir *dir,
       g_strfreev (mime_types);
     }
 
-  mime_types = g_key_file_get_keys (key_file, removed_group, NULL, NULL);
+  mime_types = g_key_file_get_keys (key_file, REMOVED_ASSOCIATIONS_GROUP, NULL, NULL);
 
   if G_UNLIKELY (mime_types != NULL && !tweaks_permitted)
     {
       g_warning ("%s contains a [%s] group, but it is not permitted here.  Only the non-desktop-specific "
-                 "mimeapps.list file may add or remove associations.", filename, removed_group);
+                 "mimeapps.list file may add or remove associations.", filename, REMOVED_ASSOCIATIONS_GROUP);
       g_strfreev (mime_types);
       mime_types = NULL;
     }
@@ -741,7 +823,7 @@ desktop_file_dir_unindexed_read_mimeapps_list (DesktopFileDir *dir,
     {
       for (i = 0; mime_types[i] != NULL; i++)
         {
-          desktop_file_ids = g_key_file_get_string_list (key_file, removed_group, mime_types[i], NULL, NULL);
+          desktop_file_ids = g_key_file_get_string_list (key_file, REMOVED_ASSOCIATIONS_GROUP, mime_types[i], NULL, NULL);
 
           if (desktop_file_ids)
             {
@@ -753,13 +835,13 @@ desktop_file_dir_unindexed_read_mimeapps_list (DesktopFileDir *dir,
       g_strfreev (mime_types);
     }
 
-  mime_types = g_key_file_get_keys (key_file, default_group, NULL, NULL);
+  mime_types = g_key_file_get_keys (key_file, DEFAULT_APPLICATIONS_GROUP, NULL, NULL);
 
   if (mime_types != NULL)
     {
       for (i = 0; mime_types[i] != NULL; i++)
         {
-          desktop_file_ids = g_key_file_get_string_list (key_file, default_group, mime_types[i], NULL, NULL);
+          desktop_file_ids = g_key_file_get_string_list (key_file, DEFAULT_APPLICATIONS_GROUP, mime_types[i], NULL, NULL);
 
           if (desktop_file_ids)
             {
@@ -794,13 +876,13 @@ desktop_file_dir_unindexed_read_mimeapps_lists (DesktopFileDir *dir)
   for (i = 0; desktops[i]; i++)
     {
       filename = g_strdup_printf ("%s/%s-mimeapps.list", dir->path, desktops[i]);
-      desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, "Added Associations", FALSE);
+      desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, ADDED_ASSOCIATIONS_GROUP, FALSE);
       g_free (filename);
     }
 
   /* Next, the non-desktop-specific mimeapps.list */
   filename = g_strdup_printf ("%s/mimeapps.list", dir->path);
-  desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, "Added Associations", TRUE);
+  desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, ADDED_ASSOCIATIONS_GROUP, TRUE);
   g_free (filename);
 
   /* The remaining files are only checked for in directories that might
@@ -815,14 +897,14 @@ desktop_file_dir_unindexed_read_mimeapps_lists (DesktopFileDir *dir)
    * version.
    */
   filename = g_strdup_printf ("%s/defaults.list", dir->path);
-  desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, "Added Associations", FALSE);
+  desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, ADDED_ASSOCIATIONS_GROUP, FALSE);
   g_free (filename);
 
   /* Finally, the mimeinfo.cache, which is just a cached copy of what we
    * would find in the MimeTypes= lines of all of the desktop files.
    */
   filename = g_strdup_printf ("%s/mimeinfo.cache", dir->path);
-  desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, "MIME Cache", TRUE);
+  desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, MIME_CACHE_GROUP, TRUE);
   g_free (filename);
 }
 
@@ -954,6 +1036,7 @@ desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir)
   gpointer app, path;
 
   dir->memory_index = memory_index_new ();
+  dir->memory_implementations = memory_index_new ();
 
   /* Nothing to search? */
   if (dir->app_names == NULL)
@@ -973,6 +1056,7 @@ desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir)
           !g_key_file_get_boolean (key_file, "Desktop Entry", "Hidden", NULL))
         {
           /* Index the interesting keys... */
+          gchar **implements;
           gint i;
 
           for (i = 0; i < G_N_ELEMENTS (desktop_key_match_category); i++)
@@ -1006,6 +1090,12 @@ desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir)
 
               g_free (raw);
             }
+
+          /* Make note of the Implements= line */
+          implements = g_key_file_get_string_list (key_file, "Desktop Entry", "Implements", NULL, NULL);
+          for (i = 0; implements && implements[i]; i++)
+            memory_index_add_token (dir->memory_implementations, implements[i], 0, app);
+          g_strfreev (implements);
         }
 
       g_key_file_free (key_file);
@@ -1112,6 +1202,20 @@ desktop_file_dir_unindexed_default_lookup (DesktopFileDir *dir,
     }
 }
 
+static void
+desktop_file_dir_unindexed_get_implementations (DesktopFileDir  *dir,
+                                                GList          **results,
+                                                const gchar     *interface)
+{
+  MemoryIndexEntry *mie;
+
+  if (!dir->memory_index)
+    desktop_file_dir_unindexed_setup_search (dir);
+
+  for (mie = g_hash_table_lookup (dir->memory_implementations, interface); mie; mie = mie->next)
+    *results = g_list_prepend (*results, g_strdup (mie->app_name));
+}
+
 /* DesktopFileDir "API" {{{2 */
 
 /*< internal >
@@ -1164,6 +1268,12 @@ desktop_file_dir_create_for_config (GArray      *array,
 static void
 desktop_file_dir_reset (DesktopFileDir *dir)
 {
+  if (dir->alternatively_watching)
+    {
+      g_free (dir->alternatively_watching);
+      dir->alternatively_watching = NULL;
+    }
+
   if (dir->monitor)
     {
       g_signal_handlers_disconnect_by_func (dir->monitor, desktop_file_dir_changed, dir);
@@ -1189,6 +1299,12 @@ desktop_file_dir_reset (DesktopFileDir *dir)
       dir->mime_tweaks = NULL;
     }
 
+  if (dir->memory_implementations)
+    {
+      g_hash_table_unref (dir->memory_implementations);
+      dir->memory_implementations = NULL;
+    }
+
   dir->is_setup = FALSE;
 }
 
@@ -1203,10 +1319,23 @@ desktop_file_dir_reset (DesktopFileDir *dir)
 static void
 desktop_file_dir_init (DesktopFileDir *dir)
 {
+  const gchar *watch_dir;
+
   g_assert (!dir->is_setup);
 
+  g_assert (!dir->alternatively_watching);
   g_assert (!dir->monitor);
-  dir->monitor = g_local_directory_monitor_new_in_worker (dir->path, G_FILE_MONITOR_NONE, NULL);
+
+  dir->alternatively_watching = desktop_file_dir_get_alternative_dir (dir);
+  watch_dir = dir->alternatively_watching ? dir->alternatively_watching : dir->path;
+
+  /* There is a very thin race here if the watch_dir has been _removed_
+   * between when we checked for it and when we establish the watch.
+   * Removes probably don't happen in usual operation, and even if it
+   * does (and we catch the unlikely race), the only degradation is that
+   * we will fall back to polling.
+   */
+  dir->monitor = g_local_directory_monitor_new_in_worker (watch_dir, G_FILE_MONITOR_NONE, NULL);
 
   if (dir->monitor)
     {
@@ -1311,6 +1440,14 @@ desktop_file_dir_search (DesktopFileDir *dir,
   desktop_file_dir_unindexed_search (dir, search_token);
 }
 
+static void
+desktop_file_dir_get_implementations (DesktopFileDir  *dir,
+                                      GList          **results,
+                                      const gchar     *interface)
+{
+  desktop_file_dir_unindexed_get_implementations (dir, results, interface);
+}
+
 /* Lock/unlock and global setup API {{{2 */
 
 static void
@@ -2003,14 +2140,16 @@ g_desktop_app_info_get_nodisplay (GDesktopAppInfo *info)
 /**
  * g_desktop_app_info_get_show_in:
  * @info: a #GDesktopAppInfo
- * @desktop_env: a string specifying a desktop name
+ * @desktop_env: (nullable): a string specifying a desktop name
  *
  * Checks if the application info should be shown in menus that list available
  * applications for a specific name of the desktop, based on the
  * `OnlyShowIn` and `NotShowIn` keys.
  *
- * If @desktop_env is %NULL, then the name of the desktop set with
- * g_desktop_app_info_set_desktop_env() is used.
+ * @desktop_env should typically be given as %NULL, in which case the
+ * `XDG_CURRENT_DESKTOP` environment variable is consulted.  If you want
+ * to override the default mechanism then you may specify @desktop_env,
+ * but this is not recommended.
  *
  * Note that g_app_info_should_show() for @info will include this check (with
  * %NULL for @desktop_env) as well as additional checks.
@@ -2025,45 +2164,33 @@ gboolean
 g_desktop_app_info_get_show_in (GDesktopAppInfo *info,
                                 const gchar     *desktop_env)
 {
-  gboolean found;
-  int i;
+  const gchar *specified_envs[] = { desktop_env, NULL };
+  const gchar * const *envs;
+  gint i;
 
   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), FALSE);
 
-  if (!desktop_env) {
-    G_LOCK (g_desktop_env);
-    desktop_env = g_desktop_env;
-    G_UNLOCK (g_desktop_env);
-  }
+  if (desktop_env)
+    envs = specified_envs;
+  else
+    envs = get_current_desktops (NULL);
 
-  if (info->only_show_in)
+  for (i = 0; envs[i]; i++)
     {
-      if (desktop_env == NULL)
-        return FALSE;
+      gint j;
 
-      found = FALSE;
-      for (i = 0; info->only_show_in[i] != NULL; i++)
-        {
-          if (strcmp (info->only_show_in[i], desktop_env) == 0)
-            {
-              found = TRUE;
-              break;
-            }
-        }
-      if (!found)
-        return FALSE;
-    }
+      if (info->only_show_in)
+        for (j = 0; info->only_show_in[j]; j++)
+          if (g_str_equal (info->only_show_in[j], envs[i]))
+            return TRUE;
 
-  if (info->not_show_in && desktop_env)
-    {
-      for (i = 0; info->not_show_in[i] != NULL; i++)
-        {
-          if (strcmp (info->not_show_in[i], desktop_env) == 0)
+      if (info->not_show_in)
+        for (j = 0; info->not_show_in[j]; j++)
+          if (g_str_equal (info->not_show_in[j], envs[i]))
             return FALSE;
-        }
     }
 
-  return TRUE;
+  return info->only_show_in == NULL;
 }
 
 /* Launching... {{{2 */
@@ -2921,26 +3048,15 @@ g_desktop_app_info_launch_uris_as_manager (GDesktopAppInfo            *appinfo,
  * `OnlyShowIn` and `NotShowIn`
  * desktop entry fields.
  *
- * The 
- * [Desktop Menu specification](http://standards.freedesktop.org/menu-spec/latest/)
- * recognizes the following:
- * - GNOME
- * - KDE
- * - ROX
- * - XFCE
- * - LXDE
- * - Unity
- * - Old
- *
  * Should be called only once; subsequent calls are ignored.
+ *
+ * Deprecated:2.42:do not use this API.  Since 2.42 the value of the
+ * `XDG_CURRENT_DESKTOP` environment variable will be used.
  */
 void
 g_desktop_app_info_set_desktop_env (const gchar *desktop_env)
 {
-  G_LOCK (g_desktop_env);
-  if (!g_desktop_env)
-    g_desktop_env = g_strdup (desktop_env);
-  G_UNLOCK (g_desktop_env);
+  get_current_desktops (desktop_env);
 }
 
 static gboolean
@@ -4012,6 +4128,55 @@ g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
 /* "Get all" API {{{2 */
 
 /**
+ * g_desktop_app_info_get_implementations:
+ * @interface: the name of the interface
+ *
+ * Gets all applications that implement @interface.
+ *
+ * An application implements an interface if that interface is listed in
+ * the Implements= line of the desktop file of the application.
+ *
+ * Returns: (element-type GDesktopAppInfo) (transfer full): a list of #GDesktopAppInfo
+ * objects.
+ *
+ * Since: 2.42
+ **/
+GList *
+g_desktop_app_info_get_implementations (const gchar *interface)
+{
+  GList *result = NULL;
+  GList **ptr;
+  gint i;
+
+  desktop_file_dirs_lock ();
+
+  for (i = 0; i < n_desktop_file_dirs; i++)
+    desktop_file_dir_get_implementations (&desktop_file_dirs[i], &result, interface);
+
+  desktop_file_dirs_unlock ();
+
+  ptr = &result;
+  while (*ptr)
+    {
+      gchar *name = (*ptr)->data;
+      GDesktopAppInfo *app;
+
+      app = g_desktop_app_info_new (name);
+      g_free (name);
+
+      if (app)
+        {
+          (*ptr)->data = app;
+          ptr = &(*ptr)->next;
+        }
+      else
+        *ptr = g_list_delete_link (*ptr, *ptr);
+    }
+
+  return result;
+}
+
+/**
  * g_desktop_app_info_search:
  * @search_string: the search string to use
  *