From d3ae4cc8097909b42d0d8fc279cd25cb1ce6301f Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Thu, 14 Jul 2022 14:25:05 +0200 Subject: [PATCH 1/8] GWin32AppInfo: Ensure COM is initialized when activating UWP apps Using the Application Activation Manager coclass. Its threading model is marked as 'both', so it can be instantiated in any apartment type without marshaling. --- gio/gwin32appinfo.c | 60 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index 0960eef5c..265cae686 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -4693,35 +4693,77 @@ g_win32_app_info_launch_uwp_internal (GWin32AppInfo *info, GWin32AppInfoShellVerb *shverb, GError **error) { - DWORD pid; IApplicationActivationManager* paam = NULL; - gboolean result = TRUE; + gboolean com_initialized = FALSE; + gboolean result = FALSE; + DWORD pid = 0; HRESULT hr; + const wchar_t *app_canonical_name = (const wchar_t *) info->app->canonical_name; - hr = CoCreateInstance (&CLSID_ApplicationActivationManager, NULL, CLSCTX_INPROC_SERVER, &IID_IApplicationActivationManager, (void **) &paam); + /* ApplicationActivationManager threading model is both, + * prefer the multithreaded apartment type, as we don't + * need anything of the STA here. */ + hr = CoInitializeEx (NULL, COINIT_MULTITHREADED); + if (SUCCEEDED (hr)) + com_initialized = TRUE; + else if (hr != RPC_E_CHANGED_MODE) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to initialize the COM support library for the thread: 0x%lx", hr); + goto cleanup; + } + + hr = CoCreateInstance (&CLSID_ApplicationActivationManager, NULL, + CLSCTX_INPROC_SERVER, + &IID_IApplicationActivationManager, (void **) &paam); if (FAILED (hr)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to create ApplicationActivationManager: 0x%lx", hr); - return FALSE; + goto cleanup; } + /* The Activate methods return a process identifier (PID), so we should consider + * those methods as potentially blocking */ if (items == NULL) - hr = IApplicationActivationManager_ActivateApplication (paam, (const wchar_t *) info->app->canonical_name, NULL, AO_NONE, &pid); + hr = IApplicationActivationManager_ActivateApplication (paam, + app_canonical_name, + NULL, AO_NONE, + &pid); else if (for_files) - hr = IApplicationActivationManager_ActivateForFile (paam, (const wchar_t *) info->app->canonical_name, items, shverb->verb_name, &pid); + hr = IApplicationActivationManager_ActivateForFile (paam, + app_canonical_name, + items, shverb->verb_name, + &pid); else - hr = IApplicationActivationManager_ActivateForProtocol (paam, (const wchar_t *) info->app->canonical_name, items, &pid); + hr = IApplicationActivationManager_ActivateForProtocol (paam, + app_canonical_name, + items, + &pid); if (FAILED (hr)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "The app %s failed to launch: 0x%lx", g_win32_appinfo_application_get_some_name (info->app), hr); - result = FALSE; + goto cleanup; } - IApplicationActivationManager_Release (paam); + result = TRUE; + +cleanup: + + if (paam) + { + IApplicationActivationManager_Release (paam); + paam = NULL; + } + + if (com_initialized) + { + CoUninitialize (); + com_initialized = FALSE; + } return result; } From bf13a5e0a4e9319c11b9d10b26dc2c23a7e956a9 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Thu, 21 Jul 2022 15:42:55 +0200 Subject: [PATCH 2/8] GWin32AppInfo: Implement launch_uris_async Fixes https://gitlab.gnome.org/GNOME/gtk/-/issues/4400 --- gio/gwin32appinfo.c | 67 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index 265cae686..27d779085 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -5139,6 +5139,71 @@ g_win32_app_info_launch_uris (GAppInfo *appinfo, return res; } +typedef struct +{ + GList *uris; /* (element-type utf8) (owned) (nullable) */ + GAppLaunchContext *context; /* (owned) (nullable) */ +} LaunchUrisData; + +static void +launch_uris_data_free (LaunchUrisData *data) +{ + g_clear_object (&data->context); + g_list_free_full (data->uris, g_free); + g_free (data); +} + +static void +launch_uris_async_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GAppInfo *appinfo = G_APP_INFO (source_object); + LaunchUrisData *data = task_data; + GError *local_error = NULL; + gboolean succeeded; + + succeeded = g_app_info_launch_uris (appinfo, data->uris, data->context, &local_error); + if (succeeded) + g_task_return_boolean (task, TRUE); + else + g_task_return_error (task, g_steal_pointer (&local_error)); +} + +static void +g_win32_app_info_launch_uris_async (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LaunchUrisData *data; + + task = g_task_new (appinfo, cancellable, callback, user_data); + g_task_set_source_tag (task, g_win32_app_info_launch_uris_async); + + data = g_new0 (LaunchUrisData, 1); + data->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL); + g_set_object (&data->context, context); + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_uris_data_free); + + g_task_run_in_thread (task, launch_uris_async_thread); + g_object_unref (task); +} + +static gboolean +g_win32_app_info_launch_uris_finish (GAppInfo *appinfo, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, appinfo), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + static gboolean g_win32_app_info_should_show (GAppInfo *appinfo) { @@ -5284,6 +5349,8 @@ g_win32_app_info_iface_init (GAppInfoIface *iface) iface->supports_uris = g_win32_app_info_supports_uris; iface->supports_files = g_win32_app_info_supports_files; iface->launch_uris = g_win32_app_info_launch_uris; + iface->launch_uris_async = g_win32_app_info_launch_uris_async; + iface->launch_uris_finish = g_win32_app_info_launch_uris_finish; iface->should_show = g_win32_app_info_should_show; /* iface->set_as_default_for_type = g_win32_app_info_set_as_default_for_type;*/ /* iface->set_as_default_for_extension = g_win32_app_info_set_as_default_for_extension;*/ From f0ff6a6af0efd6d5d35c0acf264da81d2035d56c Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Thu, 21 Jul 2022 15:57:15 +0200 Subject: [PATCH 3/8] GWin32AppInfo: Add g_win32_app_info_launch_uris_impl utility function This utility function will be called by both launch_uris and launch_uris_async, passing a from_task parameter respectively as NULL and non-NULL. The from_task parameter will be needed to know whether GAppLaunchContext signals should be emitted directly (from_task == NULL) or scheduled for emission on the main thread (from_task != NULL). --- gio/gwin32appinfo.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index 27d779085..3ce417868 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -4776,6 +4776,7 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, IShellItemArray *items, /* UWP only */ GAppLaunchContext *launch_context, GSpawnFlags spawn_flags, + GTask *from_task, GError **error) { gboolean completed = FALSE; @@ -5070,10 +5071,11 @@ make_item_array (gboolean for_files, static gboolean -g_win32_app_info_launch_uris (GAppInfo *appinfo, - GList *uris, - GAppLaunchContext *launch_context, - GError **error) +g_win32_app_info_launch_uris_impl (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *launch_context, + GTask *from_task, + GError **error) { gboolean res = FALSE; gboolean do_files; @@ -5091,7 +5093,7 @@ g_win32_app_info_launch_uris (GAppInfo *appinfo, return res; } - res = g_win32_app_info_launch_internal (info, NULL, FALSE, items, launch_context, 0, error); + res = g_win32_app_info_launch_internal (info, NULL, FALSE, items, launch_context, 0, from_task, error); if (items != NULL) IShellItemArray_Release (items); @@ -5132,6 +5134,7 @@ g_win32_app_info_launch_uris (GAppInfo *appinfo, NULL, launch_context, G_SPAWN_SEARCH_PATH, + from_task, error); g_list_free_full (objs, free_file_or_uri); @@ -5139,6 +5142,15 @@ g_win32_app_info_launch_uris (GAppInfo *appinfo, return res; } +static gboolean +g_win32_app_info_launch_uris (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *launch_context, + GError **error) +{ + return g_win32_app_info_launch_uris_impl (appinfo, uris, launch_context, NULL, error); +} + typedef struct { GList *uris; /* (element-type utf8) (owned) (nullable) */ @@ -5164,7 +5176,7 @@ launch_uris_async_thread (GTask *task, GError *local_error = NULL; gboolean succeeded; - succeeded = g_app_info_launch_uris (appinfo, data->uris, data->context, &local_error); + succeeded = g_win32_app_info_launch_uris_impl (appinfo, data->uris, data->context, task, &local_error); if (succeeded) g_task_return_boolean (task, TRUE); else @@ -5235,7 +5247,7 @@ g_win32_app_info_launch (GAppInfo *appinfo, return res; } - res = g_win32_app_info_launch_internal (info, NULL, TRUE, items, launch_context, 0, error); + res = g_win32_app_info_launch_internal (info, NULL, TRUE, items, launch_context, 0, NULL, error); if (items != NULL) IShellItemArray_Release (items); @@ -5267,6 +5279,7 @@ g_win32_app_info_launch (GAppInfo *appinfo, NULL, launch_context, G_SPAWN_SEARCH_PATH, + NULL, error); g_list_free_full (objs, free_file_or_uri); From cff3e660c1b33200f5d46822175f895d85ea5adb Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Thu, 21 Jul 2022 16:23:56 +0200 Subject: [PATCH 4/8] GWin32AppInfo: Emit GAppLaunchContext signals for all code paths --- gio/gwin32appinfo.c | 272 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 248 insertions(+), 24 deletions(-) diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index 3ce417868..2eeaa6279 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -4685,18 +4685,198 @@ get_appath_for_exe (const gchar *exe_basename) return appath; } +/* GDesktopAppInfo::launch_uris_async emits all GAppLaunchContext's signals + * on the main thread. + * + * We do the same: when g_win32_app_info_launch_uris_impl has a non-NULL + * from_task argument we schedule the signal emissions on the main loop, + * taking care not to emit signals after the task itself is completed + * (see g_task_get_completed). + */ + +typedef struct { + GAppLaunchContext *context; /* (owned) */ + GWin32AppInfo *info; /* (owned) */ +} EmitLaunchStartedData; + +static void +emit_launch_started_data_free (EmitLaunchStartedData *data) +{ + g_clear_object (&data->context); + g_clear_object (&data->info); + g_free (data); +} + +static gboolean +emit_launch_started_cb (EmitLaunchStartedData *data) +{ + g_signal_emit_by_name (data->context, "launch-started", data->info, NULL); + return G_SOURCE_REMOVE; +} + +static void +emit_launch_started (GAppLaunchContext *context, + GWin32AppInfo *info, + GTask *from_task) +{ + if (!context || !info) + return; + + if (!from_task) + g_signal_emit_by_name (context, "launch-started", info, NULL); + else + { + EmitLaunchStartedData *data; + + data = g_new (EmitLaunchStartedData, 1); + data->context = g_object_ref (context); + data->info = g_object_ref (info); + + g_main_context_invoke_full (g_task_get_context (from_task), + g_task_get_priority (from_task), + G_SOURCE_FUNC (emit_launch_started_cb), + g_steal_pointer (&data), + (GDestroyNotify) emit_launch_started_data_free); + } +} + +typedef struct { + GAppLaunchContext *context; /* (owned) */ + GWin32AppInfo *info; /* (owned) */ + GPid pid; /* (owned) */ +} EmitLaunchedData; + +static void +emit_launched_data_free (EmitLaunchedData *data) +{ + g_clear_object (&data->context); + g_clear_object (&data->info); + g_spawn_close_pid (data->pid); + g_free (data); +} + +static GVariant* +make_platform_data (GPid pid) +{ + GVariantBuilder builder; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + /* pid handles are never bigger than 2^24 as per + * https://docs.microsoft.com/en-us/windows/win32/sysinfo/kernel-objects, + * so truncating to `int32` is valid. + * The gsize cast is to silence a compiler warning + * about conversion from pointer to integer of + * different size. */ + g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 ((gsize) pid)); + + return g_variant_ref_sink (g_variant_builder_end (&builder)); +} + +static gboolean +emit_launched_cb (EmitLaunchedData *data) +{ + + GVariant *platform_data = make_platform_data (data->pid); + + g_signal_emit_by_name (data->context, "launched", data->info, platform_data); + g_variant_unref (platform_data); + + return G_SOURCE_REMOVE; +} + +static void +emit_launched (GAppLaunchContext *context, + GWin32AppInfo *info, + GPid *pid, + GTask *from_task) +{ + if (!context || !info) + return; + + if (!from_task) + { + GVariant *platform_data = make_platform_data (*pid); + g_signal_emit_by_name (context, "launched", info, platform_data); + g_variant_unref (platform_data); + } + else + { + EmitLaunchedData *data; + + data = g_new (EmitLaunchedData, 1); + data->context = g_object_ref (context); + data->info = g_object_ref (info); + data->pid = *pid; + + g_main_context_invoke_full (g_task_get_context (from_task), + g_task_get_priority (from_task), + G_SOURCE_FUNC (emit_launched_cb), + g_steal_pointer (&data), + (GDestroyNotify) emit_launched_data_free); + } + + *pid = 0; +} + +typedef struct { + GAppLaunchContext *context; /* (owned) */ + GWin32AppInfo *info; /* (owned) */ +} EmitLaunchFailedData; + +static void +emit_launch_failed_data_free (EmitLaunchFailedData *data) +{ + g_clear_object (&data->context); + g_clear_object (&data->info); + g_free (data); +} + +static gboolean +emit_launch_failed_cb (EmitLaunchFailedData *data) +{ + g_signal_emit_by_name (data->context, "launch-failed", data->info, NULL); + return G_SOURCE_REMOVE; +} + +static void +emit_launch_failed (GAppLaunchContext *context, + GWin32AppInfo *info, + GTask *from_task) +{ + if (!context || !info) + return; + + if (!from_task) + g_signal_emit_by_name (context, "launch-failed", info, NULL); + else + { + EmitLaunchFailedData *data; + + data = g_new (EmitLaunchFailedData, 1); + data->context = g_object_ref (context); + data->info = g_object_ref (info); + + g_main_context_invoke_full (g_task_get_context (from_task), + g_task_get_priority (from_task), + G_SOURCE_FUNC (emit_launch_failed_cb), + g_steal_pointer (&data), + (GDestroyNotify) emit_launch_failed_data_free); + } +} static gboolean g_win32_app_info_launch_uwp_internal (GWin32AppInfo *info, gboolean for_files, IShellItemArray *items, GWin32AppInfoShellVerb *shverb, + GAppLaunchContext *launch_context, + GTask *from_task, GError **error) { IApplicationActivationManager* paam = NULL; gboolean com_initialized = FALSE; gboolean result = FALSE; - DWORD pid = 0; + DWORD process_id = 0; HRESULT hr; const wchar_t *app_canonical_name = (const wchar_t *) info->app->canonical_name; @@ -4723,31 +4903,83 @@ g_win32_app_info_launch_uwp_internal (GWin32AppInfo *info, goto cleanup; } + emit_launch_started (launch_context, info, from_task); + /* The Activate methods return a process identifier (PID), so we should consider * those methods as potentially blocking */ if (items == NULL) hr = IApplicationActivationManager_ActivateApplication (paam, app_canonical_name, NULL, AO_NONE, - &pid); + &process_id); else if (for_files) hr = IApplicationActivationManager_ActivateForFile (paam, app_canonical_name, items, shverb->verb_name, - &pid); + &process_id); else hr = IApplicationActivationManager_ActivateForProtocol (paam, app_canonical_name, items, - &pid); + &process_id); if (FAILED (hr)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "The app %s failed to launch: 0x%lx", g_win32_appinfo_application_get_some_name (info->app), hr); + + emit_launch_failed (launch_context, info, from_task); + goto cleanup; } + else if (launch_context) + { + DWORD access_rights = 0; + HANDLE process_handle = NULL; + + /* Unfortunately, there's a race condition here. + * ApplicationActivationManager methods return a process ID, but it + * keeps no open HANDLE to the spawned process internally (tested + * on Windows 10 21H2). So we cannot guarantee that by the time + * OpenProcess is called, process ID still referes to the spawned + * process. Anyway hitting such case is extremely unlikely. + * + * https://docs.microsoft.com/en-us/answers/questions/942879/ + * iapplicationactivationmanager-race-condition.html + * + * Maybe we could make use of the WinRT APIs to activate UWP apps, + * instead? */ + + /* As documented on MSDN, the handle returned by CreateProcess has + * PROCESS_ALL_ACCESS rights. First try passing PROCESS_ALL_ACCESS + * to have the same access rights as the non-UWP code-path; should + * that fail with ERROR_ACCESS_DENIED error code, retry using safe + * access rights */ + access_rights = PROCESS_ALL_ACCESS; + + process_handle = OpenProcess (access_rights, FALSE, process_id); + + if (!process_handle && GetLastError () == ERROR_ACCESS_DENIED) + { + DWORD access_rights = PROCESS_QUERY_LIMITED_INFORMATION | + SYNCHRONIZE; + + process_handle = OpenProcess (access_rights, FALSE, process_id); + } + + if (!process_handle) + { + g_warning ("OpenProcess failed with error code %" G_GUINT32_FORMAT, + (guint32) GetLastError ()); + } + + /* Emit the launched signal regardless if we have the process + * HANDLE or NULL */ + emit_launched (launch_context, info, (GPid*) &process_handle, from_task); + + g_spawn_close_pid ((GPid) process_handle); + } result = TRUE; @@ -4785,6 +5017,7 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, const gchar *command; gchar *apppath; GWin32AppInfoShellVerb *shverb; + GPid pid = NULL; g_return_val_if_fail (info != NULL, FALSE); g_return_val_if_fail (info->app != NULL, FALSE); @@ -4819,6 +5052,8 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, for_files, items, shverb, + launch_context, + from_task, error); if (launch_context) @@ -4875,8 +5110,6 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, do { - GPid pid; - if (!expand_application_parameters (info, command, &objs, @@ -4885,6 +5118,8 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, error)) goto out; + emit_launch_started (launch_context, info, from_task); + if (!g_spawn_async (NULL, argv, envp, @@ -4894,28 +5129,16 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, NULL, &pid, error)) - goto out; - - if (launch_context != NULL) { - GVariantBuilder builder; - GVariant *platform_data; + emit_launch_failed (launch_context, info, from_task); - g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); - /* pid handles are never bigger than 2^24 as per - * https://docs.microsoft.com/en-us/windows/win32/sysinfo/kernel-objects, - * so truncating to `int32` is valid. - * The gsize cast is to silence a compiler warning - * about conversion from pointer to integer of - * different size. */ - g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 ((gsize) pid)); - - platform_data = g_variant_ref_sink (g_variant_builder_end (&builder)); - g_signal_emit_by_name (launch_context, "launched", info, platform_data); - g_variant_unref (platform_data); + goto out; } + else if (launch_context) + emit_launched (launch_context, info, &pid, from_task); g_spawn_close_pid (pid); + pid = NULL; g_strfreev (argv); argv = NULL; } @@ -4923,7 +5146,8 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, completed = TRUE; - out: +out: + g_spawn_close_pid (pid); g_strfreev (argv); g_strfreev (envp); From 42a76e1631c85784824c793038b907ed7f4e1bf1 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Fri, 12 Aug 2022 19:08:38 +0200 Subject: [PATCH 5/8] GWin32AppInfo: Check for task cancellation We cannot cancel a spawn operation, but sometimes we have to spawn the target application mutiple times (e.g. in case the target app only supports one URI in its command-line, but we were given multiple URI's), in that case continuously check the cancellation status before attempting any spawn operation --- gio/gwin32appinfo.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index 2eeaa6279..722f4660b 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -5110,6 +5110,9 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, do { + if (from_task && g_task_return_error_if_cancelled (from_task)) + goto out; + if (!expand_application_parameters (info, command, &objs, @@ -5403,7 +5406,7 @@ launch_uris_async_thread (GTask *task, succeeded = g_win32_app_info_launch_uris_impl (appinfo, data->uris, data->context, task, &local_error); if (succeeded) g_task_return_boolean (task, TRUE); - else + else if (!g_task_had_error (task)) g_task_return_error (task, g_steal_pointer (&local_error)); } From 361b4a8fc9a498fc430aeb086074c397152be3de Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Thu, 21 Jul 2022 16:36:43 +0200 Subject: [PATCH 6/8] GAppInfo: Clarify that GAppInfo launch methods can spawn multiple instances ...of the application if many URI's are provided. This is important to note because the GAppLaunchContext signals may be emitted multiple times during a single launch operation. --- gio/gappinfo.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/gio/gappinfo.c b/gio/gappinfo.c index 17f453ada..d1a1a1d65 100644 --- a/gio/gappinfo.c +++ b/gio/gappinfo.c @@ -651,7 +651,9 @@ g_app_info_supports_files (GAppInfo *appinfo) * Launches the application. This passes the @uris to the launched application * as arguments, using the optional @context to get information * about the details of the launcher (like what screen it is on). - * On error, @error will be set accordingly. + * On error, @error will be set accordingly. If the application only supports + * one URI per invocation as part of their command-line, multiple instances + * of the application will be spawned. * * To launch the application without arguments pass a %NULL @uris list. * @@ -1379,6 +1381,10 @@ g_app_launch_context_class_init (GAppLaunchContextClass *klass) * fails. The startup notification id is provided, so that the launcher * can cancel the startup notification. * + * Because a launch operation may involve spawning multiple instances of the + * target application, you should expect this signal to be emitted multiple + * times, one for each spawned instance. + * * Since: 2.36 */ signals[LAUNCH_FAILED] = g_signal_new (I_("launch-failed"), @@ -1409,6 +1415,10 @@ g_app_launch_context_class_init (GAppLaunchContextClass *klass) * It is guaranteed that this signal is followed by either a #GAppLaunchContext::launched or * #GAppLaunchContext::launch-failed signal. * + * Because a launch operation may involve spawning multiple instances of the + * target application, you should expect this signal to be emitted multiple + * times, one for each spawned instance. + * * Since: 2.72 */ signals[LAUNCH_STARTED] = g_signal_new (I_("launch-started"), @@ -1430,7 +1440,13 @@ g_app_launch_context_class_init (GAppLaunchContextClass *klass) * @platform_data: additional platform-specific data for this launch * * The #GAppLaunchContext::launched signal is emitted when a #GAppInfo is successfully - * launched. The @platform_data is an GVariant dictionary mapping + * launched. + * + * Because a launch operation may involve spawning multiple instances of the + * target application, you should expect this signal to be emitted multiple + * times, one time for each spawned instance. + * + * The @platform_data is an GVariant dictionary mapping * strings to variants (ie `a{sv}`), which contains additional, * platform-specific data about this launch. On UNIX, at least the * `pid` and `startup-notification-id` keys will be present. From d12cf95836716367f47a8ee936468ddcf18923b7 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Thu, 21 Jul 2022 16:39:27 +0200 Subject: [PATCH 7/8] GDesktopAppInfo: Tidy up code a bit * Remove an unneeded field from LaunchUrisData and add annotations * Rename local GError* variables to local_error * Use g_set_object * Fix indentation --- gio/gdesktopappinfo.c | 87 +++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index c46a8f09b..85b424dda 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -3261,9 +3261,8 @@ g_desktop_app_info_launch_uris (GAppInfo *appinfo, typedef struct { - GAppInfo *appinfo; - GList *uris; - GAppLaunchContext *context; + GList *uris; /* (element-type utf8) (owned) (nullable) */ + GAppLaunchContext *context; /* (owned) (nullable) */ } LaunchUrisData; static void @@ -3280,13 +3279,13 @@ launch_uris_with_dbus_cb (GObject *object, gpointer user_data) { GTask *task = G_TASK (user_data); - GError *error = NULL; + GError *local_error = NULL; - g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); - if (error != NULL) + g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &local_error); + if (local_error != NULL) { - g_dbus_error_strip_remote_error (error); - g_task_return_error (task, g_steal_pointer (&error)); + g_dbus_error_strip_remote_error (local_error); + g_task_return_error (task, g_steal_pointer (&local_error)); } else g_task_return_boolean (task, TRUE); @@ -3316,7 +3315,7 @@ 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; - GError *error = NULL; + GError *local_error = NULL; session_bus = g_bus_get_finish (result, NULL); @@ -3342,10 +3341,10 @@ launch_uris_bus_get_cb (GObject *object, data->uris, data->context, _SPAWN_FLAGS_DEFAULT, NULL, NULL, NULL, NULL, -1, -1, -1, - &error); - if (error != NULL) + &local_error); + if (local_error != NULL) { - g_task_return_error (task, g_steal_pointer (&error)); + g_task_return_error (task, g_steal_pointer (&local_error)); g_object_unref (task); } else if (session_bus) @@ -3379,7 +3378,7 @@ g_desktop_app_info_launch_uris_async (GAppInfo *appinfo, data = g_new0 (LaunchUrisData, 1); data->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL); - data->context = (context != NULL) ? g_object_ref (context) : NULL; + g_set_object (&data->context, context); g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_uris_data_free); g_bus_get (G_BUS_TYPE_SESSION, cancellable, launch_uris_bus_get_cb, task); @@ -3923,40 +3922,40 @@ static void run_update_command (char *command, char *subdir) { - char *argv[3] = { - NULL, - NULL, - NULL, - }; - GPid pid = 0; - GError *error = NULL; + char *argv[3] = { + NULL, + NULL, + NULL, + }; + GPid pid = 0; + GError *local_error = NULL; - argv[0] = command; - argv[1] = g_build_filename (g_get_user_data_dir (), subdir, NULL); + argv[0] = command; + argv[1] = g_build_filename (g_get_user_data_dir (), subdir, NULL); - if (g_spawn_async ("/", argv, - NULL, /* envp */ - G_SPAWN_SEARCH_PATH | - G_SPAWN_STDOUT_TO_DEV_NULL | - G_SPAWN_STDERR_TO_DEV_NULL | - G_SPAWN_DO_NOT_REAP_CHILD, - NULL, NULL, /* No setup function */ - &pid, - &error)) - g_child_watch_add (pid, update_program_done, NULL); - else - { - /* If we get an error at this point, it's quite likely the user doesn't - * have an installed copy of either 'update-mime-database' or - * 'update-desktop-database'. I don't think we want to popup an error - * dialog at this point, so we just do a g_warning to give the user a - * chance of debugging it. - */ - g_warning ("%s", error->message); - g_error_free (error); - } + if (g_spawn_async ("/", argv, + NULL, /* envp */ + G_SPAWN_SEARCH_PATH | + G_SPAWN_STDOUT_TO_DEV_NULL | + G_SPAWN_STDERR_TO_DEV_NULL | + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, /* No setup function */ + &pid, + &local_error)) + g_child_watch_add (pid, update_program_done, NULL); + else + { + /* If we get an error at this point, it's quite likely the user doesn't + * have an installed copy of either 'update-mime-database' or + * 'update-desktop-database'. I don't think we want to popup an error + * dialog at this point, so we just do a g_warning to give the user a + * chance of debugging it. + */ + g_warning ("%s", local_error->message); + g_error_free (local_error); + } - g_free (argv[1]); + g_free (argv[1]); } static gboolean From 53515197df535b46e501129a647a81b8b4f9a9b2 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Wed, 27 Jul 2022 15:15:35 +0200 Subject: [PATCH 8/8] GWin32AppInfo: Instantiate ApplicationActivationManager out-of-process This is recommended on MSDN --- gio/gwin32appinfo.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index 722f4660b..89bd879c0 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -4893,8 +4893,25 @@ g_win32_app_info_launch_uwp_internal (GWin32AppInfo *info, goto cleanup; } + /* It's best to instantiate ApplicationActivationManager out-of-proc, + * as documented on MSDN: + * + * An IApplicationActivationManager object creates a thread in its + * host process to serve any activated event arguments objects + * (LaunchActivatedEventArgs, FileActivatedEventArgs, and Protocol- + * ActivatedEventArgs) that are passed to the app. If the calling + * process is long-lived, you can create this object in-proc, + * based on the assumption that the event arguments will exist long + * enough for the target app to use them. + * However, if the calling process is spawned only to launch the + * target app, it should create the IApplicationActivationManager + * object out-of-process, by using CLSCTX_LOCAL_SERVER. This causes + * the object to be created in a Dllhost instance that automatically + * manages the object's lifetime based on outstanding references to + * the activated event argument objects. + */ hr = CoCreateInstance (&CLSID_ApplicationActivationManager, NULL, - CLSCTX_INPROC_SERVER, + CLSCTX_LOCAL_SERVER, &IID_IApplicationActivationManager, (void **) &paam); if (FAILED (hr)) {