From 42e463f726acc895b0192bf1db3e7fb860b4853f Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 30 Oct 2022 16:47:31 +0200 Subject: [PATCH] bluez5: add abstraction for remote DBus objects Add framework for enumerating remote objects via DBus object manager interfaces. --- spa/plugins/bluez5/dbus-monitor.c | 698 ++++++++++++++++++++++++++++++ spa/plugins/bluez5/dbus-monitor.h | 139 ++++++ spa/plugins/bluez5/meson.build | 1 + 3 files changed, 838 insertions(+) create mode 100644 spa/plugins/bluez5/dbus-monitor.c create mode 100644 spa/plugins/bluez5/dbus-monitor.h diff --git a/spa/plugins/bluez5/dbus-monitor.c b/spa/plugins/bluez5/dbus-monitor.c new file mode 100644 index 000000000..c315e0ed8 --- /dev/null +++ b/spa/plugins/bluez5/dbus-monitor.c @@ -0,0 +1,698 @@ +/* Spa dbus + * + * Copyright © 2022 Pauli Virtanen + * + * 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 "dbus-monitor.h" + +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT (&impl->log_topic) + +#define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" + +struct impl +{ + struct spa_dbus_monitor this; + + DBusConnection *conn; + + struct spa_log_topic log_topic; + struct spa_log *log; + + DBusPendingCall *get_managed_objects_call; + + unsigned int filters_added:1; + unsigned int objects_listed:1; + + struct spa_list objects[]; +}; + +struct object +{ + unsigned int removed:1; + unsigned int updating:1; + union { + struct spa_dbus_object this; + /* extra data follows 'this', force safer alignment */ + long double _align; + }; +}; + +static void object_emit_update(struct object *o) +{ + if (o->this.interface->update) + o->this.interface->update(&o->this); +} + +static void object_emit_remove(struct object *o) +{ + if (o->this.interface->remove) + o->this.interface->remove(&o->this); +} + +static void object_emit_property(struct object *o, const char *key, DBusMessageIter *value) +{ + if (o->this.interface->property) + o->this.interface->property(&o->this, key, value); +} + +static struct object *object_new(struct impl *impl, const struct spa_dbus_interface *iface, + struct spa_list *object_list, const char *path) +{ + struct object *o; + + spa_assert(iface->object_size >= sizeof(struct spa_dbus_object)); + + o = calloc(1, sizeof(struct object) + iface->object_size - sizeof(struct spa_dbus_object)); + if (o == NULL) + return NULL; + + o->this.path = strdup(path); + o->this.interface = iface; + o->this.user_data = impl->this.user_data; + + spa_assert(o->this.path); + + spa_list_append(object_list, &o->this.link); + + return o; +} + +static void object_remove(struct object *o) +{ + if (o->removed) + return; + + o->updating = true; + o->removed = true; + object_emit_remove(o); + spa_list_remove(&o->this.link); +} + +static void object_destroy(struct object *o) +{ + object_remove(o); + free((void *)o->this.path); + free(o); +} + +static const struct spa_dbus_interface *interface_find(struct impl *impl, const char *interface, struct spa_list **objects) +{ + const struct spa_dbus_interface *iface; + size_t i; + + for (iface = impl->this.interfaces, i = 0; iface && iface->name; ++iface, ++i) { + if (spa_streq(iface->name, interface)) { + *objects = &impl->objects[i]; + return iface; + } + } + + return NULL; +} + +static struct object *object_find(struct spa_list *object_list, const char *path) +{ + struct spa_dbus_object *object; + + spa_list_for_each(object, object_list, link) { + struct object *o = SPA_CONTAINER_OF(object, struct object, this); + + if (spa_streq(o->this.path, path)) + return o; + } + + return NULL; +} + +static int object_update_props(struct impl *impl, + struct object *o, + DBusMessageIter *props_iter, + DBusMessageIter *invalidated_iter) +{ + o->updating = true; + + if (o->this.interface->property) { + while (invalidated_iter && dbus_message_iter_get_arg_type(invalidated_iter) != DBUS_TYPE_INVALID) { + const char *key; + + dbus_message_iter_get_basic(invalidated_iter, &key); + + object_emit_property(o, key, NULL); + if (o->removed) + goto removed; + + dbus_message_iter_next(invalidated_iter); + } + + while (props_iter && dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) { + DBusMessageIter entry, value; + const char *key; + + dbus_message_iter_recurse(props_iter, &entry); + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + object_emit_property(o, key, &value); + if (o->removed) + goto removed; + + dbus_message_iter_next(props_iter); + } + } + + object_emit_update(o); + if (o->removed) + goto removed; + + o->updating = false; + return 0; + +removed: + object_destroy(o); + return 0; +} + +static void interface_added(struct impl *impl, + const char *object_path, + const char *interface_name, + DBusMessageIter *props_iter) +{ + struct object *o; + const struct spa_dbus_interface *iface; + struct spa_list *object_list; + + iface = interface_find(impl, interface_name, &object_list); + if (!iface) { + spa_log_trace(impl->log, "dbus: skip path=%s, interface=%s", + object_path, interface_name); + return; + } + + spa_log_debug(impl->log, "dbus: added path=%s, interface=%s", object_path, interface_name); + + o = object_find(object_list, object_path); + if (o == NULL) { + o = object_new(impl, iface, object_list, object_path); + if (o == NULL) { + spa_log_warn(impl->log, "can't create object: %m"); + return; + } + + } + + object_update_props(impl, o, props_iter, NULL); +} + +static void interfaces_added(struct impl *impl, DBusMessageIter *arg_iter) +{ + DBusMessageIter it[3]; + const char *object_path; + + dbus_message_iter_get_basic(arg_iter, &object_path); + dbus_message_iter_next(arg_iter); + dbus_message_iter_recurse(arg_iter, &it[0]); + + while (dbus_message_iter_get_arg_type(&it[0]) != DBUS_TYPE_INVALID) { + const char *interface_name; + + dbus_message_iter_recurse(&it[0], &it[1]); + dbus_message_iter_get_basic(&it[1], &interface_name); + dbus_message_iter_next(&it[1]); + dbus_message_iter_recurse(&it[1], &it[2]); + + interface_added(impl, object_path, interface_name, &it[2]); + + dbus_message_iter_next(&it[0]); + } +} + +static void interfaces_removed(struct impl *impl, DBusMessageIter *arg_iter) +{ + const char *object_path; + DBusMessageIter it; + + dbus_message_iter_get_basic(arg_iter, &object_path); + dbus_message_iter_next(arg_iter); + dbus_message_iter_recurse(arg_iter, &it); + + while (dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_INVALID) { + const char *interface_name; + const struct spa_dbus_interface *iface; + struct spa_list *object_list; + + dbus_message_iter_get_basic(&it, &interface_name); + + iface = interface_find(impl, interface_name, &object_list); + if (iface) { + struct object *o; + + spa_log_debug(impl->log, "dbus: removed path=%s, interface=%s", + object_path, interface_name); + + o = object_find(object_list, object_path); + if (o) + object_destroy(o); + } else { + spa_log_trace(impl->log, "dbus: skip removed path=%s, interface=%s", + object_path, interface_name); + } + + dbus_message_iter_next(&it); + } +} + +static void get_managed_objects_reply(DBusPendingCall *pending, void *user_data) +{ + struct impl *impl = user_data; + DBusMessage *r; + DBusMessageIter it[6]; + + spa_assert(pending == impl->get_managed_objects_call); + impl->get_managed_objects_call = NULL; + + r = dbus_pending_call_steal_reply(pending); + dbus_pending_call_unref(pending); + + if (r == NULL) + return; + + if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { + spa_log_warn(impl->log, "BlueZ D-Bus ObjectManager not available"); + goto done; + } + + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(impl->log, "GetManagedObjects() failed: %s", + dbus_message_get_error_name(r)); + goto done; + } + + if (!dbus_message_iter_init(r, &it[0]) || + !spa_streq(dbus_message_get_signature(r), "a{oa{sa{sv}}}")) { + spa_log_error(impl->log, "Invalid reply signature for GetManagedObjects()"); + goto done; + } + + /* Add fake object representing the service itself */ + interface_added(impl, impl->this.path, SPA_DBUS_MONITOR_NAME_OWNER_INTERFACE, NULL); + + dbus_message_iter_recurse(&it[0], &it[1]); + + while (dbus_message_iter_get_arg_type(&it[1]) != DBUS_TYPE_INVALID) { + dbus_message_iter_recurse(&it[1], &it[2]); + + interfaces_added(impl, &it[2]); + + dbus_message_iter_next(&it[1]); + } + + impl->objects_listed = true; + +done: + dbus_message_unref(r); + return; +} + +static int get_managed_objects(struct impl *impl) +{ + DBusMessage *m = NULL; + DBusPendingCall *call; + int res = 0; + + if (impl->objects_listed || impl->get_managed_objects_call) + return 0; + + m = dbus_message_new_method_call(impl->this.service, + impl->this.path, + DBUS_INTERFACE_OBJECT_MANAGER, + "GetManagedObjects"); + if (m == NULL) + return -ENOMEM; + + dbus_message_set_auto_start(m, false); + + if (!dbus_connection_send_with_reply(impl->conn, m, &call, -1)) { + res = -EIO; + goto done; + } + + dbus_pending_call_set_notify(call, get_managed_objects_reply, impl, NULL); + impl->get_managed_objects_call = call; + +done: + if (m) + dbus_message_unref(m); + + return res; +} + + +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *user_data) +{ + struct impl *impl = user_data; + DBusError err; + + dbus_error_init(&err); + + if (dbus_message_is_signal(m, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { + const char *name, *old_owner, *new_owner; + bool has_old_owner, has_new_owner; + + spa_log_debug(impl->log, "dbus: name owner changed %s", dbus_message_get_path(m)); + + if (!dbus_message_get_args(m, &err, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID)) { + spa_log_error(impl->log, + "Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", + err.message); + goto done; + } + + if (!spa_streq(name, impl->this.service)) + goto done; + + has_old_owner = old_owner && *old_owner; + has_new_owner = new_owner && *new_owner; + + if (has_old_owner) + spa_log_debug(impl->log, "dbus: %s disappeared", impl->this.service); + + if (has_old_owner || has_new_owner) { + size_t i; + const struct spa_dbus_interface *iface; + + impl->objects_listed = false; + + for (iface = impl->this.interfaces, i = 0; iface && iface->name; ++iface, ++i) { + struct spa_dbus_object *object; + + spa_list_consume(object, &impl->objects[i], link) { + struct object *o = SPA_CONTAINER_OF(object, struct object, this); + + object_destroy(o); + } + } + } + + if (has_new_owner) { + spa_log_debug(impl->log, "dbus: %s appeared", impl->this.service); + get_managed_objects(impl); + } + } else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesAdded")) { + DBusMessageIter it; + + spa_log_debug(impl->log, "dbus: interfaces added on path=%s", dbus_message_get_path(m)); + + if (!impl->objects_listed) + goto done; + + if (!dbus_message_iter_init(m, &it) || !spa_streq(dbus_message_get_signature(m), "oa{sa{sv}}")) { + spa_log_error(impl->log, "Invalid signature found in InterfacesAdded"); + goto done; + } + + interfaces_added(impl, &it); + } else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesRemoved")) { + DBusMessageIter it; + + spa_log_debug(impl->log, "dbus: interfaces removed on path=%s", dbus_message_get_path(m)); + + if (!impl->objects_listed) + goto done; + + if (!dbus_message_iter_init(m, &it) || !spa_streq(dbus_message_get_signature(m), "oas")) { + spa_log_error(impl->log, "Invalid signature found in InterfacesRemoved"); + goto done; + } + + interfaces_removed(impl, &it); + } else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged")) { + DBusMessageIter args, props, invalidated; + const char *interface_name, *path; + const struct spa_dbus_interface *iface; + struct spa_list *object_list; + + if (!impl->objects_listed) + goto done; + + if (!dbus_message_iter_init(m, &args) || + !spa_streq(dbus_message_get_signature(m), "sa{sv}as")) { + spa_log_error(impl->log, "Invalid signature found in PropertiesChanged"); + goto done; + } + path = dbus_message_get_path(m); + + dbus_message_iter_get_basic(&args, &interface_name); + dbus_message_iter_next(&args); + dbus_message_iter_recurse(&args, &props); + dbus_message_iter_next(&args); + dbus_message_iter_recurse(&args, &invalidated); + + iface = interface_find(impl, interface_name, &object_list); + if (iface) { + struct object *o; + + o = object_find(object_list, path); + if (o == NULL) { + spa_log_debug(impl->log, "Properties changed in unknown object %s", + path); + goto done; + } + + spa_log_debug(impl->log, "dbus: properties changed in path=%s", path); + object_update_props(impl, o, &props, &invalidated); + } + } + +done: + dbus_error_free(&err); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int add_filters(struct impl *impl) +{ + DBusError err; + char rule[1024]; + const struct spa_dbus_interface *iface; + + if (impl->filters_added) + return 0; + + if (!dbus_connection_add_filter(impl->conn, filter_cb, impl, NULL)) { + spa_log_error(impl->log, "failed to add DBus filter"); + return -EIO; + } + + impl->filters_added = true; + + dbus_error_init(&err); + + spa_scnprintf(rule, sizeof(rule), + "type='signal',sender='org.freedesktop.DBus'," + "interface='org.freedesktop.DBus',member='NameOwnerChanged'," + "arg0='%s'", + impl->this.service); + spa_log_trace(impl->log, "add match: %s", rule); + dbus_bus_add_match(impl->conn, rule, &err); + if (dbus_error_is_set(&err)) + goto fail; + + spa_scnprintf(rule, sizeof(rule), + "type='signal',sender='%s'," + "interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'", + impl->this.service); + spa_log_trace(impl->log, "add match: %s", rule); + dbus_bus_add_match(impl->conn, rule, &err); + if (dbus_error_is_set(&err)) + goto fail; + + spa_scnprintf(rule, sizeof(rule), + "type='signal',sender='%s'," + "interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'", + impl->this.service); + spa_log_trace(impl->log, "add match: %s", rule); + dbus_bus_add_match(impl->conn, rule, &err); + if (dbus_error_is_set(&err)) + goto fail; + + for (iface = impl->this.interfaces; iface && iface->name; ++iface) { + spa_scnprintf(rule, sizeof(rule), + "type='signal',sender='%s'," + "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'," + "arg0='%s'", + impl->this.service, iface->name); + spa_log_trace(impl->log, "add match: %s", rule); + dbus_bus_add_match(impl->conn, rule, &err); + if (dbus_error_is_set(&err)) + goto fail; + } + + dbus_error_free(&err); + return 0; + +fail: + spa_log_error(impl->log, "failed to add DBus match: %s", err.message); + dbus_error_free(&err); + return -EIO; +} + +struct spa_dbus_monitor *spa_dbus_monitor_new(DBusConnection *conn, + const char *service, + const char *path, + const struct spa_dbus_interface *interfaces, + struct spa_log *log, + void *user_data) +{ + struct impl *impl; + const struct spa_dbus_interface *iface; + size_t num_interfaces = 0, i; + + for (iface = interfaces; iface && iface->name; ++iface) { + spa_assert(iface->object_size >= sizeof(struct spa_dbus_object)); + ++num_interfaces; + } + + impl = calloc(1, sizeof(struct impl) + sizeof(struct spa_list) * num_interfaces); + if (impl == NULL) + return NULL; + + dbus_connection_ref(conn); + + impl->conn = conn; + impl->this.service = strdup(service); + impl->this.path = strdup(path); + impl->this.interfaces = interfaces; + impl->this.user_data = user_data; + + impl->log = log; + + impl->log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.dbus"); + spa_log_topic_init(impl->log, &impl->log_topic); + + for (i = 0; i < num_interfaces; ++i) + spa_list_init(&impl->objects[i]); + + spa_assert(impl->this.service); + spa_assert(impl->this.path); + + add_filters(impl); + get_managed_objects(impl); + + return &impl->this; +} + +void spa_dbus_monitor_destroy(struct spa_dbus_monitor *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + const struct spa_dbus_interface *iface; + struct spa_dbus_object *object; + size_t i; + + if (impl->filters_added) { + dbus_connection_remove_filter(impl->conn, filter_cb, impl); + impl->filters_added = false; + } + + if (impl->get_managed_objects_call) { + dbus_pending_call_cancel(impl->get_managed_objects_call); + dbus_pending_call_unref(impl->get_managed_objects_call); + impl->get_managed_objects_call = NULL; + } + + for (iface = impl->this.interfaces, i = 0; iface && iface->name; ++iface, ++i) { + spa_list_consume(object, &impl->objects[i], link) { + struct object *o = SPA_CONTAINER_OF(object, struct object, this); + + object_destroy(o); + } + } + + dbus_connection_unref(impl->conn); + + free((void *)impl->this.service); + free((void *)impl->this.path); + free(impl); +} + +struct spa_dbus_object *spa_dbus_monitor_find(struct spa_dbus_monitor *this, const char *path, const char *interface) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + struct object *o; + const struct spa_dbus_interface *iface; + struct spa_list *object_list; + + if (path == NULL) + return NULL; + + spa_assert(interface); + + iface = interface_find(impl, interface, &object_list); + if (!iface) + return NULL; + + o = object_find(object_list, path); + if (o) + return &o->this; + + return NULL; +} + +void spa_dbus_monitor_ignore_object(struct spa_dbus_monitor *this, struct spa_dbus_object *object) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + struct object *o = SPA_CONTAINER_OF(object, struct object, this); + + spa_log_trace(impl->log, "ignore path=%s", o->this.path); + + if (o->updating) + object_remove(o); + else + object_destroy(o); +} + +struct spa_list *spa_dbus_monitor_object_list(struct spa_dbus_monitor *this, const char *interface) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + const struct spa_dbus_interface *iface; + struct spa_list *object_list; + + iface = interface_find(impl, interface, &object_list); + if (!iface) + return NULL; + + return object_list; +} diff --git a/spa/plugins/bluez5/dbus-monitor.h b/spa/plugins/bluez5/dbus-monitor.h new file mode 100644 index 000000000..b59ed7177 --- /dev/null +++ b/spa/plugins/bluez5/dbus-monitor.h @@ -0,0 +1,139 @@ +/* Spa dbus + * + * Copyright © 2022 Pauli Virtanen + * + * 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. + */ + +#ifndef SPA_DBUS_MONITOR_H_ +#define SPA_DBUS_MONITOR_H_ + +#include + +#include +#include + +struct spa_dbus_object; + +/** + * DBus interface specification. + */ +struct spa_dbus_interface { + /** DBus interface name. */ + const char *name; + + /** Size of object struct. Must be at least sizeof(struct spa_dbus_object). */ + const size_t object_size; + + /** Property value updated. */ + void (*property) (struct spa_dbus_object *object, const char *key, DBusMessageIter *value); + + /** Interface at the object path added, or property updates complete. */ + void (*update) (struct spa_dbus_object *object); + + /** + * Interface at the object path removed. + * The object will be deallocated after this, so any associated data, + * for example in a custom object struct, can be freed in this hook. + */ + void (*remove) (struct spa_dbus_object *object); +}; + +/** + * DBus object instance, for one interface. + * + * A custom object struct can be also used for each interface, specified + * as + * + * struct my_dbus_object { + * struct spa_dbus_object object; + * int some_extra_value; + * } + * + * The struct will be zero-initialized when first allocated. The object + * instances are owned by spa_dbus_monitor and allocated and freed by it. + */ +struct spa_dbus_object +{ + struct spa_list link; /**< Link in interface's object list */ + const char *path; /**< DBus object path */ + const struct spa_dbus_interface *interface; /**< The interface of the object */ + void *user_data; /**< Pointer passed in spa_dbus_monitor_new */ +}; + +/** DBus object monitor */ +struct spa_dbus_monitor +{ + const char *service; /**< Service name */ + const char *path; /**< Object path */ + const struct spa_dbus_interface *interfaces; /**< Monitored interfaces + * (zero-terminated) */ + void *user_data; /**< Pointer passed in spa_dbus_monitor_new */ +}; + +/** + * Non-DBus interface type representing the monitored service itself. + * Can be used to track NameOwner events. + */ +#define SPA_DBUS_MONITOR_NAME_OWNER_INTERFACE "org.freedesktop.pipewire.spa.dbus.monitor.owner" + +/** + * Create new object monitor. + * + * \param conn DBus connection + * \param service DBus service name to monitor + * \param path Object path to monitor + * \param interfaces Zero-terminated array of interfaces to monitor. Corresponding objects + * will be created. + * \param log Log to output to + * \param user_data User data to set in object instances. + */ +struct spa_dbus_monitor *spa_dbus_monitor_new(DBusConnection *conn, + const char *service, + const char *path, + const struct spa_dbus_interface *interfaces, + struct spa_log *log, + void *user_data); + +/** + * Destroy object monitor and all interface objects owned by it. + */ +void spa_dbus_monitor_destroy(struct spa_dbus_monitor *monitor); + +/** Find interface object by name */ +struct spa_dbus_object *spa_dbus_monitor_find(struct spa_dbus_monitor *monitor, + const char *path, const char *interface); + +/** Destroy an object, and don't receive further updates concerning it. */ +void spa_dbus_monitor_ignore_object(struct spa_dbus_monitor *monitor, + struct spa_dbus_object *object); + +/** + * Get the object list for the named interface. + * + * The returned list is not mutable. + * + * \param monitor The DBus monitor. + * \param interface Interface name. Must be one of those monitored. + */ +struct spa_list *spa_dbus_monitor_object_list(struct spa_dbus_monitor *monitor, + const char *interface); + +#endif diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index fcfc1852d..1b3955274 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -30,6 +30,7 @@ bluez5_sources = [ 'bluez5-dbus.c', 'hci.c', 'dbus-manager.c', + 'dbus-monitor.c', ] bluez5_data = ['bluez-hardware.conf']