X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gio%2Fgdesktopappinfo.c;h=1d38d4af311e082c45812b5467f56a7b38e09875;hb=ffe286e647ce9ba9bb8e1631ba3cf0194038f438;hp=a76369c7724120a66c3a1147992e8a5dd428bf37;hpb=e55d33edc1336ddc6d5cdfa0e3003a69a5812d26;p=platform%2Fupstream%2Fglib.git diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index a76369c..1d38d4a 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -14,9 +14,7 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General - * Public License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. + * Public License along with this library; if not, see . * * Author: Alexander Larsson * Ryan Lortie @@ -60,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" @@ -80,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); @@ -139,21 +132,24 @@ 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; + 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 */ @@ -213,6 +209,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 @@ -245,6 +295,279 @@ add_to_table_if_appropriate (GHashTable *apps, g_hash_table_insert (apps, g_strdup (info->desktop_id), info); } +enum +{ + DESKTOP_KEY_Comment, + DESKTOP_KEY_Exec, + DESKTOP_KEY_GenericName, + DESKTOP_KEY_Keywords, + DESKTOP_KEY_Name, + DESKTOP_KEY_X_GNOME_FullName, + + N_DESKTOP_KEYS +}; + +const gchar desktop_key_match_category[N_DESKTOP_KEYS] = { + /* Note: lower numbers are a better match. + * + * In case we want two keys to match at the same level, we can just + * use the same number for the two different keys. + */ + [DESKTOP_KEY_Name] = 1, + [DESKTOP_KEY_Exec] = 2, + [DESKTOP_KEY_Keywords] = 3, + [DESKTOP_KEY_GenericName] = 4, + [DESKTOP_KEY_X_GNOME_FullName] = 5, + [DESKTOP_KEY_Comment] = 6 +}; + +static gchar * +desktop_key_get_name (guint key_id) +{ + switch (key_id) + { + case DESKTOP_KEY_Comment: + return "Comment"; + case DESKTOP_KEY_Exec: + return "Exec"; + case DESKTOP_KEY_GenericName: + return "GenericName"; + case DESKTOP_KEY_Keywords: + return "Keywords"; + case DESKTOP_KEY_Name: + return "Name"; + case DESKTOP_KEY_X_GNOME_FullName: + return "X-GNOME-FullName"; + default: + g_assert_not_reached (); + } +} + +/* Search global state {{{2 + * + * We only ever search under a global lock, so we can use (and reuse) + * some global data to reduce allocations made while searching. + * + * In short, we keep around arrays of results that we expand as needed + * (and never shrink). + * + * static_token_results: this is where we append the results for each + * token within a given desktop directory, as we handle it (which is + * a union of all matches for this term) + * + * static_search_results: this is where we build the complete results + * for a single directory (which is an intersection of the matches + * found for each term) + * + * static_total_results: this is where we build the complete results + * across all directories (which is a union of the matches found in + * each directory) + * + * The app_names that enter these tables are always pointer-unique (in + * the sense that string equality is the same as pointer equality). + * This can be guaranteed for two reasons: + * + * - we mask appids so that a given appid will only ever appear within + * the highest-precedence directory that contains it. We never + * return search results from a lower-level directory if a desktop + * file exists in a higher-level one. + * + * - within a given directory, the string is unique because it's the + * key in the hashtable of all app_ids for that directory. + * + * We perform a merging of the results in merge_token_results(). This + * works by ordering the two lists and moving through each of them (at + * the same time) looking for common elements, rejecting uncommon ones. + * "Order" here need not mean any particular thing, as long as it is + * some order. Because of the uniqueness of our strings, we can use + * pointer order. That's what's going on in compare_results() below. + */ +struct search_result +{ + const gchar *app_name; + gint category; +}; + +static struct search_result *static_token_results; +static gint static_token_results_size; +static gint static_token_results_allocated; +static struct search_result *static_search_results; +static gint static_search_results_size; +static gint static_search_results_allocated; +static struct search_result *static_total_results; +static gint static_total_results_size; +static gint static_total_results_allocated; + +/* And some functions for performing nice operations against it */ +static gint +compare_results (gconstpointer a, + gconstpointer b) +{ + const struct search_result *ra = a; + const struct search_result *rb = b; + + if (ra->app_name < rb->app_name) + return -1; + + else if (ra->app_name > rb->app_name) + return 1; + + else + return ra->category - rb->category; +} + +static gint +compare_categories (gconstpointer a, + gconstpointer b) +{ + const struct search_result *ra = a; + const struct search_result *rb = b; + + return ra->category - rb->category; +} + +static void +add_token_result (const gchar *app_name, + guint16 category) +{ + if G_UNLIKELY (static_token_results_size == static_token_results_allocated) + { + static_token_results_allocated = MAX (16, static_token_results_allocated * 2); + static_token_results = g_renew (struct search_result, static_token_results, static_token_results_allocated); + } + + static_token_results[static_token_results_size].app_name = app_name; + static_token_results[static_token_results_size].category = category; + static_token_results_size++; +} + +static void +merge_token_results (gboolean first) +{ + qsort (static_token_results, static_token_results_size, sizeof (struct search_result), compare_results); + + /* If this is the first token then we are basically merging a list with + * itself -- we only perform de-duplication. + * + * If this is not the first token then we are doing a real merge. + */ + if (first) + { + const gchar *last_name = NULL; + gint i; + + /* We must de-duplicate, but we do so by taking the best category + * in each case. + * + * The final list can be as large as the input here, so make sure + * we have enough room (even if it's too much room). + */ + + if G_UNLIKELY (static_search_results_allocated < static_token_results_size) + { + static_search_results_allocated = static_token_results_allocated; + static_search_results = g_renew (struct search_result, + static_search_results, + static_search_results_allocated); + } + + for (i = 0; i < static_token_results_size; i++) + { + /* The list is sorted so that the best match for a given id + * will be at the front, so once we have copied an id, skip + * the rest of the entries for the same id. + */ + if (static_token_results[i].app_name == last_name) + continue; + + last_name = static_token_results[i].app_name; + + static_search_results[static_search_results_size++] = static_token_results[i]; + } + } + else + { + const gchar *last_name = NULL; + gint i, j = 0; + gint k = 0; + + /* We only ever remove items from the results list, so no need to + * resize to ensure that we have enough room. + */ + for (i = 0; i < static_token_results_size; i++) + { + if (static_token_results[i].app_name == last_name) + continue; + + last_name = static_token_results[i].app_name; + + /* Now we only want to have a result in static_search_results + * if we already have it there *and* we have it in + * static_token_results as well. The category will be the + * lesser of the two. + * + * Skip past the results in static_search_results that are not + * going to be matches. + */ + while (k < static_search_results_size && + static_search_results[k].app_name < static_token_results[i].app_name) + k++; + + if (k < static_search_results_size && + static_search_results[k].app_name == static_token_results[i].app_name) + { + /* We have a match. + * + * Category should be the worse of the two (ie: + * numerically larger). + */ + static_search_results[j].app_name = static_search_results[k].app_name; + static_search_results[j].category = MAX (static_search_results[k].category, + static_token_results[i].category); + j++; + } + } + + static_search_results_size = j; + } + + /* Clear it out for next time... */ + static_token_results_size = 0; +} + +static void +reset_total_search_results (void) +{ + static_total_results_size = 0; +} + +static void +sort_total_search_results (void) +{ + qsort (static_total_results, static_total_results_size, sizeof (struct search_result), compare_categories); +} + +static void +merge_directory_results (void) +{ + if G_UNLIKELY (static_total_results_size + static_search_results_size > static_total_results_allocated) + { + static_total_results_allocated = MAX (16, static_total_results_allocated); + while (static_total_results_allocated < static_total_results_size + static_search_results_size) + static_total_results_allocated *= 2; + static_total_results = g_renew (struct search_result, static_total_results, static_total_results_allocated); + } + + memcpy (static_total_results + static_total_results_size, + static_search_results, + static_search_results_size * sizeof (struct search_result)); + + static_total_results_size += static_search_results_size; + + /* Clear it out for next time... */ + static_search_results_size = 0; +} + /* Support for unindexed DesktopFileDirs {{{2 */ static void get_apps_from_dir (GHashTable **apps, @@ -291,10 +614,246 @@ 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, mime_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) +{ + const gchar default_group[] = "Default Applications"; + const gchar removed_group[] = "Removed Assocations"; + 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_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); + 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_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_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); + + 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", 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); + 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", 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); + 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 * @@ -332,16 +891,280 @@ desktop_file_dir_unindexed_get_all (DesktopFileDir *dir, } } -/* DesktopFileDir "API" {{{2 */ +typedef struct _MemoryIndexEntry MemoryIndexEntry; +typedef GHashTable MemoryIndex; -/*< internal > - * desktop_file_dir_create: - * @array: the #GArray to add a new item to - * @data_dir: an XDG_DATA_DIR - * - * Creates a #DesktopFileDir for the corresponding @data_dir, adding it - * to @array. - */ +struct _MemoryIndexEntry +{ + const gchar *app_name; /* pointer to the hashtable key */ + gint match_category; + MemoryIndexEntry *next; +}; + +static void +memory_index_entry_free (gpointer data) +{ + MemoryIndexEntry *mie = data; + + while (mie) + { + MemoryIndexEntry *next = mie->next; + + g_slice_free (MemoryIndexEntry, mie); + mie = next; + } +} + +static void +memory_index_add_token (MemoryIndex *mi, + const gchar *token, + gint match_category, + const gchar *app_name) +{ + MemoryIndexEntry *mie, *first; + + mie = g_slice_new (MemoryIndexEntry); + mie->app_name = app_name; + mie->match_category = match_category; + + first = g_hash_table_lookup (mi, token); + + if (first) + { + mie->next = first->next; + first->next = mie; + } + else + { + mie->next = NULL; + g_hash_table_insert (mi, g_strdup (token), mie); + } +} + +static void +memory_index_add_string (MemoryIndex *mi, + const gchar *string, + gint match_category, + const gchar *app_name) +{ + gchar **tokens, **alternates; + gint i; + + tokens = g_str_tokenize_and_fold (string, NULL, &alternates); + + for (i = 0; tokens[i]; i++) + memory_index_add_token (mi, tokens[i], match_category, app_name); + + for (i = 0; alternates[i]; i++) + memory_index_add_token (mi, alternates[i], match_category, app_name); + + g_strfreev (alternates); + g_strfreev (tokens); +} + +static MemoryIndex * +memory_index_new (void) +{ + return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, memory_index_entry_free); +} + +static void +desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir) +{ + GHashTableIter iter; + gpointer app, path; + + dir->memory_index = memory_index_new (); + dir->memory_implementations = memory_index_new (); + + /* Nothing to search? */ + if (dir->app_names == NULL) + return; + + g_hash_table_iter_init (&iter, dir->app_names); + while (g_hash_table_iter_next (&iter, &app, &path)) + { + GKeyFile *key_file; + + if (desktop_file_dir_app_name_is_masked (dir, app)) + continue; + + key_file = g_key_file_new (); + + if (g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, 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++) + { + const gchar *value; + gchar *raw; + + if (!desktop_key_match_category[i]) + continue; + + 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 (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_search (DesktopFileDir *dir, + const gchar *search_token) +{ + GHashTableIter iter; + gpointer key, value; + + if (!dir->memory_index) + desktop_file_dir_unindexed_setup_search (dir); + + g_hash_table_iter_init (&iter, dir->memory_index); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + MemoryIndexEntry *mie = value; + + if (!g_str_has_prefix (key, search_token)) + continue; + + while (mie) + { + add_token_result (mie->app_name, mie->match_category); + mie = mie->next; + } + } +} + +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 > + * desktop_file_dir_create: + * @array: the #GArray to add a new item to + * @data_dir: an XDG_DATA_DIR + * + * Creates a #DesktopFileDir for the corresponding @data_dir, adding it + * to @array. + */ static void desktop_file_dir_create (GArray *array, const gchar *data_dir) @@ -354,6 +1177,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 * @@ -375,6 +1220,24 @@ desktop_file_dir_reset (DesktopFileDir *dir) dir->app_names = NULL; } + if (dir->memory_index) + { + g_hash_table_unref (dir->memory_index); + 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; } @@ -442,6 +1305,69 @@ desktop_file_dir_get_all (DesktopFileDir *dir, desktop_file_dir_unindexed_get_all (dir, apps); } +/*< 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 + * @term: a normalised and casefolded search term + * + * Finds the names of applications in @dir that match @term. + */ +static void +desktop_file_dir_search (DesktopFileDir *dir, + const gchar *search_token) +{ + 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 @@ -453,20 +1379,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; @@ -485,19 +1421,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); } @@ -866,13 +1806,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 */ @@ -1120,20 +2060,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 @@ -1142,45 +2084,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 */ @@ -1665,7 +2595,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)) @@ -1721,7 +2651,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); @@ -1828,7 +2759,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); @@ -1976,13 +2908,13 @@ g_desktop_app_info_launch (GAppInfo *appinfo, * g_desktop_app_info_launch_uris_as_manager: * @appinfo: a #GDesktopAppInfo * @uris: (element-type utf8): List of URIs - * @launch_context: a #GAppLaunchContext + * @launch_context: (allow-none): a #GAppLaunchContext * @spawn_flags: #GSpawnFlags, used for each process - * @user_setup: (scope call): a #GSpawnChildSetupFunc, used once for - * each process. - * @user_setup_data: (closure user_setup): User data for @user_setup - * @pid_callback: (scope call): Callback for child processes - * @pid_callback_data: (closure pid_callback): User data for @callback + * @user_setup: (scope call) (allow-none): a #GSpawnChildSetupFunc, used once + * for each process. + * @user_setup_data: (closure user_setup) (allow-none): User data for @user_setup + * @pid_callback: (scope call) (allow-none): Callback for child processes + * @pid_callback_data: (closure pid_callback) (allow-none): User data for @callback * @error: return location for a #GError, or %NULL * * This function performs the equivalent of g_app_info_launch_uris(), @@ -2033,30 +2965,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 @@ -2073,6 +2993,7 @@ g_desktop_app_info_should_show (GAppInfo *appinfo) /* mime types/default apps support {{{2 */ typedef enum { + CONF_DIR, APP_DIR, MIMETYPE_DIR } DirType; @@ -2084,10 +3005,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) @@ -2129,7 +3063,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; @@ -2320,7 +3254,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); @@ -2631,7 +3565,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; } @@ -2686,8 +3620,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. * @@ -2769,1057 +3703,524 @@ 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); } -/** - * g_app_info_get_recommended_for_type: - * @content_type: the content type to find a #GAppInfo for - * - * Gets a list of recommended #GAppInfos for a given content type, i.e. - * those applications which claim to support the given content type exactly, - * and not by MIME type subclassing. - * Note that the first application of the list is the last used one, i.e. - * the last one for which g_app_info_set_as_last_used_for_type() has been - * called. - * - * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos - * for given @content_type or %NULL on error. - * - * Since: 2.28 - **/ -GList * -g_app_info_get_recommended_for_type (const gchar *content_type) +static gchar ** +g_desktop_app_info_get_desktop_ids_for_content_type (const gchar *content_type, + gboolean include_fallback) { - GList *desktop_entries, *l; - GList *infos; - GDesktopAppInfo *info; - - g_return_val_if_fail (content_type != NULL, NULL); - - desktop_entries = get_all_desktop_entries_for_mime_type (content_type, NULL, FALSE, NULL); - - infos = NULL; - for (l = desktop_entries; l != NULL; l = l->next) - { - char *desktop_entry = l->data; - - info = g_desktop_app_info_new (desktop_entry); - 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); - } - - g_list_free (desktop_entries); + GPtrArray *hits, *blacklist; + gchar **types; + gint i, j; - return g_list_reverse (infos); -} + hits = g_ptr_array_new (); + blacklist = g_ptr_array_new (); -/** - * g_app_info_get_fallback_for_type: - * @content_type: the content type to find a #GAppInfo for - * - * Gets a list of fallback #GAppInfos for a given content type, i.e. - * those applications which claim to support the given content type - * by MIME type subclassing and not directly. - * - * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos - * for given @content_type or %NULL on error. - * - * Since: 2.28 - **/ -GList * -g_app_info_get_fallback_for_type (const gchar *content_type) -{ - GList *desktop_entries, *l; - GList *infos, *recommended_infos; - GDesktopAppInfo *info; + types = get_list_of_mimetypes (content_type, include_fallback); - g_return_val_if_fail (content_type != NULL, NULL); + desktop_file_dirs_lock (); - 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); + 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); - infos = NULL; - for (l = desktop_entries; l != NULL; l = l->next) - { - char *desktop_entry = l->data; + desktop_file_dirs_unlock (); - 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); - } + g_ptr_array_add (hits, NULL); - g_list_free (desktop_entries); - g_list_free_full (recommended_infos, g_object_unref); + g_ptr_array_free (blacklist, TRUE); + g_strfreev (types); - return g_list_reverse (infos); + return (gchar **) g_ptr_array_free (hits, FALSE); } -/** - * g_app_info_get_all_for_type: - * @content_type: the content type to find a #GAppInfo for - * - * Gets a list of all #GAppInfos for a given content type, - * including the recommended and fallback #GAppInfos. See - * g_app_info_get_recommended_for_type() and - * g_app_info_get_fallback_for_type(). - * - * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos - * for given @content_type or %NULL on error. - **/ -GList * -g_app_info_get_all_for_type (const char *content_type) +static gchar ** +g_desktop_app_info_get_defaults_for_content_type (const gchar *content_type) { - GList *desktop_entries, *l; - GList *infos; - char *user_default = NULL; - GDesktopAppInfo *info; - - 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; - - /* 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); - } + GPtrArray *results; + gchar **types; + gint i, j; - g_free (user_default); + types = get_list_of_mimetypes (content_type, TRUE); + results = g_ptr_array_new (); - for (l = desktop_entries; l != NULL; l = l->next) - { - char *desktop_entry = l->data; + desktop_file_dirs_lock (); - info = g_desktop_app_info_new (desktop_entry); - 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); - } + 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); - g_list_free (desktop_entries); + desktop_file_dirs_unlock (); - return g_list_reverse (infos); -} + g_ptr_array_add (results, NULL); + g_strfreev (types); -/** - * g_app_info_reset_type_associations: - * @content_type: a content type - * - * Removes all changes to the type associations done by - * g_app_info_set_as_default_for_type(), - * g_app_info_set_as_default_for_extension(), - * g_app_info_add_supports_type() or - * g_app_info_remove_supports_type(). - * - * Since: 2.20 - */ -void -g_app_info_reset_type_associations (const char *content_type) -{ - update_mimeapps_list (NULL, content_type, - UPDATE_MIME_NONE, - NULL); + return (gchar **) g_ptr_array_free (results, FALSE); } /** - * g_app_info_get_default_for_type: + * g_app_info_get_recommended_for_type: * @content_type: the content type to find a #GAppInfo for - * @must_support_uris: if %TRUE, the #GAppInfo is expected to - * support URIs - * - * Gets the default #GAppInfo for a given content type. - * - * Returns: (transfer full): #GAppInfo for given @content_type or - * %NULL on error. - */ -GAppInfo * -g_app_info_get_default_for_type (const char *content_type, - gboolean must_support_uris) -{ - GList *desktop_entries, *l; - char *user_default = NULL; - GAppInfo *info; - - g_return_val_if_fail (content_type != NULL, NULL); - - desktop_entries = get_all_desktop_entries_for_mime_type (content_type, NULL, TRUE, &user_default); - - info = NULL; - - if (user_default != NULL) - { - info = (GAppInfo *) g_desktop_app_info_new (user_default); - - if (info) - { - if (must_support_uris && !g_app_info_supports_uris (info)) - { - g_object_unref (info); - info = NULL; - } - } - } - - g_free (user_default); - - if (info != NULL) - { - g_list_free_full (desktop_entries, g_free); - return info; - } - - /* pick the first from the other list that matches our URI - * requirements. - */ - for (l = desktop_entries; l != NULL; l = l->next) - { - char *desktop_entry = l->data; - - info = (GAppInfo *)g_desktop_app_info_new (desktop_entry); - if (info) - { - if (must_support_uris && !g_app_info_supports_uris (info)) - { - g_object_unref (info); - info = NULL; - } - else - break; - } - } - - g_list_free_full (desktop_entries, g_free); - - return info; -} - -/** - * g_app_info_get_default_for_uri_scheme: - * @uri_scheme: a string containing a URI scheme. - * - * Gets the default application for handling URIs with - * the given URI scheme. A URI scheme is the initial part - * of the URI, up to but not including the ':', e.g. "http", - * "ftp" or "sip". - * - * Returns: (transfer full): #GAppInfo for given @uri_scheme or %NULL on error. - */ -GAppInfo * -g_app_info_get_default_for_uri_scheme (const char *uri_scheme) -{ - GAppInfo *app_info; - char *content_type, *scheme_down; - - scheme_down = g_ascii_strdown (uri_scheme, -1); - content_type = g_strdup_printf ("x-scheme-handler/%s", scheme_down); - g_free (scheme_down); - app_info = g_app_info_get_default_for_type (content_type, FALSE); - g_free (content_type); - - return app_info; -} - -/* "Get all" API {{{2 */ - -/** - * g_app_info_get_all: * - * Gets a list of all of the applications currently registered - * on this system. + * Gets a list of recommended #GAppInfos for a given content type, i.e. + * those applications which claim to support the given content type exactly, + * and not by MIME type subclassing. + * Note that the first application of the list is the last used one, i.e. + * the last one for which g_app_info_set_as_last_used_for_type() has been + * called. * - * 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(). - * The returned list does not include applications which have - * the Hidden key set. + * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos + * for given @content_type or %NULL on error. * - * Returns: (element-type GAppInfo) (transfer full): a newly allocated #GList of references to #GAppInfos. + * Since: 2.28 **/ GList * -g_app_info_get_all (void) +g_app_info_get_recommended_for_type (const gchar *content_type) { - GHashTable *apps; - GHashTableIter iter; - gpointer value; - int i; + gchar **desktop_ids; GList *infos; + gint i; - apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - - desktop_file_dirs_lock (); - - for (i = 0; i < n_desktop_file_dirs; i++) - desktop_file_dir_get_all (&desktop_file_dirs[i], apps); + g_return_val_if_fail (content_type != NULL, NULL); - desktop_file_dirs_unlock (); + desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, FALSE); infos = NULL; - g_hash_table_iter_init (&iter, apps); - while (g_hash_table_iter_next (&iter, NULL, &value)) - { - if (value) - infos = g_list_prepend (infos, value); - } - - g_hash_table_destroy (apps); - - 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; desktop_ids[i]; i++) { - 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; + GDesktopAppInfo *info; - error: - g_free (filename); - g_key_file_free (key_file); + info = g_desktop_app_info_new (desktop_ids[i]); + if (info) + infos = g_list_prepend (infos, info); + } - if (mime_types != NULL) - g_strfreev (mime_types); + g_strfreev (desktop_ids); - if (load_error) - g_error_free (load_error); + return g_list_reverse (infos); } -static MimeInfoCacheDir * -mime_info_cache_dir_new (const char *path) +/** + * g_app_info_get_fallback_for_type: + * @content_type: the content type to find a #GAppInfo for + * + * Gets a list of fallback #GAppInfos for a given content type, i.e. + * those applications which claim to support the given content type + * by MIME type subclassing and not directly. + * + * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos + * for given @content_type or %NULL on error. + * + * Since: 2.28 + **/ +GList * +g_app_info_get_fallback_for_type (const gchar *content_type) { - MimeInfoCacheDir *dir; - - dir = g_new0 (MimeInfoCacheDir, 1); - dir->path = g_strdup (path); + gchar **recommended_ids; + gchar **all_ids; + GList *infos; + gint i; - return dir; -} + g_return_val_if_fail (content_type != NULL, NULL); -static void -mime_info_cache_dir_free (MimeInfoCacheDir *dir) -{ - if (dir == NULL) - return; + 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); - if (dir->mime_info_cache_map != NULL) + infos = NULL; + for (i = 0; all_ids[i]; i++) { - destroy_info_cache_map (dir->mime_info_cache_map); - dir->mime_info_cache_map = NULL; + 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 (dir->defaults_list_map != NULL) - { - g_hash_table_destroy (dir->defaults_list_map); - dir->defaults_list_map = NULL; - } + if (recommended_ids[j]) + continue; - if (dir->mimeapps_list_added_map != NULL) - { - g_hash_table_destroy (dir->mimeapps_list_added_map); - dir->mimeapps_list_added_map = NULL; - } + info = g_desktop_app_info_new (all_ids[i]); - if (dir->mimeapps_list_removed_map != NULL) - { - g_hash_table_destroy (dir->mimeapps_list_removed_map); - dir->mimeapps_list_removed_map = NULL; + if (info) + infos = g_list_prepend (infos, info); } - if (dir->mimeapps_list_defaults_map != NULL) - { - g_hash_table_destroy (dir->mimeapps_list_defaults_map); - dir->mimeapps_list_defaults_map = NULL; - } + g_strfreev (recommended_ids); + g_strfreev (all_ids); - g_free (dir); + return g_list_reverse (infos); } -static void -mime_info_cache_dir_add_desktop_entries (MimeInfoCacheDir *dir, - const char *mime_type, - char **new_desktop_file_ids) +/** + * g_app_info_get_all_for_type: + * @content_type: the content type to find a #GAppInfo for + * + * Gets a list of all #GAppInfos for a given content type, + * including the recommended and fallback #GAppInfos. See + * g_app_info_get_recommended_for_type() and + * g_app_info_get_fallback_for_type(). + * + * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos + * for given @content_type or %NULL on error. + **/ +GList * +g_app_info_get_all_for_type (const char *content_type) { - GList *desktop_file_ids; - int i; + gchar **desktop_ids; + GList *infos; + gint i; - desktop_file_ids = g_hash_table_lookup (dir->mime_info_cache_map, - mime_type); + g_return_val_if_fail (content_type != NULL, NULL); + + desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE); - for (i = 0; new_desktop_file_ids[i] != NULL; i++) + infos = NULL; + for (i = 0; desktop_ids[i]; 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])); + GDesktopAppInfo *info; + + info = g_desktop_app_info_new (desktop_ids[i]); + if (info) + infos = g_list_prepend (infos, info); } - g_hash_table_insert (dir->mime_info_cache_map, g_strdup (mime_type), desktop_file_ids); + g_strfreev (desktop_ids); + + return g_list_reverse (infos); } -static void -mime_info_cache_init_dir_lists (void) +/** + * g_app_info_reset_type_associations: + * @content_type: a content type + * + * Removes all changes to the type associations done by + * g_app_info_set_as_default_for_type(), + * g_app_info_set_as_default_for_extension(), + * g_app_info_add_supports_type() or + * g_app_info_remove_supports_type(). + * + * Since: 2.20 + */ +void +g_app_info_reset_type_associations (const char *content_type) { - int i; + update_mimeapps_list (NULL, content_type, + UPDATE_MIME_NONE, + NULL); +} + +/** + * g_app_info_get_default_for_type: + * @content_type: the content type to find a #GAppInfo for + * @must_support_uris: if %TRUE, the #GAppInfo is expected to + * support URIs + * + * Gets the default #GAppInfo for a given content type. + * + * Returns: (transfer full): #GAppInfo for given @content_type or + * %NULL on error. + */ +GAppInfo * +g_app_info_get_default_for_type (const char *content_type, + gboolean must_support_uris) +{ + gchar **desktop_ids; + GAppInfo *info; + gint i; - mime_info_cache = mime_info_cache_new (); + g_return_val_if_fail (content_type != NULL, NULL); - desktop_file_dirs_refresh (); + desktop_ids = g_desktop_app_info_get_defaults_for_content_type (content_type); - for (i = 0; i < n_desktop_file_dirs; i++) + info = NULL; + for (i = 0; desktop_ids[i]; i++) { - MimeInfoCacheDir *dir; - - dir = mime_info_cache_dir_new (desktop_file_dirs[i].path); + info = (GAppInfo *) g_desktop_app_info_new (desktop_ids[i]); - if (dir != NULL) + if (info) { - mime_info_cache_dir_init (dir); - mime_info_cache_dir_init_defaults_list (dir); - mime_info_cache_dir_init_mimeapps_list (dir); + if (!must_support_uris || g_app_info_supports_uris (info)) + break; - mime_info_cache->dirs = g_list_append (mime_info_cache->dirs, dir); + g_object_unref (info); + info = NULL; } } -} - -static void -mime_info_cache_update_dir_lists (void) -{ - GList *tmp; - tmp = mime_info_cache->dirs; + g_strfreev (desktop_ids); - while (tmp != NULL) + /* 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. + */ + if (info == 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); + desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE); - tmp = tmp->next; - } -} + for (i = 0; desktop_ids[i]; i++) + { + info = (GAppInfo *) g_desktop_app_info_new (desktop_ids[i]); -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; + if (info) + { + if (!must_support_uris || g_app_info_supports_uris (info)) + break; - 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_object_unref (info); + info = NULL; + } } + + g_strfreev (desktop_ids); } - G_UNLOCK (mime_info_cache); + return info; } -static MimeInfoCache * -mime_info_cache_new (void) +/** + * g_app_info_get_default_for_uri_scheme: + * @uri_scheme: a string containing a URI scheme. + * + * Gets the default application for handling URIs with + * the given URI scheme. A URI scheme is the initial part + * of the URI, up to but not including the ':', e.g. "http", + * "ftp" or "sip". + * + * Returns: (transfer full): #GAppInfo for given @uri_scheme or %NULL on error. + */ +GAppInfo * +g_app_info_get_default_for_uri_scheme (const char *uri_scheme) { - MimeInfoCache *cache; + GAppInfo *app_info; + char *content_type, *scheme_down; - cache = g_new0 (MimeInfoCache, 1); + scheme_down = g_ascii_strdown (uri_scheme, -1); + content_type = g_strdup_printf ("x-scheme-handler/%s", scheme_down); + g_free (scheme_down); + app_info = g_app_info_get_default_for_type (content_type, FALSE); + g_free (content_type); - return cache; + return app_info; } -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); -} +/* "Get all" API {{{2 */ /** - * mime_info_cache_reload: - * @dir: directory path which needs reloading. + * g_desktop_app_info_get_implementations: + * @interface: the name of the interface * - * Reload the mime information for the @dir. - */ -static void -mime_info_cache_reload (const char *dir) + * 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. + * + * Since: 2.42 + **/ +GList * +g_desktop_app_info_get_implementations (const gchar *interface) { - /* FIXME: just reload the dir that needs reloading, - * don't blow the whole cache - */ - if (mime_info_cache != NULL) + 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) { - G_LOCK (mime_info_cache); - mime_info_cache_free (mime_info_cache); - mime_info_cache = NULL; - G_UNLOCK (mime_info_cache); - } -} + gchar *name = (*ptr)->data; + GDesktopAppInfo *app; -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)); + 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 list; + return result; } /** - * get_all_desktop_entries_for_mime_type: - * @mime_type: a mime type. - * @except: NULL or a strv list + * g_desktop_app_info_search: + * @search_string: the search string to use * - * 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. + * Searches desktop files for ones that match @search_string. * - * Optionally doesn't list the desktop ids given in the @except + * The return value is an array of strvs. Each strv contains a list of + * applications that matched @search_string with an equal score. The + * outer list is sorted by score so that the first strv contains the + * best-matching applications, and so on. + * The algorithm for determining matches is undefined and may change at + * any time. * - * Return value: a #GList containing the desktop ids which claim - * to handle @mime_type. + * Returns: (array zero-terminated=1) (element-type GStrv) (transfer full): a + * list of strvs. Free each item with g_strfreev() and free the outer + * list with g_free(). */ -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; +gchar *** +g_desktop_app_info_search (const gchar *search_string) +{ + gchar **search_tokens; + gint last_category = -1; + gchar ***results; + gint n_categories = 0; + gint start_of_category; + gint i, j; - mime_info_cache_init (); + search_tokens = g_str_tokenize_and_fold (search_string, NULL, NULL); - if (include_fallback) + desktop_file_dirs_lock (); + + reset_total_search_results (); + + for (i = 0; i < n_desktop_file_dirs; i++) { - /* 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++) + for (j = 0; search_tokens[j]; j++) { - 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); + desktop_file_dir_search (&desktop_file_dirs[i], search_tokens[j]); + merge_token_results (j == 0); } - 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; + merge_directory_results (); } - G_LOCK (mime_info_cache); + sort_total_search_results (); - removed_entries = NULL; - desktop_entries = NULL; + /* Count the total number of unique categories */ + for (i = 0; i < static_total_results_size; i++) + if (static_total_results[i].category != last_category) + { + last_category = static_total_results[i].category; + n_categories++; + } - for (i = 0; except != NULL && except[i] != NULL; i++) - removed_entries = g_list_prepend (removed_entries, g_strdup (except[i])); + results = g_new (gchar **, n_categories + 1); - for (i = 0; mime_types[i] != NULL; i++) + /* Start loading into the results list */ + start_of_category = 0; + for (i = 0; i < n_categories; 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; + gint n_items_in_category = 0; + gint this_category; + gint j; - /* 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); + this_category = static_total_results[start_of_category].category; - if (entry != NULL) - { - /* Save the default entry if it's the first one we encounter */ - if (default_entry == NULL) - default_entry = g_strdup (entry); - } - } + while (start_of_category + n_items_in_category < static_total_results_size && + static_total_results[start_of_category + n_items_in_category].category == this_category) + n_items_in_category++; - /* 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); + results[i] = g_new (gchar *, n_items_in_category + 1); + for (j = 0; j < n_items_in_category; j++) + results[i][j] = g_strdup (static_total_results[start_of_category + j].app_name); + results[i][j] = NULL; - /* 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); + start_of_category += n_items_in_category; + } + results[i] = 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_file_dirs_unlock (); - desktop_entries = append_desktop_entry (desktop_entries, default_entries[j], removed_entries); - } - } + g_strfreev (search_tokens); - /* 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; + return results; +} - 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_app_info_get_all: + * + * Gets a list of all of the applications currently registered + * 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(). + * The returned list does not include applications which have + * the `Hidden` key set. + * + * Returns: (element-type GAppInfo) (transfer full): a newly allocated #GList of references to #GAppInfos. + **/ +GList * +g_app_info_get_all (void) +{ + GHashTable *apps; + GHashTableIter iter; + gpointer value; + int i; + GList *infos; - G_UNLOCK (mime_info_cache); + apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - g_strfreev (mime_types); + desktop_file_dirs_lock (); - /* 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); + for (i = 0; i < n_desktop_file_dirs; i++) + desktop_file_dir_get_all (&desktop_file_dirs[i], apps); - if (explicit_default != NULL) - *explicit_default = default_entry; - else - g_free (default_entry); + desktop_file_dirs_unlock (); - g_list_free_full (removed_entries, g_free); + infos = NULL; + g_hash_table_iter_init (&iter, apps); + while (g_hash_table_iter_next (&iter, NULL, &value)) + { + if (value) + infos = g_list_prepend (infos, value); + } - desktop_entries = g_list_reverse (desktop_entries); + g_hash_table_destroy (apps); - return desktop_entries; + return infos; } /* GDesktopAppInfoLookup interface {{{2 */ @@ -3950,7 +4351,7 @@ g_desktop_app_info_get_boolean (GDesktopAppInfo *info, * * Returns: %TRUE if the @key exists * - * Since: 2.26 + * Since: 2.36 */ gboolean g_desktop_app_info_has_key (GDesktopAppInfo *info,