gnome-shell/src/shell-realm-tracker.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);
}