diff --git a/gio/gappinfo.c b/gio/gappinfo.c index 65a5735df128d1ed2c108e4f6ee1e07693dd8486..c8bcfa8800124d18dce05c8fb056750fbcef0da5 100644 --- a/gio/gappinfo.c +++ b/gio/gappinfo.c @@ -1149,6 +1149,104 @@ g_app_info_get_default_for_type_finish (GAsyncResult *result, return g_task_propagate_pointer (G_TASK (result), error); } +static void +get_default_for_http_thread (GTask *task, + gpointer object, + gpointer task_data, + GCancellable *cancellable) +{ + GUri *uri = task_data; + GAppInfo *info; + + info = g_app_info_get_default_for_uri_http (uri); + + if (!info) + { + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("Failed to find default application for " + "http URI")); + return; + } + + g_task_return_pointer (task, g_steal_pointer (&info), g_object_unref); +} + +/** + * g_app_info_get_default_for_uri_http_async: + * @uri: a [class@Gio.Uri]. + * @cancellable: (nullable): a [class@Gio.Cancellable] + * @callback: (scope async) (nullable): a [type@Gio.AsyncReadyCallback] to call + * when the request is done + * @user_data: (nullable): data to pass to @callback + * + * Asynchronously gets the default application for handling the specific + * http URI. + * + * Since: 2.86 + */ +void +g_app_info_get_default_for_uri_http_async (GUri *uri, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + g_return_if_fail (uri != NULL); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, g_app_info_get_default_for_uri_http_async); + g_task_set_task_data (task, g_uri_ref (uri), (GDestroyNotify) g_uri_unref); + g_task_set_check_cancellable (task, TRUE); + g_task_run_in_thread (task, get_default_for_http_thread); + g_object_unref (task); +} + +/** + * g_app_info_get_default_for_uri_http_finish: + * @result: the async result + * + * Finishes a default [iface@Gio.AppInfo] lookup started by + * [func@Gio.AppInfo.get_default_for_uri_http_async]. + * + * If no [iface@Gio.AppInfo] is found, then @error will be set to + * [error@Gio.IOErrorEnum.NOT_FOUND]. + * + * Returns: (transfer full): [iface@Gio.AppInfo] for given @uri or + * `NULL` on error. + * + * Since: 2.86 + */ +GAppInfo * +g_app_info_get_default_for_uri_http_finish (GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, NULL), NULL); + g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == + g_app_info_get_default_for_uri_http_async, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +/** + * g_app_info_get_default_for_uri_http: + * @uri: a [class@Gio.Uri]. + * + * Gets the default application for handling the specific http URI. + * + * Returns: (transfer full) (nullable): [iface@Gio.AppInfo] for given + * @uri or `NULL` on error. + */ +GAppInfo * +g_app_info_get_default_for_uri_http (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + + return g_app_info_get_default_for_uri_http_impl (uri); +} + /** * g_app_info_launch_default_for_uri: * @uri: the uri to show @@ -1169,18 +1267,26 @@ g_app_info_launch_default_for_uri (const char *uri, GAppLaunchContext *launch_context, GError **error) { - char *uri_scheme; + GUri *parsed; + const char *scheme = NULL; GAppInfo *app_info = NULL; gboolean res = FALSE; + g_return_val_if_fail (uri != NULL, FALSE); + + parsed = g_uri_parse (uri, G_URI_FLAGS_NONE, NULL); + if (parsed) + scheme = g_uri_get_scheme (parsed); + + if (g_strcmp0 (scheme, "http") == 0 || g_strcmp0 (scheme, "https") == 0) + app_info = g_app_info_get_default_for_uri_http (parsed); + /* g_file_query_default_handler() calls * g_app_info_get_default_for_uri_scheme() too, but we have to do it * here anyway in case GFile can't parse @uri correctly. */ - uri_scheme = g_uri_parse_scheme (uri); - if (uri_scheme && uri_scheme[0] != '\0') - app_info = g_app_info_get_default_for_uri_scheme (uri_scheme); - g_free (uri_scheme); + if (!app_info && scheme && scheme[0] != '\0') + app_info = g_app_info_get_default_for_uri_scheme (scheme); if (!app_info) { @@ -1235,12 +1341,15 @@ g_app_info_launch_default_for_uri (const char *uri, } #endif + g_clear_pointer (&parsed, g_uri_unref); + return res; } typedef struct { gchar *uri; + GUri *parsed; GAppLaunchContext *context; } LaunchDefaultForUriData; @@ -1249,6 +1358,7 @@ launch_default_for_uri_data_free (LaunchDefaultForUriData *data) { g_free (data->uri); g_clear_object (&data->context); + g_clear_pointer (&data->parsed, g_uri_unref); g_free (data); } @@ -1394,9 +1504,9 @@ launch_default_app_for_default_handler (GTask *task) } static void -launch_default_app_for_uri_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) +launch_default_app_for_uri_scheme_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) { GTask *task = G_TASK (user_data); GAppInfo *app_info; @@ -1414,6 +1524,38 @@ launch_default_app_for_uri_cb (GObject *object, } } +static void +launch_default_app_for_uri_http_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + GAppInfo *app_info; + + app_info = g_app_info_get_default_for_uri_http_finish (result, NULL); + + if (!app_info) + { + LaunchDefaultForUriData *data; + const char *scheme; + GCancellable *cancellable; + + data = g_task_get_task_data (task); + scheme = g_uri_get_scheme (data->parsed); + cancellable = g_task_get_cancellable (task); + + g_app_info_get_default_for_uri_scheme_async (scheme, + cancellable, + launch_default_app_for_uri_scheme_cb, + g_steal_pointer (&task)); + } + else + { + launch_default_for_uri_launch_uris (g_steal_pointer (&task), + g_steal_pointer (&app_info)); + } +} + /** * g_app_info_launch_default_for_uri_async: * @uri: the uri to show @@ -1443,37 +1585,53 @@ g_app_info_launch_default_for_uri_async (const char *uri, gpointer user_data) { GTask *task; - char *uri_scheme; + GUri *parsed; + const char *scheme = NULL; LaunchDefaultForUriData *data; g_return_if_fail (uri != NULL); + parsed = g_uri_parse (uri, G_URI_FLAGS_NONE, NULL); + if (parsed) + scheme = g_uri_get_scheme (parsed); + task = g_task_new (NULL, cancellable, callback, user_data); g_task_set_source_tag (task, g_app_info_launch_default_for_uri_async); data = g_new (LaunchDefaultForUriData, 1); data->uri = g_strdup (uri); + data->parsed = parsed ? g_uri_ref (parsed) : NULL; data->context = (context != NULL) ? g_object_ref (context) : NULL; - g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_default_for_uri_data_free); + g_task_set_task_data (task, g_steal_pointer (&data), + (GDestroyNotify) launch_default_for_uri_data_free); - /* g_file_query_default_handler_async() calls - * g_app_info_get_default_for_uri_scheme() too, but we have to do it - * here anyway in case GFile can't parse @uri correctly. - */ - uri_scheme = g_uri_parse_scheme (uri); - if (uri_scheme && uri_scheme[0] != '\0') + if (scheme && scheme[0] != '\0') { - g_app_info_get_default_for_uri_scheme_async (uri_scheme, - cancellable, - launch_default_app_for_uri_cb, - g_steal_pointer (&task)); + if (g_strcmp0 (scheme, "http") == 0 || g_strcmp0 (scheme, "https") == 0) + { + g_app_info_get_default_for_uri_http_async (parsed, + cancellable, + launch_default_app_for_uri_http_cb, + g_steal_pointer (&task)); + } + else + { + /* g_file_query_default_handler_async() calls + * g_app_info_get_default_for_uri_scheme() too, but we have to do it + * here anyway in case GFile can't parse @uri correctly. + */ + g_app_info_get_default_for_uri_scheme_async (scheme, + cancellable, + launch_default_app_for_uri_scheme_cb, + g_steal_pointer (&task)); + } } else { launch_default_app_for_default_handler (g_steal_pointer (&task)); } - g_free (uri_scheme); + g_clear_pointer (&parsed, g_uri_unref); } /** diff --git a/gio/gappinfo.h b/gio/gappinfo.h index f42ebdddf72777a29b16ea4105e52025fab815ab..893d93b75e7ec851f535ecf97eefce49b245953e 100644 --- a/gio/gappinfo.h +++ b/gio/gappinfo.h @@ -290,6 +290,19 @@ GIO_AVAILABLE_IN_2_50 gboolean g_app_info_launch_default_for_uri_finish (GAsyncResult *result, GError **error); +GIO_AVAILABLE_IN_2_86 +GAppInfo *g_app_info_get_default_for_uri_http (GUri *uri); + +GIO_AVAILABLE_IN_2_86 +void g_app_info_get_default_for_uri_http_async (GUri *uri, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GIO_AVAILABLE_IN_2_86 +GAppInfo *g_app_info_get_default_for_uri_http_finish (GAsyncResult *result, + GError **error); + /** * GAppLaunchContext: diff --git a/gio/gappinfoprivate.h b/gio/gappinfoprivate.h index df6a2d3bfb60450d22280a776563c5bd3123ae81..82f28de159be44b05480f0b04631ea35a01ef4d8 100644 --- a/gio/gappinfoprivate.h +++ b/gio/gappinfoprivate.h @@ -37,5 +37,6 @@ GAppInfo *g_app_info_get_default_for_type_impl (const char *content_type, gboolean must_support_uris); GAppInfo *g_app_info_get_default_for_uri_scheme_impl (const char *uri_scheme); GList *g_app_info_get_all_impl (void); +GAppInfo *g_app_info_get_default_for_uri_http_impl (GUri *uri); #endif /* __G_APP_INFO_PRIVATE_H__ */ diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index db58e02a28f7bfba343ea3b47714aadac9c741b5..e0cd3eb8c747e7f0a009123a693fa6c5e46accc3 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -77,10 +77,13 @@ #define ADDED_ASSOCIATIONS_GROUP "Added Associations" #define REMOVED_ASSOCIATIONS_GROUP "Removed Associations" #define MIME_CACHE_GROUP "MIME Cache" +#define INTENT_CACHE_GROUP "Intent Cache" #define GENERIC_NAME_KEY "GenericName" #define FULL_NAME_KEY "X-GNOME-FullName" #define KEYWORDS_KEY "Keywords" #define STARTUP_WM_CLASS_KEY "StartupWMClass" +#define INTENT_FDO_TERMINAL1 "org.freedesktop.Terminal1" +#define INTENT_FDO_DEEPLINK1 "org.freedesktop.handler.Deeplink1" enum { PROP_0, @@ -119,6 +122,7 @@ struct _GDesktopAppInfo char *startup_wm_class; char **mime_types; char **actions; + char **intents; guint nodisplay : 1; guint hidden : 1; @@ -150,8 +154,8 @@ typedef struct GFileMonitor *monitor; GHashTable *app_names; GHashTable *mime_tweaks; + GHashTable *intent_tweaks; GHashTable *memory_index; - GHashTable *memory_implementations; } DesktopFileDir; static GPtrArray *desktop_file_dirs = NULL; @@ -811,6 +815,48 @@ get_apps_from_dir (GHashTable **apps, g_dir_close (dir); } +typedef struct +{ + char **defaults; /* (owned); list of priority ordered desktop file IDs */ + GHashTable *defaults_scopes; /* (owned) (element-type utf8 GStrv); maps scope to list of priority ordered desktop file IDs */ + gchar **cache; /* (owned); list of desktop file IDs */ + GHashTable *cache_scopes; /* (owned) (element-type utf8 GStrv); maps scope to list of desktop file IDs */ +} UnindexedIntentTweaks; + +static void +free_intent_tweaks (gpointer data) +{ + UnindexedIntentTweaks *apps = data; + + g_strfreev (apps->defaults); + g_hash_table_unref (apps->defaults_scopes); + g_strfreev (apps->cache); + g_hash_table_unref (apps->cache_scopes); + g_free (apps); +} + +static UnindexedIntentTweaks * +desktop_file_dir_unindexed_get_intent_tweaks (DesktopFileDir *dir, + const char *interface) +{ + UnindexedIntentTweaks *tweaks; + + tweaks = g_hash_table_lookup (dir->intent_tweaks, interface); + if (tweaks == NULL) + { + tweaks = g_new0 (UnindexedIntentTweaks, 1); + tweaks->defaults_scopes = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, + (GDestroyNotify) g_strfreev); + tweaks->cache_scopes = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, + (GDestroyNotify) g_strfreev); + g_hash_table_insert (dir->intent_tweaks, g_strdup (interface), tweaks); + } + + return tweaks; +} + typedef struct { gchar **additions; @@ -1042,12 +1088,164 @@ desktop_file_dir_unindexed_read_mimeapps_lists (DesktopFileDir *dir) g_free (filename); } +static void +desktop_file_dir_unindexed_read_interface_scopes (GHashTable *tweak_scopes, + const char *filename, + GKeyFile *key_file, + const char *interface) +{ + char **scopes; + + scopes = g_key_file_get_keys (key_file, interface, NULL, NULL); + for (size_t i = 0; scopes != NULL && scopes[i] != NULL; i++) + { + GStrv scope_desktop_file_ids; + GStrv desktop_file_ids; + char *owned_key; + + desktop_file_ids = g_key_file_get_string_list (key_file, + interface, + scopes[i], + NULL, NULL); + + if (desktop_file_ids == NULL || desktop_file_ids[0] == NULL) + continue; + + if (!g_hash_table_steal_extended (tweak_scopes, scopes[i], + (gpointer *) &owned_key, + (gpointer *) &scope_desktop_file_ids)) + { + scope_desktop_file_ids = NULL; + owned_key = g_strdup (scopes[i]); + } + + expand_strv (&scope_desktop_file_ids, g_steal_pointer (&desktop_file_ids), NULL); + + g_hash_table_insert (tweak_scopes, + g_steal_pointer (&owned_key), + g_steal_pointer (&scope_desktop_file_ids)); + } + g_clear_pointer (&scopes, g_strfreev); +} + +static void +desktop_file_dir_unindexed_read_intentapps_list (DesktopFileDir *dir, + const gchar *filename, + const gchar *added_group, + gboolean is_cache) +{ + GKeyFile *key_file; + char **interfaces; + + /* This implements the XDG intent-apps-spec: + * https://gitlab.freedesktop.org/xdg/xdg-specs/-/merge_requests/81 + * TODO: ^ replace with canonical link when merged + */ + + 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; + } + + interfaces = g_key_file_get_keys (key_file, added_group, NULL, NULL); + for (size_t i = 0; interfaces != NULL && interfaces[i] != NULL; i++) + { + UnindexedIntentTweaks *tweaks; + gchar ***list; + GHashTable *scopes; + char **desktop_file_ids; + + tweaks = desktop_file_dir_unindexed_get_intent_tweaks (dir, interfaces[i]); + + if (is_cache) + { + list = &tweaks->cache; + scopes = tweaks->cache_scopes; + } + else + { + list = &tweaks->defaults; + scopes = tweaks->defaults_scopes; + } + + desktop_file_ids = g_key_file_get_string_list (key_file, + added_group, + interfaces[i], + NULL, NULL); + + if (desktop_file_ids) + expand_strv (list, g_steal_pointer (&desktop_file_ids), NULL); + + desktop_file_dir_unindexed_read_interface_scopes (scopes, + filename, + key_file, + interfaces[i]); + } + g_clear_pointer (&interfaces, g_strfreev); + + g_key_file_free (key_file); +} + +static void +desktop_file_dir_unindexed_read_intentapps_lists (DesktopFileDir *dir) +{ + const char * const *desktops; + char *filename; + + dir->intent_tweaks = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, + free_intent_tweaks); + + /* We process in order of precedence, using a blocklisting 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 (size_t i = 0; desktops[i]; i++) + { + char *basename; + + basename = g_strdup_printf ("%s-intentapps.list", desktops[i]); + filename = g_build_filename (dir->path, basename, NULL); + desktop_file_dir_unindexed_read_intentapps_list (dir, filename, + DEFAULT_APPLICATIONS_GROUP, + FALSE); + g_free (basename); + g_free (filename); + } + + /* Next, the non-desktop-specific intentapps.list */ + filename = g_build_filename (dir->path, "intentapps.list", NULL); + desktop_file_dir_unindexed_read_intentapps_list (dir, filename, + DEFAULT_APPLICATIONS_GROUP, + FALSE); + g_free (filename); + + if (dir->is_config) + return; + + /* Finally, the intent.cache, which is just a cached copy of what we + * would find in the Implements= lines of all of the desktop files. + */ + filename = g_strdup_printf ("%s/intent.cache", dir->path); + desktop_file_dir_unindexed_read_intentapps_list (dir, filename, + INTENT_CACHE_GROUP, + TRUE); + g_free (filename); +} + static void desktop_file_dir_unindexed_init (DesktopFileDir *dir) { if (!dir->is_config) get_apps_from_dir (&dir->app_names, dir->path, ""); + desktop_file_dir_unindexed_read_intentapps_lists (dir); desktop_file_dir_unindexed_read_mimeapps_lists (dir); } @@ -1187,7 +1385,6 @@ desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir) gpointer app, path; dir->memory_index = memory_index_new (); - dir->memory_implementations = memory_index_new (); /* Nothing to search? */ if (dir->app_names == NULL) @@ -1207,7 +1404,6 @@ desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir) !g_key_file_get_boolean (key_file, "Desktop Entry", "Hidden", NULL)) { /* Index the interesting keys... */ - gchar **implements; gsize i; for (i = 0; i < G_N_ELEMENTS (desktop_key_match_category); i++) @@ -1245,12 +1441,6 @@ desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir) 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], i, 0, app); - g_strfreev (implements); } g_key_file_free (key_file); @@ -1349,9 +1539,9 @@ desktop_file_dir_unindexed_mime_lookup (DesktopFileDir *dir, } static void -desktop_file_dir_unindexed_default_lookup (DesktopFileDir *dir, - const gchar *mime_type, - GPtrArray *results) +desktop_file_dir_unindexed_mime_lookup_default (DesktopFileDir *dir, + const gchar *mime_type, + GPtrArray *results) { UnindexedMimeTweaks *tweaks; gint i; @@ -1371,17 +1561,56 @@ desktop_file_dir_unindexed_default_lookup (DesktopFileDir *dir, } static void -desktop_file_dir_unindexed_get_implementations (DesktopFileDir *dir, - GList **results, - const gchar *interface) +unindexed_intent_lookup (DesktopFileDir *dir, + const char *interface, + const char *scope, + gboolean is_cache, + GPtrArray *results) { - MemoryIndexEntry *mie; + UnindexedIntentTweaks *tweaks; + gchar **list; - if (!dir->memory_index) - desktop_file_dir_unindexed_setup_search (dir); + tweaks = g_hash_table_lookup (dir->intent_tweaks, interface); + if (!tweaks) + return; + + if (scope && is_cache) + list = g_hash_table_lookup (tweaks->cache_scopes, scope); + else if (scope && !is_cache) + list = g_hash_table_lookup (tweaks->defaults_scopes, scope); + else if (!scope && is_cache) + list = tweaks->cache; + else if (!scope && !is_cache) + list = tweaks->defaults; + else + g_assert_not_reached (); + + for (size_t i = 0; list && list[i]; i++) + { + const char *app_name = list[i]; + + if (!desktop_file_dir_app_name_is_masked (dir, app_name) && + !array_contains (results, app_name)) + g_ptr_array_add (results, (char *)app_name); + } +} - for (mie = g_hash_table_lookup (dir->memory_implementations, interface); mie; mie = mie->next) - *results = g_list_prepend (*results, g_strdup (mie->app_name)); +static void +desktop_file_dir_unindexed_intent_lookup (DesktopFileDir *dir, + const char *interface, + const char *scope, + GPtrArray *results) +{ + unindexed_intent_lookup (dir, interface, scope, TRUE, results); +} + +static void +desktop_file_dir_unindexed_intent_lookup_default (DesktopFileDir *dir, + const char *interface, + const char *scope, + GPtrArray *results) +{ + unindexed_intent_lookup (dir, interface, scope, FALSE, results); } /* DesktopFileDir "API" {{{2 */ @@ -1465,11 +1694,7 @@ desktop_file_dir_reset (DesktopFileDir *dir) dir->mime_tweaks = NULL; } - if (dir->memory_implementations) - { - g_hash_table_unref (dir->memory_implementations); - dir->memory_implementations = NULL; - } + g_clear_pointer (&dir->intent_tweaks, g_hash_table_unref); dir->is_setup = FALSE; } @@ -1582,7 +1807,7 @@ desktop_file_dir_mime_lookup (DesktopFileDir *dir, } /*< internal > - * desktop_file_dir_default_lookup: + * desktop_file_dir_mime_lookup_default: * @dir: a #DesktopFileDir * @mime_type: the mime type to look up * @results: an array to store the results in @@ -1590,11 +1815,47 @@ desktop_file_dir_mime_lookup (DesktopFileDir *dir, * 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_mime_lookup_default (DesktopFileDir *dir, + const gchar *mime_type, + GPtrArray *results) { - desktop_file_dir_unindexed_default_lookup (dir, mime_type, results); + desktop_file_dir_unindexed_mime_lookup_default (dir, mime_type, results); +} + +/*< internal > + * desktop_file_dir_intent_lookup: + * @dir: a #DesktopFileDir + * @interface: the intent interface name + * @scope: (nullable): optional scope + * @results: an array to store the results in + * + * Collects the "applications for a given intent from @dir in order of priority. + */ +static void +desktop_file_dir_intent_lookup (DesktopFileDir *dir, + const char *interface, + const char *scope, + GPtrArray *results) +{ + desktop_file_dir_unindexed_intent_lookup (dir, interface, scope, results); +} + +/*< internal > + * desktop_file_dir_intent_lookup_default: + * @dir: a #DesktopFileDir + * @interface: the intent interface name + * @scope: (nullable): optional scope + * @results: an array to store the results in + * + * Collects the "default" applications for a given intent from @dir in order of priority. + */ +static void +desktop_file_dir_intent_lookup_default (DesktopFileDir *dir, + const char *interface, + const char *scope, + GPtrArray *results) +{ + desktop_file_dir_unindexed_intent_lookup_default (dir, interface, scope, results); } /*< internal > @@ -1611,14 +1872,6 @@ desktop_file_dir_search (DesktopFileDir *dir, desktop_file_dir_unindexed_search (dir, search_token); } -static void -desktop_file_dir_get_implementations (DesktopFileDir *dir, - GList **results, - const gchar *interface) -{ - desktop_file_dir_unindexed_get_implementations (dir, results, interface); -} - /* Lock/unlock and global setup API {{{2 */ static void @@ -1740,6 +1993,7 @@ g_desktop_app_info_finalize (GObject *object) g_strfreev (info->mime_types); g_free (info->app_id); g_strfreev (info->actions); + g_strfreev (info->intents); G_OBJECT_CLASS (g_desktop_app_info_parent_class)->finalize (object); } @@ -2026,6 +2280,7 @@ g_desktop_app_info_load_from_keyfile (GDesktopAppInfo *info, info->mime_types = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_MIME_TYPE, NULL, NULL); bus_activatable = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_DBUS_ACTIVATABLE, NULL); info->actions = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ACTIONS, NULL, NULL); + info->intents = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_IMPLEMENTS, NULL, NULL); /* Remove the special-case: no Actions= key just means 0 extra actions */ if (info->actions == NULL) @@ -2249,6 +2504,7 @@ g_desktop_app_info_dup (GAppInfo *appinfo) new_info->hidden = info->hidden; new_info->terminal = info->terminal; new_info->startup_notify = info->startup_notify; + new_info->intents = g_strdupv (info->intents); return G_APP_INFO (new_info); } @@ -2399,6 +2655,36 @@ g_desktop_app_info_get_keywords (GDesktopAppInfo *info) return (const char * const *)info->keywords; } +/** + * g_desktop_app_info_get_intents: + * @info: a [class@GioUnix.DesktopAppInfo] + * + * Gets the implemented intents from the desktop file. + * + * Returns: (transfer none): The value of the + * [`Implements` key](https://specifications.freedesktop.org/desktop-entry-spec/latest/recognized-keys.html) + * + * Since: 2.86 + */ +const char * const * +g_desktop_app_info_get_intents (GDesktopAppInfo *info) +{ + return (const char * const *)info->intents; +} + +gboolean +g_desktop_app_info_has_intent (GDesktopAppInfo *info, + const char *intent) +{ + for (size_t i = 0; info->intents && info->intents[i]; i++) + { + if (g_str_equal (info->intents[i], intent)) + return TRUE; + } + + return FALSE; +} + /** * g_desktop_app_info_get_generic_name: * @info: a [class@GioUnix.DesktopAppInfo] @@ -3269,6 +3555,162 @@ launch_uris_with_dbus_signal_cb (GObject *object, launch_uris_with_dbus_data_free (data); } +static GDesktopAppInfo * +g_desktop_app_info_get_for_terminal1 (GDesktopAppInfo *info) +{ + GDesktopAppInfo *terminal_info; + + if (!info->terminal) + return NULL; + + terminal_info = + (GDesktopAppInfo *) g_desktop_app_info_get_default_for_intent (INTENT_FDO_TERMINAL1, + NULL); + + if (terminal_info == NULL || terminal_info->app_id == NULL) + { + g_clear_object (&terminal_info); + return NULL; + } + + return g_steal_pointer (&terminal_info); +} + +static gboolean +prepare_launch_uris_with_terminal1 (GDesktopAppInfo *terminal_info, + GDesktopAppInfo *info, + GList *uris, + GAppLaunchContext *launch_context, + GVariant **call_data_out, + char **startup_id_out, + GError **error) +{ + GList *remaining_uris; + GVariantBuilder builder; + + g_variant_builder_init_static (&builder, G_VARIANT_TYPE_TUPLE); + + g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY); + + /* invocations */ + remaining_uris = uris; + do + { + GVariantBuilder invocation; + GVariantBuilder exec; + GVariantBuilder env; + char **argv; + int argc; + + if (!expand_application_parameters (info, + info->exec, + &remaining_uris, + &argc, + &argv, + error)) + { + g_variant_builder_clear (&builder); + return FALSE; + } + + g_variant_builder_init_static (&invocation, G_VARIANT_TYPE_VARDICT); + + g_variant_builder_init_static (&exec, G_VARIANT_TYPE_BYTESTRING_ARRAY); + for (int i = 0; i < argc; i++) + g_variant_builder_add_value (&exec, g_variant_new_bytestring (argv[i])); + g_variant_builder_add (&invocation, "{sv}", + "exec", g_variant_builder_end (&exec)); + + g_variant_builder_init_static (&env, G_VARIANT_TYPE_BYTESTRING_ARRAY); + g_variant_builder_add (&invocation, "{sv}", + "env", g_variant_builder_end (&env)); + + g_variant_builder_add (&invocation, "{sv}", + "working_directory", + g_variant_new_bytestring (info->path ? info->path : "")); + + g_variant_builder_add_value (&builder, g_variant_builder_end (&invocation)); + + g_clear_pointer (&argv, g_strfreev); + } + while (remaining_uris != NULL); + + g_variant_builder_close (&builder); + + /* desktop file path (desktop_entry) */ + g_variant_builder_add_value (&builder, g_variant_new_bytestring (info->filename)); + + /* options */ + g_variant_builder_open (&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_close (&builder); + + /* platform data */ + { + GVariant *platform_data; + + platform_data = g_desktop_app_info_make_platform_data (terminal_info, + uris, + launch_context); + + if (startup_id_out) + { + GVariantDict dict; + + g_variant_dict_init (&dict, platform_data); + g_variant_dict_lookup (&dict, "desktop-startup-id", "s", startup_id_out); + g_variant_dict_clear (&dict); + } + + g_variant_builder_add_value (&builder, platform_data); + } + + *call_data_out = g_variant_builder_end (&builder); + + return TRUE; +} + +static gboolean +launch_uris_with_terminal1 (GDesktopAppInfo *terminal_info, + GDesktopAppInfo *info, + GDBusConnection *session_bus, + GVariant *call_data, + char *startup_id, + GAppLaunchContext *launch_context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LaunchUrisWithDBusData *data; + char *object_path; + + if (launch_context) + emit_launch_started (launch_context, terminal_info, startup_id); + + data = g_new0 (LaunchUrisWithDBusData, 1); + data->info = g_object_ref (info); + data->callback = callback; + data->user_data = user_data; + data->launch_context = launch_context ? g_object_ref (launch_context) : NULL; + data->startup_id = g_steal_pointer (&startup_id); + + object_path = object_path_from_appid (terminal_info->app_id); + g_dbus_connection_call (session_bus, + terminal_info->app_id, + object_path, + INTENT_FDO_TERMINAL1, + "LaunchCommand", + g_steal_pointer (&call_data), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + launch_uris_with_dbus_signal_cb, + g_steal_pointer (&data)); + g_free (object_path); + + return TRUE; +} + static void launch_uris_with_dbus (GDesktopAppInfo *info, GDBusConnection *session_bus, @@ -3321,19 +3763,19 @@ launch_uris_with_dbus (GDesktopAppInfo *info, g_variant_dict_clear (&dict); } -static gboolean -g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo *info, - GDBusConnection *session_bus, - GList *uris, - GAppLaunchContext *launch_context, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) +static void +g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo *info, + GDBusConnection *session_bus, + GList *uris, + GAppLaunchContext *launch_context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { GList *ruris = uris; char *app_id = NULL; - g_return_val_if_fail (info != NULL, FALSE); + g_return_if_fail (info != NULL); #ifdef G_OS_UNIX app_id = g_desktop_app_info_get_string (info, "X-Flatpak"); @@ -3352,8 +3794,6 @@ g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo *info, g_list_free_full (ruris, g_free); g_free (app_id); - - return TRUE; } static gboolean @@ -3372,22 +3812,63 @@ g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo, { GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); GDBusConnection *session_bus; + GDesktopAppInfo *terminal_info; gboolean success = TRUE; session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); - if (session_bus && info->app_id) - /* This is non-blocking API. Similar to launching via fork()/exec() - * we don't wait around to see if the program crashed during startup. - * This is what startup-notification's job is... - */ - g_desktop_app_info_launch_uris_with_dbus (info, session_bus, uris, launch_context, - NULL, NULL, NULL); + terminal_info = g_desktop_app_info_get_for_terminal1 (info); + + if (session_bus && terminal_info) + { + GVariant *call_data; + char *startup_id; + + success = prepare_launch_uris_with_terminal1 (terminal_info, + info, + uris, + launch_context, + &call_data, + &startup_id, + error); + if (success) + { + launch_uris_with_terminal1 (terminal_info, + info, + session_bus, + g_steal_pointer (&call_data), + g_steal_pointer (&startup_id), + launch_context, + NULL, NULL, NULL); + } + } + else if (session_bus && info->app_id) + { + /* This is non-blocking API. Similar to launching via fork()/exec() + * we don't wait around to see if the program crashed during startup. + * This is what startup-notification's job is... + */ + g_desktop_app_info_launch_uris_with_dbus (info, + session_bus, + uris, + launch_context, + NULL, NULL, NULL); + } else - success = g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec, uris, launch_context, - spawn_flags, user_setup, user_setup_data, - pid_callback, pid_callback_data, - stdin_fd, stdout_fd, stderr_fd, error); + { + success = g_desktop_app_info_launch_uris_with_spawn (info, + session_bus, + info->exec, + uris, + launch_context, + spawn_flags, + user_setup, + user_setup_data, + pid_callback, + pid_callback_data, + stdin_fd, stdout_fd, stderr_fd, + error); + } if (session_bus != NULL) { @@ -3399,6 +3880,8 @@ g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo, g_object_unref (session_bus); } + g_clear_object (&terminal_info); + return success; } @@ -3476,18 +3959,55 @@ launch_uris_bus_get_cb (GObject *object, LaunchUrisData *data = g_task_get_task_data (task); GCancellable *cancellable = g_task_get_cancellable (task); GDBusConnection *session_bus; + GDesktopAppInfo *terminal_info; GError *local_error = NULL; session_bus = g_bus_get_finish (result, NULL); - if (session_bus && info->app_id) + terminal_info = g_desktop_app_info_get_for_terminal1 (info); + + if (session_bus && terminal_info) + { + gboolean success; + GVariant *call_data; + char *startup_id; + + success = prepare_launch_uris_with_terminal1 (terminal_info, + info, + data->uris, + data->context, + &call_data, + &startup_id, + &local_error); + if (!success) + { + g_task_return_error (task, g_steal_pointer (&local_error)); + g_object_unref (task); + } + else + { + launch_uris_with_terminal1 (terminal_info, + info, + session_bus, + g_steal_pointer (&call_data), + g_steal_pointer (&startup_id), + data->context, + cancellable, + launch_uris_with_dbus_cb, + g_steal_pointer (&task)); + } + } + else if (session_bus && info->app_id) { /* FIXME: The g_document_portal_add_documents() function, which is called * from the g_desktop_app_info_launch_uris_with_dbus() function, still * uses blocking calls. */ - g_desktop_app_info_launch_uris_with_dbus (info, session_bus, - data->uris, data->context, + + g_desktop_app_info_launch_uris_with_dbus (info, + session_bus, + data->uris, + data->context, cancellable, launch_uris_with_dbus_cb, g_steal_pointer (&task)); @@ -3509,10 +4029,12 @@ launch_uris_bus_get_cb (GObject *object, g_object_unref (task); } else if (session_bus) - g_dbus_connection_flush (session_bus, - cancellable, - launch_uris_flush_cb, - g_steal_pointer (&task)); + { + g_dbus_connection_flush (session_bus, + cancellable, + launch_uris_flush_cb, + g_steal_pointer (&task)); + } else { g_task_return_boolean (task, TRUE); @@ -3520,6 +4042,7 @@ launch_uris_bus_get_cb (GObject *object, } } + g_clear_object (&terminal_info); g_clear_object (&session_bus); } @@ -4664,7 +5187,7 @@ g_app_info_get_default_for_type_impl (const char *content_type, { /* Collect all the default apps for this type */ for (j = 0; j < desktop_file_dirs->len; j++) - desktop_file_dir_default_lookup (g_ptr_array_index (desktop_file_dirs, j), types[i], results); + desktop_file_dir_mime_lookup_default (g_ptr_array_index (desktop_file_dirs, j), types[i], results); /* Consider the associations as well... */ for (j = 0; j < desktop_file_dirs->len; j++) @@ -4705,6 +5228,42 @@ out: return info; } +/** + * g_desktop_app_info_get_default_for_intent: + * @interface: the intent interface to find a `GAppInfo` for + * @scope: (nullable): an optional scope + * + * Gets the default [iface@Gio.AppInfo] that implement @interface and, if @scope + * is not %NULL, also supports @scope. + * + * An application implements an interface if that interface is listed in + * the `Implements` line of the desktop file. + * + * An application implements a scope of an interface, if the scope is listed in + * the `Supports` line of the interface section of the desktop file. + * + * Application priority can be adjusted via `intentapps.list` files. + * + * Returns: (transfer full) (nullable): `GAppInfo` for given @intent and @scope, + * or %NULL if not found. + * + * Since: 2.86 + */ +GAppInfo * +g_desktop_app_info_get_default_for_intent (const char *interface, + const char *scope) +{ + GAppInfo *def = NULL; + GList *apps; + + apps = g_desktop_app_info_get_for_intent (interface, scope); + if (apps) + def = g_object_ref (apps->data); + + g_list_free_full (apps, g_object_unref); + return def; +} + GAppInfo * g_app_info_get_default_for_uri_scheme_impl (const char *uri_scheme) { @@ -4722,55 +5281,195 @@ g_app_info_get_default_for_uri_scheme_impl (const char *uri_scheme) return app_info; } +static char * +get_match_path_for_uri (GUri *uri) +{ + GString *path_ref = NULL; + const char *path = NULL; + const char *query = NULL; + const char *fragment = NULL; + + /* Compose an absolute-ref ("/" path [ "?" query ] [ "#" fragment ]) */ + + path = g_uri_get_path (uri); + if (path != NULL && *path != '\0') + path_ref = g_string_new (path); + else + path_ref = g_string_new ("/"); + + query = g_uri_get_query (uri); + if (query != NULL && *query != '\0') + g_string_append_printf (path_ref, "?%s", query); + + fragment = g_uri_get_fragment (uri); + if (fragment != NULL && *fragment != '\0') + g_string_append_printf (path_ref, "#%s", fragment); + + return g_string_free (path_ref, FALSE); +} + +GAppInfo * +g_app_info_get_default_for_uri_http_impl (GUri *uri) +{ + GAppInfo *app_info = NULL; + GList *apps; + const gchar *host; + const gchar *scheme; + gchar *uri_path = NULL; + + scheme = g_uri_get_scheme (uri); + if (!g_str_equal (scheme, "http") && !g_str_equal (scheme, "https")) + { + g_debug ("Could not get a default for an http URI because " + "the scheme is not http or https"); + return NULL; + } + + host = g_uri_get_host (uri); + if (host == NULL) + { + g_debug ("Could not get a default for an http URI because " + "the URI contains no host"); + return NULL; + } + + apps = g_desktop_app_info_get_for_intent (INTENT_FDO_DEEPLINK1, host); + + if (apps) + uri_path = get_match_path_for_uri (uri); + + for (GList *l = apps; l != NULL; l = l->next) + { + GDesktopAppInfo *candidate = l->data; + GStrv patterns; + + patterns = g_key_file_get_string_list (candidate->keyfile, + INTENT_FDO_DEEPLINK1, + host, + NULL, NULL); + + for (gsize i = 0; patterns[i]; i++) + { + if (g_pattern_match_simple (patterns[i], uri_path)) + { + app_info = g_object_ref (G_APP_INFO (candidate)); + break; + } + } + g_clear_pointer (&patterns, g_strfreev); + + if (app_info) + break; + } + + g_list_free_full (apps, g_object_unref); + g_clear_pointer (&uri_path, g_free); + + return g_steal_pointer (&app_info); +} + /* "Get all" API {{{2 */ /** - * g_desktop_app_info_get_implementations: - * @interface: the name of the interface + * g_desktop_app_info_get_for_intent: + * @interface: the intent interface to find `GAppInfo`s for + * @scope: (nullable): an optional scope * - * Gets all applications that implement @interface. + * Gets all [iface@Gio.AppInfo]s that implement @interface and, if @scope is not + * %NULL, also supports @scope. + * + * Applications earlier in the list have higher priority. * * An application implements an interface if that interface is listed in - * the `Implements` line of the desktop file of the application. + * the `Implements` line of the desktop file. + * + * An application implements a scope of an interface, if the scope is listed in + * the `Supports` line of the interface section of the desktop file. + * + * Application priority can be adjusted via `intentapps.list` files. * * Returns: (element-type GDesktopAppInfo) (transfer full): a list of * [class@GioUnix.DesktopAppInfo] objects. * - * Since: 2.42 + * Since: 2.86 **/ GList * -g_desktop_app_info_get_implementations (const gchar *interface) +g_desktop_app_info_get_for_intent (const gchar *interface, + const char *scope) { - GList *result = NULL; - GList **ptr; - guint i; + GPtrArray *results = g_ptr_array_new (); + GList *infos = NULL; desktop_file_dirs_lock (); - for (i = 0; i < desktop_file_dirs->len; i++) - desktop_file_dir_get_implementations (g_ptr_array_index (desktop_file_dirs, i), &result, interface); - - desktop_file_dirs_unlock (); + /* Collect all the default apps for the interface and scope */ + for (size_t i = 0; i < desktop_file_dirs->len; i++) + { + desktop_file_dir_intent_lookup_default (g_ptr_array_index (desktop_file_dirs, i), + interface, + scope, + results); + } - ptr = &result; - while (*ptr) + /* Collect all the default apps for the interface and scope */ + for (size_t i = 0; i < desktop_file_dirs->len; i++) { - gchar *name = (*ptr)->data; - GDesktopAppInfo *app; + desktop_file_dir_intent_lookup (g_ptr_array_index (desktop_file_dirs, i), + interface, + scope, + results); + } - app = g_desktop_app_info_new (name); - g_free (name); + /* (If any), see if one of those apps is installed... */ + for (size_t i = 0; i < results->len; i++) + { + const char *desktop_id = g_ptr_array_index (results, i); - if (app) + for (size_t j = 0; j < desktop_file_dirs->len; j++) { - (*ptr)->data = app; - ptr = &(*ptr)->next; + GDesktopAppInfo *info; + + info = desktop_file_dir_get_app (g_ptr_array_index (desktop_file_dirs, j), + desktop_id); + if (!info) + continue; + + if (!g_desktop_app_info_has_intent (info, interface)) + { + g_debug ("%s: %s claimed to, but does not support intent %s", + G_STRFUNC, info->filename, interface); + g_clear_object (&info); + continue; + } + + infos = g_list_append (infos, G_APP_INFO (info)); } - else - *ptr = g_list_delete_link (*ptr, *ptr); } - return result; + desktop_file_dirs_unlock (); + g_ptr_array_unref (results); + + return infos; +} + +/** + * g_desktop_app_info_get_implementations: + * @interface: the name of the interface + * + * Gets all applications that implement @interface. + * + * An application implements an interface if that interface is listed in + * the `Implements` line of the desktop file of the application. + * + * Returns: (element-type GDesktopAppInfo) (transfer full): a list of + * [class@GioUnix.DesktopAppInfo] objects. + * + * Since: 2.42 + **/ +GList * +g_desktop_app_info_get_implementations (const gchar *interface) +{ + return g_desktop_app_info_get_for_intent (interface, NULL); } /** diff --git a/gio/gdesktopappinfo.h b/gio/gdesktopappinfo.h index 7981a026f7e2dfdd1dec38106d421ebffc155fe2..78861a7546ef674e7137780e44add3f91956247a 100644 --- a/gio/gdesktopappinfo.h +++ b/gio/gdesktopappinfo.h @@ -62,6 +62,11 @@ GIO_AVAILABLE_IN_2_30 const char * g_desktop_app_info_get_categories (GDesktopAppInfo *info); GIO_AVAILABLE_IN_2_30 const char * const *g_desktop_app_info_get_keywords (GDesktopAppInfo *info); +GIO_AVAILABLE_IN_2_86 +const char * const *g_desktop_app_info_get_intents (GDesktopAppInfo *info); +GIO_AVAILABLE_IN_2_86 +gboolean g_desktop_app_info_has_intent (GDesktopAppInfo *info, + const char *intent); GIO_AVAILABLE_IN_2_30 gboolean g_desktop_app_info_get_nodisplay (GDesktopAppInfo *info); GIO_AVAILABLE_IN_2_30 @@ -192,8 +197,14 @@ gboolean g_desktop_app_info_launch_uris_as_manager_with_fds (GDesktopAppInfo GIO_AVAILABLE_IN_2_40 gchar *** g_desktop_app_info_search (const gchar *search_string); -GIO_AVAILABLE_IN_2_42 +GIO_DEPRECATED_IN_2_86_FOR (g_desktop_app_info_get_for_intent) GList *g_desktop_app_info_get_implementations (const gchar *interface); +GIO_AVAILABLE_IN_2_86 +GList *g_desktop_app_info_get_for_intent (const gchar *interface, + const char *scope); +GIO_AVAILABLE_IN_2_86 +GAppInfo *g_desktop_app_info_get_default_for_intent (const char *interface, + const char *scope); G_END_DECLS diff --git a/gio/gosxappinfo.m b/gio/gosxappinfo.m index 917d72bdde2452fdc3c9ae5564a2c2347003cf6f..307f6d8e1c0eb7df6ecc1e60bd684292a16f59f4 100644 --- a/gio/gosxappinfo.m +++ b/gio/gosxappinfo.m @@ -820,3 +820,9 @@ void g_app_info_reset_type_associations_impl (const char *content_type) { } + +GAppInfo * +g_app_info_get_default_for_uri_http_impl (GUri *uri) +{ + return NULL; +} diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index 39adfcaa3008977e366711962c25dd810179eede..a981c656fa0f8a2fd03e7bea7182a213949ccff8 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -6060,3 +6060,9 @@ g_app_info_reset_type_associations_impl (const char *content_type) { /* nothing to do */ } + +GAppInfo * +g_app_info_get_default_for_uri_http_impl (GUri *uri) +{ + return NULL; +} diff --git a/gio/tests/apps.c b/gio/tests/apps.c index d656b6c92640c22bc35f9ab3f7aaf48e52891ffb..191baf65933025cbdd071e58678aed965b7b944e 100644 --- a/gio/tests/apps.c +++ b/gio/tests/apps.c @@ -101,7 +101,7 @@ main (int argc, char **argv) { GList *results; - results = g_desktop_app_info_get_implementations (argv[2]); + results = g_desktop_app_info_get_for_intent (argv[2], NULL); print_app_list (results); } else if (g_str_equal (argv[1], "show-info")) diff --git a/gio/tests/desktop-files/usr/applications/intent.cache b/gio/tests/desktop-files/usr/applications/intent.cache new file mode 100644 index 0000000000000000000000000000000000000000..af98a9387a796d017c39e2f5de8175c5ea0b6d61 --- /dev/null +++ b/gio/tests/desktop-files/usr/applications/intent.cache @@ -0,0 +1,3 @@ +[Intent Cache] +org.freedesktop.ImageProvider=cheese.desktop; +org.gnome.Shell.SearchProvider2=eog.desktop;gnome-contacts.desktop;gnome-music.desktop; diff --git a/gio/tests/file.c b/gio/tests/file.c index 6df726ee249dbb2b36ab7231ee4f44e3d1463468..96c9eddf5637f7bdd2704950d9bec5691b43eb54 100644 --- a/gio/tests/file.c +++ b/gio/tests/file.c @@ -2746,9 +2746,9 @@ test_measure (void) g_assert_true (ok); g_assert_no_error (error); - g_assert_cmpuint (num_bytes, ==, 96702); + g_assert_cmpuint (num_bytes, ==, 96851); g_assert_cmpuint (num_dirs, ==, 6); - g_assert_cmpuint (num_files, ==, 34); + g_assert_cmpuint (num_files, ==, 35); g_object_unref (file); g_free (path); @@ -2829,9 +2829,9 @@ test_measure_async (void) file = g_file_new_for_path (path); g_free (path); - data->expected_bytes = 96702; + data->expected_bytes = 96851; data->expected_dirs = 6; - data->expected_files = 34; + data->expected_files = 35; g_file_measure_disk_usage_async (file, G_FILE_MEASURE_APPARENT_SIZE, diff --git a/gio/tests/mimeapps.c b/gio/tests/mimeapps.c index 46862414871ab8bc5b00f30c4b3143089cd8a94a..547af16f654b3f145a4e23fe37924a93ad448c5f 100644 --- a/gio/tests/mimeapps.c +++ b/gio/tests/mimeapps.c @@ -39,7 +39,8 @@ const gchar *myapp_data = "Version=1.0\n" "Type=Application\n" "Exec=true %f\n" - "Name=my app\n"; + "Name=my app\n" + "Implements=org.freedesktop.UriHandler;org.freedesktop.Terminal\n"; const gchar *myapp2_data = "[Desktop Entry]\n" @@ -47,7 +48,11 @@ const gchar *myapp2_data = "Version=1.0\n" "Type=Application\n" "Exec=sleep %f\n" - "Name=my app 2\n"; + "Name=my app 2\n" + "Implements=org.freedesktop.UriHandler;org.freedesktop.Terminal\n" + "[org.freedesktop.UriHandler]\n" + "Supports=example.org;x.org\n" + "SomeExtraData=foo;bar\n"; const gchar *myapp3_data = "[Desktop Entry]\n" @@ -56,7 +61,10 @@ const gchar *myapp3_data = "Type=Application\n" "Exec=sleep 1\n" "Name=my app 3\n" - "MimeType=image/png;"; + "Implements=org.freedesktop.UriHandler\n" + "MimeType=image/png;" + "[org.freedesktop.UriHandler]\n" + "Supports=example.org;\n"; const gchar *myapp4_data = "[Desktop Entry]\n" @@ -95,6 +103,20 @@ const gchar *mimecache_data = "image/bmp=myapp4.desktop;myapp5.desktop;\n" "image/png=myapp3.desktop;\n"; +const gchar *intentapps_data = + "[Default Applications]\n" + "org.freedesktop.UriHandler=myapp2.desktop;\n" + "[org.freedesktop.UriHandler]\n" + "example.org=myapp3.desktop;\n"; + +const gchar *intentcache_data = + "[Intent Cache]\n" + "org.freedesktop.Terminal=myapp.desktop;myapp2.desktop;\n" + "org.freedesktop.UriHandler=myapp.desktop;myapp2.desktop;myapp3.desktop;\n" + "[org.freedesktop.UriHandler]\n" + "x.org=myapp2.desktop;\n" + "example.org=myapp2.desktop;myapp3.desktop\n"; + typedef struct { gchar *mimeapps_list_home; /* (owned) */ @@ -211,6 +233,43 @@ setup (Fixture *fixture, g_assert_no_error (error); g_free (name); + if (!GPOINTER_TO_INT (test_data)) + { + name = g_build_filename (apphome, "intentapps.list", NULL); + } + else + { + GFile *file_a = NULL, *file_b = NULL, *appdir = NULL; + + /* + * Ensure intentapps.list can be reachable via a symlink chain. + */ + appdir = g_file_new_for_path (apphome); + file_a = g_file_get_child (appdir, "intentapps.list"); + g_file_make_symbolic_link (file_a, "intentapps.list.b", NULL, &error); + g_assert_no_error (error); + g_object_unref (file_a); + + file_b = g_file_get_child (appdir, "intentapps.list.b"); + g_file_make_symbolic_link (file_b, "intentapps.list.c", NULL, &error); + g_assert_no_error (error); + g_object_unref (file_b); + + g_object_unref (appdir); + name = g_build_filename (apphome, "intentapps.list.c", NULL); + } + + g_test_message ("creating '%s'", name); + g_file_set_contents (name, intentapps_data, -1, &error); + g_assert_no_error (error); + g_free (name); + + name = g_build_filename (apphome, "intent.cache", NULL); + g_test_message ("creating '%s'", name); + g_file_set_contents (name, intentcache_data, -1, &error); + g_assert_no_error (error); + g_free (name); + g_free (apphome); g_free (appdir_name); g_free (mimeapps); @@ -637,6 +696,54 @@ test_mime_ignore_nonexisting (Fixture *fixture, g_assert_null (appinfo); } +static void +test_intent_api (Fixture *fixture, + gconstpointer test_data) +{ + GAppInfo *appinfo; + GAppInfo *appinfo2; + GAppInfo *appinfo3; + GAppInfo *def; + GList *infos, *l; + + appinfo = (GAppInfo*)g_desktop_app_info_new ("myapp.desktop"); + appinfo2 = (GAppInfo*)g_desktop_app_info_new ("myapp2.desktop"); + appinfo3 = (GAppInfo*)g_desktop_app_info_new ("myapp3.desktop"); + + def = g_desktop_app_info_get_default_for_intent ("org.freedesktop.NonExisting", NULL); + g_assert_null (def); + + def = g_desktop_app_info_get_default_for_intent ("org.freedesktop.Terminal", NULL); + g_assert_nonnull (def); + g_object_unref (def); + + def = g_desktop_app_info_get_default_for_intent ("org.freedesktop.Terminal", "NonExistingScope"); + g_assert_null (def); + + def = g_desktop_app_info_get_default_for_intent ("org.freedesktop.UriHandler", NULL); + g_assert_nonnull (def); + g_assert_true (g_app_info_equal (def, appinfo2)); + g_object_unref (def); + + def = g_desktop_app_info_get_default_for_intent ("org.freedesktop.UriHandler", "example.org"); + g_assert_nonnull (def); + g_assert_true (g_app_info_equal (def, appinfo3)); + g_object_unref (def); + + infos = g_desktop_app_info_get_for_intent ("org.freedesktop.UriHandler", "example.org"); + l = infos; + g_assert_nonnull (l->data); + g_assert_true (g_app_info_equal (l->data, appinfo3)); + l = l->next; + g_assert_nonnull (l->data); + g_assert_true (g_app_info_equal (l->data, appinfo2)); + + g_list_free_full (infos, g_object_unref); + g_object_unref (appinfo); + g_object_unref (appinfo2); + g_object_unref (appinfo3); +} + static void test_all (Fixture *fixture, gconstpointer test_data) @@ -682,6 +789,12 @@ main (int argc, char *argv[]) g_test_add ("/appinfo/mime-symlinked/ignore-nonexisting", Fixture, GINT_TO_POINTER (TRUE), setup, test_mime_ignore_nonexisting, teardown); + g_test_add ("/appinfo/intent/api", Fixture, GINT_TO_POINTER (FALSE), setup, + test_intent_api, teardown); + + g_test_add ("/appinfo/intent-symlinked/api", Fixture, GINT_TO_POINTER (TRUE), setup, + test_intent_api, teardown); + g_test_add ("/appinfo/all", Fixture, NULL, setup, test_all, teardown); return g_test_run (); diff --git a/glib/gkeyfile.c b/glib/gkeyfile.c index 7991c964d27bfcc73b65e919c1e292a80b60592a..27faa9515031489567074130fd0ec087909ff7b4 100644 --- a/glib/gkeyfile.c +++ b/glib/gkeyfile.c @@ -474,6 +474,15 @@ * Since: 2.38 */ +/** + * G_KEY_FILE_DESKTOP_KEY_IMPLEMENTS: + * + * A key under [const@GLib.KEY_FILE_DESKTOP_GROUP], whose value is a string list + * giving the available intents. + * + * Since: 2.86 + */ + /** * G_KEY_FILE_DESKTOP_TYPE_APPLICATION: * diff --git a/glib/gkeyfile.h b/glib/gkeyfile.h index 9d026d681d167ffbb45ce1fd1be8c34c2059eea2..e53688e14e0fe48d43a39cb693b7e46602b28eef 100644 --- a/glib/gkeyfile.h +++ b/glib/gkeyfile.h @@ -322,6 +322,7 @@ gboolean g_key_file_remove_group (GKeyFile *key_file, #define G_KEY_FILE_DESKTOP_KEY_URL "URL" #define G_KEY_FILE_DESKTOP_KEY_DBUS_ACTIVATABLE "DBusActivatable" #define G_KEY_FILE_DESKTOP_KEY_ACTIONS "Actions" +#define G_KEY_FILE_DESKTOP_KEY_IMPLEMENTS "Implements" #define G_KEY_FILE_DESKTOP_TYPE_APPLICATION "Application" #define G_KEY_FILE_DESKTOP_TYPE_LINK "Link"