374 lines
11 KiB
C
374 lines
11 KiB
C
#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"
|
|
|
|
enum {
|
|
REALM_PID_LOOKUP_UNKNOWN = 1,
|
|
REALM_PID_LOOKUP_REALM = 2,
|
|
REALM_PID_LOOKUP_CITADEL = 3,
|
|
};
|
|
|
|
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;
|
|
guint64 namespace;
|
|
guint8 status = 0;
|
|
|
|
if (g_str_equal (signal_name, "RealmStarted")) {
|
|
|
|
g_variant_get (parameters, "(&sty)", &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 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
|
|
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 void
|
|
realm_state_process_elements (ShellRealmTracker *self, GVariant *response)
|
|
{
|
|
|
|
GVariantIter *iter = NULL;
|
|
const gchar *name = NULL;
|
|
const gchar *description = NULL;
|
|
guint64 namespace = 0;
|
|
guchar status = 0;
|
|
|
|
ShellRealms *realms = shell_realms_get_default ();
|
|
shell_realms_untag_all (realms);
|
|
|
|
g_variant_get (response, "(a(sssty))", &iter);
|
|
|
|
// (name, desc, realmfs, namespace, status)
|
|
while (g_variant_iter_next (iter, "(&s&ssty)", &name, &description, NULL, &namespace, &status)) {
|
|
shell_realms_process_list_realm (realms, name, description, namespace, status);
|
|
}
|
|
|
|
shell_realms_remove_untagged (realms);
|
|
shell_realms_update_contexts (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 ("ShellRealmTracker: 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);
|
|
subscribe_bus_signals (self);
|
|
}
|
|
|
|
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 GVariant *
|
|
call_dbus_method_sync (ShellRealmTracker *self, const gchar *method, GVariant *parameters, const GVariantType *reply_type, GError **error)
|
|
{
|
|
if (!self->dbus) {
|
|
g_warning("ShellRealmTracker: call_dbus_method_sync (%s) called when no bus connection present", method);
|
|
return NULL;
|
|
}
|
|
return g_dbus_connection_call_sync (self->dbus,
|
|
REALMS_BUS_NAME,
|
|
REALMS_OBJECT_PATH,
|
|
REALMS_MANAGER_INTERFACE,
|
|
method,
|
|
parameters,
|
|
reply_type,
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
-1,
|
|
NULL,
|
|
error);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
gchar *
|
|
shell_realm_tracker_call_realm_from_citadel_pid (ShellRealmTracker *self, pid_t pid)
|
|
{
|
|
GError *error = NULL;
|
|
GVariant *result;
|
|
|
|
guint32 code = 0;
|
|
gchar *realm_name = NULL;
|
|
gchar *retval = NULL;
|
|
|
|
if (!self->dbus) {
|
|
return NULL;
|
|
}
|
|
|
|
result = call_dbus_method_sync (self, "RealmFromCitadelPid",
|
|
g_variant_new("(u)", pid),
|
|
G_VARIANT_TYPE("(us)"),
|
|
&error);
|
|
|
|
if (!result) {
|
|
if (error != NULL) {
|
|
g_warning ("Error calling RealmCitadelFromPid(%d): %s", pid, error->message);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
g_variant_get (result, "(u&s)", &code, &realm_name);
|
|
// g_warning("Calling RealmFromCitadelPid(%u) -> (%d,'%s')", pid, code, realm_name);
|
|
|
|
if (code == REALM_PID_LOOKUP_REALM) {
|
|
retval = g_strdup (realm_name);
|
|
} else if (code == REALM_PID_LOOKUP_CITADEL) {
|
|
retval = g_strdup (CITADEL_REALM_TAG);
|
|
}
|
|
g_variant_unref (result);
|
|
return retval;
|
|
}
|
|
|
|
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);
|
|
} 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 (void)
|
|
{
|
|
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);
|
|
|
|
}
|