diff options
Diffstat (limited to 'src/systemd_manager.c')
-rw-r--r-- | src/systemd_manager.c | 559 |
1 files changed, 369 insertions, 190 deletions
diff --git a/src/systemd_manager.c b/src/systemd_manager.c index 63e7094..e6d67f8 100644 --- a/src/systemd_manager.c +++ b/src/systemd_manager.c @@ -1,26 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 /* * 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 <stdbool.h> +#include "systemd_manager.h" +#include "utils.h" + +// Pull in for sd_bus_path_encode, as there's no obvious alternative +// other than coding up a duplicate. No other sd_bus functions should +// be used! #include <systemd/sd-bus.h> -#include "app_launcher.h" -#include "systemd_manager.h" +// gdbus generated headers +#include "systemd1_manager_interface.h" +#include "systemd1_unit_interface.h" +extern GMainLoop *main_loop; + +// Object data struct _SystemdManager { GObject parent_instance; + + GDBusConnection *conn; + Systemd1Manager *proxy; + + GList *apps_list; }; G_DEFINE_TYPE(SystemdManager, systemd_manager, G_TYPE_OBJECT); @@ -37,12 +42,189 @@ static guint signals[N_SIGNALS]; * in the `running_apps` list */ struct systemd_runtime_data { - const gchar *esc_service; + gchar *esc_service; SystemdManager *mgr; - sd_bus_slot *slot; + Systemd1Unit *proxy; }; /* + * Internal functions + */ + +/* + * Get app unit list + */ +static gboolean systemd_manager_enumerate_app_units(SystemdManager *self, + GList **units) +{ + g_return_val_if_fail(APPLAUNCHD_IS_SYSTEMD_MANAGER(self), FALSE); + g_return_val_if_fail(units != NULL, FALSE); + + GVariant *matched_units = NULL; + GError *error = NULL; + const gchar *const states[1] = { NULL }; + const gchar *const patterns[2] = { "agl-app*@*.service", NULL }; + if (!systemd1_manager_call_list_unit_files_by_patterns_sync(self->proxy, + states, + patterns, + &matched_units, + NULL, + &error)) { + g_critical("Failed to issue method call: %s", error ? error->message : "unspecified"); + g_error_free(error); + goto finish; + } + + // We expect the response GVariant to be format "a(ss)" + GVariantIter *array; + g_variant_get(matched_units, "a(ss)", &array); + const char *unit; + const char *status; + while (g_variant_iter_loop(array, "(ss)", &unit, &status)) { + if (!g_str_has_suffix(unit, "@.service")) + *units = g_list_prepend(*units, g_strdup(unit)); + } + g_variant_iter_free(array); + g_variant_unref(matched_units); + + return TRUE; + +finish: + g_list_free_full(*units, g_free); + *units = NULL; + return FALSE; +} + +/* + * Get app unit description property + */ +static gboolean systemd_manager_get_app_description(SystemdManager *self, + gchar *service, + gchar **description) +{ + g_return_val_if_fail(APPLAUNCHD_IS_SYSTEMD_MANAGER(self), FALSE); + g_return_val_if_fail(service != NULL, FALSE); + g_return_val_if_fail(description != NULL, FALSE); + + // Get the escaped unit name in the systemd hierarchy + gchar *esc_service = NULL; + sd_bus_path_encode("/org/freedesktop/systemd1/unit", service, &esc_service); + + GError *error = NULL; + Systemd1Unit *proxy = systemd1_unit_proxy_new_sync(self->conn, + G_DBUS_PROXY_FLAGS_NONE, + "org.freedesktop.systemd1", + esc_service, + NULL, + &error); + if (!proxy) { + g_critical("Failed to create org.freedesktop.systemd1.Unit proxy: %s", + error ? error->message : "unspecified"); + g_error_free(error); + goto finish; + } + + gchar *value = systemd1_unit_dup_description(proxy); + if (value) { + *description = value; + } + + g_object_unref(proxy); + return TRUE; + +finish: + g_free(*description); + *description = NULL; + return FALSE; +} + +/* + * This function is executed during the object initialization. It goes through + * all available applications on the system and creates a static list + * containing all the relevant info (ID, name, unit, icon...) for further + * processing. + */ +static void systemd_manager_update_applications_list(SystemdManager *self) +{ + g_auto(GStrv) dirlist = NULL; + + char *xdg_data_dirs = getenv("XDG_DATA_DIRS"); + if (xdg_data_dirs) + dirlist = g_strsplit(getenv("XDG_DATA_DIRS"), ":", -1); + + GList *units = NULL; + if (!systemd_manager_enumerate_app_units(self, &units)) { + return; + } + + GList *iterator; + for (iterator = units; iterator != NULL; iterator = iterator->next) { + g_autofree const gchar *app_id = NULL; + g_autofree const gchar *icon_path = NULL; + gchar *service = NULL; + AppInfo *app_info = NULL; + + if (!iterator->data) + continue; + + // Parse service and app id out of unit filename + gchar *p = g_strrstr(iterator->data, "/"); + if (!p) + service = iterator->data; + else + service = p + 1; + + g_autofree char *tmp = g_strdup(service); + char *end = tmp + strlen(tmp); + while (end > tmp && *end != '.') { + --end; + } + if (end > tmp) { + *end = '\0'; + } else { + g_free(tmp); + continue; + } + while (end > tmp && *end != '@') { + --end; + } + if (end > tmp) { + app_id = g_strdup(end + 1); + } + // Potentially handle non-template agl-app-foo.service units here + + // Try getting display name from unit Description property + g_autofree gchar *name = NULL; + if (!systemd_manager_get_app_description(self, + service, + &name) || + name == NULL) { + + // Fall back to the application ID + g_warning("Could not retrieve Description of '%s'", service); + name = g_strdup(app_id); + } + + /* + * GAppInfo retrieves the icon data but doesn't provide a way to retrieve + * the corresponding file name, so we have to look it up by ourselves. + */ + if (app_id && dirlist) + icon_path = applaunchd_utils_get_icon(dirlist, app_id); + + app_info = app_info_new(app_id, + name, + icon_path ? icon_path : "", + service); + + g_debug("Adding application '%s' with display name '%s'", app_id, name); + self->apps_list = g_list_append(self->apps_list, app_info); + } + g_list_free_full(units, g_free); +} + + +/* * Initialization & cleanup functions */ @@ -52,6 +234,12 @@ static void systemd_manager_dispose(GObject *object) g_return_if_fail(APPLAUNCHD_IS_SYSTEMD_MANAGER(self)); + if (self->apps_list) + g_list_free_full(g_steal_pointer(&self->apps_list), g_object_unref); + + g_object_unref(self->proxy); + g_object_unref(self->conn); + G_OBJECT_CLASS(systemd_manager_parent_class)->dispose(object); } @@ -80,8 +268,33 @@ static void systemd_manager_class_init(SystemdManagerClass *klass) static void systemd_manager_init(SystemdManager *self) { + GError *error = NULL; + GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!conn) { + g_critical("Failed to connect to D-Bus: %s", error ? error->message : "unspecified"); + g_error_free(error); + return; + } + self->conn = conn; + + Systemd1Manager *proxy = systemd1_manager_proxy_new_sync(conn, + G_DBUS_PROXY_FLAGS_NONE, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + NULL, + &error); + if (!proxy) { + g_critical("Failed to create org.freedesktop.systemd1.Manager proxy: %s", + error ? error->message : "unspecified"); + g_error_free(error); + return; + } + self->proxy = proxy; + + systemd_manager_update_applications_list(self); } + /* * Internal callbacks */ @@ -90,43 +303,59 @@ static void systemd_manager_init(SystemdManager *self) * 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) +static void unit_properties_changed_cb(GDBusProxy *proxy, + GVariant *changed_properties, + const gchar* const *invalidated_properties, + gpointer user_data) { - AppLauncher *launcher = app_launcher_get_default(); - sd_bus_error err = SD_BUS_ERROR_NULL; - char* msg = NULL; - AppInfo *app_info = userdata; + AppInfo *app_info = user_data; struct systemd_runtime_data *data; data = app_info_get_runtime_data(app_info); - if(!data) - { + if(!data) { g_critical("Couldn't find runtime data for %s!", app_info_get_app_id(app_info)); - return 0; + return; + } + + // NOTE: changed_properties and invalidated_properties are guaranteed to never be NULL + gchar *new_state = NULL; + bool found = false; + if (g_variant_n_children(changed_properties) > 0) { + GVariantIter *iter; + const gchar *key; + GVariant *value; + + g_variant_get(changed_properties, "a{sv}", &iter); + while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { + if (!g_strcmp0(key, "ActiveState")) { + new_state = g_variant_dup_string(value, NULL); + found = true; + } + } + g_variant_iter_free(iter); } - 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")) + // Ignore invalidated_properties for now + + // Return if the changed property isn't "ActiveState" + if (!found) + return; + + if(!g_strcmp0(new_state, "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); + if(app_info_get_status(app_info) != APP_STATUS_STARTING) + { + 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); + 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")) + else if(!g_strcmp0(new_state, "active")) { - /* PropertiesChanged signal gets triggered multiple times, only handle it once */ + // 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)); @@ -134,128 +363,70 @@ int systemd_manager_cb(sd_bus_message *m, void *userdata, sd_bus_error *ret_erro g_signal_emit(data->mgr, signals[STARTED], 0, app_info_get_app_id(app_info)); } } - return 0; + g_free(new_state); } + /* * Public functions */ -SystemdManager *systemd_manager_new(void) +SystemdManager *systemd_manager_get_default(void) { - return g_object_new(APPLAUNCHD_TYPE_SYSTEMD_MANAGER, NULL); + static SystemdManager *manager; + + /* + * SystemdManager is a singleton, only create the object if it doesn't + * exist already. + */ + if (manager == NULL) { + g_debug("Initializing app launcher service..."); + manager = g_object_new(APPLAUNCHD_TYPE_SYSTEMD_MANAGER, NULL); + g_object_add_weak_pointer(G_OBJECT(manager), (gpointer*) &manager); + } + + return manager; +} + +void systemd_manager_connect_callbacks(SystemdManager *self, + GCallback started_cb, + GCallback terminated_cb, + void *data) +{ + if (started_cb) + g_signal_connect_swapped(self, "started", started_cb, data); + + if (terminated_cb) + g_signal_connect_swapped(self, "terminated", terminated_cb, data); } /* - * Get app unit list + * Search the applications list for an app which matches the provided app-id + * and return the corresponding AppInfo object. */ -gboolean systemd_manager_enumerate_app_units(SystemdManager *self, - AppLauncher *launcher, - GList **units) +AppInfo *systemd_manager_get_app_info(SystemdManager *self, const gchar *app_id) { - g_return_val_if_fail(APPLAUNCHD_IS_SYSTEMD_MANAGER(self), FALSE); - g_return_val_if_fail(APPLAUNCHD_IS_APP_LAUNCHER(launcher), FALSE); - g_return_val_if_fail(units != NULL, FALSE); + g_return_val_if_fail(APPLAUNCHD_IS_SYSTEMD_MANAGER(self), NULL); - sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus_message *m = NULL; - const char *path; - int r; - - 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 */ - "ListUnitFilesByPatterns", /* method name */ - &error, /* object to return error in */ - &m, /* return message on success */ - "asas", /* input signature */ - 0, /* first argument (empty array) */ - 1, /* second argument (array) */ - "agl-app*@*.service" - ); - if (r < 0) { - g_critical("Failed to issue method call: %s", error.message); - goto finish; - } + guint len = g_list_length(self->apps_list); - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) { - g_critical("Failed to parse response message: %s", strerror(-r)); - goto finish; - } + for (guint i = 0; i < len; i++) { + AppInfo *app_info = g_list_nth_data(self->apps_list, i); - const char *unit; - const char *state; - while ((r = sd_bus_message_read(m, "(ss)", &unit, &state)) > 0) { - if (!g_str_has_suffix(unit, "@.service")) - *units = g_list_prepend(*units, g_strdup(unit)); - } - if (r < 0) { - g_critical("Failed to parse unit entry: %s", strerror(-r)); - goto finish; + if (g_strcmp0(app_info_get_app_id(app_info), app_id) == 0) + return app_info; } - // Exit array - r = sd_bus_message_exit_container(m); - if (r < 0) { - g_critical("Failed to parse response message 5: %s", strerror(-r)); - goto finish; - } - - return TRUE; + g_warning("Unable to find application with ID '%s'", app_id); -finish: - sd_bus_error_free(&error); - sd_bus_message_unref(m); - g_list_free_full(*units, g_free); - *units = NULL; - return FALSE; + return NULL; } -/* - * Get app unit description property - */ -gboolean systemd_manager_get_app_description(SystemdManager *self, - AppLauncher *launcher, - gchar *service, - gchar **description) +GList *systemd_manager_get_app_list(SystemdManager *self) { - g_return_val_if_fail(APPLAUNCHD_IS_SYSTEMD_MANAGER(self), FALSE); - g_return_val_if_fail(APPLAUNCHD_IS_APP_LAUNCHER(launcher), FALSE); - g_return_val_if_fail(service != NULL, FALSE); - g_return_val_if_fail(description != NULL, FALSE); - - sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus_message *m = NULL; - gchar *esc_service = NULL; - const char *path; - int r; - - /* Get the escaped unit name in the systemd hierarchy */ - sd_bus_path_encode("/org/freedesktop/systemd1/unit", service, &esc_service); + g_return_val_if_fail(APPLAUNCHD_IS_SYSTEMD_MANAGER(self), NULL); - r = sd_bus_get_property_string( - app_launcher_get_bus(launcher), /* bus */ - "org.freedesktop.systemd1", /* service to contact */ - esc_service, /* object path */ - "org.freedesktop.systemd1.Unit", - "Description", - &error, - description); - if (r < 0) { - g_critical("Failed to issue method call: %s", error.message); - goto finish; - } - return TRUE; - -finish: - sd_bus_error_free(&error); - sd_bus_message_unref(m); - g_free(*description); - *description = NULL; - return FALSE; + return self->apps_list; } /* @@ -267,81 +438,90 @@ gboolean systemd_manager_start_app(SystemdManager *self, 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(); - gchar *esc_service = NULL; + AppStatus app_status = app_info_get_status(app_info); const gchar *app_id = app_info_get_app_id(app_info); + + switch (app_status) { + case APP_STATUS_STARTING: + g_debug("Application '%s' is already starting", app_id); + return TRUE; + case APP_STATUS_RUNNING: + g_debug("Application '%s' is already running", app_id); + + /* + * The application may be running in the background, notify + * subscribers it should be activated/brought to the foreground. + */ + g_signal_emit(self, signals[STARTED], 0, app_id); + return TRUE; + case APP_STATUS_INACTIVE: + // Fall through and start the application + break; + default: + g_critical("Unknown status %d for application '%s'", app_status, app_id); + return FALSE; + } + + gchar *esc_service = NULL; const gchar *service = app_info_get_service(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; } - /* Get the escaped unit name in the systemd hierarchy */ + // Get the escaped unit name in the systemd hierarchy sd_bus_path_encode("/org/freedesktop/systemd1/unit", service, &esc_service); g_debug("Trying to start service '%s', unit path '%s'", 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; + GError *error = NULL; + Systemd1Unit *proxy = systemd1_unit_proxy_new_sync(self->conn, + G_DBUS_PROXY_FLAGS_NONE, + "org.freedesktop.systemd1", + esc_service, + NULL, + &error); + if (!proxy) { + g_critical("Failed to create org.freedesktop.systemd1.Unit proxy: %s", + error ? error->message : "unspecified"); + g_error_free(error); + goto finish; } + runtime_data->proxy = proxy; app_info_set_runtime_data(app_info, runtime_data); + g_signal_connect(proxy, + "g-properties-changed", + G_CALLBACK(unit_properties_changed_cb), + app_info); - /* The application is now starting, wait for notification to mark it running */ + // 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); + + if (!systemd1_manager_call_start_unit_sync(self->proxy, + service, + "replace", + NULL, + NULL, + &error)) { + + g_critical("Failed to issue method call: %s", error ? error->message : "unspecified"); + g_error_free(error); + goto finish; + } + 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; } @@ -351,7 +531,6 @@ void systemd_manager_free_runtime_data(gpointer 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); } |