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;
/* 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,
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.
* 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 */
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
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 ();
}
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)
{
goto no_add;
/* Don't add duplicates already in the list */
- for (j = 0; strv[j]; j++)
+ for (j = 0; j < strv_len; j++)
if (g_str_equal (to_add[i], strv[j]))
goto no_add;
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;
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;
}
{
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)
{
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)
{
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
* 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);
}
gpointer app, path;
dir->memory_index = memory_index_new ();
+ dir->memory_implementations = memory_index_new ();
/* Nothing to search? */
if (dir->app_names == NULL)
!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++)
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);
}
}
+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 >
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);
dir->mime_tweaks = NULL;
}
+ if (dir->memory_implementations)
+ {
+ g_hash_table_unref (dir->memory_implementations);
+ dir->memory_implementations = NULL;
+ }
+
dir->is_setup = FALSE;
}
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)
{
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
/**
* 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.
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 */
* `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
/* "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
*