summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Berg <bberg@redhat.com>2020-07-27 15:44:08 +0200
committerBenjamin Berg <bberg@redhat.com>2020-07-27 16:54:18 +0200
commite2c54e2acb7b328a563c6248707f10c2e8651b6d (patch)
tree398929edc944fe4803644d6292a7221c2d1d31f5
parent5ba5f5b686749197889f4d007494d2432143bbed (diff)
downloadglib-benzea/systemd-launch-transient-unit.tar.gz
gdesktopappinfo: Use systemd to launch applications when possiblebenzea/systemd-launch-transient-unit
This commits adds support to launch applications using systemd. This will only be done if systemd is running (XDG_RUNTIME_DIR/systemd exists) and if the session bus we are connected to belongs to this user session.
-rw-r--r--gio/gdesktopappinfo.c384
1 files changed, 366 insertions, 18 deletions
diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c
index ed98570fe..a1eb649b0 100644
--- a/gio/gdesktopappinfo.c
+++ b/gio/gdesktopappinfo.c
@@ -41,6 +41,11 @@
#ifdef G_OS_UNIX
#include "glib-unix.h"
#endif
+/* Needed for systemd detection. */
+#if defined(__linux__) && !defined(__BIONIC__)
+#include "gunixconnection.h"
+#include "gunixsocketaddress.h"
+#endif
#include "gfile.h"
#include "gioerror.h"
#include "gthemedicon.h"
@@ -2985,6 +2990,342 @@ g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo *info,
return TRUE;
}
+typedef struct
+{
+ GAppInfo *appinfo;
+ GList *uris;
+ GAppLaunchContext *context;
+ gint systemd_pending;
+} 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);
+}
+
+#if defined(__linux__) && !defined(__BIONIC__)
+static gchar *
+systemd_unit_name_escape (const gchar *in)
+{
+ /* Adapted from systemd source */
+ GString *const str = g_string_sized_new (strlen (in));
+
+ for (; *in; in++)
+ {
+ if (g_ascii_isalnum (*in) || *in == ':' || *in == '_' || *in == '.')
+ g_string_append_c (str, *in);
+ else
+ g_string_append_printf (str, "\\x%02x", *in);
+ }
+ return g_string_free (str, FALSE);
+}
+
+static void
+launch_command_with_systemd (GDesktopAppInfo *info,
+ GDBusConnection *session_bus,
+ const char *executable,
+ const char *const *argv,
+ int argc,
+ const char *sn_id,
+ GAppLaunchContext *launch_context,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GVariantBuilder builder;
+ const char *app_name = g_get_application_name ();
+ g_autofree char *appid_escaped = NULL;
+ g_autofree char *appid = NULL;
+ g_autofree char *unit_name = NULL;
+
+ /* In this order:
+ * 1. Actual application ID from file
+ * 2. Stripping the .desktop from the desktop ID
+ * 3. Fall back to using the binary name
+ */
+ if (info->app_id)
+ appid = g_strdup (info->app_id);
+ else if (info->desktop_id && g_str_has_suffix (info->desktop_id, ".desktop"))
+ appid = g_strndup (info->desktop_id, strlen (info->desktop_id) - 8);
+ else
+ appid = g_path_get_basename (executable);
+
+ appid_escaped = systemd_unit_name_escape (appid);
+
+ /* Generate a name conforming to
+ * https://systemd.io/DESKTOP_ENVIRONMENTS/
+ */
+ if (sn_id)
+ unit_name = g_strdup_printf ("app-glib-%s@%s.service", appid_escaped, sn_id);
+ else
+ unit_name = g_strdup_printf ("app-glib-%s@%d.service", appid_escaped, g_random_int ());
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("(ssa(sv)a(sa(sv)))"));
+ g_variant_builder_add (&builder, "s", unit_name);
+ g_variant_builder_add (&builder, "s", "fail");
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(sv)"));
+
+ /* Add a generic human readable description, can be changed at will. */
+ if (app_name)
+ g_variant_builder_add (&builder,
+ "(sv)",
+ "Description",
+ g_variant_new_take_string (g_strdup_printf ("Application launched by %s",
+ app_name)));
+
+ {
+ GVariantBuilder exec_start_builder;
+
+ g_variant_builder_init (&exec_start_builder, G_VARIANT_TYPE ("a(sasb)"));
+ g_variant_builder_add (&exec_start_builder,
+ "(s@asb)",
+ executable,
+ g_variant_new_strv (argv, argc),
+ TRUE);
+
+ g_variant_builder_add (&builder,
+ "(sv)",
+ "ExecStart",
+ g_variant_builder_end (&exec_start_builder));
+ }
+
+ {
+ g_autoptr (GPtrArray) env = NULL;
+
+ env = g_ptr_array_new_full (2, g_free);
+
+ if (info->filename)
+ g_ptr_array_add (env,
+ g_strdup_printf ("GIO_LAUNCHED_DESKTOP_FILE=%s",
+ info->filename));
+
+ if (sn_id)
+ g_ptr_array_add (env,
+ g_strdup_printf ("DESKTOP_STARTUP_ID=%s",
+ sn_id));
+
+ g_variant_builder_add (&builder,
+ "(sv)",
+ "Environment",
+ g_variant_new_strv ((const char *const *) env->pdata, env->len));
+ }
+ g_variant_builder_close (&builder);
+
+ /* NOTE:
+ * We do not currently set GIO_LAUNCHED_DESKTOP_FILE_PID in this codepath.
+ * We could do so though if we use a wrapper similar/identical to the
+ * normal spawn path.
+ */
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(sa(sv))"));
+ g_variant_builder_close (&builder);
+
+ g_dbus_connection_call (session_bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "StartTransientUnit",
+ g_variant_builder_end (&builder),
+ G_VARIANT_TYPE ("(o)"),
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ 1000,
+ cancellable,
+ callback,
+ user_data);
+}
+
+static void
+task_return_first_error (GTask *task,
+ GError *error)
+{
+ if (g_task_get_completed (task))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ g_task_return_error (task, error);
+}
+
+static void
+launch_command_with_systemd_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GTask *task = G_TASK (user_data);
+ GError *error = NULL;
+ LaunchUrisData *data = g_task_get_task_data (task);
+
+ data->systemd_pending -= 1;
+
+ g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
+ if (error != NULL)
+ {
+ g_dbus_error_strip_remote_error (error);
+ task_return_first_error (task, g_steal_pointer (&error));
+ }
+ else
+ {
+ if (data->systemd_pending == 0 && !g_task_get_completed (task))
+ g_task_return_boolean (task, TRUE);
+ }
+
+ g_object_unref (task);
+}
+
+static void
+g_desktop_app_info_launch_uris_with_systemd (GDesktopAppInfo *info,
+ GDBusConnection *session_bus,
+ const char *exec_line,
+ GList *uris,
+ GAppLaunchContext *launch_context,
+ GCancellable *cancellable,
+ GTask *task_in)
+{
+ g_autoptr (GTask) task = task_in;
+ LaunchUrisData *data = NULL;
+ GError *error = NULL;
+ GList *dup_uris;
+ g_auto (GStrv) argv = NULL;
+ int argc;
+
+ g_assert (info != NULL);
+
+ if (task)
+ data = g_task_get_task_data (task);
+
+ /* The GList* passed to expand_application_parameters() will be modified
+ * internally by expand_macro(), so we need to pass a copy of it instead,
+ * and also use that copy to control the exit condition of the loop below.
+ */
+ dup_uris = uris;
+ do
+ {
+ g_autoptr (GList) launched_uris = NULL;
+ GList *old_uris;
+ GList *iter;
+ g_autofree char *executable = NULL;
+ g_autofree char *sn_id = NULL;
+
+ old_uris = dup_uris;
+ if (!expand_application_parameters (info, exec_line, &dup_uris, &argc, &argv, &error))
+ {
+ task_return_first_error (task, error);
+ return;
+ }
+
+ /* Get the subset of URIs we're launching with this process */
+ for (iter = old_uris; iter != NULL && iter != dup_uris; iter = iter->next)
+ launched_uris = g_list_prepend (launched_uris, iter->data);
+ launched_uris = g_list_reverse (launched_uris);
+
+ if (info->terminal && !prepend_terminal_to_vector (&argc, &argv))
+ {
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Unable to find terminal required for application"));
+ task_return_first_error (task, error);
+ return;
+ }
+
+ sn_id = NULL;
+ if (launch_context)
+ {
+ GList *launched_files = create_files_for_uris (launched_uris);
+
+ if (info->startup_notify)
+ sn_id = g_app_launch_context_get_startup_notify_id (launch_context,
+ G_APP_INFO (info),
+ launched_files);
+
+ g_list_free_full (launched_files, g_object_unref);
+ }
+
+ if (data)
+ data->systemd_pending += 1;
+
+ executable = g_find_program_in_path (argv[0]);
+ if (!executable)
+ {
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Unable to find requrested executable"));
+ task_return_first_error (task, error);
+ return;
+ }
+
+ if (task)
+ launch_command_with_systemd (info,
+ session_bus,
+ executable,
+ (const char *const *) argv, argc,
+ sn_id,
+ launch_context,
+ cancellable,
+ launch_command_with_systemd_cb,
+ g_object_ref (task));
+ else
+ launch_command_with_systemd (info,
+ session_bus,
+ executable,
+ (const char *const *) argv, argc,
+ sn_id,
+ launch_context,
+ cancellable,
+ NULL,
+ NULL);
+ }
+ while (dup_uris != NULL);
+}
+
+static gboolean
+systemd_user_instance_usable (GDBusConnection *session_bus)
+{
+ g_autofree char *systemd = NULL;
+ g_autofree char *bus = NULL;
+ GIOStream *stream;
+ g_autoptr (GSocketAddress) socket_addr = NULL;
+ const char *socket_path;
+
+ g_return_val_if_fail (session_bus, FALSE);
+
+ /* Note: A simpler test we could do is to inspect the cgroup name and figure
+ * out whether we are running in a unit started by the systemd user instance
+ * (basically what sd_pid_get_user_unit () does).
+ * However, that does not tell us whether we are actually able to talk to
+ * the systemd instance, as it could e.g. return an enclosing terminal unit
+ * in which dbus-run-session was invoked. */
+
+ /* First of all check that XDG_RUNTIME_DIR/systemd exists, if it does not
+ * exist, then we don't have a user systemd instance running.
+ */
+ systemd = g_build_filename (g_get_user_runtime_dir (), "systemd", NULL);
+ if (!g_file_test (systemd, G_FILE_TEST_IS_DIR))
+ return FALSE;
+
+ /* Let's be extra safe here, and check that we are actually talking to the
+ * correct session bus (i.e. on XDG_RUNTIME_DIR/bus).
+ *
+ * An example where this is not the case is when dbus-run-session is used.
+ */
+ bus = g_build_filename (g_get_user_runtime_dir (), "bus", NULL);
+
+ stream = g_dbus_connection_get_stream (session_bus);
+ if (!G_IS_UNIX_CONNECTION (stream))
+ return FALSE;
+
+ socket_addr = g_socket_connection_get_remote_address ((GSocketConnection *) stream, NULL);
+ if (!socket_addr)
+ return FALSE;
+
+ socket_path = g_unix_socket_address_get_path ((GUnixSocketAddress *) socket_addr);
+
+ return g_strcmp0 (socket_path, bus) == 0;
+}
+#endif
+
static gboolean
g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo,
GList *uris,
@@ -3012,6 +3353,11 @@ g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo,
*/
g_desktop_app_info_launch_uris_with_dbus (info, session_bus, uris, launch_context,
NULL, NULL, NULL);
+#if defined(__linux__) && !defined(__BIONIC__)
+ else if (session_bus && systemd_user_instance_usable (session_bus))
+ g_desktop_app_info_launch_uris_with_systemd (info, session_bus, info->exec, uris, launch_context,
+ NULL, NULL);
+#endif
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,
@@ -3045,21 +3391,6 @@ g_desktop_app_info_launch_uris (GAppInfo *appinfo,
error);
}
-typedef struct
-{
- GAppInfo *appinfo;
- GList *uris;
- GAppLaunchContext *context;
-} 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_with_dbus_cb (GObject *object,
GAsyncResult *result,
@@ -3118,6 +3449,15 @@ launch_uris_bus_get_cb (GObject *object,
launch_uris_with_dbus_cb,
g_steal_pointer (&task));
}
+#if defined(__linux__) && !defined(__BIONIC__)
+ else if (session_bus && systemd_user_instance_usable (session_bus))
+ {
+ g_desktop_app_info_launch_uris_with_systemd (info, session_bus,
+ info->exec, data->uris, data->context,
+ cancellable,
+ g_steal_pointer (&task));
+ }
+#endif
else
{
/* FIXME: The D-Bus message from the notify_desktop_launch() function
@@ -4953,9 +5293,17 @@ g_desktop_app_info_launch_action (GDesktopAppInfo *info,
g_free (group_name);
if (exec_line)
- g_desktop_app_info_launch_uris_with_spawn (info, session_bus, exec_line, NULL, launch_context,
- _SPAWN_FLAGS_DEFAULT, NULL, NULL, NULL, NULL,
- -1, -1, -1, NULL);
+ {
+#if defined(__linux__) && !defined(__BIONIC__)
+ if (session_bus && systemd_user_instance_usable (session_bus))
+ g_desktop_app_info_launch_uris_with_systemd (info, session_bus, exec_line, NULL, launch_context,
+ NULL, NULL);
+ else
+#endif
+ g_desktop_app_info_launch_uris_with_spawn (info, session_bus, exec_line, NULL, launch_context,
+ _SPAWN_FLAGS_DEFAULT, NULL, NULL, NULL, NULL,
+ -1, -1, -1, NULL);
+ }
g_free (exec_line);
}