/* This file deals with the pain and suffering of using libdbus. * * Some day, I'll implement dbus directly here. */ #include #include #include "libnul-private.h" typedef struct object_info_t object_info_t; struct object_info_t { char * name; connection_t * connection; message_func_t func; gpointer data; }; struct connection_t { DBusConnection *dconnection; object_info_t **objects; char ** names; }; static void decode_arg (DBusMessageIter *iter, primitive_t *primitive, value_t *value) { int dbus_type = dbus_message_iter_get_arg_type (iter); switch (dbus_type) { case DBUS_TYPE_UINT32: *primitive = UINT32; dbus_message_iter_get_basic (iter, &value->u32); break; case DBUS_TYPE_INT32: *primitive = INT32; dbus_message_iter_get_basic (iter, &value->i32); break; case DBUS_TYPE_STRING: *primitive = STRING; dbus_message_iter_get_basic (iter, &value->s); value->s = g_strdup (value->s); break; } } static message_t * decode_message (DBusMessage *dmsg, char **err) { DBusMessageIter iter; message_t *message; message = g_new0 (message_t, 1); message->arguments = nul_array_new (typed_value_t); if (dbus_message_iter_init (dmsg, &iter)) { do { typed_value_t value; decode_arg (&iter, &value.type, &value.value); message->arguments = nul_array_append (message->arguments, value); } while (dbus_message_iter_next (&iter)); } return message; } static void process_watch (DBusWatch *watch, int flags) { dbus_watch_handle (watch, flags); } static void on_hangup (gpointer data) { DBusWatch *watch = data; process_watch (watch, DBUS_WATCH_HANGUP); } static void on_error (gpointer data) { DBusWatch *watch = data; process_watch (watch, DBUS_WATCH_ERROR); } static void on_read (gpointer data) { GList *list = data; for (list = data; list != NULL; list = list->next) { DBusWatch *watch = list->data; if (dbus_watch_get_enabled (watch) && (dbus_watch_get_flags (watch) & DBUS_WATCH_READABLE)) { process_watch (watch, DBUS_WATCH_READABLE); } } } static void on_write (gpointer data) { GList *list = data; for (list = data; list != NULL; list = list->next) { DBusWatch *watch = list->data; if (dbus_watch_get_enabled (watch) && (dbus_watch_get_flags (watch) & DBUS_WATCH_WRITABLE)) { process_watch (watch, DBUS_WATCH_WRITABLE); } } } static void update_fd (int fd) { int enabled, flags; GList *watches, *list; watches = nul_fd_get_data (fd); enabled = FALSE; flags = 0; for (list = watches; list != NULL; list = list->next) { DBusWatch *watch = list->data; if (dbus_watch_get_enabled (watch)) { enabled = TRUE; flags |= dbus_watch_get_flags (watch); } } if (enabled) { nul_fd_set_error_callback (fd, on_hangup); nul_fd_set_hangup_callback (fd, on_error); if (flags & DBUS_WATCH_READABLE) nul_fd_set_read_callback (fd, on_read); else nul_fd_set_read_callback (fd, NULL); if (flags & DBUS_WATCH_WRITABLE) nul_fd_set_write_callback (fd, on_write); else nul_fd_set_write_callback (fd, NULL); } else { nul_fd_set_error_callback (fd, NULL); nul_fd_set_hangup_callback (fd, NULL); nul_fd_set_read_callback (fd, NULL); nul_fd_set_write_callback (fd, NULL); } } static dbus_bool_t add_watch (DBusWatch *watch, void *data) { DBusConnection *connection = data; int fd = dbus_watch_get_unix_fd (watch); dbus_watch_set_data (watch, connection, NULL); /* DBus apparently will create multiple watches for * the same fd. */ if (nul_fd_is_watched (fd)) { GList *watches = nul_fd_get_data (fd); watches = g_list_prepend (watches, watch); nul_fd_set_data (fd, watches); } else { nul_fd_add_watch (fd, g_list_prepend (NULL, watch)); } update_fd (fd); return TRUE; } static void remove_watch (DBusWatch *watch, void *data) { int fd = dbus_watch_get_unix_fd (watch); GList *watches = nul_fd_get_data (fd); watches = g_list_remove (watches, watch); if (!watches) { nul_fd_remove_watch (fd); } else { nul_fd_set_data (fd, watches); update_fd (fd); } } static void toggle_watch (DBusWatch *watch, void *data) { update_fd (dbus_watch_get_unix_fd (watch)); } static gboolean on_timeout (gpointer data) { DBusTimeout *timeout = data; dbus_timeout_handle (timeout); return TRUE; } static dbus_bool_t add_timeout (DBusTimeout *timeout, void *data) { dbus_timeout_set_data (timeout, GINT_TO_POINTER (0), NULL); if (dbus_timeout_get_enabled (timeout)) { int id; id = g_timeout_add (dbus_timeout_get_interval (timeout), on_timeout, timeout); dbus_timeout_set_data (timeout, GINT_TO_POINTER (id), NULL); } return TRUE; } static void remove_timeout (DBusTimeout *timeout, void *data) { int id = GPOINTER_TO_INT (dbus_timeout_get_data (timeout)); if (id) { g_source_remove (id); dbus_timeout_set_data (timeout, GINT_TO_POINTER (0), NULL); } } static void toggle_timeout (DBusTimeout *timeout, void *data) { remove_timeout (timeout, NULL); add_timeout (timeout, NULL); } static dbus_int32_t idle_id_slot = -1; static int get_idle_id (DBusConnection *connection) { if (idle_id_slot == -1) dbus_connection_allocate_data_slot (&idle_id_slot); return (int)dbus_connection_get_data (connection, idle_id_slot); } static void set_idle_id (DBusConnection *connection, int id) { if (idle_id_slot == -1) dbus_connection_allocate_data_slot (&idle_id_slot); dbus_connection_set_data (connection, idle_id_slot, (void *)id, NULL); } static gboolean do_dispatch (gpointer data) { DBusConnection *connection = data; while (dbus_connection_dispatch (connection) == DBUS_DISPATCH_DATA_REMAINS) ; set_idle_id (connection, 0); return FALSE; } static void on_dispatch_status_changed (DBusConnection *connection, DBusDispatchStatus new_status, void *data) { if (!get_idle_id (connection)) set_idle_id (connection, g_idle_add (do_dispatch, connection)); } static connection_t * ensure_connection (connection_t **connection, DBusBusType type, DBusError *err) { if (!*connection) { DBusConnection *dconn; dconn = dbus_bus_get (type, err); if (dconn) { connection_t *conn; dbus_connection_set_dispatch_status_function ( dconn, on_dispatch_status_changed, *connection, NULL); dbus_connection_set_watch_functions ( dconn, add_watch, remove_watch, toggle_watch, dconn, NULL); dbus_connection_set_timeout_functions ( dconn, add_timeout, remove_timeout, toggle_timeout, dconn, NULL); do_dispatch (dconn); conn = g_new0 (connection_t, 1); conn->dconnection = dconn; conn->objects = nul_array_new (object_info_t *); conn->names = nul_array_new (char *); *connection = conn; } } return *connection; } static connection_t * get_connection (DBusBusType type, DBusError *err) { static connection_t *session_bus; static connection_t *system_bus; if (type == DBUS_BUS_SESSION) return ensure_connection (&session_bus, DBUS_BUS_SESSION, err); else if (type == DBUS_BUS_SYSTEM) return ensure_connection (&system_bus, DBUS_BUS_SYSTEM, err); else return NULL; } connection_t * connection_new_session (void) { return get_connection (DBUS_BUS_SESSION, NULL /* FIXME */); } connection_t * connection_new_system (void) { return get_connection (DBUS_BUS_SYSTEM, NULL /* FIXME */); } static void dbus_int_unregister_connection (DBusConnection *connection, gpointer data) { /* nothing */ } static DBusHandlerResult dbus_int_message_function (DBusConnection *connection, DBusMessage *message, gpointer data) { object_info_t *info = data; /* Parse the message into something sensible here */ if (info->func (info->connection, message, info->data)) return DBUS_HANDLER_RESULT_HANDLED; else return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static const DBusObjectPathVTable vtable = { dbus_int_unregister_connection, dbus_int_message_function, NULL, }; void connection_register_object (connection_t *connection, const char *object_name, message_func_t func, nul_ptr_t data) { object_info_t *info = g_new0 (object_info_t, 1); info->connection = connection; info->name = g_strdup (object_name); info->func = func; info->data = data; connection->objects = nul_array_append (connection->objects, info); dbus_connection_register_object_path ( connection->dconnection, object_name, &vtable, info); } void connection_unregister_object (connection_t *connection, const char *object_name) { int i; for (i = 0; connection->objects[i] != NULL; ++i) { object_info_t *info = connection->objects[i]; if (strcmp (info->name, object_name) == 0) { connection->objects = nul_array_remove_fast (connection->objects, info); g_free (info->name); g_free (info); break; } } } static char * find_name (connection_t *connection, const char *name) { char **n; for (n = connection->names; *n; n++) { if (strcmp (*n, name) == 0) return *n; } return NULL; } void connection_release_name (connection_t *connection, const char * name) { char *n; if (!(n = find_name (connection, name))) return; g_free (n); connection->names = nul_array_remove_fast (connection->names, n); dbus_bus_release_name (connection->dconnection, name, NULL); } gboolean connection_request_name (connection_t *connection, const char *name) { int r; gboolean owner; if (find_name (connection, name)) return TRUE; /* Should we g_critical here? */ /* Possible flags: * * DBUS_NAME_FLAG_ALLOW_REPLACEMENT * DBUS_NAME_FLAG_REPLACE_EXISTING * DBUS_FLAG_DO_NOT_QUEUE */ r = dbus_bus_request_name (connection->dconnection, name, DBUS_NAME_FLAG_DO_NOT_QUEUE, NULL); owner = FALSE; /* FIXME: errors */ if (r == -1) { return FALSE; } else { switch (r) { case DBUS_REQUEST_NAME_REPLY_IN_QUEUE: g_warning ("this should not happen according to dbus docs"); break; case DBUS_REQUEST_NAME_REPLY_EXISTS: break; case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: g_warning ("this should not happen"); owner = TRUE; break; case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: owner = TRUE; break; } } if (owner) { connection->names = nul_array_append ( connection->names, g_strdup (name)); } return owner; } typedef struct reply_info { message_func_t func; gpointer data; connection_t *connection; } reply_info_t; static void dbus_int_handle_reply (DBusPendingCall *pending, gpointer data) { reply_info_t *info = data; DBusMessage *reply; if (!info->func) return; reply = dbus_pending_call_steal_reply (pending); if (!reply) { /* FIXME: can this happen? */ return; } info->func (info->connection, reply, info->data); } gboolean connection_send_with_reply (connection_t *connection, DBusMessage *message, message_func_t func, gpointer data) { reply_info_t *info; DBusPendingCall *pending; dbus_connection_send_with_reply ( connection->dconnection, message, &pending, -1); if (!pending) { /* FIXME: we should do something, not sure what */ return FALSE; } else { info = g_new0 (reply_info_t, 1); info->func = func; info->data = data; dbus_pending_call_set_notify ( pending, dbus_int_handle_reply, info, g_free); return TRUE; } } void connection_send (connection_t *connection, DBusMessage *message) { connection_send_with_reply (connection, message, NULL, NULL); }