From e77393fb1e205c6f59674c05f88c9b8335aeb9f6 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Fri, 12 Sep 2025 00:07:45 +0200 Subject: [PATCH 1/9] gdesktopappinfo: Rename lookup_default to mime_lookup_default We can lookup other default things, too. It also makes it more symmetrical to the already existing mime_lookup. --- gio/gdesktopappinfo.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index e020230986..48d129b081 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -1350,9 +1350,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; @@ -1583,7 +1583,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 @@ -1591,11 +1591,11 @@ 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 > @@ -4704,7 +4704,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++) -- GitLab From dd323e3fc503a19410cb2e3b9922b802eec0e2b1 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Wed, 10 Sep 2025 18:15:46 +0200 Subject: [PATCH 2/9] gkeyfile: Add G_KEY_FILE_DESKTOP_KEY_IMPLEMENTS This has been defined for a while now and we already look up that key, so let's just add it to the public API as well. --- gio/gdesktopappinfo.c | 5 ++++- glib/gkeyfile.c | 9 +++++++++ glib/gkeyfile.h | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 48d129b081..6ff6f8a2fc 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -1248,7 +1248,10 @@ desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir) } /* Make note of the Implements= line */ - implements = g_key_file_get_string_list (key_file, "Desktop Entry", "Implements", NULL, NULL); + implements = g_key_file_get_string_list (key_file, + "Desktop Entry", + G_KEY_FILE_DESKTOP_KEY_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); diff --git a/glib/gkeyfile.c b/glib/gkeyfile.c index 7991c964d2..e46142c064 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.88 + */ + /** * G_KEY_FILE_DESKTOP_TYPE_APPLICATION: * diff --git a/glib/gkeyfile.h b/glib/gkeyfile.h index 9d026d681d..e53688e14e 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" -- GitLab From 5014ba9a9aa5035283dee612b3c24f5935443da2 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Wed, 10 Sep 2025 18:21:36 +0200 Subject: [PATCH 3/9] gdesktopappinfo: Add get_intents and has_intent methods Besides finding apps which implement certain intents, we might also have an existing GDesktopAppInfo for which we want to know if an intent is supported or which intents are implemented. --- gio/gdesktopappinfo.c | 34 ++++++++++++++++++++++++++++++++++ gio/gdesktopappinfo.h | 5 +++++ 2 files changed, 39 insertions(+) diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 6ff6f8a2fc..b876ef7579 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -119,6 +119,7 @@ struct _GDesktopAppInfo char *startup_wm_class; char **mime_types; char **actions; + char **intents; guint nodisplay : 1; guint hidden : 1; @@ -1744,6 +1745,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); } @@ -2030,6 +2032,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) @@ -2253,6 +2256,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); } @@ -2403,6 +2407,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] diff --git a/gio/gdesktopappinfo.h b/gio/gdesktopappinfo.h index 7981a026f7..8b806052ae 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_88 +const char * const *g_desktop_app_info_get_intents (GDesktopAppInfo *info); +GIO_AVAILABLE_IN_2_88 +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 -- GitLab From 1d2b246668d1bc6aced13384bafc774b198a7c81 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Wed, 10 Sep 2025 18:27:54 +0200 Subject: [PATCH 4/9] gdesktopappinfo: Add support for the intentapps specification Add support for the intentapps-spec, inspired by the mimeapps-spec. The specification describes directories to look for `intentapps.list` files, the format for this file and relevant fields in the desktop entry. The new functions takes an interface and an optional scope. The interfaces correspond to the `Implements` key and the scope may be used by interfaces to define different default applications for a specific context. --- gio/gdesktopappinfo.c | 404 ++++++++++++++++++++++++++++++++++++++++++ gio/gdesktopappinfo.h | 6 + gio/tests/mimeapps.c | 119 ++++++++++++- 3 files changed, 526 insertions(+), 3 deletions(-) diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index b876ef7579..bd155de782 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -77,6 +77,7 @@ #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" @@ -151,6 +152,7 @@ typedef struct GFileMonitor *monitor; GHashTable *app_names; GHashTable *mime_tweaks; + GHashTable *intent_tweaks; GHashTable *memory_index; GHashTable *memory_implementations; } DesktopFileDir; @@ -813,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; @@ -1044,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); } @@ -1389,6 +1585,59 @@ desktop_file_dir_unindexed_get_implementations (DesktopFileDir *dir, *results = g_list_prepend (*results, g_strdup (mie->app_name)); } +static void +unindexed_intent_lookup (DesktopFileDir *dir, + const char *interface, + const char *scope, + gboolean is_cache, + GPtrArray *results) +{ + UnindexedIntentTweaks *tweaks; + gchar **list; + + 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); + } +} + +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 */ /*< internal > @@ -1475,6 +1724,7 @@ desktop_file_dir_reset (DesktopFileDir *dir) 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; } @@ -1602,6 +1852,42 @@ desktop_file_dir_mime_lookup_default (DesktopFileDir *dir, 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 > * desktop_file_dir_search: * @dir: a #DesktopFileDir @@ -4782,6 +5068,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) { @@ -4801,6 +5123,88 @@ g_app_info_get_default_for_uri_scheme_impl (const char *uri_scheme) /* "Get all" API {{{2 */ +/** + * g_desktop_app_info_get_for_intent: + * @interface: the intent interface to find `GAppInfo`s for + * @scope: (nullable): an optional scope + * + * 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. + * + * 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.86 + **/ +GList * +g_desktop_app_info_get_for_intent (const gchar *interface, + const char *scope) +{ + GPtrArray *results = g_ptr_array_new (); + GList *infos = NULL; + + desktop_file_dirs_lock (); + + /* Collect all the default apps for the interface and scope */ + for (guint 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); + } + + /* Collect all the default apps for the interface and scope */ + for (guint i = 0; i < desktop_file_dirs->len; i++) + { + desktop_file_dir_intent_lookup (g_ptr_array_index (desktop_file_dirs, i), + interface, + scope, + results); + } + + /* (If any), see if one of those apps is installed... */ + for (guint i = 0; i < results->len; i++) + { + const char *desktop_id = g_ptr_array_index (results, i); + + for (guint j = 0; j < desktop_file_dirs->len; j++) + { + 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)); + } + } + + desktop_file_dirs_unlock (); + g_ptr_array_unref (results); + + return infos; +} + /** * g_desktop_app_info_get_implementations: * @interface: the name of the interface diff --git a/gio/gdesktopappinfo.h b/gio/gdesktopappinfo.h index 8b806052ae..f2876f9053 100644 --- a/gio/gdesktopappinfo.h +++ b/gio/gdesktopappinfo.h @@ -199,6 +199,12 @@ gchar *** g_desktop_app_info_search (const gchar *search_string); GIO_AVAILABLE_IN_2_42 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/tests/mimeapps.c b/gio/tests/mimeapps.c index 4686241487..547af16f65 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 (); -- GitLab From 050b09b9b48eea9b983d15b8d3d034730e126fcf Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Wed, 10 Sep 2025 18:14:30 +0200 Subject: [PATCH 5/9] tests: Update desktop-files with an intent-aware update-desktop-database The result is that we get the `intent.cache` cache for fast lookups of intents. In the next commit, we will switch over the implementation for finding apps which implement interfaces to the new one which uses `intentapps.list` and `intent.cache` instead of iterating over all the desktop files. --- gio/tests/desktop-files/usr/applications/intent.cache | 3 +++ gio/tests/file.c | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 gio/tests/desktop-files/usr/applications/intent.cache 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 0000000000..af98a9387a --- /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 57d71f7a05..cd620cc29c 100644 --- a/gio/tests/file.c +++ b/gio/tests/file.c @@ -2860,9 +2860,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); @@ -2943,9 +2943,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, -- GitLab From a12c28ec01232b5dfbc324a1bc61a86dd2f8d606 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Wed, 10 Sep 2025 18:31:31 +0200 Subject: [PATCH 6/9] gdesktopappinfo: Deprecate get_implementations and switch to new impl The new impl uses `intentapps.list` and `intent.cache` to look up GDestkopAppInfos which implement an interface. --- gio/gdesktopappinfo.c | 70 +------------------------------------------ gio/gdesktopappinfo.h | 2 +- gio/tests/apps.c | 2 +- 3 files changed, 3 insertions(+), 71 deletions(-) diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index bd155de782..37e06fe069 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -154,7 +154,6 @@ typedef struct GHashTable *mime_tweaks; GHashTable *intent_tweaks; GHashTable *memory_index; - GHashTable *memory_implementations; } DesktopFileDir; static GPtrArray *desktop_file_dirs = NULL; @@ -1385,7 +1384,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) @@ -1405,7 +1403,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++) @@ -1443,15 +1440,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", - G_KEY_FILE_DESKTOP_KEY_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); @@ -1571,20 +1559,6 @@ desktop_file_dir_unindexed_mime_lookup_default (DesktopFileDir *dir, } } -static void -desktop_file_dir_unindexed_get_implementations (DesktopFileDir *dir, - GList **results, - const gchar *interface) -{ - MemoryIndexEntry *mie; - - if (!dir->memory_index) - desktop_file_dir_unindexed_setup_search (dir); - - for (mie = g_hash_table_lookup (dir->memory_implementations, interface); mie; mie = mie->next) - *results = g_list_prepend (*results, g_strdup (mie->app_name)); -} - static void unindexed_intent_lookup (DesktopFileDir *dir, const char *interface, @@ -1719,11 +1693,6 @@ 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; @@ -1902,14 +1871,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 @@ -5222,36 +5183,7 @@ g_desktop_app_info_get_for_intent (const gchar *interface, GList * g_desktop_app_info_get_implementations (const gchar *interface) { - GList *result = NULL; - GList **ptr; - guint i; - - 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 (); - - ptr = &result; - while (*ptr) - { - gchar *name = (*ptr)->data; - GDesktopAppInfo *app; - - app = g_desktop_app_info_new (name); - g_free (name); - - if (app) - { - (*ptr)->data = app; - ptr = &(*ptr)->next; - } - else - *ptr = g_list_delete_link (*ptr, *ptr); - } - - return result; + return g_desktop_app_info_get_for_intent (interface, NULL); } /** diff --git a/gio/gdesktopappinfo.h b/gio/gdesktopappinfo.h index f2876f9053..2a7dd06592 100644 --- a/gio/gdesktopappinfo.h +++ b/gio/gdesktopappinfo.h @@ -197,7 +197,7 @@ 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, diff --git a/gio/tests/apps.c b/gio/tests/apps.c index d656b6c926..191baf6593 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")) -- GitLab From 0d05f18d1d49958fccf5bb2a29008d30c4ccd316 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Wed, 10 Sep 2025 19:09:10 +0200 Subject: [PATCH 7/9] gdesktopappinfo: Return void instead of bool from launch_uris_with_dbus The function doesn't fail, so let's not pretend otherwise. --- gio/gdesktopappinfo.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 37e06fe069..b7c65a8c4a 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -3632,19 +3632,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"); @@ -3676,8 +3676,6 @@ g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo *info, if (ruris != uris) g_list_free_full (ruris, g_free); - - return TRUE; } static gboolean -- GitLab From a22f0f3d624be21cc3039636c82f1b909c8be55d Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Wed, 10 Sep 2025 19:11:16 +0200 Subject: [PATCH 8/9] gdesktopappinfo: Clean up the code style in some places The next commit will touch those functions and this keeps the noise down in the actual changes there. --- gio/gdesktopappinfo.c | 52 ++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index b7c65a8c4a..02018853fb 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -3699,17 +3699,32 @@ g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo, 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); + { + /* 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) { @@ -3808,8 +3823,11 @@ launch_uris_bus_get_cb (GObject *object, * 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)); @@ -3831,10 +3849,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); -- GitLab From 9beafaf1277846c87b374cff5e096c7343ec5018 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Wed, 10 Sep 2025 19:12:05 +0200 Subject: [PATCH 9/9] gdesktopappinfo: Use intent org.freedesktop.Terminal1 for terminal apps We now have everything in place to quickly look up the default app which implements the org.freedesktop.Terminal1 intent. We can then call the dbus interface on that app to launch desktop files which require a terminal ro run, instead of exec-ing a terminal from the hard-coded list we currently have. --- gio/gdesktopappinfo.c | 224 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 222 insertions(+), 2 deletions(-) diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 02018853fb..711ef3c29a 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -82,6 +82,7 @@ #define FULL_NAME_KEY "X-GNOME-FullName" #define KEYWORDS_KEY "Keywords" #define STARTUP_WM_CLASS_KEY "StartupWMClass" +#define INTENT_FDO_TERMINAL1 "org.freedesktop.Terminal1" enum { PROP_0, @@ -3580,6 +3581,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, @@ -3694,11 +3851,37 @@ 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) + terminal_info = g_desktop_app_info_get_for_terminal1 (info); + + if (session_bus && terminal_info) + { + GVariant *call_data = NULL; + char *startup_id = NULL; + + 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. @@ -3736,6 +3919,8 @@ g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo, g_object_unref (session_bus); } + g_clear_object (&terminal_info); + return success; } @@ -3813,11 +3998,45 @@ 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 = NULL; + char *startup_id = NULL; + + 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 @@ -3862,6 +4081,7 @@ launch_uris_bus_get_cb (GObject *object, } } + g_clear_object (&terminal_info); g_clear_object (&session_bus); } -- GitLab