X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gio%2Fgdesktopappinfo.c;h=7bfc904719c73e3af44f8a15d5423aa16336c77c;hb=7fd6f07d498063470903a886b4805a13bd333908;hp=caccef079932ea8a41bfc68d058c1fe02b823d20;hpb=078dbda148a81af1b3a76fbda72f089b963087f1;p=platform%2Fupstream%2Fglib.git diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index caccef0..7bfc904 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -58,9 +58,9 @@ * #GDesktopAppInfo is an implementation of #GAppInfo based on * desktop files. * - * Note that <gio/gdesktopappinfo.h> belongs to - * the UNIX-specific GIO interfaces, thus you have to use the - * gio-unix-2.0.pc pkg-config file when using it. + * Note that `` belongs to the UNIX-specific + * GIO interfaces, thus you have to use the `gio-unix-2.0.pc` pkg-config + * file when using it. */ #define DEFAULT_APPLICATIONS_GROUP "Default Applications" @@ -78,11 +78,6 @@ enum { }; static void g_desktop_app_info_iface_init (GAppInfoIface *iface); -static GList * get_all_desktop_entries_for_mime_type (const char *base_mime_type, - const char **except, - gboolean include_fallback, - char **explicit_default); -static void mime_info_cache_reload (const char *dir); static gboolean g_desktop_app_info_ensure_saved (GDesktopAppInfo *info, GError **error); @@ -137,27 +132,76 @@ 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; - gboolean is_setup; + GHashTable *mime_tweaks; GHashTable *memory_index; + GHashTable *memory_implementations; } DesktopFileDir; static DesktopFileDir *desktop_file_dirs; static guint n_desktop_file_dirs; +static const guint desktop_file_dir_user_config_index = 0; +static guint desktop_file_dir_user_data_index; 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, @@ -166,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. @@ -173,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 */ @@ -212,6 +272,60 @@ desktop_file_dir_app_name_is_masked (DesktopFileDir *dir, return FALSE; } +static const gchar * const * +get_lowercase_current_desktops (void) +{ + static gchar **result; + + if (g_once_init_enter (&result)) + { + const gchar *envvar; + gchar **tmp; + + envvar = g_getenv ("XDG_CURRENT_DESKTOP"); + + if (envvar) + { + gint i, j; + + tmp = g_strsplit (envvar, G_SEARCHPATH_SEPARATOR_S, 0); + + for (i = 0; tmp[i]; i++) + for (j = 0; tmp[i][j]; j++) + tmp[i][j] = g_ascii_tolower (tmp[i][j]); + } + else + tmp = g_new0 (gchar *, 0 + 1); + + g_once_init_leave (&result, tmp); + } + + 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 @@ -247,6 +361,7 @@ add_to_table_if_appropriate (GHashTable *apps, enum { DESKTOP_KEY_Comment, + DESKTOP_KEY_Exec, DESKTOP_KEY_GenericName, DESKTOP_KEY_Keywords, DESKTOP_KEY_Name, @@ -262,10 +377,11 @@ const gchar desktop_key_match_category[N_DESKTOP_KEYS] = { * use the same number for the two different keys. */ [DESKTOP_KEY_Name] = 1, - [DESKTOP_KEY_Keywords] = 2, - [DESKTOP_KEY_GenericName] = 3, - [DESKTOP_KEY_X_GNOME_FullName] = 4, - [DESKTOP_KEY_Comment] = 5 + [DESKTOP_KEY_Exec] = 2, + [DESKTOP_KEY_Keywords] = 3, + [DESKTOP_KEY_GenericName] = 4, + [DESKTOP_KEY_X_GNOME_FullName] = 5, + [DESKTOP_KEY_Comment] = 6 }; static gchar * @@ -275,14 +391,16 @@ desktop_key_get_name (guint key_id) { case DESKTOP_KEY_Comment: return "Comment"; + 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 (); } @@ -513,7 +631,6 @@ merge_directory_results (void) static_search_results_size = 0; } - /* Support for unindexed DesktopFileDirs {{{2 */ static void get_apps_from_dir (GHashTable **apps, @@ -560,10 +677,244 @@ get_apps_from_dir (GHashTable **apps, g_dir_close (dir); } +typedef struct +{ + gchar **additions; + gchar **removals; + gchar **defaults; +} UnindexedMimeTweaks; + +static void +free_mime_tweaks (gpointer data) +{ + UnindexedMimeTweaks *tweaks = data; + + g_strfreev (tweaks->additions); + g_strfreev (tweaks->removals); + g_strfreev (tweaks->defaults); + + g_slice_free (UnindexedMimeTweaks, tweaks); +} + +static UnindexedMimeTweaks * +desktop_file_dir_unindexed_get_tweaks (DesktopFileDir *dir, + const gchar *mime_type) +{ + UnindexedMimeTweaks *tweaks; + gchar *unaliased_type; + + unaliased_type = _g_unix_content_type_unalias (mime_type); + tweaks = g_hash_table_lookup (dir->mime_tweaks, unaliased_type); + + if (tweaks == NULL) + { + tweaks = g_slice_new0 (UnindexedMimeTweaks); + g_hash_table_insert (dir->mime_tweaks, unaliased_type, tweaks); + } + else + g_free (unaliased_type); + + return tweaks; +} + +/* consumes 'to_add' */ +static void +expand_strv (gchar ***strv_ptr, + gchar **to_add, + gchar * const *blacklist) +{ + guint strv_len, add_len; + gchar **strv; + guint i, j; + + if (!*strv_ptr) + { + *strv_ptr = to_add; + return; + } + + strv = *strv_ptr; + strv_len = g_strv_length (strv); + add_len = g_strv_length (to_add); + strv = g_renew (gchar *, strv, strv_len + add_len + 1); + + for (i = 0; to_add[i]; i++) + { + /* Don't add blacklisted strings */ + if (blacklist) + for (j = 0; blacklist[j]; j++) + if (g_str_equal (to_add[i], blacklist[j])) + goto no_add; + + /* Don't add duplicates already in the list */ + for (j = 0; j < strv_len; j++) + if (g_str_equal (to_add[i], strv[j])) + goto no_add; + + strv[strv_len++] = to_add[i]; + continue; + +no_add: + g_free (to_add[i]); + } + + strv[strv_len] = NULL; + *strv_ptr = strv; + + g_free (to_add); +} + +static void +desktop_file_dir_unindexed_read_mimeapps_list (DesktopFileDir *dir, + const gchar *filename, + const gchar *added_group, + gboolean tweaks_permitted) +{ + UnindexedMimeTweaks *tweaks; + char **desktop_file_ids; + GKeyFile *key_file; + gchar **mime_types; + int i; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL)) + { + g_key_file_free (key_file); + return; + } + + mime_types = g_key_file_get_keys (key_file, added_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, added_group); + g_strfreev (mime_types); + mime_types = NULL; + } + + if (mime_types != NULL) + { + for (i = 0; mime_types[i] != NULL; i++) + { + desktop_file_ids = g_key_file_get_string_list (key_file, added_group, mime_types[i], NULL, NULL); + + if (desktop_file_ids) + { + tweaks = desktop_file_dir_unindexed_get_tweaks (dir, mime_types[i]); + expand_strv (&tweaks->additions, desktop_file_ids, tweaks->removals); + } + } + + g_strfreev (mime_types); + } + + 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_ASSOCIATIONS_GROUP); + g_strfreev (mime_types); + mime_types = NULL; + } + + if (mime_types != NULL) + { + for (i = 0; mime_types[i] != NULL; i++) + { + desktop_file_ids = g_key_file_get_string_list (key_file, REMOVED_ASSOCIATIONS_GROUP, mime_types[i], NULL, NULL); + + if (desktop_file_ids) + { + tweaks = desktop_file_dir_unindexed_get_tweaks (dir, mime_types[i]); + expand_strv (&tweaks->removals, desktop_file_ids, tweaks->additions); + } + } + + g_strfreev (mime_types); + } + + 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_APPLICATIONS_GROUP, mime_types[i], NULL, NULL); + + if (desktop_file_ids) + { + tweaks = desktop_file_dir_unindexed_get_tweaks (dir, mime_types[i]); + expand_strv (&tweaks->defaults, desktop_file_ids, NULL); + } + } + + g_strfreev (mime_types); + } + + g_key_file_free (key_file); +} + +static void +desktop_file_dir_unindexed_read_mimeapps_lists (DesktopFileDir *dir) +{ + const gchar * const *desktops; + gchar *filename; + gint i; + + dir->mime_tweaks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_mime_tweaks); + + /* We process in order of precedence, using a blacklisting approach to + * avoid recording later instructions that conflict with ones we found + * earlier. + * + * We first start with the XDG_CURRENT_DESKTOP files, in precedence + * order. + */ + desktops = get_lowercase_current_desktops (); + 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_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_GROUP, TRUE); + g_free (filename); + + /* The remaining files are only checked for in directories that might + * contain desktop files (ie: not the config dirs). + */ + if (dir->is_config) + return; + + /* We have 'defaults.list' which was only ever understood by GLib. It + * exists widely, but it has never been part of any spec and it should + * be treated as deprecated. This will be removed in a future + * version. + */ + filename = g_strdup_printf ("%s/defaults.list", dir->path); + 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_GROUP, TRUE); + g_free (filename); +} + static void desktop_file_dir_unindexed_init (DesktopFileDir *dir) { - get_apps_from_dir (&dir->app_names, dir->path, ""); + if (!dir->is_config) + get_apps_from_dir (&dir->app_names, dir->path, ""); + + desktop_file_dir_unindexed_read_mimeapps_lists (dir); } static GDesktopAppInfo * @@ -685,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) @@ -704,22 +1056,46 @@ 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++) { - gchar *value; + const gchar *value; + gchar *raw; if (!desktop_key_match_category[i]) continue; - value = g_key_file_get_locale_string (key_file, "Desktop Entry", desktop_key_get_name (i), NULL, NULL); + raw = g_key_file_get_locale_string (key_file, "Desktop Entry", desktop_key_get_name (i), NULL, NULL); + value = raw; + + if (i == DESKTOP_KEY_Exec && raw != NULL) + { + /* Special handling: only match basename of first field */ + gchar *space; + gchar *slash; + + /* Remove extra arguments, if any */ + space = raw + strcspn (raw, " \t\n"); /* IFS */ + *space = '\0'; + + /* Skip the pathname, if any */ + if ((slash = strrchr (raw, '/'))) + value = slash + 1; + } if (value) memory_index_add_string (dir->memory_index, value, desktop_key_match_category[i], app); - g_free (value); + 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); @@ -752,6 +1128,94 @@ desktop_file_dir_unindexed_search (DesktopFileDir *dir, } } +static gboolean +array_contains (GPtrArray *array, + const gchar *str) +{ + gint i; + + for (i = 0; i < array->len; i++) + if (g_str_equal (array->pdata[i], str)) + return TRUE; + + return FALSE; +} + +static void +desktop_file_dir_unindexed_mime_lookup (DesktopFileDir *dir, + const gchar *mime_type, + GPtrArray *hits, + GPtrArray *blacklist) +{ + UnindexedMimeTweaks *tweaks; + gint i; + + tweaks = g_hash_table_lookup (dir->mime_tweaks, mime_type); + + if (!tweaks) + return; + + if (tweaks->additions) + { + for (i = 0; tweaks->additions[i]; i++) + { + gchar *app_name = tweaks->additions[i]; + + if (!desktop_file_dir_app_name_is_masked (dir, app_name) && + !array_contains (blacklist, app_name) && !array_contains (hits, app_name)) + g_ptr_array_add (hits, g_strdup (app_name)); + } + } + + if (tweaks->removals) + { + for (i = 0; tweaks->removals[i]; i++) + { + gchar *app_name = tweaks->removals[i]; + + if (!desktop_file_dir_app_name_is_masked (dir, app_name) && + !array_contains (blacklist, app_name) && !array_contains (hits, app_name)) + g_ptr_array_add (blacklist, app_name); + } + } +} + +static void +desktop_file_dir_unindexed_default_lookup (DesktopFileDir *dir, + const gchar *mime_type, + GPtrArray *results) +{ + UnindexedMimeTweaks *tweaks; + gint i; + + tweaks = g_hash_table_lookup (dir->mime_tweaks, mime_type); + + if (!tweaks || !tweaks->defaults) + return; + + for (i = 0; tweaks->defaults[i]; i++) + { + gchar *app_name = tweaks->defaults[i]; + + if (!array_contains (results, app_name)) + g_ptr_array_add (results, g_strdup (app_name)); + } +} + +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 > @@ -774,6 +1238,28 @@ desktop_file_dir_create (GArray *array, } /*< internal > + * desktop_file_dir_create: + * @array: the #GArray to add a new item to + * @config_dir: an XDG_CONFIG_DIR + * + * Just the same as desktop_file_dir_create() except that it does not + * add the "applications" directory. It also marks the directory as + * config-only, which prevents us from attempting to find desktop files + * here. + */ +static void +desktop_file_dir_create_for_config (GArray *array, + const gchar *config_dir) +{ + DesktopFileDir dir = { 0, }; + + dir.path = g_strdup (config_dir); + dir.is_config = TRUE; + + g_array_append_val (array, dir); +} + +/*< internal > * desktop_file_dir_reset: * @dir: a #DesktopFileDir * @@ -782,6 +1268,12 @@ desktop_file_dir_create (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); @@ -801,6 +1293,18 @@ desktop_file_dir_reset (DesktopFileDir *dir) dir->memory_index = NULL; } + if (dir->mime_tweaks) + { + g_hash_table_unref (dir->mime_tweaks); + dir->mime_tweaks = NULL; + } + + if (dir->memory_implementations) + { + g_hash_table_unref (dir->memory_implementations); + dir->memory_implementations = NULL; + } + dir->is_setup = FALSE; } @@ -815,12 +1319,25 @@ 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); - if (dir->monitor) + 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) { g_signal_connect (dir->monitor, "changed", G_CALLBACK (desktop_file_dir_changed), dir); g_local_directory_monitor_start (dir->monitor); @@ -869,8 +1386,49 @@ desktop_file_dir_get_all (DesktopFileDir *dir, } /*< internal > + * desktop_file_dir_mime_lookup: + * @dir: a #DesktopFileDir + * @mime_type: the mime type to look up + * @hits: the array to store the hits + * @blacklist: the array to store the blacklist + * + * Does a lookup of a mimetype against one desktop file directory, + * recording any hits and blacklisting and "Removed" associations (so + * later directories don't record them as hits). + * + * The items added to @hits are duplicated, but the ones in @blacklist + * are weak pointers. This facilitates simply freeing the blacklist + * (which is only used for internal bookkeeping) but using the pdata of + * @hits as the result of the operation. + */ +static void +desktop_file_dir_mime_lookup (DesktopFileDir *dir, + const gchar *mime_type, + GPtrArray *hits, + GPtrArray *blacklist) +{ + desktop_file_dir_unindexed_mime_lookup (dir, mime_type, hits, blacklist); +} + +/*< internal > + * desktop_file_dir_default_lookup: + * @dir: a #DesktopFileDir + * @mime_type: the mime type to look up + * @results: an array to store the results in + * + * Collects the "default" applications for a given mime type from @dir. + */ +static void +desktop_file_dir_default_lookup (DesktopFileDir *dir, + const gchar *mime_type, + GPtrArray *results) +{ + desktop_file_dir_unindexed_default_lookup (dir, mime_type, results); +} + +/*< internal > * desktop_file_dir_search: - * @dir: a #DesktopFilEDir + * @dir: a #DesktopFileDir * @term: a normalised and casefolded search term * * Finds the names of applications in @dir that match @term. @@ -882,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 @@ -893,20 +1459,30 @@ desktop_file_dirs_lock (void) if (desktop_file_dirs == NULL) { - const char * const *data_dirs; + const char * const *dirs; GArray *tmp; gint i; tmp = g_array_new (FALSE, FALSE, sizeof (DesktopFileDir)); - /* Highest priority: the user's ~/.local/share/applications */ + /* First, the configs. Highest priority: the user's ~/.config */ + desktop_file_dir_create_for_config (tmp, g_get_user_config_dir ()); + + /* Next, the system configs (/etc/xdg, and so on). */ + dirs = g_get_system_config_dirs (); + for (i = 0; dirs[i]; i++) + desktop_file_dir_create_for_config (tmp, dirs[i]); + + /* Now the data. Highest priority: the user's ~/.local/share/applications */ + desktop_file_dir_user_data_index = tmp->len; desktop_file_dir_create (tmp, g_get_user_data_dir ()); /* Following that, XDG_DATA_DIRS/applications, in order */ - data_dirs = g_get_system_data_dirs (); - for (i = 0; data_dirs[i]; i++) - desktop_file_dir_create (tmp, data_dirs[i]); + dirs = g_get_system_data_dirs (); + for (i = 0; dirs[i]; i++) + desktop_file_dir_create (tmp, dirs[i]); + /* The list of directories will never change after this. */ desktop_file_dirs = (DesktopFileDir *) tmp->data; n_desktop_file_dirs = tmp->len; @@ -925,19 +1501,23 @@ desktop_file_dirs_unlock (void) } static void -desktop_file_dirs_refresh (void) +desktop_file_dirs_invalidate_user_config (void) { - desktop_file_dirs_lock (); - desktop_file_dirs_unlock (); + g_mutex_lock (&desktop_file_dir_lock); + + if (n_desktop_file_dirs) + desktop_file_dir_reset (&desktop_file_dirs[desktop_file_dir_user_config_index]); + + g_mutex_unlock (&desktop_file_dir_lock); } static void -desktop_file_dirs_invalidate_user (void) +desktop_file_dirs_invalidate_user_data (void) { g_mutex_lock (&desktop_file_dir_lock); if (n_desktop_file_dirs) - desktop_file_dir_reset (&desktop_file_dirs[0]); + desktop_file_dir_reset (&desktop_file_dirs[desktop_file_dir_user_data_index]); g_mutex_unlock (&desktop_file_dir_lock); } @@ -1306,13 +1886,13 @@ g_desktop_app_info_new_from_filename (const char *filename) * * A desktop file id is the basename of the desktop file, including the * .desktop extension. GIO is looking for a desktop file with this name - * in the applications subdirectories of the XDG data - * directories (i.e. the directories specified in the - * XDG_DATA_HOME and XDG_DATA_DIRS environment - * variables). GIO also supports the prefix-to-subdirectory mapping that is - * described in the Menu Spec + * in the `applications` subdirectories of the XDG + * data directories (i.e. the directories specified in the `XDG_DATA_HOME` + * and `XDG_DATA_DIRS` environment variables). GIO also supports the + * prefix-to-subdirectory mapping that is described in the + * [Menu Spec](http://standards.freedesktop.org/menu-spec/latest/) * (i.e. a desktop id of kde-foo.desktop will match - * /usr/share/applications/kde/foo.desktop). + * `/usr/share/applications/kde/foo.desktop`). * * Returns: a new #GDesktopAppInfo, or %NULL if no desktop file with that id */ @@ -1560,20 +2140,22 @@ 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. + * `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. * * Returns: %TRUE if the @info should be shown in @desktop_env according to the - * OnlyShowIn and NotShowIn keys, %FALSE + * `OnlyShowIn` and `NotShowIn` keys, %FALSE * otherwise. * * Since: 2.30 @@ -1582,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 */ @@ -2105,7 +2675,7 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, GPid pid; GList *launched_uris; GList *iter; - char *display, *sn_id; + char *display, *sn_id = NULL; old_uris = uris; if (!expand_application_parameters (info, exec_line, &uris, &argc, &argv, error)) @@ -2161,7 +2731,8 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, sn_id = g_app_launch_context_get_startup_notify_id (launch_context, G_APP_INFO (info), launched_files); - envp = g_environ_setenv (envp, "DESKTOP_STARTUP_ID", sn_id, TRUE); + if (sn_id) + envp = g_environ_setenv (envp, "DESKTOP_STARTUP_ID", sn_id, TRUE); } g_list_free_full (launched_files, g_object_unref); @@ -2268,7 +2839,8 @@ g_desktop_app_info_make_platform_data (GDesktopAppInfo *info, gchar *sn_id; sn_id = g_app_launch_context_get_startup_notify_id (launch_context, G_APP_INFO (info), launched_files); - g_variant_builder_add (&builder, "{sv}", "desktop-startup-id", g_variant_new_take_string (sn_id)); + if (sn_id) + g_variant_builder_add (&builder, "{sv}", "desktop-startup-id", g_variant_new_take_string (sn_id)); } g_list_free_full (launched_files, g_object_unref); @@ -2473,30 +3045,18 @@ g_desktop_app_info_launch_uris_as_manager (GDesktopAppInfo *appinfo, * Sets the name of the desktop that the application is running in. * This is used by g_app_info_should_show() and * g_desktop_app_info_get_show_in() to evaluate the - * OnlyShowIn and NotShowIn + * `OnlyShowIn` and `NotShowIn` * desktop entry fields. * - * The Desktop - * Menu specification 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 @@ -2513,6 +3073,7 @@ g_desktop_app_info_should_show (GAppInfo *appinfo) /* mime types/default apps support {{{2 */ typedef enum { + CONF_DIR, APP_DIR, MIMETYPE_DIR } DirType; @@ -2524,10 +3085,23 @@ ensure_dir (DirType type, char *path, *display_name; int errsv; - if (type == APP_DIR) - path = g_build_filename (g_get_user_data_dir (), "applications", NULL); - else - path = g_build_filename (g_get_user_data_dir (), "mime", "packages", NULL); + switch (type) + { + case CONF_DIR: + path = g_build_filename (g_get_user_config_dir (), NULL); + break; + + case APP_DIR: + path = g_build_filename (g_get_user_data_dir (), "applications", NULL); + break; + + case MIMETYPE_DIR: + path = g_build_filename (g_get_user_data_dir (), "mime", "packages", NULL); + break; + + default: + g_assert_not_reached (); + } errno = 0; if (g_mkdir_with_parents (path, 0700) == 0) @@ -2569,7 +3143,7 @@ update_mimeapps_list (const char *desktop_id, g_assert (!((flags & UPDATE_MIME_SET_DEFAULT) && (flags & UPDATE_MIME_SET_NON_DEFAULT))); - dirname = ensure_dir (APP_DIR, error); + dirname = ensure_dir (CONF_DIR, error); if (!dirname) return FALSE; @@ -2760,7 +3334,7 @@ update_mimeapps_list (const char *desktop_id, res = g_file_set_contents (filename, data, data_size, error); - mime_info_cache_reload (NULL); + desktop_file_dirs_invalidate_user_config (); g_free (filename); g_free (data); @@ -3071,7 +3645,7 @@ g_desktop_app_info_ensure_saved (GDesktopAppInfo *info, * This means that calls directly following this will be able to see * the results immediately. */ - desktop_file_dirs_invalidate_user (); + desktop_file_dirs_invalidate_user_data (); return TRUE; } @@ -3126,8 +3700,8 @@ g_desktop_app_info_delete (GAppInfo *appinfo) * Creates a new #GAppInfo from the given information. * * Note that for @commandline, the quoting rules of the Exec key of the - * freedesktop.org Desktop - * Entry Specification are applied. For example, if the @commandline contains + * [freedesktop.org Desktop Entry Specification](http://freedesktop.org/Standards/desktop-entry-spec) + * are applied. For example, if the @commandline contains * percent-encoded URIs, the percent-character must be doubled in order to prevent it from * being swallowed by Exec key unquoting. See the specification for exact quoting rules. * @@ -3209,17 +3783,99 @@ g_desktop_app_info_iface_init (GAppInfoIface *iface) /* Recommended applications {{{2 */ -static gboolean -app_info_in_list (GAppInfo *info, - GList *list) +/* Converts content_type into a list of itself with all of its parent + * types (if include_fallback is enabled) or just returns a single-item + * list with the unaliased content type. + */ +static gchar ** +get_list_of_mimetypes (const gchar *content_type, + gboolean include_fallback) { - while (list != NULL) + gchar *unaliased; + GPtrArray *array; + + array = g_ptr_array_new (); + unaliased = _g_unix_content_type_unalias (content_type); + g_ptr_array_add (array, unaliased); + + if (include_fallback) { - if (g_app_info_equal (info, list->data)) - return TRUE; - list = list->next; + gint i; + + /* Iterate the array as we grow it, until we have nothing more to add */ + for (i = 0; i < array->len; i++) + { + gchar **parents = _g_unix_content_type_get_parents (g_ptr_array_index (array, i)); + gint j; + + for (j = 0; parents[j]; j++) + /* Don't add duplicates */ + if (!array_contains (array, parents[j])) + g_ptr_array_add (array, parents[j]); + else + g_free (parents[j]); + + /* We already stole or freed each element. Free the container. */ + g_free (parents); + } } - return FALSE; + + g_ptr_array_add (array, NULL); + + return (gchar **) g_ptr_array_free (array, FALSE); +} + +static gchar ** +g_desktop_app_info_get_desktop_ids_for_content_type (const gchar *content_type, + gboolean include_fallback) +{ + GPtrArray *hits, *blacklist; + gchar **types; + gint i, j; + + hits = g_ptr_array_new (); + blacklist = g_ptr_array_new (); + + types = get_list_of_mimetypes (content_type, include_fallback); + + desktop_file_dirs_lock (); + + for (i = 0; types[i]; i++) + for (j = 0; j < n_desktop_file_dirs; j++) + desktop_file_dir_mime_lookup (&desktop_file_dirs[j], types[i], hits, blacklist); + + desktop_file_dirs_unlock (); + + g_ptr_array_add (hits, NULL); + + g_ptr_array_free (blacklist, TRUE); + g_strfreev (types); + + return (gchar **) g_ptr_array_free (hits, FALSE); +} + +static gchar ** +g_desktop_app_info_get_defaults_for_content_type (const gchar *content_type) +{ + GPtrArray *results; + gchar **types; + gint i, j; + + types = get_list_of_mimetypes (content_type, TRUE); + results = g_ptr_array_new (); + + desktop_file_dirs_lock (); + + for (i = 0; types[i]; i++) + for (j = 0; j < n_desktop_file_dirs; j++) + desktop_file_dir_default_lookup (&desktop_file_dirs[j], types[i], results); + + desktop_file_dirs_unlock (); + + g_ptr_array_add (results, NULL); + g_strfreev (types); + + return (gchar **) g_ptr_array_free (results, FALSE); } /** @@ -3241,31 +3897,25 @@ app_info_in_list (GAppInfo *info, GList * g_app_info_get_recommended_for_type (const gchar *content_type) { - GList *desktop_entries, *l; + gchar **desktop_ids; GList *infos; - GDesktopAppInfo *info; + gint i; g_return_val_if_fail (content_type != NULL, NULL); - desktop_entries = get_all_desktop_entries_for_mime_type (content_type, NULL, FALSE, NULL); + desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, FALSE); infos = NULL; - for (l = desktop_entries; l != NULL; l = l->next) + for (i = 0; desktop_ids[i]; i++) { - char *desktop_entry = l->data; + GDesktopAppInfo *info; - info = g_desktop_app_info_new (desktop_entry); + info = g_desktop_app_info_new (desktop_ids[i]); if (info) - { - if (app_info_in_list (G_APP_INFO (info), infos)) - g_object_unref (info); - else - infos = g_list_prepend (infos, info); - } - g_free (desktop_entry); + infos = g_list_prepend (infos, info); } - g_list_free (desktop_entries); + g_strfreev (desktop_ids); return g_list_reverse (infos); } @@ -3286,34 +3936,38 @@ g_app_info_get_recommended_for_type (const gchar *content_type) GList * g_app_info_get_fallback_for_type (const gchar *content_type) { - GList *desktop_entries, *l; - GList *infos, *recommended_infos; - GDesktopAppInfo *info; + gchar **recommended_ids; + gchar **all_ids; + GList *infos; + gint i; g_return_val_if_fail (content_type != NULL, NULL); - desktop_entries = get_all_desktop_entries_for_mime_type (content_type, NULL, TRUE, NULL); - recommended_infos = g_app_info_get_recommended_for_type (content_type); + recommended_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, FALSE); + all_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE); infos = NULL; - for (l = desktop_entries; l != NULL; l = l->next) + for (i = 0; all_ids[i]; i++) { - char *desktop_entry = l->data; + GDesktopAppInfo *info; + gint j; + + /* Don't return the ones on the recommended list */ + for (j = 0; recommended_ids[j]; j++) + if (g_str_equal (all_ids[i], recommended_ids[j])) + break; + + if (recommended_ids[j]) + continue; + + info = g_desktop_app_info_new (all_ids[i]); - info = g_desktop_app_info_new (desktop_entry); if (info) - { - if (app_info_in_list (G_APP_INFO (info), infos) || - app_info_in_list (G_APP_INFO (info), recommended_infos)) - g_object_unref (info); - else - infos = g_list_prepend (infos, info); - } - g_free (desktop_entry); + infos = g_list_prepend (infos, info); } - g_list_free (desktop_entries); - g_list_free_full (recommended_infos, g_object_unref); + g_strfreev (recommended_ids); + g_strfreev (all_ids); return g_list_reverse (infos); } @@ -3333,43 +3987,25 @@ g_app_info_get_fallback_for_type (const gchar *content_type) GList * g_app_info_get_all_for_type (const char *content_type) { - GList *desktop_entries, *l; + gchar **desktop_ids; GList *infos; - char *user_default = NULL; - GDesktopAppInfo *info; + gint i; g_return_val_if_fail (content_type != NULL, NULL); - desktop_entries = get_all_desktop_entries_for_mime_type (content_type, NULL, TRUE, &user_default); - infos = NULL; + desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE); - /* put the user default in front of the list, for compatibility */ - if (user_default != NULL) - { - info = g_desktop_app_info_new (user_default); - - if (info != NULL) - infos = g_list_prepend (infos, info); - } - - g_free (user_default); - - for (l = desktop_entries; l != NULL; l = l->next) + infos = NULL; + for (i = 0; desktop_ids[i]; i++) { - char *desktop_entry = l->data; + GDesktopAppInfo *info; - info = g_desktop_app_info_new (desktop_entry); + info = g_desktop_app_info_new (desktop_ids[i]); if (info) - { - if (app_info_in_list (G_APP_INFO (info), infos)) - g_object_unref (info); - else - infos = g_list_prepend (infos, info); - } - g_free (desktop_entry); + infos = g_list_prepend (infos, info); } - g_list_free (desktop_entries); + g_strfreev (desktop_ids); return g_list_reverse (infos); } @@ -3409,59 +4045,56 @@ GAppInfo * g_app_info_get_default_for_type (const char *content_type, gboolean must_support_uris) { - GList *desktop_entries, *l; - char *user_default = NULL; + gchar **desktop_ids; GAppInfo *info; + gint i; g_return_val_if_fail (content_type != NULL, NULL); - desktop_entries = get_all_desktop_entries_for_mime_type (content_type, NULL, TRUE, &user_default); + desktop_ids = g_desktop_app_info_get_defaults_for_content_type (content_type); info = NULL; - - if (user_default != NULL) + for (i = 0; desktop_ids[i]; i++) { - info = (GAppInfo *) g_desktop_app_info_new (user_default); + info = (GAppInfo *) g_desktop_app_info_new (desktop_ids[i]); if (info) { - if (must_support_uris && !g_app_info_supports_uris (info)) - { - g_object_unref (info); - info = NULL; - } + if (!must_support_uris || g_app_info_supports_uris (info)) + break; + + g_object_unref (info); + info = NULL; } } - g_free (user_default); - - if (info != NULL) - { - g_list_free_full (desktop_entries, g_free); - return info; - } + g_strfreev (desktop_ids); - /* pick the first from the other list that matches our URI - * requirements. + /* If we can't find a default app for this content type, pick one from + * the list of all supported apps. This will be ordered by the user's + * preference and by "recommended" apps first, so the first one we + * find is probably the best fallback. */ - for (l = desktop_entries; l != NULL; l = l->next) + if (info == NULL) { - char *desktop_entry = l->data; + desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE); - info = (GAppInfo *)g_desktop_app_info_new (desktop_entry); - if (info) + for (i = 0; desktop_ids[i]; i++) { - if (must_support_uris && !g_app_info_supports_uris (info)) + info = (GAppInfo *) g_desktop_app_info_new (desktop_ids[i]); + + if (info) { + if (!must_support_uris || g_app_info_supports_uris (info)) + break; + g_object_unref (info); info = NULL; } - else - break; } - } - g_list_free_full (desktop_entries, g_free); + g_strfreev (desktop_ids); + } return info; } @@ -3495,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 * @@ -3586,13 +4268,12 @@ g_desktop_app_info_search (const gchar *search_string) * on this system. * * For desktop files, this includes applications that have - * NoDisplay=true set or are excluded from - * display by means of OnlyShowIn or - * NotShowIn. See g_app_info_should_show(). + * `NoDisplay=true` set or are excluded from display by means + * of `OnlyShowIn` or `NotShowIn`. See g_app_info_should_show(). * The returned list does not include applications which have - * the Hidden key set. + * the `Hidden` key set. * - * Returns: (element-type GAppInfo) (transfer full): a newly allocated #GList of references to #GAppInfos. + * Returns: (element-type GAppInfo) (transfer full): a newly allocated #GList of references to #GAppInfos. **/ GList * g_app_info_get_all (void) @@ -3625,728 +4306,6 @@ g_app_info_get_all (void) return infos; } -/* Caching of mimeinfo.cache and defaults.list files {{{2 */ - -typedef struct { - char *path; - GHashTable *mime_info_cache_map; - GHashTable *defaults_list_map; - GHashTable *mimeapps_list_added_map; - GHashTable *mimeapps_list_removed_map; - GHashTable *mimeapps_list_defaults_map; - time_t mime_info_cache_timestamp; - time_t defaults_list_timestamp; - time_t mimeapps_list_timestamp; -} MimeInfoCacheDir; - -typedef struct { - GList *dirs; /* mimeinfo.cache and defaults.list */ - time_t last_stat_time; -} MimeInfoCache; - -static MimeInfoCache *mime_info_cache = NULL; -G_LOCK_DEFINE_STATIC (mime_info_cache); - -static void mime_info_cache_dir_add_desktop_entries (MimeInfoCacheDir *dir, - const char *mime_type, - char **new_desktop_file_ids); - -static MimeInfoCache * mime_info_cache_new (void); - -static void -destroy_info_cache_value (gpointer key, - GList *value, - gpointer data) -{ - g_list_free_full (value, g_free); -} - -static void -destroy_info_cache_map (GHashTable *info_cache_map) -{ - g_hash_table_foreach (info_cache_map, (GHFunc)destroy_info_cache_value, NULL); - g_hash_table_destroy (info_cache_map); -} - -static gboolean -mime_info_cache_dir_out_of_date (MimeInfoCacheDir *dir, - const char *cache_file, - time_t *timestamp) -{ - struct stat buf; - char *filename; - - filename = g_build_filename (dir->path, cache_file, NULL); - - if (g_stat (filename, &buf) < 0) - { - g_free (filename); - return TRUE; - } - g_free (filename); - - if (buf.st_mtime != *timestamp) - return TRUE; - - return FALSE; -} - -/* Call with lock held */ -static void -mime_info_cache_dir_init (MimeInfoCacheDir *dir) -{ - GError *load_error; - GKeyFile *key_file; - gchar *filename, **mime_types; - int i; - struct stat buf; - - load_error = NULL; - mime_types = NULL; - - if (dir->mime_info_cache_map != NULL && - !mime_info_cache_dir_out_of_date (dir, "mimeinfo.cache", - &dir->mime_info_cache_timestamp)) - return; - - if (dir->mime_info_cache_map != NULL) - destroy_info_cache_map (dir->mime_info_cache_map); - - dir->mime_info_cache_map = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify) g_free, - NULL); - - key_file = g_key_file_new (); - - filename = g_build_filename (dir->path, "mimeinfo.cache", NULL); - - if (g_stat (filename, &buf) < 0) - goto error; - - dir->mime_info_cache_timestamp = buf.st_mtime; - - g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &load_error); - - g_free (filename); - filename = NULL; - - if (load_error != NULL) - goto error; - - mime_types = g_key_file_get_keys (key_file, MIME_CACHE_GROUP, - NULL, &load_error); - - if (load_error != NULL) - goto error; - - for (i = 0; mime_types[i] != NULL; i++) - { - gchar **desktop_file_ids; - char *unaliased_type; - desktop_file_ids = g_key_file_get_string_list (key_file, - MIME_CACHE_GROUP, - mime_types[i], - NULL, - NULL); - - if (desktop_file_ids == NULL) - continue; - - unaliased_type = _g_unix_content_type_unalias (mime_types[i]); - mime_info_cache_dir_add_desktop_entries (dir, - unaliased_type, - desktop_file_ids); - g_free (unaliased_type); - - g_strfreev (desktop_file_ids); - } - - g_strfreev (mime_types); - g_key_file_free (key_file); - - return; - error: - g_free (filename); - g_key_file_free (key_file); - - if (mime_types != NULL) - g_strfreev (mime_types); - - if (load_error) - g_error_free (load_error); -} - -static void -mime_info_cache_dir_init_defaults_list (MimeInfoCacheDir *dir) -{ - GKeyFile *key_file; - GError *load_error; - gchar *filename, **mime_types; - char *unaliased_type; - char **desktop_file_ids; - int i; - struct stat buf; - - load_error = NULL; - mime_types = NULL; - - if (dir->defaults_list_map != NULL && - !mime_info_cache_dir_out_of_date (dir, "defaults.list", - &dir->defaults_list_timestamp)) - return; - - if (dir->defaults_list_map != NULL) - g_hash_table_destroy (dir->defaults_list_map); - dir->defaults_list_map = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, (GDestroyNotify)g_strfreev); - - - key_file = g_key_file_new (); - - filename = g_build_filename (dir->path, "defaults.list", NULL); - if (g_stat (filename, &buf) < 0) - goto error; - - dir->defaults_list_timestamp = buf.st_mtime; - - g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &load_error); - g_free (filename); - filename = NULL; - - if (load_error != NULL) - goto error; - - 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_APPLICATIONS_GROUP, - mime_types[i], - NULL, - NULL); - if (desktop_file_ids == NULL) - continue; - - unaliased_type = _g_unix_content_type_unalias (mime_types[i]); - g_hash_table_replace (dir->defaults_list_map, - unaliased_type, - desktop_file_ids); - } - - g_strfreev (mime_types); - } - - g_key_file_free (key_file); - return; - - error: - g_free (filename); - g_key_file_free (key_file); - - if (mime_types != NULL) - g_strfreev (mime_types); - - if (load_error) - g_error_free (load_error); -} - -static void -mime_info_cache_dir_init_mimeapps_list (MimeInfoCacheDir *dir) -{ - GKeyFile *key_file; - GError *load_error; - gchar *filename, **mime_types; - char *unaliased_type; - char **desktop_file_ids; - char *desktop_id; - int i; - struct stat buf; - - load_error = NULL; - mime_types = NULL; - - if (dir->mimeapps_list_added_map != NULL && - !mime_info_cache_dir_out_of_date (dir, "mimeapps.list", - &dir->mimeapps_list_timestamp)) - return; - - if (dir->mimeapps_list_added_map != NULL) - g_hash_table_destroy (dir->mimeapps_list_added_map); - dir->mimeapps_list_added_map = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, (GDestroyNotify)g_strfreev); - - if (dir->mimeapps_list_removed_map != NULL) - g_hash_table_destroy (dir->mimeapps_list_removed_map); - dir->mimeapps_list_removed_map = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, (GDestroyNotify)g_strfreev); - - if (dir->mimeapps_list_defaults_map != NULL) - g_hash_table_destroy (dir->mimeapps_list_defaults_map); - dir->mimeapps_list_defaults_map = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, g_free); - - key_file = g_key_file_new (); - - filename = g_build_filename (dir->path, "mimeapps.list", NULL); - if (g_stat (filename, &buf) < 0) - goto error; - - dir->mimeapps_list_timestamp = buf.st_mtime; - - g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &load_error); - g_free (filename); - filename = NULL; - - if (load_error != NULL) - goto error; - - mime_types = g_key_file_get_keys (key_file, ADDED_ASSOCIATIONS_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, - ADDED_ASSOCIATIONS_GROUP, - mime_types[i], - NULL, - NULL); - if (desktop_file_ids == NULL) - continue; - - unaliased_type = _g_unix_content_type_unalias (mime_types[i]); - g_hash_table_replace (dir->mimeapps_list_added_map, - unaliased_type, - desktop_file_ids); - } - - g_strfreev (mime_types); - } - - mime_types = g_key_file_get_keys (key_file, REMOVED_ASSOCIATIONS_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, - REMOVED_ASSOCIATIONS_GROUP, - mime_types[i], - NULL, - NULL); - if (desktop_file_ids == NULL) - continue; - - unaliased_type = _g_unix_content_type_unalias (mime_types[i]); - g_hash_table_replace (dir->mimeapps_list_removed_map, - unaliased_type, - desktop_file_ids); - } - - g_strfreev (mime_types); - } - - 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_id = g_key_file_get_string (key_file, - DEFAULT_APPLICATIONS_GROUP, - mime_types[i], - NULL); - if (desktop_id == NULL) - continue; - - unaliased_type = _g_unix_content_type_unalias (mime_types[i]); - g_hash_table_replace (dir->mimeapps_list_defaults_map, - unaliased_type, - desktop_id); - } - - g_strfreev (mime_types); - } - - g_key_file_free (key_file); - return; - - error: - g_free (filename); - g_key_file_free (key_file); - - if (mime_types != NULL) - g_strfreev (mime_types); - - if (load_error) - g_error_free (load_error); -} - -static MimeInfoCacheDir * -mime_info_cache_dir_new (const char *path) -{ - MimeInfoCacheDir *dir; - - dir = g_new0 (MimeInfoCacheDir, 1); - dir->path = g_strdup (path); - - return dir; -} - -static void -mime_info_cache_dir_free (MimeInfoCacheDir *dir) -{ - if (dir == NULL) - return; - - if (dir->mime_info_cache_map != NULL) - { - destroy_info_cache_map (dir->mime_info_cache_map); - dir->mime_info_cache_map = NULL; - - } - - if (dir->defaults_list_map != NULL) - { - g_hash_table_destroy (dir->defaults_list_map); - dir->defaults_list_map = NULL; - } - - if (dir->mimeapps_list_added_map != NULL) - { - g_hash_table_destroy (dir->mimeapps_list_added_map); - dir->mimeapps_list_added_map = NULL; - } - - if (dir->mimeapps_list_removed_map != NULL) - { - g_hash_table_destroy (dir->mimeapps_list_removed_map); - dir->mimeapps_list_removed_map = NULL; - } - - if (dir->mimeapps_list_defaults_map != NULL) - { - g_hash_table_destroy (dir->mimeapps_list_defaults_map); - dir->mimeapps_list_defaults_map = NULL; - } - - g_free (dir); -} - -static void -mime_info_cache_dir_add_desktop_entries (MimeInfoCacheDir *dir, - const char *mime_type, - char **new_desktop_file_ids) -{ - GList *desktop_file_ids; - int i; - - desktop_file_ids = g_hash_table_lookup (dir->mime_info_cache_map, - mime_type); - - for (i = 0; new_desktop_file_ids[i] != NULL; i++) - { - if (!g_list_find_custom (desktop_file_ids, new_desktop_file_ids[i], (GCompareFunc) strcmp)) - desktop_file_ids = g_list_append (desktop_file_ids, - g_strdup (new_desktop_file_ids[i])); - } - - g_hash_table_insert (dir->mime_info_cache_map, g_strdup (mime_type), desktop_file_ids); -} - -static void -mime_info_cache_init_dir_lists (void) -{ - int i; - - mime_info_cache = mime_info_cache_new (); - - desktop_file_dirs_refresh (); - - for (i = 0; i < n_desktop_file_dirs; i++) - { - MimeInfoCacheDir *dir; - - dir = mime_info_cache_dir_new (desktop_file_dirs[i].path); - - if (dir != NULL) - { - mime_info_cache_dir_init (dir); - mime_info_cache_dir_init_defaults_list (dir); - mime_info_cache_dir_init_mimeapps_list (dir); - - mime_info_cache->dirs = g_list_append (mime_info_cache->dirs, dir); - } - } -} - -static void -mime_info_cache_update_dir_lists (void) -{ - GList *tmp; - - tmp = mime_info_cache->dirs; - - while (tmp != NULL) - { - MimeInfoCacheDir *dir = (MimeInfoCacheDir *) tmp->data; - - /* No need to do this if we had file monitors... */ - mime_info_cache_dir_init (dir); - mime_info_cache_dir_init_defaults_list (dir); - mime_info_cache_dir_init_mimeapps_list (dir); - - tmp = tmp->next; - } -} - -static void -mime_info_cache_init (void) -{ - G_LOCK (mime_info_cache); - if (mime_info_cache == NULL) - mime_info_cache_init_dir_lists (); - else - { - time_t now; - - time (&now); - if (now >= mime_info_cache->last_stat_time + 10) - { - mime_info_cache_update_dir_lists (); - mime_info_cache->last_stat_time = now; - } - } - - G_UNLOCK (mime_info_cache); -} - -static MimeInfoCache * -mime_info_cache_new (void) -{ - MimeInfoCache *cache; - - cache = g_new0 (MimeInfoCache, 1); - - return cache; -} - -static void -mime_info_cache_free (MimeInfoCache *cache) -{ - if (cache == NULL) - return; - - g_list_free_full (cache->dirs, (GDestroyNotify) mime_info_cache_dir_free); - g_free (cache); -} - -/** - * mime_info_cache_reload: - * @dir: directory path which needs reloading. - * - * Reload the mime information for the @dir. - */ -static void -mime_info_cache_reload (const char *dir) -{ - /* FIXME: just reload the dir that needs reloading, - * don't blow the whole cache - */ - if (mime_info_cache != NULL) - { - G_LOCK (mime_info_cache); - mime_info_cache_free (mime_info_cache); - mime_info_cache = NULL; - G_UNLOCK (mime_info_cache); - } -} - -static GList * -append_desktop_entry (GList *list, - const char *desktop_entry, - GList *removed_entries) -{ - /* Add if not already in list, and valid */ - if (!g_list_find_custom (list, desktop_entry, (GCompareFunc) strcmp) && - !g_list_find_custom (removed_entries, desktop_entry, (GCompareFunc) strcmp)) - list = g_list_prepend (list, g_strdup (desktop_entry)); - - return list; -} - -/** - * get_all_desktop_entries_for_mime_type: - * @mime_type: a mime type. - * @except: NULL or a strv list - * - * Returns all the desktop ids for @mime_type. The desktop files - * are listed in an order so that default applications are listed before - * non-default ones, and handlers for inherited mimetypes are listed - * after the base ones. - * - * Optionally doesn't list the desktop ids given in the @except - * - * Return value: a #GList containing the desktop ids which claim - * to handle @mime_type. - */ -static GList * -get_all_desktop_entries_for_mime_type (const char *base_mime_type, - const char **except, - gboolean include_fallback, - char **explicit_default) -{ - GList *desktop_entries, *removed_entries, *list, *dir_list, *tmp; - MimeInfoCacheDir *dir; - char *mime_type, *default_entry = NULL; - char *old_default_entry = NULL; - const char *entry; - char **mime_types; - char **default_entries; - char **removed_associations; - gboolean already_found_handler; - int i, j, k; - GPtrArray *array; - char **anc; - - mime_info_cache_init (); - - if (include_fallback) - { - /* collect all ancestors */ - mime_types = _g_unix_content_type_get_parents (base_mime_type); - array = g_ptr_array_new (); - for (i = 0; mime_types[i]; i++) - g_ptr_array_add (array, mime_types[i]); - g_free (mime_types); - for (i = 0; i < array->len; i++) - { - anc = _g_unix_content_type_get_parents (g_ptr_array_index (array, i)); - for (j = 0; anc[j]; j++) - { - for (k = 0; k < array->len; k++) - { - if (strcmp (anc[j], g_ptr_array_index (array, k)) == 0) - break; - } - if (k == array->len) /* not found */ - g_ptr_array_add (array, g_strdup (anc[j])); - } - g_strfreev (anc); - } - g_ptr_array_add (array, NULL); - mime_types = (char **)g_ptr_array_free (array, FALSE); - } - else - { - mime_types = g_malloc0 (2 * sizeof (gchar *)); - mime_types[0] = g_strdup (base_mime_type); - mime_types[1] = NULL; - } - - G_LOCK (mime_info_cache); - - removed_entries = NULL; - desktop_entries = NULL; - - for (i = 0; except != NULL && except[i] != NULL; i++) - removed_entries = g_list_prepend (removed_entries, g_strdup (except[i])); - - for (i = 0; mime_types[i] != NULL; i++) - { - mime_type = mime_types[i]; - - /* This is true if we already found a handler for a more specific - mimetype. If its set we ignore any defaults for the less specific - mimetypes. */ - already_found_handler = (desktop_entries != NULL); - - /* Go through all apps listed in user and system dirs */ - for (dir_list = mime_info_cache->dirs; - dir_list != NULL; - dir_list = dir_list->next) - { - dir = dir_list->data; - - /* Pick the explicit default application if we got no result earlier - * (ie, for more specific mime types) - */ - if (!already_found_handler) - { - entry = g_hash_table_lookup (dir->mimeapps_list_defaults_map, mime_type); - - if (entry != NULL) - { - /* Save the default entry if it's the first one we encounter */ - if (default_entry == NULL) - default_entry = g_strdup (entry); - } - } - - /* Then added associations from mimeapps.list */ - default_entries = g_hash_table_lookup (dir->mimeapps_list_added_map, mime_type); - for (j = 0; default_entries != NULL && default_entries[j] != NULL; j++) - desktop_entries = append_desktop_entry (desktop_entries, default_entries[j], removed_entries); - - /* Then removed associations from mimeapps.list */ - removed_associations = g_hash_table_lookup (dir->mimeapps_list_removed_map, mime_type); - for (j = 0; removed_associations != NULL && removed_associations[j] != NULL; j++) - removed_entries = append_desktop_entry (removed_entries, removed_associations[j], NULL); - - /* Then system defaults (or old per-user config) (using removed associations from this dir or earlier) */ - default_entries = g_hash_table_lookup (dir->defaults_list_map, mime_type); - for (j = 0; default_entries != NULL && default_entries[j] != NULL; j++) - { - if (default_entry == NULL && old_default_entry == NULL && !already_found_handler) - old_default_entry = g_strdup (default_entries[j]); - - desktop_entries = append_desktop_entry (desktop_entries, default_entries[j], removed_entries); - } - } - - /* Go through all entries that support the mimetype */ - for (dir_list = mime_info_cache->dirs; - dir_list != NULL; - dir_list = dir_list->next) - { - dir = dir_list->data; - - list = g_hash_table_lookup (dir->mime_info_cache_map, mime_type); - for (tmp = list; tmp != NULL; tmp = tmp->next) - desktop_entries = append_desktop_entry (desktop_entries, tmp->data, removed_entries); - } - } - - G_UNLOCK (mime_info_cache); - - g_strfreev (mime_types); - - /* If we have no default from mimeapps.list, take it from - * defaults.list intead. - * - * If we do have a default from mimeapps.list, free any one that came - * from defaults.list. - */ - if (default_entry == NULL) - default_entry = old_default_entry; - else - g_free (old_default_entry); - - if (explicit_default != NULL) - *explicit_default = default_entry; - else - g_free (default_entry); - - g_list_free_full (removed_entries, g_free); - - desktop_entries = g_list_reverse (desktop_entries); - - return desktop_entries; -} - /* GDesktopAppInfoLookup interface {{{2 */ G_GNUC_BEGIN_IGNORE_DEPRECATIONS