diff options
-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 |