mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	bluez5: remove libdbus object manager wrappers
This commit is contained in:
		
							parent
							
								
									bd45f846fc
								
							
						
					
					
						commit
						8d438d26ab
					
				
					 4 changed files with 0 additions and 1764 deletions
				
			
		| 
						 | 
				
			
			@ -1,686 +0,0 @@
 | 
			
		|||
/* 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 <stdalign.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;
 | 
			
		||||
	struct spa_dbus_local_object alignas(max_align_t) this;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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 dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS,
 | 
			
		||||
				"Invalid arguments");
 | 
			
		||||
 | 
			
		||||
	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)) {
 | 
			
		||||
		dbus_message_unref(r);
 | 
			
		||||
		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);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,188 +0,0 @@
 | 
			
		|||
/* 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
 | 
			
		||||
| 
						 | 
				
			
			@ -1,717 +0,0 @@
 | 
			
		|||
/* 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 <stdalign.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 "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;
 | 
			
		||||
 | 
			
		||||
	struct spa_dbus_async_call get_managed_objects_call;
 | 
			
		||||
 | 
			
		||||
	unsigned int objects_listed:1;
 | 
			
		||||
 | 
			
		||||
	struct spa_list objects[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct object
 | 
			
		||||
{
 | 
			
		||||
	unsigned int removed:1;
 | 
			
		||||
	unsigned int updating:1;
 | 
			
		||||
	struct spa_dbus_object alignas(max_align_t) this;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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(struct spa_dbus_async_call *call, DBusMessage *r)
 | 
			
		||||
{
 | 
			
		||||
	struct impl *impl = SPA_CONTAINER_OF(call, struct impl, get_managed_objects_call);
 | 
			
		||||
	DBusMessageIter it[6];
 | 
			
		||||
 | 
			
		||||
	if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
 | 
			
		||||
		spa_log_warn(impl->log, "ObjectManager not available at path=%s",
 | 
			
		||||
			impl->this.path);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
 | 
			
		||||
		spa_log_error(impl->log, "GetManagedObjects() at %s failed: %s",
 | 
			
		||||
				impl->this.path, dbus_message_get_error_name(r));
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!dbus_message_iter_init(r, &it[0]) ||
 | 
			
		||||
			!dbus_message_has_signature(r, "a{oa{sa{sv}}}")) {
 | 
			
		||||
		spa_log_error(impl->log, "Invalid reply signature for GetManagedObjects() at %s",
 | 
			
		||||
				impl->this.path);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* 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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int get_managed_objects(struct impl *impl)
 | 
			
		||||
{
 | 
			
		||||
	DBusMessage *m;
 | 
			
		||||
 | 
			
		||||
	if (impl->objects_listed || impl->get_managed_objects_call.pending)
 | 
			
		||||
		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);
 | 
			
		||||
 | 
			
		||||
	return spa_dbus_async_call_send(&impl->get_managed_objects_call, impl->conn, m,
 | 
			
		||||
			get_managed_objects_reply);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *user_data)
 | 
			
		||||
{
 | 
			
		||||
	struct impl *impl = user_data;
 | 
			
		||||
	DBusError err = DBUS_ERROR_INIT;
 | 
			
		||||
 | 
			
		||||
	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) || !dbus_message_has_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) || !dbus_message_has_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) ||
 | 
			
		||||
				!dbus_message_has_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 = DBUS_ERROR_INIT;
 | 
			
		||||
	char rule[1024];
 | 
			
		||||
	const struct spa_dbus_interface *iface;
 | 
			
		||||
 | 
			
		||||
	if (!dbus_connection_add_filter(impl->conn, filter_cb, impl, NULL)) {
 | 
			
		||||
		spa_log_error(impl->log, "failed to add DBus filter");
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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:
 | 
			
		||||
	dbus_connection_remove_filter(impl->conn, filter_cb, impl);
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
	int res;
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
 | 
			
		||||
	if ((res = add_filters(impl)) < 0) {
 | 
			
		||||
		free((void *)impl->this.service);
 | 
			
		||||
		free((void *)impl->this.path);
 | 
			
		||||
		free(impl);
 | 
			
		||||
		errno = -res;
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dbus_connection_ref(conn);
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
 | 
			
		||||
	dbus_connection_remove_filter(impl->conn, filter_cb, impl);
 | 
			
		||||
 | 
			
		||||
	spa_dbus_async_call_cancel(&impl->get_managed_objects_call);
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void async_call_reply(DBusPendingCall *pending, void *user_data)
 | 
			
		||||
{
 | 
			
		||||
	struct spa_dbus_async_call *call = user_data;
 | 
			
		||||
	DBusMessage *r;
 | 
			
		||||
 | 
			
		||||
	spa_assert(pending == call->pending);
 | 
			
		||||
	call->pending = NULL;
 | 
			
		||||
 | 
			
		||||
	r = dbus_pending_call_steal_reply(pending);
 | 
			
		||||
	dbus_pending_call_unref(pending);
 | 
			
		||||
 | 
			
		||||
	spa_assert(call->reply != NULL);
 | 
			
		||||
 | 
			
		||||
	if (r == NULL)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	call->reply(call, r);
 | 
			
		||||
	dbus_message_unref(r);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int spa_dbus_async_call_send(struct spa_dbus_async_call *call,
 | 
			
		||||
		DBusConnection *conn, DBusMessage *msg,
 | 
			
		||||
		void (*reply)(struct spa_dbus_async_call *call, DBusMessage *reply))
 | 
			
		||||
{
 | 
			
		||||
	int res = -EIO;
 | 
			
		||||
	DBusPendingCall *pending;
 | 
			
		||||
 | 
			
		||||
	spa_assert(msg);
 | 
			
		||||
	spa_assert(conn);
 | 
			
		||||
	spa_assert(call);
 | 
			
		||||
 | 
			
		||||
	if (call->pending) {
 | 
			
		||||
		res = -EBUSY;
 | 
			
		||||
		goto done;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!dbus_connection_send_with_reply(conn, msg, &pending, -1))
 | 
			
		||||
		goto done;
 | 
			
		||||
 | 
			
		||||
	if (!dbus_pending_call_set_notify(pending, async_call_reply, call, NULL)) {
 | 
			
		||||
		dbus_pending_call_cancel(pending);
 | 
			
		||||
		dbus_pending_call_unref(pending);
 | 
			
		||||
		goto done;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	call->reply = reply;
 | 
			
		||||
	call->pending = pending;
 | 
			
		||||
	res = 0;
 | 
			
		||||
 | 
			
		||||
done:
 | 
			
		||||
	dbus_message_unref(msg);
 | 
			
		||||
	return res;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,173 +0,0 @@
 | 
			
		|||
/* 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 <dbus/dbus.h>
 | 
			
		||||
 | 
			
		||||
#include <spa/utils/list.h>
 | 
			
		||||
#include <spa/support/log.h>
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
	 * No other hooks will be called after this.
 | 
			
		||||
	 * 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);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
struct spa_dbus_async_call
 | 
			
		||||
{
 | 
			
		||||
	DBusPendingCall *pending;
 | 
			
		||||
	void (*reply)(struct spa_dbus_async_call *call, DBusMessage *reply);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Make an asynchronous DBus call.
 | 
			
		||||
 *
 | 
			
		||||
 * On successful return, call contains the pending call handle.
 | 
			
		||||
 * The same address is passed to the reply function, and can be used
 | 
			
		||||
 * to locate the address container object. The reply function shall not
 | 
			
		||||
 * unref the reply.
 | 
			
		||||
 *
 | 
			
		||||
 * The msg passed in is stolen, and unref'd also on errors.
 | 
			
		||||
 *
 | 
			
		||||
 * \returns 0 on success, < 0 on error.
 | 
			
		||||
 */
 | 
			
		||||
int spa_dbus_async_call_send(struct spa_dbus_async_call *call,
 | 
			
		||||
		DBusConnection *conn, DBusMessage *msg,
 | 
			
		||||
		void (*reply)(struct spa_dbus_async_call *call, DBusMessage *reply));
 | 
			
		||||
 | 
			
		||||
/** Convenience for pending call cancel + unref */
 | 
			
		||||
static inline void spa_dbus_async_call_cancel(struct spa_dbus_async_call *call)
 | 
			
		||||
{
 | 
			
		||||
	if (call->pending) {
 | 
			
		||||
		dbus_pending_call_cancel(call->pending);
 | 
			
		||||
		dbus_pending_call_unref(call->pending);
 | 
			
		||||
		spa_zero(*call);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue