/* PipeWire * * Copyright © 2018 Wim Taymans * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #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 uint32_t dbus_to_io(DBusWatch *watch) { uint32_t 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(uint32_t 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, uint32_t 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(void *object, enum spa_dbus_type type) { struct impl *impl = object; 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_methods impl_dbus = { SPA_VERSION_DBUS_METHODS, .get_connection = impl_get_connection, }; static int impl_get_interface(struct spa_handle *handle, uint32_t type, 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 (type == SPA_TYPE_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.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_DBus, SPA_VERSION_DBUS, &impl_dbus, this); for (i = 0; i < n_support; i++) { switch (support[i].type) { case SPA_TYPE_INTERFACE_Log: this->log = support[i].data; break; case SPA_TYPE_INTERFACE_LoopUtils: this->utils = support[i].data; break; } } 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_TYPE_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, }; SPA_EXPORT 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; }