mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-02 09:01:50 -05:00
bluez5: add abstraction for local DBus object manager
Add framework for exposing local objects via DBus object manager interfaces.
This commit is contained in:
parent
3bfee14d7f
commit
57956ad100
3 changed files with 876 additions and 1 deletions
686
spa/plugins/bluez5/dbus-manager.c
Normal file
686
spa/plugins/bluez5/dbus-manager.c
Normal file
|
|
@ -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 <errno.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
|
||||
#include <spa/support/log.h>
|
||||
#include <spa/utils/hook.h>
|
||||
#include <spa/utils/list.h>
|
||||
#include <spa/utils/string.h>
|
||||
#include <spa/utils/result.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
188
spa/plugins/bluez5/dbus-manager.h
Normal file
188
spa/plugins/bluez5/dbus-manager.h
Normal file
|
|
@ -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 <dbus/dbus.h>
|
||||
|
||||
#include <spa/utils/list.h>
|
||||
#include <spa/support/log.h>
|
||||
|
||||
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
|
||||
|
|
@ -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']
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue