diff options
author | Denys Dmytriyenko <denys@konsulko.com> | 2022-06-28 20:48:17 +0000 |
---|---|---|
committer | Denys Dmytriyenko <denys@konsulko.com> | 2022-07-11 20:02:21 +0000 |
commit | efbd734aca8b813710d7564d79696b1cf150a88c (patch) | |
tree | d68747e79b2b6723dc238106e23f6dbf3a14f102 | |
parent | c84836ec5ddaf2d0e91c46713475c35652bb540f (diff) |
Add systemd_manager support.
This replaces dbus_activation_manager and retains basic process_manager for now.
The first version of systemd_manager supports starting apps as services, handles
aynchronous events to emit corresponding STARTED and TERMINATED signals. But it
still relies on .desktop files for application enumeration and icon setting,
plus uses DBusActivatable=true to prefer systemd_manager over process_manager.
These shortcomings will be addressed in future revisions.
And systemd_manager supports sandboxing and templating with overrides:
* There's a generic systemd service template called systemd/system/agl-app@.service,
that becomes agl-app@<app>.service for a given <app>
* Overrides for an <app> are symlinks in systemd/system/agl-app@<app>.service.d/
pointing to corresponding generic configs in systemd/sandboxing/*.conf
Bug-AGL: SPEC-4466
Signed-off-by: Denys Dmytriyenko <denys@konsulko.com>
Change-Id: Id62cbf848f09250f1a8989a79cc61292a3ce054a
-rw-r--r-- | src/app_info.c | 22 | ||||
-rw-r--r-- | src/app_info.h | 4 | ||||
-rw-r--r-- | src/app_launcher.c | 58 | ||||
-rw-r--r-- | src/app_launcher.h | 4 | ||||
-rw-r--r-- | src/dbus_activation_manager.c | 257 | ||||
-rw-r--r-- | src/dbus_activation_manager.h | 42 | ||||
-rw-r--r-- | src/main.c | 53 | ||||
-rw-r--r-- | src/meson.build | 3 | ||||
-rw-r--r-- | src/systemd_manager.c | 247 | ||||
-rw-r--r-- | src/systemd_manager.h | 38 |
10 files changed, 393 insertions, 335 deletions
diff --git a/src/app_info.c b/src/app_info.c index 03e1f02..0d25d37 100644 --- a/src/app_info.c +++ b/src/app_info.c @@ -17,7 +17,6 @@ #include <gio/gio.h> #include "app_info.h" -#include "dbus_activation_manager.h" struct _AppInfo { GObject parent_instance; @@ -26,14 +25,14 @@ struct _AppInfo { gchar *name; gchar *icon_path; gchar *command; - gboolean dbus_activated; + gboolean systemd_activated; gboolean graphical; AppStatus status; /* * `runtime_data` is an opaque pointer depending on the app startup method. - * It is set in by ProcessManager or DBusActivationManager. + * It is set in by ProcessManager or SystemdManager. */ gpointer runtime_data; }; @@ -52,13 +51,7 @@ static void app_info_dispose(GObject *object) g_clear_pointer(&self->name, g_free); g_clear_pointer(&self->icon_path, g_free); g_clear_pointer(&self->app_id, g_free); - - if (self->dbus_activated) { - g_clear_pointer(&self->runtime_data, - dbus_activation_manager_free_runtime_data); - } else { - g_clear_pointer(&self->runtime_data, g_free); - } + g_clear_pointer(&self->runtime_data, g_free); G_OBJECT_CLASS(app_info_parent_class)->dispose(object); } @@ -86,7 +79,8 @@ static void app_info_init(AppInfo *self) AppInfo *app_info_new(const gchar *app_id, const gchar *name, const gchar *icon_path, const gchar *command, - gboolean dbus_activated, gboolean graphical) + gboolean systemd_activated, + gboolean graphical) { AppInfo *self = g_object_new(APPLAUNCHD_TYPE_APP_INFO, NULL); @@ -94,7 +88,7 @@ AppInfo *app_info_new(const gchar *app_id, const gchar *name, self->name = g_strdup(name); self->icon_path = g_strdup(icon_path); self->command = g_strdup(command); - self->dbus_activated = dbus_activated; + self->systemd_activated = systemd_activated; self->graphical = graphical; return self; @@ -128,11 +122,11 @@ const gchar *app_info_get_command(AppInfo *self) return self->command; } -gboolean app_info_get_dbus_activated(AppInfo *self) +gboolean app_info_get_systemd_activated(AppInfo *self) { g_return_val_if_fail(APPLAUNCHD_IS_APP_INFO(self), FALSE); - return self->dbus_activated; + return self->systemd_activated; } gboolean app_info_get_graphical(AppInfo *self) diff --git a/src/app_info.h b/src/app_info.h index dd312c4..5e69dc3 100644 --- a/src/app_info.h +++ b/src/app_info.h @@ -34,14 +34,14 @@ G_DECLARE_FINAL_TYPE(AppInfo, app_info, APPLAUNCHD, AppInfo *app_info_new(const gchar *app_id, const gchar *name, const gchar *icon_path, const gchar *command, - gboolean dbus_activated, gboolean graphical); + gboolean systemd_activated, gboolean graphical); /* Accessors for read-only members */ const gchar *app_info_get_app_id(AppInfo *self); const gchar *app_info_get_name(AppInfo *self); const gchar *app_info_get_icon_path(AppInfo *self); const gchar *app_info_get_command(AppInfo *self); -gboolean app_info_get_dbus_activated(AppInfo *self); +gboolean app_info_get_systemd_activated(AppInfo *self); gboolean app_info_get_graphical(AppInfo *self); /* Accessors for read-write members */ diff --git a/src/app_launcher.c b/src/app_launcher.c index 5c9bddb..423bf19 100644 --- a/src/app_launcher.c +++ b/src/app_launcher.c @@ -18,19 +18,27 @@ #include "app_info.h" #include "app_launcher.h" -#include "dbus_activation_manager.h" #include "process_manager.h" +#include "systemd_manager.h" #include "utils.h" typedef struct _AppLauncher { applaunchdAppLaunchSkeleton parent; - DBusActivationManager *dbus_manager; +/* TODO: try to move event and bus properties down to systemd_manager */ + sd_event *event; + sd_bus *bus; + ProcessManager *process_manager; + SystemdManager *systemd_manager; GList *apps_list; } AppLauncher; +extern GMainLoop *main_loop; + +extern GSource *g_sd_event_create_source(sd_event *event, sd_bus *bus); + static void app_launcher_iface_init(applaunchdAppLaunchIface *iface); G_DEFINE_TYPE_WITH_CODE(AppLauncher, app_launcher, @@ -70,7 +78,7 @@ static void app_launcher_update_applications_list(AppLauncher *self) g_autofree const gchar *app_id = NULL; g_autofree const gchar *icon_path = NULL; AppInfo *app_info = NULL; - gboolean dbus_activated, graphical; + gboolean systemd_activated, graphical; if (!desktop_info) { g_warning("Unable to find .desktop file for application '%s'", desktop_id); @@ -109,13 +117,14 @@ static void app_launcher_update_applications_list(AppLauncher *self) * An application can be D-Bus activated if one of those conditions are met: * - its .desktop file contains a "DBusActivatable=true" line * - it provides a corresponding D-Bus service file + * HACK: Use "DBusActivatable=true" in .desktop to mark systemd-based service */ /* Default to non-DBus-activatable */ - dbus_activated = FALSE; + systemd_activated = FALSE; if (g_desktop_app_info_get_boolean(desktop_info, G_KEY_FILE_DESKTOP_KEY_DBUS_ACTIVATABLE)) { - dbus_activated = TRUE; + systemd_activated = TRUE; } else if (dirlist) { const gchar *desktop_filename = g_desktop_app_info_get_filename(desktop_info); g_autofree gchar *service_file = g_strconcat(app_id, ".service", NULL); @@ -130,7 +139,7 @@ static void app_launcher_update_applications_list(AppLauncher *self) service_path = g_build_filename(*xdg_data_dir, "dbus-1", "services", service_file, NULL); if (g_file_test(service_path, G_FILE_TEST_EXISTS)) { - dbus_activated = TRUE; + systemd_activated = TRUE; break; } } @@ -149,8 +158,8 @@ static void app_launcher_update_applications_list(AppLauncher *self) app_info = app_info_new(app_id, g_app_info_get_name(appinfo), icon_path ? icon_path : "", - dbus_activated ? "" : g_app_info_get_commandline(appinfo), - dbus_activated, graphical); + g_app_info_get_commandline(appinfo), + systemd_activated, graphical); g_debug("Adding application '%s'", app_id); @@ -217,13 +226,11 @@ static gboolean app_launcher_start_app(AppLauncher *self, AppInfo *app_info) * and notify subscribers it should be activated/brought to the * foreground */ - if (app_info_get_dbus_activated(app_info)) - dbus_activation_manager_activate_app(self->dbus_manager, app_info); app_launcher_started_cb(self, app_id, NULL); return TRUE; case APP_STATUS_INACTIVE: - if (app_info_get_dbus_activated(app_info)) - dbus_activation_manager_start_app(self->dbus_manager, app_info); + if (app_info_get_systemd_activated(app_info)) + systemd_manager_start_app(self->systemd_manager, app_info); else process_manager_start_app(self->process_manager, app_info); return TRUE; @@ -336,8 +343,8 @@ static void app_launcher_dispose(GObject *object) if (self->apps_list) g_list_free_full(g_steal_pointer(&self->apps_list), g_object_unref); - g_clear_object(&self->dbus_manager); g_clear_object(&self->process_manager); + g_clear_object(&self->systemd_manager); G_OBJECT_CLASS(app_launcher_parent_class)->dispose(object); } @@ -362,6 +369,11 @@ static void app_launcher_iface_init(applaunchdAppLaunchIface *iface) static void app_launcher_init (AppLauncher *self) { + sd_bus_open_system(&self->bus); + sd_event_default(&self->event); + sd_bus_attach_event(self->bus, self->event, SD_EVENT_PRIORITY_NORMAL); + g_source_attach(g_sd_event_create_source(self->event, self->bus), g_main_loop_get_context(main_loop)); + /* * Create the process manager and connect to its signals * so we get notified on app startup/termination @@ -374,14 +386,14 @@ static void app_launcher_init (AppLauncher *self) G_CALLBACK(app_launcher_terminated_cb), self); /* - * Create the D-Bus activation manager and connect to its signals + * Create the systemd manager and connect to its signals * so we get notified on app startup/termination */ - self->dbus_manager = g_object_new(APPLAUNCHD_TYPE_DBUS_ACTIVATION_MANAGER, - NULL); - g_signal_connect_swapped(self->dbus_manager, "started", + self->systemd_manager = g_object_new(APPLAUNCHD_TYPE_SYSTEMD_MANAGER, + NULL); + g_signal_connect_swapped(self->systemd_manager, "started", G_CALLBACK(app_launcher_started_cb), self); - g_signal_connect_swapped(self->dbus_manager, "terminated", + g_signal_connect_swapped(self->systemd_manager, "terminated", G_CALLBACK(app_launcher_terminated_cb), self); /* Initialize the applications list */ @@ -430,3 +442,13 @@ AppInfo *app_launcher_get_app_info(AppLauncher *self, const gchar *app_id) return NULL; } + +sd_bus *app_launcher_get_bus(AppLauncher *self) +{ + return self->bus; +} + +sd_event *app_launcher_get_event(AppLauncher *self) +{ + return self->event; +} diff --git a/src/app_launcher.h b/src/app_launcher.h index 789f091..bfbe281 100644 --- a/src/app_launcher.h +++ b/src/app_launcher.h @@ -18,6 +18,8 @@ #define APPLAUNCHER_H #include <glib-object.h> +#include <systemd/sd-bus.h> +#include <systemd/sd-event.h> #include "applaunch-dbus.h" #include "app_info.h" @@ -32,6 +34,8 @@ G_DECLARE_FINAL_TYPE(AppLauncher, app_launcher, APPLAUNCHD, APP_LAUNCHER, AppLauncher *app_launcher_get_default(void); AppInfo *app_launcher_get_app_info(AppLauncher *self, const gchar *app_id); +sd_bus *app_launcher_get_bus(AppLauncher *self); +sd_event *app_launcher_get_event(AppLauncher *self); G_END_DECLS diff --git a/src/dbus_activation_manager.c b/src/dbus_activation_manager.c deleted file mode 100644 index 8c65485..0000000 --- a/src/dbus_activation_manager.c +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2021 Collabora Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <gio/gio.h> - -#include "app_launcher.h" -#include "dbus_activation_manager.h" -#include "fdo-dbus.h" - -struct _DBusActivationManager { - GObject parent_instance; -}; - -G_DEFINE_TYPE(DBusActivationManager, dbus_activation_manager, G_TYPE_OBJECT); - -enum { - STARTED, - TERMINATED, - N_SIGNALS -}; -static guint signals[N_SIGNALS]; - -/* - * Application info structure, used for storing relevant data - * in the `starting_apps` and `running_apps` lists - */ -struct dbus_runtime_data { - guint watcher; - fdoApplication *fdo_proxy; -}; - -/* - * Initialization & cleanup functions - */ - -static void dbus_activation_manager_dispose(GObject *object) -{ - G_OBJECT_CLASS(dbus_activation_manager_parent_class)->dispose(object); -} - -static void dbus_activation_manager_finalize(GObject *object) -{ - G_OBJECT_CLASS(dbus_activation_manager_parent_class)->finalize(object); -} - -static void dbus_activation_manager_class_init(DBusActivationManagerClass *klass) -{ - GObjectClass *object_class = (GObjectClass *)klass; - - object_class->dispose = dbus_activation_manager_dispose; - object_class->finalize = dbus_activation_manager_finalize; - - signals[STARTED] = g_signal_new("started", G_TYPE_FROM_CLASS(klass), - G_SIGNAL_RUN_LAST, 0 , - NULL, NULL, NULL, G_TYPE_NONE, - 1, G_TYPE_STRING); - - signals[TERMINATED] = g_signal_new("terminated", G_TYPE_FROM_CLASS(klass), - G_SIGNAL_RUN_LAST, 0, - NULL, NULL, NULL, G_TYPE_NONE, - 1, G_TYPE_STRING); -} - -static void dbus_activation_manager_init(DBusActivationManager *self) -{ -} - -/* - * Internal callbacks - */ - -/* - * This function is called when a D-Bus name we're watching just vanished - * from the session bus. This indicates the underlying application terminated, - * so we must remove it from the list of running apps and notify listeners - * of the app termination so they can act accordingly. - */ -static void dbus_activation_manager_app_terminated_cb(GDBusConnection *connection, - const gchar *name, - gpointer data) -{ - DBusActivationManager *self = data; - AppLauncher *app_launcher = app_launcher_get_default(); - AppInfo *app_info; - struct dbus_runtime_data *runtime_data; - - g_return_if_fail(APPLAUNCHD_IS_DBUS_ACTIVATION_MANAGER(self)); - g_return_if_fail(APPLAUNCHD_IS_APP_LAUNCHER(app_launcher)); - - app_info = app_launcher_get_app_info(app_launcher, name); - g_return_if_fail(APPLAUNCHD_IS_APP_INFO(app_info)); - - runtime_data = app_info_get_runtime_data(app_info); - g_debug("Application '%s' vanished from D-Bus", name); - - app_info_set_status(app_info, APP_STATUS_INACTIVE); - app_info_set_runtime_data(app_info, NULL); - - dbus_activation_manager_free_runtime_data(runtime_data); - - g_signal_emit(self, signals[TERMINATED], 0, name); -} - -/* - * Function called when the name appeared on D-Bus, meaning the application - * successfully started and registered its D-Bus service. - */ -static void dbus_activation_manager_app_started_cb(GDBusConnection *connection, - const gchar *name, - const gchar *name_owner, - gpointer data) -{ - DBusActivationManager *self = data; - AppLauncher *app_launcher = app_launcher_get_default(); - AppInfo *app_info; - struct dbus_runtime_data *runtime_data; - - g_return_if_fail(APPLAUNCHD_IS_DBUS_ACTIVATION_MANAGER(self)); - g_return_if_fail(APPLAUNCHD_IS_APP_LAUNCHER(app_launcher)); - - app_info = app_launcher_get_app_info(app_launcher, name); - g_return_if_fail(APPLAUNCHD_IS_APP_INFO(app_info)); - - runtime_data = app_info_get_runtime_data(app_info); - - g_debug("Application '%s' appeared on D-Bus", name); - app_info_set_status(app_info, APP_STATUS_RUNNING); - dbus_activation_manager_activate_app(self, app_info); - g_signal_emit(self, signals[STARTED], 0, app_info_get_app_id(app_info)); -} - -/* - * Public functions - */ - -DBusActivationManager *dbus_activation_manager_new(void) -{ - return g_object_new(APPLAUNCHD_TYPE_DBUS_ACTIVATION_MANAGER, NULL); -} - -/* - * Start an application using D-Bus activation. - */ -gboolean dbus_activation_manager_start_app(DBusActivationManager *self, - AppInfo *app_info) -{ - g_return_val_if_fail(APPLAUNCHD_IS_DBUS_ACTIVATION_MANAGER(self), FALSE); - g_return_val_if_fail(APPLAUNCHD_IS_APP_INFO(app_info), FALSE); - - g_autoptr(GError) error = NULL; - const gchar *app_id = app_info_get_app_id(app_info); - struct dbus_runtime_data *runtime_data = g_new0(struct dbus_runtime_data, 1); - - if (!runtime_data) { - g_critical("Unable to allocate runtime data structure for '%s'", - app_id); - return FALSE; - } - - /* - * g_bus_watch_name() requests D-Bus activation (due to - * G_BUS_NAME_WATCHER_FLAGS_AUTO_START) and subscribes to name - * owner changes so we get notified when the requested application - * appears and vanishes from D-Bus. - */ - runtime_data->watcher = g_bus_watch_name(G_BUS_TYPE_SESSION, app_id, - G_BUS_NAME_WATCHER_FLAGS_AUTO_START, - dbus_activation_manager_app_started_cb, - dbus_activation_manager_app_terminated_cb, - self, NULL); - if (runtime_data->watcher == 0) { - g_critical("Unable to request D-Bus activation for '%s'", app_id); - dbus_activation_manager_free_runtime_data(runtime_data); - return FALSE; - } - - /* Update application status */ - app_info_set_status(app_info, APP_STATUS_STARTING); - app_info_set_runtime_data(app_info, runtime_data); - - return TRUE; -} - -/* - * Once an application has been started through D-Bus, we must activate it - * so it shows its main window, if any. - * This function doesn't raise an error as headless applications will likely - * not implement the org.freedesktop.Application interface. - */ -gboolean dbus_activation_manager_activate_app(DBusActivationManager *self, - AppInfo *app) -{ - g_return_val_if_fail(APPLAUNCHD_IS_DBUS_ACTIVATION_MANAGER(self), FALSE); - g_return_val_if_fail(APPLAUNCHD_IS_APP_INFO(app), FALSE); - - g_autoptr(GError) error = NULL; - struct dbus_runtime_data *runtime_data = app_info_get_runtime_data(app); - const gchar *app_id = app_info_get_app_id(app); - - g_return_val_if_fail(runtime_data != NULL, FALSE); - - /* Base object for interface "org.example.Iface" is "/org/example/Iface" */ - g_autofree gchar *path = g_strconcat("/", app_id, NULL); - g_strdelimit(path, ".", '/'); - - if (!runtime_data->fdo_proxy) { - runtime_data->fdo_proxy = - fdo_application_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, - G_DBUS_PROXY_FLAGS_NONE, - app_id, path, NULL, - &error); - } - - if (runtime_data->fdo_proxy) { - GVariantDict dict; - - g_variant_dict_init(&dict, NULL); - - fdo_application_call_activate_sync (runtime_data->fdo_proxy, - g_variant_dict_end(&dict), - NULL, &error); - if (error) { - g_warning("Error activating application %s: %s", app_id, - error->message); - } - } else if (error) { - g_warning("Error creating D-Bus proxy for %s: %s", app_id, - error->message); - } - - return TRUE; -} - -void dbus_activation_manager_free_runtime_data(gpointer data) -{ - struct dbus_runtime_data *runtime_data = data; - - g_return_if_fail(runtime_data != NULL); - - g_clear_object(&runtime_data->fdo_proxy); - if (runtime_data->watcher > 0) - g_bus_unwatch_name(runtime_data->watcher); - g_free(runtime_data); -} diff --git a/src/dbus_activation_manager.h b/src/dbus_activation_manager.h deleted file mode 100644 index e475da4..0000000 --- a/src/dbus_activation_manager.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2021 Collabora Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef DBUSACTIVATIONMANAGER_H -#define DBUSACTIVATIONMANAGER_H - -#include <glib-object.h> - -#include "app_info.h" - -G_BEGIN_DECLS - -#define APPLAUNCHD_TYPE_DBUS_ACTIVATION_MANAGER dbus_activation_manager_get_type() - -G_DECLARE_FINAL_TYPE(DBusActivationManager, dbus_activation_manager, - APPLAUNCHD, DBUS_ACTIVATION_MANAGER, GObject); - -DBusActivationManager *dbus_activation_manager_new(void); - -gboolean dbus_activation_manager_start_app(DBusActivationManager *self, - AppInfo *app_info); -gboolean dbus_activation_manager_activate_app(DBusActivationManager *self, - AppInfo *app_info); - -void dbus_activation_manager_free_runtime_data(gpointer data); - -G_END_DECLS - -#endif @@ -16,6 +16,8 @@ #include <glib.h> #include <glib-unix.h> +#include <systemd/sd-bus.h> +#include <systemd/sd-event.h> #include "app_launcher.h" #include "applaunch-dbus.h" @@ -23,7 +25,16 @@ #define APPLAUNCH_DBUS_NAME "org.automotivelinux.AppLaunch" #define APPLAUNCH_DBUS_PATH "/org/automotivelinux/AppLaunch" -static GMainLoop *main_loop = NULL; +/* TODO: see if it makes sense to move systemd event handling specifics + and interacting with GLib main loop into systemd_manager.c */ +typedef struct SDEventSource { + GSource source; + GPollFD pollfd; + sd_event *event; + sd_bus *bus; +} SDEventSource; + +GMainLoop *main_loop = NULL; static gboolean quit_cb(gpointer user_data) { @@ -60,6 +71,45 @@ static void name_lost_cb(GDBusConnection *connection, const gchar *name, g_main_loop_quit(main_loop); } +static gboolean event_prepare(GSource *source, gint *timeout_) { + return sd_event_prepare(((SDEventSource *)source)->event) > 0; +} + +static gboolean event_check(GSource *source) { + return sd_event_wait(((SDEventSource *)source)->event, 0) > 0; +} + +static gboolean event_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { + return sd_event_dispatch(((SDEventSource *)source)->event) > 0; +} + +static void event_finalize(GSource *source) { + sd_event_unref(((SDEventSource *)source)->event); +} + +static GSourceFuncs event_funcs = { + .prepare = event_prepare, + .check = event_check, + .dispatch = event_dispatch, + .finalize = event_finalize, +}; + +GSource *g_sd_event_create_source(sd_event *event, sd_bus *bus) +{ + SDEventSource *source; + + source = (SDEventSource *)g_source_new(&event_funcs, sizeof(SDEventSource)); + + source->event = sd_event_ref(event); + source->bus = sd_bus_ref(bus); + source->pollfd.fd = sd_bus_get_fd(bus); + source->pollfd.events = sd_bus_get_events(bus); + + g_source_add_poll((GSource *)source, &source->pollfd); + + return (GSource *)source; +} + int main(int argc, char *argv[]) { g_unix_signal_add(SIGTERM, quit_cb, NULL); @@ -68,6 +118,7 @@ int main(int argc, char *argv[]) main_loop = g_main_loop_new(NULL, FALSE); AppLauncher *launcher = app_launcher_get_default(); + gint owner_id = g_bus_own_name(G_BUS_TYPE_SESSION, APPLAUNCH_DBUS_NAME, G_BUS_NAME_OWNER_FLAGS_NONE, bus_acquired_cb, name_acquired_cb, name_lost_cb, diff --git a/src/meson.build b/src/meson.build index 81d1f0c..761ad9b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -17,6 +17,7 @@ applaunchd_deps = [ dependency('gobject-2.0'), dependency('gio-unix-2.0'), + dependency('libsystemd'), ] executable ( @@ -27,8 +28,8 @@ executable ( 'main.c', 'app_info.c', 'app_info.h', 'app_launcher.c', 'app_launcher.h', - 'dbus_activation_manager.c', 'dbus_activation_manager.h', 'process_manager.c', 'process_manager.h', + 'systemd_manager.c', 'systemd_manager.h', 'utils.c', 'utils.h', ], dependencies : applaunchd_deps, diff --git a/src/systemd_manager.c b/src/systemd_manager.c new file mode 100644 index 0000000..3fae7fc --- /dev/null +++ b/src/systemd_manager.c @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2022 Konsulko Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <systemd/sd-bus.h> + +#include "app_launcher.h" +#include "systemd_manager.h" + +struct _SystemdManager { + GObject parent_instance; +}; + +G_DEFINE_TYPE(SystemdManager, systemd_manager, G_TYPE_OBJECT); + +enum { + STARTED, + TERMINATED, + N_SIGNALS +}; +static guint signals[N_SIGNALS]; + +/* + * Application info structure, used for storing relevant data + * in the `running_apps` list + */ +struct systemd_runtime_data { + const gchar *esc_service; + SystemdManager *mgr; + sd_bus_slot *slot; +}; + +/* + * Initialization & cleanup functions + */ + +static void systemd_manager_dispose(GObject *object) +{ + SystemdManager *self = APPLAUNCHD_SYSTEMD_MANAGER(object); + + g_return_if_fail(APPLAUNCHD_IS_SYSTEMD_MANAGER(self)); + + G_OBJECT_CLASS(systemd_manager_parent_class)->dispose(object); +} + +static void systemd_manager_finalize(GObject *object) +{ + G_OBJECT_CLASS(systemd_manager_parent_class)->finalize(object); +} + +static void systemd_manager_class_init(SystemdManagerClass *klass) +{ + GObjectClass *object_class = (GObjectClass *)klass; + + object_class->dispose = systemd_manager_dispose; + object_class->finalize = systemd_manager_finalize; + + signals[STARTED] = g_signal_new("started", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0 , + NULL, NULL, NULL, G_TYPE_NONE, + 1, G_TYPE_STRING); + + signals[TERMINATED] = g_signal_new("terminated", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0 , + NULL, NULL, NULL, G_TYPE_NONE, + 1, G_TYPE_STRING); +} + +static void systemd_manager_init(SystemdManager *self) +{ +} + +/* + * Internal callbacks + */ + +/* + * This function is called when "PropertiesChanged" signal happens for + * the matched Unit - check its "ActiveState" to update the app status + */ +int systemd_manager_cb(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) +{ + AppLauncher *launcher = app_launcher_get_default(); + sd_bus_error err = SD_BUS_ERROR_NULL; + char* msg = NULL; + AppInfo *app_info = userdata; + struct systemd_runtime_data *data; + + data = app_info_get_runtime_data(app_info); + if(!data) + { + g_critical("Couldn't find runtime data for %s!", app_info_get_app_id(app_info)); + return 0; + } + + sd_bus_get_property_string( + app_launcher_get_bus(launcher), /* bus */ + "org.freedesktop.systemd1", /* destination */ + data->esc_service, /* path */ + "org.freedesktop.systemd1.Unit", /* interface */ + "ActiveState", /* member */ + &err, + &msg + ); + + if(!g_strcmp0(msg, "inactive")) + { + g_debug("Application %s has terminated", app_info_get_app_id(app_info)); + app_info_set_status(app_info, APP_STATUS_INACTIVE); + app_info_set_runtime_data(app_info, NULL); + + g_signal_emit(data->mgr, signals[TERMINATED], 0, app_info_get_app_id(app_info)); + systemd_manager_free_runtime_data(data); + } + else if(!g_strcmp0(msg, "active")) + { + /* PropertiesChanged signal gets triggered multiple times, only handle it once */ + if(app_info_get_status(app_info) != APP_STATUS_RUNNING) + { + g_debug("Application %s has started", app_info_get_app_id(app_info)); + app_info_set_status(app_info, APP_STATUS_RUNNING); + g_signal_emit(data->mgr, signals[STARTED], 0, app_info_get_app_id(app_info)); + } + } + return 0; +} + +/* + * Public functions + */ + +SystemdManager *systemd_manager_new(void) +{ + return g_object_new(APPLAUNCHD_TYPE_SYSTEMD_MANAGER, NULL); +} + +/* + * Start an application by executing the provided command line. + */ +gboolean systemd_manager_start_app(SystemdManager *self, + AppInfo *app_info) +{ + g_return_val_if_fail(APPLAUNCHD_IS_SYSTEMD_MANAGER(self), FALSE); + g_return_val_if_fail(APPLAUNCHD_IS_APP_INFO(app_info), FALSE); + + AppLauncher *launcher = app_launcher_get_default(); + g_autofree gchar *service = NULL; + gchar *esc_service = NULL; + const gchar *app_id = app_info_get_app_id(app_info); + const gchar *command = app_info_get_command(app_info); + struct systemd_runtime_data *runtime_data; + + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *m = NULL; + const char *path; + int r; + + runtime_data = g_new0(struct systemd_runtime_data, 1); + if (!runtime_data) { + g_critical("Unable to allocate runtime data structure for '%s'", app_id); + return FALSE; + } + + /* Compose the corresponding service name */ + service = g_strdup_printf("agl-app@%s.service", command); + /* Get the escaped unit name in the systemd hierarchy */ + sd_bus_path_encode("/org/freedesktop/systemd1/unit", service, &esc_service); + + runtime_data->mgr = self; + runtime_data->esc_service = esc_service; + + r = sd_bus_call_method( + app_launcher_get_bus(launcher), /* bus */ + "org.freedesktop.systemd1", /* service to contact */ + "/org/freedesktop/systemd1", /* object path */ + "org.freedesktop.systemd1.Manager", /* interface name */ + "StartUnit", /* method name */ + &error, /* object to return error in */ + &m, /* return message on success */ + "ss", /* input signature */ + service, /* first argument */ + "replace" /* second argument */ + ); + if (r < 0) { + g_critical("Failed to issue method call: %s", error.message); + goto finish; + } + + r = sd_bus_message_read(m, "o", &path); + if (r < 0) { + g_critical("Failed to parse response message: %s", strerror(-r)); + goto finish; + } + + r = sd_bus_match_signal( + app_launcher_get_bus(launcher), /* bus */ + &runtime_data->slot, /* slot */ + NULL, /* sender */ + esc_service, /* path */ + "org.freedesktop.DBus.Properties", /* interface */ + "PropertiesChanged", /* member */ + systemd_manager_cb, /* callback */ + app_info /* userdata */ + ); + if (r < 0) { + g_critical("Failed to set match signal: %s", strerror(-r)); + goto finish; + } + + app_info_set_runtime_data(app_info, runtime_data); + + /* The application is now starting, wait for notification to mark it running */ + g_debug("Application %s is now being started", app_info_get_app_id(app_info)); + app_info_set_status(app_info, APP_STATUS_STARTING); + return TRUE; + +finish: + if(runtime_data->esc_service) + g_free(runtime_data->esc_service); + g_free(runtime_data); + sd_bus_error_free(&error); + sd_bus_message_unref(m); + return FALSE; +} + +void systemd_manager_free_runtime_data(gpointer data) +{ + struct systemd_runtime_data *runtime_data = data; + + g_return_if_fail(runtime_data != NULL); + + sd_bus_slot_unref(runtime_data->slot); + g_free(runtime_data->esc_service); + g_free(runtime_data); +} diff --git a/src/systemd_manager.h b/src/systemd_manager.h new file mode 100644 index 0000000..a8a4ac7 --- /dev/null +++ b/src/systemd_manager.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 Konsulko Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYSTEMDMANAGER_H +#define SYSTEMDMANAGER_H + +#include <glib-object.h> + +#include "app_info.h" + +G_BEGIN_DECLS + +#define APPLAUNCHD_TYPE_SYSTEMD_MANAGER systemd_manager_get_type() + +G_DECLARE_FINAL_TYPE(SystemdManager, systemd_manager, + APPLAUNCHD, SYSTEMD_MANAGER, GObject); + +SystemdManager *systemd_manager_new(void); + +gboolean systemd_manager_start_app(SystemdManager *self, + AppInfo *app_info); + +G_END_DECLS + +#endif |