From 54bc3aa4f54cb5452c29f81fada808224a18afa1 Mon Sep 17 00:00:00 2001 From: Julian Sparber Date: Mon, 19 Feb 2024 09:30:25 +0100 Subject: [PATCH] shell/app: Add method to activate an action via FDO DBus activation There are multiple different places where we activate an app action, but none set the correct activation token. Therefore this adds an async method to to call `ActivateAction` on `org.freedesktop.Appliaction` with the correct activation token/startup id as platform data. Part-of: --- js/ui/environment.js | 1 + src/shell-app.c | 190 +++++++++++++++++++++++++++++++++++++++++++ src/shell-app.h | 13 +++ 3 files changed, 204 insertions(+) diff --git a/js/ui/environment.js b/js/ui/environment.js index 6a2b60864..4150542db 100644 --- a/js/ui/environment.js +++ b/js/ui/environment.js @@ -30,6 +30,7 @@ Gio._promisify(Gio.DBusProxy.prototype, 'init_async'); Gio._promisify(Gio.DBusProxy.prototype, 'call_with_unix_fd_list'); Gio._promisify(Gio.File.prototype, 'query_info_async'); Gio._promisify(Polkit.Permission, 'new'); +Gio._promisify(Shell.App.prototype, 'activate_action'); // We can't import shell JS modules yet, because they may have // variable initializations, etc, that depend on this file's diff --git a/src/shell-app.c b/src/shell-app.c index 371bc6cf7..3b5df0aab 100644 --- a/src/shell-app.c +++ b/src/shell-app.c @@ -1447,6 +1447,196 @@ shell_app_launch_action (ShellApp *app, g_object_unref (context); } +static gchar * +object_path_from_app_id (const gchar *app_id) +{ + gchar *app_id_path, *iter; + + app_id_path = g_strconcat ("/", app_id, NULL); + for (iter = app_id_path; *iter; iter++) + { + if (*iter == '.') + *iter = '/'; + + if (*iter == '-') + *iter = '_'; + } + + return app_id_path; +} + +static GVariant * +get_platform_data (ShellApp *app, + guint timestamp, + int workspace) +{ + GVariantBuilder builder; + ShellGlobal *global; + g_autoptr (GAppLaunchContext) context = NULL; + gchar *startup_id; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + if (!app->info) + return g_variant_builder_end (&builder); + + global = shell_global_get (); + context = shell_global_create_app_launch_context (global, timestamp, workspace); + + if (!context) + return g_variant_builder_end (&builder); + + startup_id = g_app_launch_context_get_startup_notify_id (context, G_APP_INFO (app->info), NULL); + + if (!startup_id) + return g_variant_builder_end (&builder); + + + g_variant_builder_add (&builder, "{sv}", + "desktop-startup-id", g_variant_new_string (startup_id)); + g_variant_builder_add (&builder, "{sv}", + "activation-token", g_variant_new_take_string (g_steal_pointer (&startup_id))); + + return g_variant_builder_end (&builder); +} + +static void +on_activate_action_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + g_autoptr (GVariant) value = NULL; + g_autoptr (GError) error = NULL; + + value = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), + res, &error); + + if (error) + g_task_return_error (task, g_steal_pointer (&error)); + else + g_task_return_boolean (task, TRUE); +} + +static void +activate_action_get_bus_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + ShellApp *app = NULL; + g_autoptr (GDBusConnection) session_bus = NULL; + g_autoptr (GError) error = NULL; + g_autofree gchar *object_path = NULL; + g_autofree gchar *app_id = NULL; + gchar *last_dot; + + session_bus = g_bus_get_finish (result, &error); + + if (error) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + app = SHELL_APP (g_task_get_source_object (task)); + + app_id = g_strdup (g_app_info_get_id (G_APP_INFO (app->info))); + last_dot = strrchr (app_id, '.'); + if (last_dot && g_str_equal (last_dot, ".desktop")) + *last_dot = '\0'; + + object_path = object_path_from_app_id (app_id); + + g_dbus_connection_call (session_bus, + app_id, object_path, + "org.freedesktop.Application", "ActivateAction", + g_task_get_task_data (task), + NULL, G_DBUS_CALL_FLAGS_NONE, -1, + g_task_get_cancellable (task), + on_activate_action_cb, task); +} + + +/** + * shell_app_activate_action + * @app: the #ShellApp + * @action_name: the name of an action to activate + * @parameter: (nullable): the parameter to the activation + * @timestamp: Event timestamp, or 0 for current event timestamp + * @workspace: Start on this workspace, or -1 for default + * @cancellable: (nullable): a #GCancellable or %NULL + * @callback: (scope async): A #GAsyncReadyCallback to call when the request is satisfied. + * @user_data: (nullable): User data to pass to @callback + * + * This activates an action using 'org.freedesktop.Application' DBus interface. + * + * This function will fail if this #ShellApp doesn't have a valid #GDesktopAppInfo + * with a valid id. + */ +void +shell_app_activate_action (ShellApp *app, + const char *action_name, + GVariant *parameter, + guint timestamp, + int workspace, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr (GTask) task = NULL; + g_autoptr (GVariant) task_data = NULL; + + g_return_if_fail (SHELL_IS_APP (app)); + g_return_if_fail (G_IS_DESKTOP_APP_INFO (app->info)); + g_return_if_fail (g_application_id_is_valid (g_app_info_get_id (G_APP_INFO (app->info)))); + g_return_if_fail (action_name != NULL && action_name[0] != '\0'); + g_return_if_fail (parameter == NULL || g_variant_is_of_type (parameter, G_VARIANT_TYPE ("av"))); + g_return_if_fail (timestamp >= 0); + g_return_if_fail (workspace >= -1); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (app, cancellable, callback, user_data); + g_task_set_source_tag (task, shell_app_activate_action); + + if (!parameter) + parameter = g_variant_new("av", NULL); + + task_data = g_variant_ref_sink (g_variant_new ("(s@av@a{sv})", action_name, parameter, + get_platform_data (app, timestamp, workspace))); + + g_task_set_task_data (task, g_steal_pointer (&task_data), (GDestroyNotify) g_variant_unref); + + g_bus_get (G_BUS_TYPE_SESSION, cancellable, activate_action_get_bus_cb, g_steal_pointer (&task)); +} + +/** + * shell_app_activate_action_finish: + * @app: the #ShellApp + * @error: #GError for error reporting + * + * Finish the asynchronous operation started by shell_app_activate_action() + * and obtain its result. + * + * Returns: whether the operation was successful + * + */ +gboolean +shell_app_activate_action_finish (ShellApp *app, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (SHELL_IS_APP (app), FALSE); + g_return_val_if_fail (G_IS_TASK (result), FALSE); + g_return_val_if_fail (g_async_result_is_tagged (result, + shell_app_activate_action), + FALSE); + + if (!g_task_propagate_boolean (G_TASK (result), error)) + return FALSE; + + return TRUE; +} + /** * shell_app_get_app_info: * @app: a #ShellApp diff --git a/src/shell-app.h b/src/shell-app.h index bf6f45ecb..a3e087eae 100644 --- a/src/shell-app.h +++ b/src/shell-app.h @@ -69,6 +69,19 @@ void shell_app_launch_action (ShellApp *app, guint timestamp, int workspace); +void shell_app_activate_action (ShellApp *app, + const char *action_name, + GVariant *parameter, + guint timestamp, + int workspace, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean +shell_app_activate_action_finish (ShellApp *app, + GAsyncResult *result, + GError **error); + int shell_app_compare_by_name (ShellApp *app, ShellApp *other); int shell_app_compare (ShellApp *app, ShellApp *other);