From 57956ad100e649b8db03153e89f8ad9bb2839f21 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 30 Oct 2022 16:46:38 +0200 Subject: [PATCH] bluez5: add abstraction for local DBus object manager Add framework for exposing local objects via DBus object manager interfaces. --- spa/plugins/bluez5/dbus-manager.c | 686 ++++++++++++++++++++++++++++++ spa/plugins/bluez5/dbus-manager.h | 188 ++++++++ spa/plugins/bluez5/meson.build | 3 +- 3 files changed, 876 insertions(+), 1 deletion(-) create mode 100644 spa/plugins/bluez5/dbus-manager.c create mode 100644 spa/plugins/bluez5/dbus-manager.h diff --git a/spa/plugins/bluez5/dbus-manager.c b/spa/plugins/bluez5/dbus-manager.c new file mode 100644 index 000000000..fe79b11a5 --- /dev/null +++ b/spa/plugins/bluez5/dbus-manager.c @@ -0,0 +1,686 @@ +/* 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 + +#include "dbus-manager.h" + +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT (&impl->log_topic) + +#define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" + +struct object; + +struct impl +{ + struct spa_dbus_object_manager this; + + DBusConnection *conn; + + struct spa_log_topic log_topic; + struct spa_log *log; + + struct object *root; +}; + +struct object +{ + struct impl *impl; + union { + struct spa_dbus_local_object this; + /* extra data follows 'this', force safer alignment */ + long double _align; + }; +}; + +const struct spa_dbus_property *object_interface_get_property(struct object *o, + const struct spa_dbus_local_interface *iface, const char *name) +{ + const struct spa_dbus_property *prop; + + for (prop = iface->properties; prop && prop->name; ++prop) { + if (spa_streq(prop->name, name) && (prop->exists == NULL || prop->exists(&o->this))) + return prop; + } + + return NULL; +} + +const struct spa_dbus_local_interface *object_get_interface(struct object *o, const char *interface) +{ + const struct spa_dbus_local_interface *iface; + + for (iface = o->this.interfaces; iface && iface->name; ++iface) + if (spa_streq(interface, iface->name)) + return iface; + + return NULL; +} + +static DBusMessage *object_properties_get(struct object *o, DBusMessage *m) +{ + struct impl *impl = o->impl; + const char *interface, *name; + const struct spa_dbus_local_interface *iface; + const struct spa_dbus_property *prop; + DBusMessage *r; + DBusMessageIter i, v; + int res; + + if (!dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"); + + iface = object_get_interface(o, interface); + if (!iface) + return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, + "No such interface"); + + prop = object_interface_get_property(o, iface, name); + if (!prop) + return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_PROPERTY, + "No such property"); + + if (!prop->get) + return dbus_message_new_error(m, DBUS_ERROR_FAILED, + "Write-only property"); + + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, prop->signature, &v); + + if ((res = prop->get(&o->this, &v)) < 0) { + spa_log_debug(impl->log, "failed to get property %s value: %s", + prop->name, spa_strerror(res)); + dbus_message_unref(r); + return dbus_message_new_error(m, DBUS_ERROR_FAILED, + "Failed to get property"); + } + + dbus_message_iter_close_container(&i, &v); + + return r; +} + +static int object_append_properties(struct object *o, const struct spa_dbus_property *properties, DBusMessageIter *i) +{ + struct impl *impl = o->impl; + DBusMessageIter d; + const struct spa_dbus_property *prop; + + dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "{sv}", &d); + + for (prop = properties; prop && prop->name; ++prop) { + DBusMessageIter e, v; + int res; + + if (!(prop->exists == NULL || prop->exists(&o->this)) || !prop->get) + continue; + + dbus_message_iter_open_container(&d, DBUS_TYPE_DICT_ENTRY, NULL, &e); + dbus_message_iter_append_basic(&e, DBUS_TYPE_STRING, &prop->name); + dbus_message_iter_open_container(&e, DBUS_TYPE_VARIANT, prop->signature, &v); + + if ((res = prop->get(&o->this, &v)) < 0) { + spa_log_debug(impl->log, "failed to get property %s value: %s", + prop->name, spa_strerror(res)); + return res; + } + + dbus_message_iter_close_container(&e, &v); + dbus_message_iter_close_container(&d, &e); + } + + dbus_message_iter_close_container(i, &d); + + return 0; +} + +static DBusMessage *object_properties_get_all(struct object *o, DBusMessage *m) +{ + const char *interface; + const struct spa_dbus_local_interface *iface; + DBusMessageIter i; + DBusMessage *r; + + if (!dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_INVALID)) + return dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"); + + iface = object_get_interface(o, interface); + if (!iface) + return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, + "No such interface"); + + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + + dbus_message_iter_init_append(r, &i); + + if (object_append_properties(o, iface->properties, &i) < 0) { + dbus_message_unref(r); + return dbus_message_new_error(m, DBUS_ERROR_FAILED, + "Failed to get properties"); + } + + return r; +} + +static DBusMessage *object_properties_set(struct object *o, DBusMessage *m) +{ + struct impl *impl = o->impl; + const char *interface, *name; + const struct spa_dbus_local_interface *iface; + const struct spa_dbus_property *prop; + DBusMessageIter it, value; + char *value_signature; + bool valid_signature; + int res; + + if (!dbus_message_has_signature(m, "ssv")) + return NULL; + + dbus_message_iter_init(m, &it); + dbus_message_iter_get_basic(&it, &interface); + dbus_message_iter_next(&it); + dbus_message_iter_get_basic(&it, &name); + dbus_message_iter_next(&it); + + iface = object_get_interface(o, interface); + if (!iface) + return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, + "No such interface"); + + prop = object_interface_get_property(o, iface, name); + if (!prop) + return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_PROPERTY, + "No such property"); + + if (prop->set == NULL) + return dbus_message_new_error(m, DBUS_ERROR_PROPERTY_READ_ONLY, + "Read-only property"); + + dbus_message_iter_recurse(&it, &value); + + value_signature = dbus_message_iter_get_signature(&value); + valid_signature = spa_streq(prop->signature, value_signature); + dbus_free(value_signature); + if (!valid_signature) + return dbus_message_new_error(m, DBUS_ERROR_INVALID_SIGNATURE, + "Invalid value signature"); + + if ((res = prop->set(&o->this, &value)) < 0) { + spa_log_debug(impl->log, "failed to set property %s value: %s", + prop->name, spa_strerror(res)); + return dbus_message_new_error(m, DBUS_ERROR_FAILED, + "Failed to set property"); + } + + return dbus_message_new_method_return(m); +} + +static DBusHandlerResult object_handler(DBusConnection *c, DBusMessage *m, void *userdata) +{ + struct object *o = userdata; + struct impl *impl = o->impl; + const char *path, *interface, *member; + DBusMessage *r = NULL; + + path = dbus_message_get_path(m); + interface = dbus_message_get_interface(m); + member = dbus_message_get_member(m); + + spa_log_debug(impl->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); + + if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get")) { + r = object_properties_get(o, m); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "GetAll")) { + r = object_properties_get_all(o, m); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Set")) { + r = object_properties_set(o, m); + } else { + const struct spa_dbus_local_interface *iface = object_get_interface(o, interface); + bool called = false; + + if (iface) { + const struct spa_dbus_method *method; + + for (method = iface->methods; method && method->name; ++method) { + if (dbus_message_is_method_call(m, iface->name, method->name)) { + r = method->call(&o->this, m); + called = true; + break; + } + } + } + + if (!called) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (r != NULL && dbus_connection_send(impl->conn, r, NULL)) + return DBUS_HANDLER_RESULT_HANDLED; + else if (r) + dbus_message_unref(r); + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static int object_signal_interfaces_added(struct object *o) +{ + struct impl *impl = o->impl; + struct object *root = impl->root; + const struct spa_dbus_local_interface *iface; + DBusMessage *s; + DBusMessageIter i, a; + + if (root == NULL) + root = o; /* we're root, impl still initializing */ + + s = dbus_message_new_signal(root->this.path, + DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesAdded"); + if (s == NULL) + return -ENOMEM; + + dbus_message_iter_init_append(s, &i); + dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &o->this.path); + + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, "{sa{sv}}", &a); + + for (iface = o->this.interfaces; iface && iface->name; ++iface) { + DBusMessageIter e; + int res; + + spa_log_debug(impl->log, "dbus: signal add interface path=%s interface=%s", + o->this.path, iface->name); + + dbus_message_iter_open_container(&a, DBUS_TYPE_DICT_ENTRY, NULL, &e); + dbus_message_iter_append_basic(&e, DBUS_TYPE_STRING, &iface->name); + + if ((res = object_append_properties(o, iface->properties, &e)) < 0) { + dbus_message_unref(s); + return res; + } + + dbus_message_iter_close_container(&a, &e); + } + + dbus_message_iter_close_container(&i, &a); + + dbus_connection_send(impl->conn, s, NULL); + dbus_message_unref(s); + + return 0; +} + +static int object_signal_interfaces_removed(struct object *o) +{ + struct impl *impl = o->impl; + struct object *root = impl->root; + const struct spa_dbus_local_interface *iface; + DBusMessage *s; + DBusMessageIter i, a; + + spa_assert(root); + + s = dbus_message_new_signal(root->this.path, + DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesRemoved"); + if (s == NULL) + return -ENOMEM; + + dbus_message_iter_init_append(s, &i); + dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &o->this.path); + + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, "s", &a); + + for (iface = o->this.interfaces; iface && iface->name; ++iface) { + spa_log_debug(impl->log, "dbus: signal remove interface path=%s interface=%s", + o->this.path, iface->name); + + dbus_message_iter_append_basic(&a, DBUS_TYPE_STRING, &iface->name); + } + + dbus_message_iter_close_container(&i, &a); + + dbus_connection_send(impl->conn, s, NULL); + dbus_message_unref(s); + + return 0; +} + +static int object_signal_properties_changed(struct object *o, + const struct spa_dbus_local_interface *iface, + const struct spa_dbus_property *properties) +{ + struct impl *impl = o->impl; + const struct spa_dbus_property *prop; + DBusMessage *s; + DBusMessageIter i, a; + int res; + + if (properties == NULL || properties->name == NULL) { + /* nothing was changed */ + return 0; + } + + s = dbus_message_new_signal(o->this.path, + DBUS_INTERFACE_PROPERTIES, + "PropertiesChanged"); + if (s == NULL) + return -ENOMEM; + + dbus_message_iter_init_append(s, &i); + dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &iface->name); + + if ((res = object_append_properties(o, properties, &i)) < 0) { + dbus_message_unref(s); + return res; + } + + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, "s", &a); + + for (prop = properties; prop && prop->name; ++prop) { + spa_log_debug(impl->log, "dbus: signal properties changed path=%s interface=%s property=%s", + o->this.path, iface->name, prop->name); + + if (prop->exists == NULL || prop->exists(&o->this)) + continue; + dbus_message_iter_append_basic(&a, DBUS_TYPE_STRING, &prop->name); + } + + dbus_message_iter_close_container(&i, &a); + + dbus_connection_send(impl->conn, s, NULL); + dbus_message_unref(s); + + return 0; +} + +static struct object *object_new(struct impl *impl, + const char *path, + const struct spa_dbus_local_interface *interfaces, + size_t object_size, + void *user_data) +{ + static const DBusObjectPathVTable vtable = { + .message_function = object_handler, + }; + const struct spa_dbus_local_interface *iface; + struct object *o; + + o = calloc(1, sizeof(struct object) - sizeof(struct spa_dbus_local_object) + object_size); + if (o == NULL) + return NULL; + + spa_log_debug(impl->log, "dbus: register path=%s", path); + + if (!dbus_connection_register_object_path(impl->conn, path, &vtable, o)) { + free(o); + errno = EIO; + return NULL; + } + + spa_list_append(&impl->this.object_list, &o->this.link); + o->impl = impl; + o->this.path = strdup(path); + o->this.interfaces = interfaces; + o->this.user_data = user_data; + + spa_assert(o->this.path); + + for (iface = o->this.interfaces; iface && iface->name; ++iface) { + if (iface->init) + iface->init(&o->this); + } + + object_signal_interfaces_added(o); + + return o; +} + +static void object_destroy(struct object *o) +{ + struct impl *impl = o->impl; + const struct spa_dbus_local_interface *iface; + + object_signal_interfaces_removed(o); + + spa_list_remove(&o->this.link); + + for (iface = o->this.interfaces; iface && iface->name; ++iface) { + if (iface->destroy) + iface->destroy(&o->this); + } + + spa_log_debug(impl->log, "dbus: unregister path=%s", o->this.path); + + dbus_connection_unregister_object_path(impl->conn, o->this.path); + + free(o); +} + +static struct object *object_find(struct impl *impl, const char *path) +{ + struct spa_dbus_local_object *obj; + + spa_list_for_each(obj, &impl->this.object_list, link) { + struct object *o = SPA_CONTAINER_OF(obj, struct object, this); + + if (spa_streq(o->this.path, path)) + return o; + } + + return NULL; +} + +static DBusMessage *root_get_managed_objects(struct spa_dbus_local_object *object, DBusMessage *m) +{ + struct impl *impl = object->user_data; + DBusMessage *r; + DBusMessageIter i, object_array; + struct spa_dbus_local_object *obj; + + if ((r = dbus_message_new_method_return(m)) == NULL) + return NULL; + + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, "{oa{sa{sv}}}", &object_array); + + spa_list_for_each(obj, &impl->this.object_list, link) { + struct object *o = SPA_CONTAINER_OF(obj, struct object, this); + + const struct spa_dbus_local_interface *iface; + DBusMessageIter object_entry; + DBusMessageIter interface_array; + + dbus_message_iter_open_container(&object_array, DBUS_TYPE_DICT_ENTRY, NULL, &object_entry); + dbus_message_iter_append_basic(&object_entry, DBUS_TYPE_OBJECT_PATH, &o->this.path); + + dbus_message_iter_open_container(&object_entry, DBUS_TYPE_ARRAY, "{sa{sv}}", &interface_array); + + for (iface = o->this.interfaces; iface && iface->name; ++iface) { + DBusMessageIter interface_entry; + + dbus_message_iter_open_container(&interface_array, DBUS_TYPE_DICT_ENTRY, NULL, &interface_entry); + dbus_message_iter_append_basic(&interface_entry, DBUS_TYPE_STRING, &iface->name); + + if (object_append_properties(o, iface->properties, &interface_entry) < 0) { + dbus_message_unref(r); + return dbus_message_new_error(m, DBUS_ERROR_FAILED, + "Failed to get properties"); + } + + dbus_message_iter_close_container(&interface_array, &interface_entry); + } + + dbus_message_iter_close_container(&object_entry, &interface_array); + dbus_message_iter_close_container(&object_array, &object_entry); + } + + dbus_message_iter_close_container(&i, &object_array); + + return r; +} + +static const struct spa_dbus_method root_methods[] = { + { + .name = "GetManagedObjects", + .call = root_get_managed_objects, + }, + {NULL} +}; + +static const struct spa_dbus_local_interface root_interfaces[] = { + { + .name = DBUS_INTERFACE_OBJECT_MANAGER, + .methods = root_methods, + }, + {NULL} +}; + +struct spa_dbus_object_manager *spa_dbus_object_manager_new(DBusConnection *conn, const char *path, struct spa_log *log) +{ + struct impl *impl; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return NULL; + + impl->conn = conn; + impl->log = log; + + impl->log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.dbus"); + + spa_log_topic_init(impl->log, &impl->log_topic); + + spa_list_init(&impl->this.object_list); + + impl->root = object_new(impl, path, root_interfaces, sizeof(struct spa_dbus_local_object), impl); + if (impl->root == NULL) { + free(impl); + return NULL; + } + + impl->this.path = impl->root->this.path; + + dbus_connection_ref(impl->conn); + + return &impl->this; +} + +void spa_dbus_object_manager_destroy(struct spa_dbus_object_manager *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + struct spa_dbus_local_object *obj; + struct spa_list tmp; + + spa_list_init(&tmp); + spa_list_remove(&impl->root->this.link); + spa_list_append(&tmp, &impl->root->this.link); + + spa_list_consume(obj, &impl->this.object_list, link) { + struct object *o = SPA_CONTAINER_OF(obj, struct object, this); + + object_destroy(o); + } + + object_destroy(impl->root); + + dbus_connection_unref(impl->conn); + free(impl); +} + +struct spa_dbus_local_object *spa_dbus_object_manager_register(struct spa_dbus_object_manager *this, + const char *path, + const struct spa_dbus_local_interface *interfaces, + size_t object_size, void *user_data) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + struct object *o; + int root_len = strlen(this->path); + + if (!(spa_strstartswith(path, this->path) && path[root_len] == '/' && + path[root_len + 1] != '\0')) { + errno = EINVAL; + return NULL; + } + + o = object_new(impl, path, interfaces, object_size, user_data); + if (o == NULL) + return NULL; + + return &o->this; +} + +void spa_dbus_object_manager_unregister(struct spa_dbus_object_manager *this, + struct spa_dbus_local_object *object) +{ + struct object *o = SPA_CONTAINER_OF(object, struct object, this); + + object_destroy(o); +} + +struct spa_dbus_local_object *spa_dbus_object_manager_find(struct spa_dbus_object_manager *this, + const char *path) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + struct object *o; + + o = object_find(impl, path); + if (o) + return &o->this; + + return NULL; +} + + +int spa_dbus_object_manager_properties_changed(struct spa_dbus_object_manager *this, + struct spa_dbus_local_object *object, + const struct spa_dbus_local_interface *interface, + const struct spa_dbus_property *properties) +{ + struct object *o = SPA_CONTAINER_OF(object, struct object, this); + + return object_signal_properties_changed(o, interface, properties); +} diff --git a/spa/plugins/bluez5/dbus-manager.h b/spa/plugins/bluez5/dbus-manager.h new file mode 100644 index 000000000..9b5acf843 --- /dev/null +++ b/spa/plugins/bluez5/dbus-manager.h @@ -0,0 +1,188 @@ +/* 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_MANAGER_H_ +#define SPA_DBUS_MANAGER_H_ + +#include + +#include +#include + +struct spa_dbus_local_object; + +/** DBus object manager */ +struct spa_dbus_object_manager +{ + /** Root DBus object path */ + const char *path; + + /** List (non-mutable) of objects. */ + struct spa_list object_list; +}; + +/** DBus property specification a local DBus object. */ +struct spa_dbus_property +{ + const char *name; /**< Name of property */ + const char *signature; /**< DBus type signature of the value */ + + /** + * Hook to append bare DBus value to the iterator. + * If NULL, the property is considered to not be readable. + */ + int (*get)(struct spa_dbus_local_object *object, DBusMessageIter *value); + + /** + * Hook to get DBus value from the iterator, and apply it. + * If NULL, the property is considered read-only. + */ + int (*set)(struct spa_dbus_local_object *object, DBusMessageIter *value); + + /** + * Hook to check if the property currently exists. + * If NULL, the property always exists. + */ + bool (*exists)(struct spa_dbus_local_object *object); +}; + +/** DBus method specification for a local DBus object. */ +struct spa_dbus_method +{ + const char *name; /**< Name of method */ + + /** Hook to react and reply to DBus method call */ + DBusMessage *(*call)(struct spa_dbus_local_object *object, DBusMessage *m); +}; + +/** DBus interface specification for a local DBus object. */ +struct spa_dbus_local_interface +{ + /** Name of the DBus interface */ + const char *name; + + /** Array of properties, zero-terminated */ + const struct spa_dbus_property *properties; + + /** Array of methods, zero-terminated */ + const struct spa_dbus_method *methods; + + /** + * Hook called when initializing the object, before + * calling any other hooks. + */ + void (*init)(struct spa_dbus_local_object *object); + + /** + * Hook called once when interface is destroyed. + * No other hooks are called after this. + */ + void (*destroy)(struct spa_dbus_local_object *object); +}; + +/** + * DBus local object structure. + * + * One object struct exists for each registered object path. The same object + * struct may have multiple interfaces. The object structures are owned, + * allocated and freed by the object manager. + * + * A custom object struct can also be used, for example + * + * struct my_local_object { + * struct spa_dbus_local_object object; + * int my_extra_value; + * }; + * + * Its initialization and teardown can be done via the interface + * init/destroy hooks. Note that the hooks of all interfaces + * the object has are called on the same object struct. + * + * The struct size is specified in the call to + * spa_dbus_object_manager_register. + */ +struct spa_dbus_local_object +{ + struct spa_list link; /**< Link (non-mutable) to manager object list */ + const char *path; /**< DBus object path */ + + /** Zero-terminated array of the DBus interfaces of the objects */ + const struct spa_dbus_local_interface *interfaces; + + /** Pointer passed to spa_dbus_object_manager_register */ + void *user_data; +}; + +/** + * Create and register new DBus object manager at the given object path. + * + * Registers a DBus object with the object manager interface at the given path. + * + * \param conn DBus connection. + * \param path Object path to register the new manager at. + * \param log Logging output. + */ +struct spa_dbus_object_manager *spa_dbus_object_manager_new(DBusConnection *conn, const char *path, struct spa_log *log); + +/** Destroy and unregister the object manager and all objects owned by it. */ +void spa_dbus_object_manager_destroy(struct spa_dbus_object_manager *manager); + +/** + * Create and register a new DBus object under the object manager. + * + * The DBus object path must be a sub-path of the object manager path. + * + * \param manager Manager that owns the new object. + * \param path DBus object path. + * \param interfaces Zero-terminated array of interfaces for the new object. + * \param object_size Size of the object struct. Must be >= sizeof(struct spa_dbus_local_object). + * \param user_data User data pointer to set in the object. + */ +struct spa_dbus_local_object *spa_dbus_object_manager_register(struct spa_dbus_object_manager *manager, + const char *path, + const struct spa_dbus_local_interface *interfaces, + size_t object_size, void *user_data); + +/** Find previously registered local DBus object by object path */ +struct spa_dbus_local_object *spa_dbus_object_manager_find(struct spa_dbus_object_manager *manager, + const char *path); + +/** Unregister and destroy a previously registered local DBus object */ +void spa_dbus_object_manager_unregister(struct spa_dbus_object_manager *manager, + struct spa_dbus_local_object *object); + +/** + * Emit PropertiesChanged signal for a previously registered local DBus object. + * + * \param manager Object manager + * \param object The DBus object + * \param interface The interface to emit the signal for + * \param properties Zero-terminated array of properties that were changed + */ +int spa_dbus_object_manager_properties_changed(struct spa_dbus_object_manager *manager, + struct spa_dbus_local_object *object, + const struct spa_dbus_local_interface *interface, + const struct spa_dbus_property *properties); + +#endif diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index db010697f..fcfc1852d 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -28,7 +28,8 @@ bluez5_sources = [ 'player.c', 'bluez5-device.c', 'bluez5-dbus.c', - 'hci.c' + 'hci.c', + 'dbus-manager.c', ] bluez5_data = ['bluez-hardware.conf']