#include "shell-realm-tracker.h" #include "shell-realms-private.h" #define NUM_BUS_SIGNAL_IDS 5 #define REALMS_BUS_NAME "com.subgraph.realms" #define REALMS_OBJECT_PATH "/com/subgraph/realms" #define REALMS_MANAGER_INTERFACE "com.subgraph.realms.Manager" struct _ShellRealmTracker { GObject parent; GDBusConnection *dbus; guint realms_watch_id; guint bus_signal_ids[NUM_BUS_SIGNAL_IDS]; gboolean destroy_in_progress; }; G_DEFINE_TYPE (ShellRealmTracker, shell_realm_tracker, G_TYPE_OBJECT); static void shell_realm_tracker_init (ShellRealmTracker *tracker) { tracker->dbus = NULL; tracker->realms_watch_id = 0; tracker->destroy_in_progress = FALSE; } static void shell_realm_tracker_class_init (ShellRealmTrackerClass *klass) { } static void on_realm_bus_signal(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { ShellRealms *realms = shell_realms_get_default(); const gchar *realm_name = NULL; const gchar *description = NULL; const gchar *namespace = NULL; guint8 status = 0; if (g_str_equal (signal_name, "RealmStarted")) { g_variant_get (parameters, "(&s&sy)", &realm_name, &namespace, &status); shell_realms_on_realm_started (realms, realm_name, namespace, status); } else if (g_str_equal (signal_name, "RealmStopped")) { g_variant_get (parameters, "(&sy)", &realm_name, &status); shell_realms_on_realm_stopped (realms, realm_name); } else if (g_str_equal (signal_name, "RealmRemoved")) { g_variant_get (parameters, "(&s)", &realm_name); shell_realms_on_realm_removed (realms, realm_name); } else if (g_str_equal (signal_name, "RealmCurrent")) { g_variant_get (parameters, "(&sy)", &realm_name, &status); shell_realms_on_realm_current (realms, realm_name); } else if (g_str_equal (signal_name, "RealmNew")) { g_variant_get (parameters, "(&s&sy)", &realm_name, &description, status); shell_realms_on_realm_new (realms, realm_name, description, status); } else { g_warning("Unexpected signal name '%s' received from realms manager DBUS", signal_name); } } static void realm_state_process_elements (ShellRealmTracker *self, GVariant *response) { GVariantIter *iter = NULL; const gchar *name = NULL; const gchar *description = NULL; const gchar *namespace = NULL; guchar status = 0; ShellRealms *realms = shell_realms_get_default(); shell_realms_untag_all (realms); g_variant_get(response, "(a(ssssy))", &iter); // (name, desc, realmfs, namespace, status) while (g_variant_iter_next(iter, "(&s&ss&sy)", &name, &description, NULL, &namespace, &status)) { shell_realms_update_realm (realms, name, description, namespace, status); } shell_realms_remove_untagged (realms); g_variant_iter_free(iter); } static void request_realm_state_finish(GObject *object, GAsyncResult *result, gpointer data) { ShellRealmTracker *self = data; GError *error = NULL; GVariant *response = g_dbus_connection_call_finish (G_DBUS_CONNECTION(object), result, &error); if (!response) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning("MetaRealmDbus: Error calling 'List' bus method: %s", error->message); } g_clear_error (&error); return; } if (self->destroy_in_progress) { g_variant_unref (response); return; } realm_state_process_elements(self, response); g_variant_unref(response); } static void set_realm_current_finish (GObject *object, GAsyncResult *result, gpointer data) { GError *error = NULL; GVariant *response = g_dbus_connection_call_finish (G_DBUS_CONNECTION(object), result, &error); if (!response) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning("MetaRealmDbus: Error calling 'SetCurrent' bus method: %s", error->message); } g_clear_error (&error); return; } g_variant_unref (response); } static void call_dbus_method (ShellRealmTracker *self, const gchar *method, GVariant *parameters, GAsyncReadyCallback callback, gpointer user_data) { if (!self->dbus) { g_warning("ShellRealmTracker: call_dbus_method(%s) called when no bus connection present", method); return; } g_dbus_connection_call(self->dbus, REALMS_BUS_NAME, REALMS_OBJECT_PATH, REALMS_MANAGER_INTERFACE, method, parameters, NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, callback, user_data); } static void request_realm_state(ShellRealmTracker *self) { call_dbus_method (self, "List", NULL, request_realm_state_finish, self); } void shell_realm_tracker_call_set_current (ShellRealmTracker *self, const char *realm_name) { call_dbus_method (self, "SetCurrent", g_variant_new("(s)", realm_name), set_realm_current_finish, NULL); } static void unsubscribe_signals (ShellRealmTracker *self) { for (int i = 0; i < NUM_BUS_SIGNAL_IDS; i++) { if (self->bus_signal_ids[i]) { if (self->dbus) { g_dbus_connection_signal_unsubscribe(self->dbus, self->bus_signal_ids[i]); } self->bus_signal_ids[i] = 0; } } } static guint bus_signal_subscribe (ShellRealmTracker *self, const gchar *signal_name) { g_assert(self->dbus); return g_dbus_connection_signal_subscribe(self->dbus, REALMS_BUS_NAME, REALMS_MANAGER_INTERFACE, signal_name, REALMS_OBJECT_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, on_realm_bus_signal, self, NULL); } static void subscribe_bus_signals (ShellRealmTracker *self) { if (self->dbus) { int idx = 0; self->bus_signal_ids[idx++] = bus_signal_subscribe(self, "RealmStarted"); self->bus_signal_ids[idx++] = bus_signal_subscribe(self, "RealmStopped"); self->bus_signal_ids[idx++] = bus_signal_subscribe(self, "RealmCurrent"); self->bus_signal_ids[idx++] = bus_signal_subscribe(self, "RealmNew"); self->bus_signal_ids[idx++] = bus_signal_subscribe(self, "RealmRemoved"); g_assert(idx == NUM_BUS_SIGNAL_IDS); } } static void on_realm_manager_appeared (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) { ShellRealmTracker *self = user_data; // Avoid processing spurious events while destroying 'self' if (self->destroy_in_progress) { return; } if (!self->dbus) { self->dbus = g_object_ref(connection); subscribe_bus_signals (self); } else { g_warning("Realm tracker already has a connection in on_realm_manager_appeared()"); } request_realm_state (self); } static void on_realm_manager_vanished (GDBusConnection *connection, const gchar *name, gpointer user_data) { ShellRealmTracker *self = user_data; // Avoid processing spurious events while destroying 'self' if (self->destroy_in_progress) { return; } if (!connection) { g_clear_object (&self->dbus); } unsubscribe_signals(self); } /** * shell_realm_tracker_get_default: * * Return Value: (transfer none): The global #ShellRealmTracker singleton */ ShellRealmTracker * shell_realm_tracker_get_default(void) { static ShellRealmTracker *instance; if (instance == NULL) { instance = g_object_new (SHELL_TYPE_REALM_TRACKER, NULL); } return instance; } void shell_realm_tracker_start () { ShellRealmTracker *tracker = shell_realm_tracker_get_default(); if (tracker->realms_watch_id) { g_warning ("ShellRealmTracker: shell_realm_tracker_start() called when already started"); return; } tracker->realms_watch_id = g_bus_watch_name(G_BUS_TYPE_SYSTEM, REALMS_BUS_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE, on_realm_manager_appeared, on_realm_manager_vanished, tracker, g_free); } void shell_realm_tracker_destroy(ShellRealmTracker *self) { if (self->dbus) { unsubscribe_signals (self); g_clear_object (&self->dbus); } // event handlers check this and will bail early in case there are // any in queue. see docs for g_bus_unwatch_name() self->destroy_in_progress = TRUE; // frees 'self' in destroy notifier g_bus_unwatch_name(self->realms_watch_id); }