From 678bcad92c2a6350cd5dbf4ea3a567d99cf4c29d Mon Sep 17 00:00:00 2001 From: Cosimo Cecchi Date: Wed, 15 Dec 2010 17:56:22 +0100 Subject: [PATCH] appinfo: add g_app_info_set_as_last_used_for_type() This commit also changes (maintaining compatibility) the way user-specified default applications are stored (as in, those for which g_app_info_set_as_default_for_type() has been called. We now store the default application for a content type in a new group in the mimeapps.list keyfile, and "Added Associations" tracks only the applications that have been added by the user, following a most-recently-used first order. This is useful in GtkAppChooser-like widgets to pre-select the last used application when constructing a widget. https://bugzilla.gnome.org/show_bug.cgi?id=636311 --- docs/reference/gio/gio-sections.txt | 1 + gio/gappinfo.c | 27 +++ gio/gappinfo.h | 7 + gio/gdesktopappinfo.c | 291 ++++++++++++++++++++++------ gio/gio.symbols | 1 + gio/tests/desktop-app-info.c | 51 +++++ 6 files changed, 314 insertions(+), 64 deletions(-) diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index 431af131f..2facc2101 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -1256,6 +1256,7 @@ g_app_info_delete g_app_info_reset_type_associations g_app_info_set_as_default_for_type g_app_info_set_as_default_for_extension +g_app_info_set_as_last_used_for_type g_app_info_add_supports_type g_app_info_can_remove_supports_type g_app_info_remove_supports_type diff --git a/gio/gappinfo.c b/gio/gappinfo.c index 755731e58..6f6375c4c 100644 --- a/gio/gappinfo.c +++ b/gio/gappinfo.c @@ -308,6 +308,33 @@ g_app_info_set_as_default_for_type (GAppInfo *appinfo, return (* iface->set_as_default_for_type) (appinfo, content_type, error); } +/** + * g_app_info_set_as_last_used_for_type: + * @appinfo: a #GAppInfo. + * @content_type: the content type. + * @error: a #GError. + * + * Sets the application as the last used application for a given type. + * This will make the application appear as first in the list returned by + * #g_app_info_get_recommended_for_type, regardless of the default application + * for that content type. + * + * Returns: %TRUE on success, %FALSE on error. + **/ +gboolean +g_app_info_set_as_last_used_for_type (GAppInfo *appinfo, + const char *content_type, + GError **error) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), FALSE); + g_return_val_if_fail (content_type != NULL, FALSE); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + return (* iface->set_as_last_used_for_type) (appinfo, content_type, error); +} /** * g_app_info_set_as_default_for_extension: diff --git a/gio/gappinfo.h b/gio/gappinfo.h index a52e21644..11a4dad53 100644 --- a/gio/gappinfo.h +++ b/gio/gappinfo.h @@ -128,6 +128,9 @@ struct _GAppInfoIface gboolean (* do_delete) (GAppInfo *appinfo); const char * (* get_commandline) (GAppInfo *appinfo); const char * (* get_display_name) (GAppInfo *appinfo); + gboolean (* set_as_last_used_for_type) (GAppInfo *appinfo, + const char *content_type, + GError **error); }; GType g_app_info_get_type (void) G_GNUC_CONST; @@ -173,6 +176,10 @@ gboolean g_app_info_remove_supports_type (GAppInfo *appin gboolean g_app_info_can_delete (GAppInfo *appinfo); gboolean g_app_info_delete (GAppInfo *appinfo); +gboolean g_app_info_set_as_last_used_for_type (GAppInfo *appinfo, + const char *content_type, + GError **error); + GList * g_app_info_get_all (void); GList * g_app_info_get_all_for_type (const char *content_type); GList * g_app_info_get_recommended_for_type (const gchar *content_type); diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index a7648c984..4f1cfe0e9 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -69,7 +69,8 @@ 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); + 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); @@ -107,6 +108,14 @@ struct _GDesktopAppInfo /* FIXME: what about StartupWMClass ? */ }; +typedef enum { + UPDATE_MIME_NONE = 1 << 0, + UPDATE_MIME_SET_DEFAULT = 1 << 1, + UPDATE_MIME_SET_NON_DEFAULT = 1 << 2, + UPDATE_MIME_REMOVE = 1 << 3, + UPDATE_MIME_SET_LAST_USED = 1 << 4, +} UpdateMimeFlags; + 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)) @@ -1169,25 +1178,23 @@ ensure_dir (DirType type, static gboolean update_mimeapps_list (const char *desktop_id, - const char *content_type, - gboolean add_as_default, - gboolean add_non_default, - gboolean remove, + const char *content_type, + UpdateMimeFlags flags, GError **error) { - char *dirname, *filename; + char *dirname, *filename, *string; GKeyFile *key_file; gboolean load_succeeded, res; char **old_list, **list; - GList *system_list, *l; gsize length, data_size; char *data; int i, j, k; char **content_types; /* Don't add both at start and end */ - g_assert (!(add_as_default && add_non_default)); - + g_assert (!((flags & UPDATE_MIME_SET_DEFAULT) && + (flags & UPDATE_MIME_SET_NON_DEFAULT))); + dirname = ensure_dir (APP_DIR, error); if (!dirname) return FALSE; @@ -1211,9 +1218,51 @@ update_mimeapps_list (const char *desktop_id, } else { - content_types = g_key_file_get_keys (key_file, ADDED_ASSOCIATIONS_GROUP, NULL, NULL); + content_types = g_key_file_get_keys (key_file, DEFAULT_APPLICATIONS_GROUP, NULL, NULL); } + for (k = 0; content_types && content_types[k]; k++) + { + /* set as default, if requested so */ + string = g_key_file_get_string (key_file, + DEFAULT_APPLICATIONS_GROUP, + content_types[k], + NULL); + + if (g_strcmp0 (string, desktop_id) != 0 && + (flags & UPDATE_MIME_SET_DEFAULT)) + { + g_free (string); + string = g_strdup (desktop_id); + + /* add in the non-default list too, if it's not already there */ + flags |= UPDATE_MIME_SET_NON_DEFAULT; + } + + if (string == NULL || desktop_id == NULL) + g_key_file_remove_key (key_file, + DEFAULT_APPLICATIONS_GROUP, + content_types[k], + NULL); + else + g_key_file_set_string (key_file, + DEFAULT_APPLICATIONS_GROUP, + content_types[k], + string); + + g_free (string); + } + + if (content_type) + { + /* reuse the list from above */ + } + else + { + g_strfreev (content_types); + content_types = g_key_file_get_keys (key_file, ADDED_ASSOCIATIONS_GROUP, NULL, NULL); + } + for (k = 0; content_types && content_types[k]; k++) { /* Add to the right place in the list */ @@ -1225,48 +1274,41 @@ update_mimeapps_list (const char *desktop_id, list = g_new (char *, 1 + length + 1); i = 0; - if (add_as_default) - list[i++] = g_strdup (desktop_id); + + /* if we're adding a last-used hint, just put the application in front of the list */ + if (flags & UPDATE_MIME_SET_LAST_USED) + { + /* avoid adding this again as non-default later */ + if (flags & UPDATE_MIME_SET_NON_DEFAULT) + flags ^= UPDATE_MIME_SET_NON_DEFAULT; + + list[i++] = g_strdup (desktop_id); + } + if (old_list) { for (j = 0; old_list[j] != NULL; j++) { if (g_strcmp0 (old_list[j], desktop_id) != 0) - list[i++] = g_strdup (old_list[j]); - else if (add_non_default) + { + /* rewrite other entries if they're different from the new one */ + list[i++] = g_strdup (old_list[j]); + } + else if (flags & UPDATE_MIME_SET_NON_DEFAULT) { - /* If adding as non-default, and it's already in, - don't change order of desktop ids */ - add_non_default = FALSE; + /* we encountered an old entry which is equal to the one we're adding as non-default, + * don't change its position in the list. + */ + flags ^= UPDATE_MIME_SET_NON_DEFAULT; list[i++] = g_strdup (old_list[j]); } } } - - if (add_non_default) - { - /* We're adding as non-default, and it wasn't already in the list, - so we add at the end. But to avoid listing the app before the - current system default (thus changing the default) we have to - add the current list of (not yet listed) apps before it. */ - list[i] = NULL; /* Terminate current list so we can use it */ - system_list = get_all_desktop_entries_for_mime_type (content_type, (const char **)list, FALSE); + /* add it at the end of the list */ + if (flags & UPDATE_MIME_SET_NON_DEFAULT) + list[i++] = g_strdup (desktop_id); - list = g_renew (char *, list, 1 + length + g_list_length (system_list) + 1); - - for (l = system_list; l != NULL; l = l->next) - { - list[i++] = l->data; /* no strdup, taking ownership */ - if (g_strcmp0 (l->data, desktop_id) == 0) - add_non_default = FALSE; - } - g_list_free (system_list); - - if (add_non_default) - list[i++] = g_strdup (desktop_id); - } - list[i] = NULL; g_strfreev (old_list); @@ -1306,7 +1348,7 @@ update_mimeapps_list (const char *desktop_id, list = g_new (char *, 1 + length + 1); i = 0; - if (remove) + if (flags & UPDATE_MIME_REMOVE) list[i++] = g_strdup (desktop_id); if (old_list) { @@ -1333,7 +1375,7 @@ update_mimeapps_list (const char *desktop_id, g_strfreev (list); } - + g_strfreev (content_types); data = g_key_file_to_data (key_file, &data_size, error); @@ -1349,6 +1391,23 @@ update_mimeapps_list (const char *desktop_id, return res; } +static gboolean +g_desktop_app_info_set_as_last_used_for_type (GAppInfo *appinfo, + const char *content_type, + GError **error) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + + if (!g_desktop_app_info_ensure_saved (info, error)) + return FALSE; + + /* both add support for the content type and set as last used */ + return update_mimeapps_list (info->desktop_id, content_type, + UPDATE_MIME_SET_NON_DEFAULT | + UPDATE_MIME_SET_LAST_USED, + error); +} + static gboolean g_desktop_app_info_set_as_default_for_type (GAppInfo *appinfo, const char *content_type, @@ -1359,7 +1418,9 @@ g_desktop_app_info_set_as_default_for_type (GAppInfo *appinfo, if (!g_desktop_app_info_ensure_saved (info, error)) return FALSE; - return update_mimeapps_list (info->desktop_id, content_type, TRUE, FALSE, FALSE, error); + return update_mimeapps_list (info->desktop_id, content_type, + UPDATE_MIME_SET_DEFAULT, + error); } static void @@ -1476,7 +1537,9 @@ g_desktop_app_info_add_supports_type (GAppInfo *appinfo, if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error)) return FALSE; - return update_mimeapps_list (info->desktop_id, content_type, FALSE, TRUE, FALSE, error); + return update_mimeapps_list (info->desktop_id, content_type, + UPDATE_MIME_SET_NON_DEFAULT, + error); } static gboolean @@ -1495,7 +1558,9 @@ g_desktop_app_info_remove_supports_type (GAppInfo *appinfo, if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error)) return FALSE; - return update_mimeapps_list (info->desktop_id, content_type, FALSE, FALSE, TRUE, error); + return update_mimeapps_list (info->desktop_id, content_type, + UPDATE_MIME_REMOVE, + error); } static gboolean @@ -1616,7 +1681,9 @@ g_desktop_app_info_delete (GAppInfo *appinfo) { if (g_remove (info->filename) == 0) { - update_mimeapps_list (info->desktop_id, NULL, FALSE, FALSE, FALSE, NULL); + update_mimeapps_list (info->desktop_id, NULL, + UPDATE_MIME_NONE, + NULL); g_free (info->filename); info->filename = NULL; @@ -1709,6 +1776,7 @@ g_desktop_app_info_iface_init (GAppInfoIface *iface) iface->do_delete = g_desktop_app_info_delete; iface->get_commandline = g_desktop_app_info_get_commandline; iface->get_display_name = g_desktop_app_info_get_display_name; + iface->set_as_last_used_for_type = g_desktop_app_info_set_as_last_used_for_type; } static gboolean @@ -1731,6 +1799,9 @@ app_info_in_list (GAppInfo *info, * 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. @@ -1746,7 +1817,7 @@ g_app_info_get_recommended_for_type (const gchar *content_type) g_return_val_if_fail (content_type != NULL, NULL); - desktop_entries = get_all_desktop_entries_for_mime_type (content_type, NULL, FALSE); + 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) @@ -1765,7 +1836,7 @@ g_app_info_get_recommended_for_type (const gchar *content_type) } g_list_free (desktop_entries); - + return g_list_reverse (infos); } @@ -1791,7 +1862,7 @@ g_app_info_get_fallback_for_type (const gchar *content_type) g_return_val_if_fail (content_type != NULL, NULL); - desktop_entries = get_all_desktop_entries_for_mime_type (content_type, NULL, TRUE); + 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); infos = NULL; @@ -1831,13 +1902,25 @@ g_app_info_get_all_for_type (const char *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); - + 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); + } + + g_free (user_default); + for (l = desktop_entries; l != NULL; l = l->next) { char *desktop_entry = l->data; @@ -1872,7 +1955,9 @@ g_app_info_get_all_for_type (const char *content_type) void g_app_info_reset_type_associations (const char *content_type) { - update_mimeapps_list (NULL, content_type, FALSE, FALSE, FALSE, NULL); + update_mimeapps_list (NULL, content_type, + UPDATE_MIME_NONE, + NULL); } /** @@ -1890,13 +1975,40 @@ 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); + 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; @@ -1914,9 +2026,8 @@ g_app_info_get_default_for_type (const char *content_type, } } - g_list_foreach (desktop_entries, (GFunc)g_free, NULL); - g_list_free (desktop_entries); - + g_list_free_full (desktop_entries, g_free); + return info; } @@ -2066,6 +2177,7 @@ typedef struct { 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; @@ -2318,6 +2430,7 @@ mime_info_cache_dir_init_mimeapps_list (MimeInfoCacheDir *dir) gchar *filename, **mime_types; char *unaliased_type; char **desktop_file_ids; + char *desktop_id; int i; struct stat buf; @@ -2339,6 +2452,11 @@ mime_info_cache_dir_init_mimeapps_list (MimeInfoCacheDir *dir) 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); @@ -2403,6 +2521,28 @@ mime_info_cache_dir_init_mimeapps_list (MimeInfoCacheDir *dir) 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; @@ -2458,7 +2598,13 @@ mime_info_cache_dir_free (MimeInfoCacheDir *dir) 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); } @@ -2635,13 +2781,15 @@ append_desktop_entry (GList *list, * to handle @mime_type. */ static GList * -get_all_desktop_entries_for_mime_type (const char *base_mime_type, +get_all_desktop_entries_for_mime_type (const char *base_mime_type, const char **except, - gboolean include_fallback) + gboolean include_fallback, + char **explicit_default) { GList *desktop_entries, *removed_entries, *list, *dir_list, *tmp; MimeInfoCacheDir *dir; - char *mime_type; + char *mime_type, *default_entry = NULL; + const char *entry; char **mime_types; char **default_entries; char **removed_associations; @@ -2696,17 +2844,27 @@ get_all_desktop_entries_for_mime_type (const char *base_mime_type, { mime_type = mime_types[i]; - /* Go through all apps listed as defaults */ + /* 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; - /* First added associations from mimeapps.list */ + /* Pick the explicit default application */ + 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); + 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); @@ -2736,9 +2894,14 @@ get_all_desktop_entries_for_mime_type (const char *base_mime_type, g_strfreev (mime_types); + if (explicit_default != NULL) + *explicit_default = default_entry; + else + g_free (default_entry); + g_list_foreach (removed_entries, (GFunc)g_free, NULL); g_list_free (removed_entries); - + desktop_entries = g_list_reverse (desktop_entries); return desktop_entries; diff --git a/gio/gio.symbols b/gio/gio.symbols index 4ae32eca9..dc030662e 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -84,6 +84,7 @@ g_app_info_launch_uris g_app_info_should_show g_app_info_set_as_default_for_type g_app_info_set_as_default_for_extension +g_app_info_set_as_last_used_for_type g_app_info_add_supports_type g_app_info_can_remove_supports_type g_app_info_remove_supports_type diff --git a/gio/tests/desktop-app-info.c b/gio/tests/desktop-app-info.c index db9ec70a1..b9ae777a0 100644 --- a/gio/tests/desktop-app-info.c +++ b/gio/tests/desktop-app-info.c @@ -242,6 +242,56 @@ test_fallback (void) g_object_unref (info2); } +static void +test_last_used (void) +{ + GList *applications; + GAppInfo *info1, *info2, *default_app; + GError *error = NULL; + + info1 = create_app_info ("Test1"); + info2 = create_app_info ("Test2"); + + g_app_info_set_as_default_for_type (info1, "application/x-test", &error); + g_assert (error == NULL); + + g_app_info_add_supports_type (info2, "application/x-test", &error); + g_assert (error == NULL); + + applications = g_app_info_get_recommended_for_type ("application/x-test"); + g_assert (g_list_length (applications) == 2); + + /* the first should be the default app now */ + g_assert (g_app_info_equal (g_list_nth_data (applications, 0), info1)); + g_assert (g_app_info_equal (g_list_nth_data (applications, 1), info2)); + + g_list_free_full (applications, g_object_unref); + + g_app_info_set_as_last_used_for_type (info2, "application/x-test", &error); + g_assert (error == NULL); + + applications = g_app_info_get_recommended_for_type ("application/x-test"); + g_assert (g_list_length (applications) == 2); + + default_app = g_app_info_get_default_for_type ("application/x-test", FALSE); + g_assert (g_app_info_equal (default_app, info1)); + + /* the first should be the other app now */ + g_assert (g_app_info_equal (g_list_nth_data (applications, 0), info2)); + g_assert (g_app_info_equal (g_list_nth_data (applications, 1), info1)); + + g_list_free_full (applications, g_object_unref); + + g_app_info_reset_type_associations ("application/x-test"); + + g_app_info_delete (info1); + g_app_info_delete (info2); + + g_object_unref (info1); + g_object_unref (info2); + g_object_unref (default_app); +} + static void cleanup_dir_recurse (GFile *parent, GFile *root) { @@ -319,6 +369,7 @@ main (int argc, g_test_add_func ("/desktop-app-info/delete", test_delete); g_test_add_func ("/desktop-app-info/default", test_default); g_test_add_func ("/desktop-app-info/fallback", test_fallback); + g_test_add_func ("/desktop-app-info/lastused", test_last_used); result = g_test_run ();