diff options
Diffstat (limited to 'src/dbus.c')
-rw-r--r-- | src/dbus.c | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/src/dbus.c b/src/dbus.c new file mode 100644 index 00000000..a1abbd54 --- /dev/null +++ b/src/dbus.c @@ -0,0 +1,404 @@ +/* + * Copyright © 2013 David Herrmann <dh.herrmann@gmail.com> + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * DBus Helpers + * This file contains the dbus mainloop integration and several helpers to + * make lowlevel libdbus access easier. + */ + +#include "config.h" + +#include <dbus/dbus.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/eventfd.h> +#include <sys/timerfd.h> +#include <unistd.h> +#include <wayland-server.h> + +#include "compositor.h" +#include "dbus.h" + +/* + * DBus Mainloop Integration + * weston_dbus_bind() and weston_dbus_unbind() allow to bind an existing + * DBusConnection to an existing wl_event_loop object. All dbus dispatching + * is then nicely integrated into the wayland event loop. + * Note that this only provides basic watch and timeout dispatching. No + * remote thread wakeup, signal handling or other dbus insanity is supported. + * This is fine as long as you don't use any of the deprecated libdbus + * interfaces (like waking up remote threads..). There is really no rational + * reason to support these. + */ + +static int weston_dbus_dispatch_watch(int fd, uint32_t mask, void *data) +{ + DBusWatch *watch = data; + uint32_t flags = 0; + + if (dbus_watch_get_enabled(watch)) { + if (mask & WL_EVENT_READABLE) + flags |= DBUS_WATCH_READABLE; + if (mask & WL_EVENT_WRITABLE) + flags |= DBUS_WATCH_WRITABLE; + if (mask & WL_EVENT_HANGUP) + flags |= DBUS_WATCH_HANGUP; + if (mask & WL_EVENT_ERROR) + flags |= DBUS_WATCH_ERROR; + + dbus_watch_handle(watch, flags); + } + + return 0; +} + +static dbus_bool_t weston_dbus_add_watch(DBusWatch *watch, void *data) +{ + struct wl_event_loop *loop = data; + struct wl_event_source *s; + int fd; + uint32_t mask = 0, flags; + + if (dbus_watch_get_enabled(watch)) { + flags = dbus_watch_get_flags(watch); + if (flags & DBUS_WATCH_READABLE) + mask |= WL_EVENT_READABLE; + if (flags & DBUS_WATCH_WRITABLE) + mask |= WL_EVENT_WRITABLE; + } + + fd = dbus_watch_get_unix_fd(watch); + s = wl_event_loop_add_fd(loop, fd, mask, weston_dbus_dispatch_watch, + watch); + if (!s) + return FALSE; + + dbus_watch_set_data(watch, s, NULL); + return TRUE; +} + +static void weston_dbus_remove_watch(DBusWatch *watch, void *data) +{ + struct wl_event_source *s; + + s = dbus_watch_get_data(watch); + if (!s) + return; + + wl_event_source_remove(s); +} + +static void weston_dbus_toggle_watch(DBusWatch *watch, void *data) +{ + struct wl_event_source *s; + uint32_t mask = 0, flags; + + s = dbus_watch_get_data(watch); + if (!s) + return; + + if (dbus_watch_get_enabled(watch)) { + flags = dbus_watch_get_flags(watch); + if (flags & DBUS_WATCH_READABLE) + mask |= WL_EVENT_READABLE; + if (flags & DBUS_WATCH_WRITABLE) + mask |= WL_EVENT_WRITABLE; + } + + wl_event_source_fd_update(s, mask); +} + +static int weston_dbus_dispatch_timeout(void *data) +{ + DBusTimeout *timeout = data; + + if (dbus_timeout_get_enabled(timeout)) + dbus_timeout_handle(timeout); + + return 0; +} + +static int weston_dbus_adjust_timeout(DBusTimeout *timeout, + struct wl_event_source *s) +{ + int64_t t = 0; + + if (dbus_timeout_get_enabled(timeout)) + t = dbus_timeout_get_interval(timeout); + + return wl_event_source_timer_update(s, t); +} + +static dbus_bool_t weston_dbus_add_timeout(DBusTimeout *timeout, void *data) +{ + struct wl_event_loop *loop = data; + struct wl_event_source *s; + int r; + + s = wl_event_loop_add_timer(loop, weston_dbus_dispatch_timeout, + timeout); + if (!s) + return FALSE; + + r = weston_dbus_adjust_timeout(timeout, s); + if (r < 0) { + wl_event_source_remove(s); + return FALSE; + } + + dbus_timeout_set_data(timeout, s, NULL); + return TRUE; +} + +static void weston_dbus_remove_timeout(DBusTimeout *timeout, void *data) +{ + struct wl_event_source *s; + + s = dbus_timeout_get_data(timeout); + if (!s) + return; + + wl_event_source_remove(s); +} + +static void weston_dbus_toggle_timeout(DBusTimeout *timeout, void *data) +{ + struct wl_event_source *s; + + s = dbus_timeout_get_data(timeout); + if (!s) + return; + + weston_dbus_adjust_timeout(timeout, s); +} + +static int weston_dbus_dispatch(int fd, uint32_t mask, void *data) +{ + DBusConnection *c = data; + int r; + + do { + r = dbus_connection_dispatch(c); + if (r == DBUS_DISPATCH_COMPLETE) + r = 0; + else if (r == DBUS_DISPATCH_DATA_REMAINS) + r = -EAGAIN; + else if (r == DBUS_DISPATCH_NEED_MEMORY) + r = -ENOMEM; + else + r = -EIO; + } while (r == -EAGAIN); + + if (r) + weston_log("cannot dispatch dbus events: %d\n", r); + + return 0; +} + +static int weston_dbus_bind(struct wl_event_loop *loop, DBusConnection *c, + struct wl_event_source **ctx_out) +{ + bool b; + int r, fd; + + /* Idle events cannot reschedule themselves, therefore we use a dummy + * event-fd and mark it for post-dispatch. Hence, the dbus + * dispatcher is called after every dispatch-round. + * This is required as dbus doesn't allow dispatching events from + * within its own event sources. */ + fd = eventfd(0, EFD_CLOEXEC); + if (fd < 0) + return -errno; + + *ctx_out = wl_event_loop_add_fd(loop, fd, 0, weston_dbus_dispatch, c); + close(fd); + + if (!*ctx_out) + return -ENOMEM; + + wl_event_source_check(*ctx_out); + + b = dbus_connection_set_watch_functions(c, + weston_dbus_add_watch, + weston_dbus_remove_watch, + weston_dbus_toggle_watch, + loop, + NULL); + if (!b) { + r = -ENOMEM; + goto error; + } + + b = dbus_connection_set_timeout_functions(c, + weston_dbus_add_timeout, + weston_dbus_remove_timeout, + weston_dbus_toggle_timeout, + loop, + NULL); + if (!b) { + r = -ENOMEM; + goto error; + } + + dbus_connection_ref(c); + return 0; + +error: + dbus_connection_set_timeout_functions(c, NULL, NULL, NULL, + NULL, NULL); + dbus_connection_set_watch_functions(c, NULL, NULL, NULL, + NULL, NULL); + wl_event_source_remove(*ctx_out); + *ctx_out = NULL; + return r; +} + +static void weston_dbus_unbind(DBusConnection *c, struct wl_event_source *ctx) +{ + dbus_connection_set_timeout_functions(c, NULL, NULL, NULL, + NULL, NULL); + dbus_connection_set_watch_functions(c, NULL, NULL, NULL, + NULL, NULL); + dbus_connection_unref(c); + wl_event_source_remove(ctx); +} + +/* + * Convenience Helpers + * Several convenience helpers are provided to make using dbus in weston + * easier. We don't use any of the gdbus or qdbus helpers as they pull in + * huge dependencies and actually are quite awful to use. Instead, we only + * use the basic low-level libdbus library. + */ + +int weston_dbus_open(struct wl_event_loop *loop, DBusBusType bus, + DBusConnection **out, struct wl_event_source **ctx_out) +{ + DBusConnection *c; + int r; + + /* Ihhh, global state.. stupid dbus. */ + dbus_connection_set_change_sigpipe(FALSE); + + /* This is actually synchronous. It blocks for some authentication and + * setup. We just trust the dbus-server here and accept this blocking + * call. There is no real reason to complicate things further and make + * this asynchronous/non-blocking. A context should be created during + * thead/process/app setup, so blocking calls should be fine. */ + c = dbus_bus_get_private(bus, NULL); + if (!c) + return -EIO; + + dbus_connection_set_exit_on_disconnect(c, FALSE); + + r = weston_dbus_bind(loop, c, ctx_out); + if (r < 0) + goto error; + + *out = c; + return r; + +error: + dbus_connection_close(c); + dbus_connection_unref(c); + return r; +} + +void weston_dbus_close(DBusConnection *c, struct wl_event_source *ctx) +{ + weston_dbus_unbind(c, ctx); + dbus_connection_close(c); + dbus_connection_unref(c); +} + +int weston_dbus_add_match(DBusConnection *c, const char *format, ...) +{ + DBusError err; + int r; + va_list list; + char *str; + + va_start(list, format); + r = vasprintf(&str, format, list); + va_end(list); + + if (r < 0) + return -ENOMEM; + + dbus_error_init(&err); + dbus_bus_add_match(c, str, &err); + free(str); + if (dbus_error_is_set(&err)) { + dbus_error_free(&err); + return -EIO; + } + + return 0; +} + +int weston_dbus_add_match_signal(DBusConnection *c, const char *sender, + const char *iface, const char *member, + const char *path) +{ + return weston_dbus_add_match(c, + "type='signal'," + "sender='%s'," + "interface='%s'," + "member='%s'," + "path='%s'", + sender, iface, member, path); +} + +void weston_dbus_remove_match(DBusConnection *c, const char *format, ...) +{ + int r; + va_list list; + char *str; + + va_start(list, format); + r = vasprintf(&str, format, list); + va_end(list); + + if (r < 0) + return; + + dbus_bus_remove_match(c, str, NULL); + free(str); +} + +void weston_dbus_remove_match_signal(DBusConnection *c, const char *sender, + const char *iface, const char *member, + const char *path) +{ + return weston_dbus_remove_match(c, + "type='signal'," + "sender='%s'," + "interface='%s'," + "member='%s'," + "path='%s'", + sender, iface, member, path); +} |