Commit 678bcad9 authored by Cosimo Cecchi's avatar Cosimo Cecchi

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
parent 01ba7bd8
......@@ -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
......
......@@ -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:
......
......@@ -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);
......
......@@ -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);
list = g_renew (char *, list, 1 + length + g_list_length (system_list) + 1);
/* add it at the end of the list */
if (flags & UPDATE_MIME_SET_NON_DEFAULT)
list[i++] = g_strdup (desktop_id);
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;
......
......@@ -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
......
......@@ -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 ();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment