/* PipeWire * Copyright (C) 2017 Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #define NAME "dbus" struct impl { struct spa_handle handle; struct spa_dbus dbus; struct spa_log *log; struct spa_loop_utils *utils; struct spa_list connection_list; }; struct connection { struct spa_list link; struct spa_dbus_connection this; struct impl *impl; DBusConnection *conn; struct spa_source *dispatch_event; }; static void dispatch_cb(void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; if (dbus_connection_dispatch(conn->conn) == DBUS_DISPATCH_COMPLETE) spa_loop_utils_enable_idle(impl->utils, conn->dispatch_event, false); } static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata) { struct connection *c = userdata; struct impl *impl = c->impl; spa_loop_utils_enable_idle(impl->utils, c->dispatch_event, status == DBUS_DISPATCH_COMPLETE ? false : true); } static inline enum spa_io dbus_to_io(DBusWatch *watch) { enum spa_io mask; unsigned int flags; /* no watch flags for disabled watches */ if (!dbus_watch_get_enabled(watch)) return 0; flags = dbus_watch_get_flags(watch); mask = SPA_IO_HUP | SPA_IO_ERR; if (flags & DBUS_WATCH_READABLE) mask |= SPA_IO_IN; if (flags & DBUS_WATCH_WRITABLE) mask |= SPA_IO_OUT; return mask; } static inline unsigned int io_to_dbus(enum spa_io mask) { unsigned int flags = 0; if (mask & SPA_IO_IN) flags |= DBUS_WATCH_READABLE; if (mask & SPA_IO_OUT) flags |= DBUS_WATCH_WRITABLE; if (mask & SPA_IO_HUP) flags |= DBUS_WATCH_HANGUP; if (mask & SPA_IO_ERR) flags |= DBUS_WATCH_ERROR; return flags; } static void handle_io_event(void *userdata, int fd, enum spa_io mask) { DBusWatch *watch = userdata; if (!dbus_watch_get_enabled(watch)) { fprintf(stderr, "Asked to handle disabled watch: %p %i", (void *) watch, fd); return; } dbus_watch_handle(watch, io_to_dbus(mask)); } static dbus_bool_t add_watch(DBusWatch *watch, void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; struct spa_source *source; spa_log_debug(impl->log, "add watch %p %d", watch, dbus_watch_get_unix_fd(watch)); /* we dup because dbus tends to add the same fd multiple times and our epoll * implementation does not like that */ source = spa_loop_utils_add_io(impl->utils, dup(dbus_watch_get_unix_fd(watch)), dbus_to_io(watch), true, handle_io_event, watch); dbus_watch_set_data(watch, source, NULL); return TRUE; } static void remove_watch(DBusWatch *watch, void *userdata) { struct connection *conn = userdata; struct spa_source *source; if ((source = dbus_watch_get_data(watch))) spa_loop_utils_destroy_source(conn->impl->utils, source); } static void toggle_watch(DBusWatch *watch, void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; struct spa_source *source; source = dbus_watch_get_data(watch); spa_loop_utils_update_io(impl->utils, source, dbus_to_io(watch)); } struct timeout_data { struct spa_source *source; struct connection *conn; }; static void handle_timer_event(void *userdata, uint64_t expirations) { DBusTimeout *timeout = userdata; uint64_t t; struct timespec ts; struct timeout_data *data = dbus_timeout_get_data(timeout); struct connection *conn = data->conn; struct impl *impl = conn->impl; if (dbus_timeout_get_enabled(timeout)) { t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC; ts.tv_sec = t / SPA_NSEC_PER_SEC; ts.tv_nsec = t % SPA_NSEC_PER_SEC; spa_loop_utils_update_timer(impl->utils, data->source, &ts, NULL, false); dbus_timeout_handle(timeout); } } static dbus_bool_t add_timeout(DBusTimeout *timeout, void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; struct timespec ts; struct timeout_data *data; uint64_t t; if (!dbus_timeout_get_enabled(timeout)) return FALSE; data = calloc(1, sizeof(struct timeout_data)); data->conn = conn; data->source = spa_loop_utils_add_timer(impl->utils, handle_timer_event, timeout); dbus_timeout_set_data(timeout, data, NULL); t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC; ts.tv_sec = t / SPA_NSEC_PER_SEC; ts.tv_nsec = t % SPA_NSEC_PER_SEC; spa_loop_utils_update_timer(impl->utils, data->source, &ts, NULL, false); return TRUE; } static void remove_timeout(DBusTimeout *timeout, void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; struct timeout_data *data; if ((data = dbus_timeout_get_data(timeout))) { spa_loop_utils_destroy_source(impl->utils, data->source); free(data); } } static void toggle_timeout(DBusTimeout *timeout, void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; struct timeout_data *data; struct timespec ts, *tsp; data = dbus_timeout_get_data(timeout); if (dbus_timeout_get_enabled(timeout)) { uint64_t t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC; ts.tv_sec = t / SPA_NSEC_PER_SEC; ts.tv_nsec = t % SPA_NSEC_PER_SEC; tsp = &ts; } else { tsp = NULL; } spa_loop_utils_update_timer(impl->utils, data->source, tsp, NULL, false); } static void wakeup_main(void *userdata) { struct connection *this = userdata; struct impl *impl = this->impl; spa_loop_utils_enable_idle(impl->utils, this->dispatch_event, true); } static void * impl_connection_get(struct spa_dbus_connection *conn) { struct connection *this = SPA_CONTAINER_OF(conn, struct connection, this); return this->conn; } static void impl_connection_destroy(struct spa_dbus_connection *conn) { struct connection *this = SPA_CONTAINER_OF(conn, struct connection, this); struct impl *impl = this->impl; dbus_connection_close(this->conn); dbus_connection_unref(this->conn); spa_loop_utils_destroy_source(impl->utils, this->dispatch_event); spa_list_remove(&this->link); free(this); } static const struct spa_dbus_connection impl_connection = { SPA_VERSION_DBUS_CONNECTION, impl_connection_get, impl_connection_destroy, }; static struct spa_dbus_connection * impl_get_connection(struct spa_dbus *dbus, enum spa_dbus_type type) { struct impl *impl = SPA_CONTAINER_OF(dbus, struct impl, dbus); struct connection *conn; DBusError error; dbus_error_init(&error); conn = calloc(1, sizeof(struct connection)); conn->this = impl_connection; conn->impl = impl; conn->conn = dbus_bus_get_private(type, &error); if (conn->conn == NULL) goto error; conn->dispatch_event = spa_loop_utils_add_idle(impl->utils, false, dispatch_cb, conn); dbus_connection_set_exit_on_disconnect(conn->conn, false); dbus_connection_set_dispatch_status_function(conn->conn, dispatch_status, conn, NULL); dbus_connection_set_watch_functions(conn->conn, add_watch, remove_watch, toggle_watch, conn, NULL); dbus_connection_set_timeout_functions(conn->conn, add_timeout, remove_timeout, toggle_timeout, conn, NULL); dbus_connection_set_wakeup_main_function(conn->conn, wakeup_main, conn, NULL); spa_list_append(&impl->connection_list, &conn->link); return &conn->this; error: spa_log_error(impl->log, "Failed to connect to system bus: %s", error.message); dbus_error_free(&error); free(conn); return NULL; } static const struct spa_dbus impl_dbus = { SPA_VERSION_DBUS, impl_get_connection, }; static int impl_get_interface(struct spa_handle *handle, uint32_t interface_id, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (interface_id == SPA_ID_INTERFACE_DBus) *interface = &this->dbus; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { spa_return_val_if_fail(handle != NULL, -EINVAL); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; uint32_t i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; spa_list_init(&this->connection_list); this->dbus = impl_dbus; for (i = 0; i < n_support; i++) { if (support[i].type == SPA_ID_INTERFACE_Log) this->log = support[i].data; else if (support[i].type == SPA_ID_INTERFACE_LoopUtils) this->utils = support[i].data; } if (this->utils == NULL) { spa_log_error(this->log, "a LoopUtils is needed"); return -EINVAL; } spa_log_debug(this->log, NAME " %p: initialized", this); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_ID_INTERFACE_DBus,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_handle_factory dbus_factory = { SPA_VERSION_HANDLE_FACTORY, NAME, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &dbus_factory; break; default: return 0; } (*index)++; return 1; }