mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	Similarly as in spa_dbus, don't exit if bus goes down. Neither the audio or midi Bluetooth backend reconnects to DBus, but they shouldn't exit the process.
		
			
				
	
	
		
			869 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			869 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* Spa midi dbus */
 | 
						|
/* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */
 | 
						|
/* SPDX-License-Identifier: MIT */
 | 
						|
 | 
						|
#include <errno.h>
 | 
						|
#include <stddef.h>
 | 
						|
 | 
						|
#include <spa/support/log.h>
 | 
						|
#include <spa/support/loop.h>
 | 
						|
#include <spa/support/plugin.h>
 | 
						|
#include <spa/monitor/device.h>
 | 
						|
#include <spa/monitor/utils.h>
 | 
						|
#include <spa/utils/hook.h>
 | 
						|
#include <spa/utils/type.h>
 | 
						|
#include <spa/utils/keys.h>
 | 
						|
#include <spa/utils/names.h>
 | 
						|
#include <spa/utils/result.h>
 | 
						|
#include <spa/utils/string.h>
 | 
						|
#include <spa/utils/json.h>
 | 
						|
#include <spa/node/node.h>
 | 
						|
#include <spa/node/keys.h>
 | 
						|
 | 
						|
#include "midi.h"
 | 
						|
#include "config.h"
 | 
						|
 | 
						|
#include "bluez5-interface-gen.h"
 | 
						|
#include "dbus-monitor.h"
 | 
						|
 | 
						|
#define MIDI_OBJECT_PATH	"/midi"
 | 
						|
#define MIDI_PROFILE_PATH	MIDI_OBJECT_PATH "/profile"
 | 
						|
 | 
						|
SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.midi");
 | 
						|
#undef SPA_LOG_TOPIC_DEFAULT
 | 
						|
#define SPA_LOG_TOPIC_DEFAULT &log_topic
 | 
						|
 | 
						|
struct impl
 | 
						|
{
 | 
						|
	struct spa_handle handle;
 | 
						|
	struct spa_device device;
 | 
						|
 | 
						|
	struct spa_log *log;
 | 
						|
 | 
						|
	GDBusConnection *conn;
 | 
						|
	struct dbus_monitor monitor;
 | 
						|
	GDBusObjectManagerServer *manager;
 | 
						|
 | 
						|
	struct spa_hook_list hooks;
 | 
						|
 | 
						|
	uint32_t id;
 | 
						|
};
 | 
						|
 | 
						|
struct _MidiEnumCharacteristicProxy
 | 
						|
{
 | 
						|
	Bluez5GattCharacteristic1Proxy parent_instance;
 | 
						|
 | 
						|
	struct impl *impl;
 | 
						|
 | 
						|
	gchar *description;
 | 
						|
	uint32_t id;
 | 
						|
	GCancellable *read_call;
 | 
						|
	GCancellable *dsc_call;
 | 
						|
	unsigned int node_emitted:1;
 | 
						|
	unsigned int read_probed:1;
 | 
						|
	unsigned int read_done:1;
 | 
						|
	unsigned int dsc_probed:1;
 | 
						|
	unsigned int dsc_done:1;
 | 
						|
};
 | 
						|
 | 
						|
G_DECLARE_FINAL_TYPE(MidiEnumCharacteristicProxy, midi_enum_characteristic_proxy, MIDI_ENUM,
 | 
						|
		CHARACTERISTIC_PROXY, Bluez5GattCharacteristic1Proxy)
 | 
						|
G_DEFINE_TYPE(MidiEnumCharacteristicProxy, midi_enum_characteristic_proxy, BLUEZ5_TYPE_GATT_CHARACTERISTIC1_PROXY)
 | 
						|
#define MIDI_ENUM_TYPE_CHARACTERISTIC_PROXY	(midi_enum_characteristic_proxy_get_type())
 | 
						|
 | 
						|
struct _MidiEnumManagerProxy
 | 
						|
{
 | 
						|
	Bluez5GattManager1Proxy parent_instance;
 | 
						|
 | 
						|
	GCancellable *register_call;
 | 
						|
	unsigned int registered:1;
 | 
						|
};
 | 
						|
 | 
						|
G_DECLARE_FINAL_TYPE(MidiEnumManagerProxy, midi_enum_manager_proxy, MIDI_ENUM,
 | 
						|
		MANAGER_PROXY, Bluez5GattManager1Proxy)
 | 
						|
G_DEFINE_TYPE(MidiEnumManagerProxy, midi_enum_manager_proxy, BLUEZ5_TYPE_GATT_MANAGER1_PROXY)
 | 
						|
#define MIDI_ENUM_TYPE_MANAGER_PROXY	(midi_enum_manager_proxy_get_type())
 | 
						|
 | 
						|
 | 
						|
static void emit_chr_node(struct impl *impl, MidiEnumCharacteristicProxy *chr, Bluez5Device1 *device)
 | 
						|
{
 | 
						|
	struct spa_device_object_info info;
 | 
						|
	char nick[512], class[16];
 | 
						|
	struct spa_dict_item items[23];
 | 
						|
	uint32_t n_items = 0;
 | 
						|
	const char *path = g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr));
 | 
						|
	const char *alias = bluez5_device1_get_alias(device);
 | 
						|
 | 
						|
	spa_log_debug(impl->log, "emit node for path=%s", path);
 | 
						|
 | 
						|
	info = SPA_DEVICE_OBJECT_INFO_INIT();
 | 
						|
	info.type = SPA_TYPE_INTERFACE_Node;
 | 
						|
	info.factory_name = SPA_NAME_API_BLUEZ5_MIDI_NODE;
 | 
						|
	info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS |
 | 
						|
		SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
 | 
						|
	info.flags = 0;
 | 
						|
 | 
						|
	items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "bluez5");
 | 
						|
	items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS, "bluetooth");
 | 
						|
	items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Midi/Bridge");
 | 
						|
	items[n_items++] = SPA_DICT_ITEM_INIT("node.description",
 | 
						|
			alias ? alias : bluez5_device1_get_name(device));
 | 
						|
	if (chr->description && chr->description[0] != '\0') {
 | 
						|
		spa_scnprintf(nick, sizeof(nick), "%s (%s)", alias, chr->description);
 | 
						|
		items[n_items++] = SPA_DICT_ITEM_INIT("node.nick", nick);
 | 
						|
	}
 | 
						|
	items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ICON, bluez5_device1_get_icon(device));
 | 
						|
	items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PATH, path);
 | 
						|
	items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ADDRESS, bluez5_device1_get_address(device));
 | 
						|
	snprintf(class, sizeof(class), "0x%06x", bluez5_device1_get_class(device));
 | 
						|
	items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CLASS, class);
 | 
						|
	items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ROLE, "client");
 | 
						|
 | 
						|
	info.props = &SPA_DICT_INIT(items, n_items);
 | 
						|
	spa_device_emit_object_info(&impl->hooks, chr->id, &info);
 | 
						|
}
 | 
						|
 | 
						|
static void remove_chr_node(struct impl *impl, MidiEnumCharacteristicProxy *chr)
 | 
						|
{
 | 
						|
	spa_log_debug(impl->log, "remove node for path=%s", g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr)));
 | 
						|
 | 
						|
	spa_device_emit_object_info(&impl->hooks, chr->id, NULL);
 | 
						|
}
 | 
						|
 | 
						|
static void check_chr_node(struct impl *impl, MidiEnumCharacteristicProxy *chr);
 | 
						|
 | 
						|
static void read_probe_reply(GObject *source_object, GAsyncResult *res, gpointer user_data)
 | 
						|
{
 | 
						|
	MidiEnumCharacteristicProxy *chr = MIDI_ENUM_CHARACTERISTIC_PROXY(source_object);
 | 
						|
	struct impl *impl = user_data;
 | 
						|
	gchar *value = NULL;
 | 
						|
	GError *err = NULL;
 | 
						|
 | 
						|
	bluez5_gatt_characteristic1_call_read_value_finish(
 | 
						|
		BLUEZ5_GATT_CHARACTERISTIC1(source_object), &value, res, &err);
 | 
						|
 | 
						|
	if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
 | 
						|
		/* Operation canceled: user_data may be invalid by now */
 | 
						|
		g_error_free(err);
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
	if (err) {
 | 
						|
		spa_log_error(impl->log, "%s.ReadValue() failed: %s",
 | 
						|
				BLUEZ_GATT_CHR_INTERFACE,
 | 
						|
				err->message);
 | 
						|
		g_error_free(err);
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
 | 
						|
	g_free(value);
 | 
						|
 | 
						|
	spa_log_debug(impl->log, "MIDI GATT read probe done for path=%s",
 | 
						|
			g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr)));
 | 
						|
 | 
						|
	chr->read_done = true;
 | 
						|
 | 
						|
	check_chr_node(impl, chr);
 | 
						|
 | 
						|
done:
 | 
						|
	g_clear_object(&chr->read_call);
 | 
						|
}
 | 
						|
 | 
						|
static int read_probe(struct impl *impl, MidiEnumCharacteristicProxy *chr)
 | 
						|
{
 | 
						|
	GVariantBuilder builder;
 | 
						|
	GVariant *options;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * BLE MIDI-1.0 §5: The Central shall read the MIDI I/O characteristic
 | 
						|
	 * of the Peripheral after establishing a connection with the accessory.
 | 
						|
	 */
 | 
						|
 | 
						|
	if (chr->read_probed)
 | 
						|
		return 0;
 | 
						|
	if (chr->read_call)
 | 
						|
		return -EBUSY;
 | 
						|
 | 
						|
	chr->read_probed = true;
 | 
						|
 | 
						|
	spa_log_debug(impl->log, "MIDI GATT read probe for path=%s",
 | 
						|
			g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr)));
 | 
						|
 | 
						|
	chr->read_call = g_cancellable_new();
 | 
						|
 | 
						|
	g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
 | 
						|
	options = g_variant_builder_end(&builder);
 | 
						|
 | 
						|
	bluez5_gatt_characteristic1_call_read_value(BLUEZ5_GATT_CHARACTERISTIC1(chr),
 | 
						|
			options,
 | 
						|
			chr->read_call,
 | 
						|
			read_probe_reply,
 | 
						|
			impl);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static Bluez5GattDescriptor1 *find_dsc(struct impl *impl, MidiEnumCharacteristicProxy *chr)
 | 
						|
{
 | 
						|
	const char *path = g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr));
 | 
						|
	Bluez5GattDescriptor1 *found = NULL;;
 | 
						|
	GList *objects;
 | 
						|
 | 
						|
	objects = g_dbus_object_manager_get_objects(dbus_monitor_manager(&impl->monitor));
 | 
						|
 | 
						|
	for (GList *llo = g_list_first(objects); llo; llo = llo->next) {
 | 
						|
		GList *interfaces = g_dbus_object_get_interfaces(G_DBUS_OBJECT(llo->data));
 | 
						|
 | 
						|
		for (GList *lli = g_list_first(interfaces); lli; lli = lli->next) {
 | 
						|
			Bluez5GattDescriptor1 *dsc;
 | 
						|
 | 
						|
			if (!BLUEZ5_IS_GATT_DESCRIPTOR1(lli->data))
 | 
						|
				continue;
 | 
						|
 | 
						|
			dsc = BLUEZ5_GATT_DESCRIPTOR1(lli->data);
 | 
						|
 | 
						|
			if (!spa_streq(bluez5_gatt_descriptor1_get_uuid(dsc),
 | 
						|
							BT_GATT_CHARACTERISTIC_USER_DESCRIPTION_UUID))
 | 
						|
				continue;
 | 
						|
 | 
						|
			if (spa_streq(bluez5_gatt_descriptor1_get_characteristic(dsc), path)) {
 | 
						|
				found = dsc;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		g_list_free_full(interfaces, g_object_unref);
 | 
						|
 | 
						|
		if (found)
 | 
						|
			break;
 | 
						|
	}
 | 
						|
	g_list_free_full(objects, g_object_unref);
 | 
						|
 | 
						|
	return found;
 | 
						|
}
 | 
						|
 | 
						|
static void read_dsc_reply(GObject *source_object, GAsyncResult *res, gpointer user_data)
 | 
						|
{
 | 
						|
	MidiEnumCharacteristicProxy *chr = MIDI_ENUM_CHARACTERISTIC_PROXY(user_data);
 | 
						|
	struct impl *impl = chr->impl;
 | 
						|
	gchar *value = NULL;
 | 
						|
	GError *err = NULL;
 | 
						|
 | 
						|
	chr->dsc_done = true;
 | 
						|
 | 
						|
	bluez5_gatt_descriptor1_call_read_value_finish(
 | 
						|
		BLUEZ5_GATT_DESCRIPTOR1(source_object), &value, res, &err);
 | 
						|
 | 
						|
	if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
 | 
						|
		/* Operation canceled: user_data may be invalid by now */
 | 
						|
		g_error_free(err);
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
	if (err) {
 | 
						|
		spa_log_error(impl->log, "%s.ReadValue() failed: %s",
 | 
						|
				BLUEZ_GATT_DSC_INTERFACE,
 | 
						|
				err->message);
 | 
						|
		g_error_free(err);
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
 | 
						|
	spa_log_debug(impl->log, "MIDI GATT read probe done for path=%s",
 | 
						|
			g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr)));
 | 
						|
 | 
						|
	g_free(chr->description);
 | 
						|
	chr->description = value;
 | 
						|
 | 
						|
	spa_log_debug(impl->log, "MIDI GATT user descriptor value: '%s'",
 | 
						|
			chr->description);
 | 
						|
 | 
						|
	check_chr_node(impl, chr);
 | 
						|
 | 
						|
done:
 | 
						|
	g_clear_object(&chr->dsc_call);
 | 
						|
}
 | 
						|
 | 
						|
static int read_dsc(struct impl *impl, MidiEnumCharacteristicProxy *chr)
 | 
						|
{
 | 
						|
	Bluez5GattDescriptor1 *dsc;
 | 
						|
	GVariant *options;
 | 
						|
	GVariantBuilder builder;
 | 
						|
 | 
						|
	if (chr->dsc_probed)
 | 
						|
		return 0;
 | 
						|
	if (chr->dsc_call)
 | 
						|
		return -EBUSY;
 | 
						|
 | 
						|
	chr->dsc_probed = true;
 | 
						|
 | 
						|
	dsc = find_dsc(impl, chr);
 | 
						|
	if (dsc == NULL) {
 | 
						|
		chr->dsc_done = true;
 | 
						|
		return -ENOENT;
 | 
						|
	}
 | 
						|
 | 
						|
	spa_log_debug(impl->log, "MIDI GATT user descriptor read, path=%s",
 | 
						|
			g_dbus_proxy_get_object_path(G_DBUS_PROXY(dsc)));
 | 
						|
 | 
						|
	chr->dsc_call = g_cancellable_new();
 | 
						|
 | 
						|
	g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
 | 
						|
	options = g_variant_builder_end(&builder);
 | 
						|
 | 
						|
	bluez5_gatt_descriptor1_call_read_value(BLUEZ5_GATT_DESCRIPTOR1(dsc),
 | 
						|
			options,
 | 
						|
			chr->dsc_call,
 | 
						|
			read_dsc_reply,
 | 
						|
			chr);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int read_probe_reset(struct impl *impl, MidiEnumCharacteristicProxy *chr)
 | 
						|
{
 | 
						|
	g_cancellable_cancel(chr->read_call);
 | 
						|
	g_clear_object(&chr->read_call);
 | 
						|
 | 
						|
	g_cancellable_cancel(chr->dsc_call);
 | 
						|
	g_clear_object(&chr->dsc_call);
 | 
						|
 | 
						|
	chr->read_probed = false;
 | 
						|
	chr->read_done = false;
 | 
						|
	chr->dsc_probed = false;
 | 
						|
	chr->dsc_done = false;
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void lookup_chr_node(struct impl *impl, MidiEnumCharacteristicProxy *chr,
 | 
						|
		Bluez5GattService1 **service, Bluez5Device1 **device)
 | 
						|
{
 | 
						|
	GDBusObject *object;
 | 
						|
	const char *service_path;
 | 
						|
	const char *device_path;
 | 
						|
 | 
						|
	*service = NULL;
 | 
						|
	*device = NULL;
 | 
						|
 | 
						|
	service_path = bluez5_gatt_characteristic1_get_service(BLUEZ5_GATT_CHARACTERISTIC1(chr));
 | 
						|
	if (!service_path)
 | 
						|
		return;
 | 
						|
 | 
						|
	object = g_dbus_object_manager_get_object(dbus_monitor_manager(&impl->monitor), service_path);
 | 
						|
	if (object) {
 | 
						|
		GDBusInterface *iface = g_dbus_object_get_interface(object, BLUEZ_GATT_SERVICE_INTERFACE);
 | 
						|
		*service = BLUEZ5_GATT_SERVICE1(iface);
 | 
						|
	}
 | 
						|
 | 
						|
	if (!*service)
 | 
						|
		return;
 | 
						|
 | 
						|
	device_path = bluez5_gatt_service1_get_device(*service);
 | 
						|
	if (!device_path)
 | 
						|
		return;
 | 
						|
 | 
						|
	object = g_dbus_object_manager_get_object(dbus_monitor_manager(&impl->monitor), device_path);
 | 
						|
	if (object) {
 | 
						|
		GDBusInterface *iface = g_dbus_object_get_interface(object, BLUEZ_DEVICE_INTERFACE);
 | 
						|
		*device = BLUEZ5_DEVICE1(iface);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void check_chr_node(struct impl *impl, MidiEnumCharacteristicProxy *chr)
 | 
						|
{
 | 
						|
	Bluez5GattService1 *service;
 | 
						|
	Bluez5Device1 *device;
 | 
						|
	bool available;
 | 
						|
 | 
						|
	lookup_chr_node(impl, chr, &service, &device);
 | 
						|
 | 
						|
	if (!device || !bluez5_device1_get_connected(device)) {
 | 
						|
		/* Retry read probe on each connection */
 | 
						|
		read_probe_reset(impl, chr);
 | 
						|
	}
 | 
						|
 | 
						|
	spa_log_debug(impl->log,
 | 
						|
			"At %s, connected:%d resolved:%d",
 | 
						|
			g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr)),
 | 
						|
			bluez5_device1_get_connected(device),
 | 
						|
			bluez5_device1_get_services_resolved(device));
 | 
						|
 | 
						|
	available = service && device &&
 | 
						|
			bluez5_device1_get_connected(device) &&
 | 
						|
			bluez5_device1_get_services_resolved(device) &&
 | 
						|
			spa_streq(bluez5_gatt_service1_get_uuid(service), BT_MIDI_SERVICE_UUID) &&
 | 
						|
			spa_streq(bluez5_gatt_characteristic1_get_uuid(BLUEZ5_GATT_CHARACTERISTIC1(chr)),
 | 
						|
					BT_MIDI_CHR_UUID);
 | 
						|
 | 
						|
	if (available && !chr->read_done) {
 | 
						|
		read_probe(impl, chr);
 | 
						|
		available = false;
 | 
						|
	}
 | 
						|
 | 
						|
	if (available && !chr->dsc_done) {
 | 
						|
		read_dsc(impl, chr);
 | 
						|
		available = chr->dsc_done;
 | 
						|
	}
 | 
						|
 | 
						|
	if (chr->node_emitted && !available) {
 | 
						|
		remove_chr_node(impl, chr);
 | 
						|
		chr->node_emitted = false;
 | 
						|
	} else if (!chr->node_emitted && available) {
 | 
						|
		emit_chr_node(impl, chr, device);
 | 
						|
		chr->node_emitted = true;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static GList *get_all_valid_chr(struct impl *impl)
 | 
						|
{
 | 
						|
	GList *lst = NULL;
 | 
						|
	GList *objects;
 | 
						|
 | 
						|
	if (!dbus_monitor_manager(&impl->monitor)) {
 | 
						|
		/* Still initializing (or it failed) */
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	objects = g_dbus_object_manager_get_objects(dbus_monitor_manager(&impl->monitor));
 | 
						|
	for (GList *p = g_list_first(objects); p; p = p->next) {
 | 
						|
		GList *interfaces = g_dbus_object_get_interfaces(G_DBUS_OBJECT(p->data));
 | 
						|
 | 
						|
		for (GList *p2 = g_list_first(interfaces); p2; p2 = p2->next) {
 | 
						|
			MidiEnumCharacteristicProxy *chr;
 | 
						|
 | 
						|
			if (!MIDI_ENUM_IS_CHARACTERISTIC_PROXY(p2->data))
 | 
						|
				continue;
 | 
						|
 | 
						|
			chr = MIDI_ENUM_CHARACTERISTIC_PROXY(p2->data);
 | 
						|
			if (chr->impl == NULL)
 | 
						|
				continue;
 | 
						|
 | 
						|
			lst = g_list_append(lst, g_object_ref(chr));
 | 
						|
		}
 | 
						|
		g_list_free_full(interfaces, g_object_unref);
 | 
						|
	}
 | 
						|
	g_list_free_full(objects, g_object_unref);
 | 
						|
 | 
						|
	return lst;
 | 
						|
}
 | 
						|
 | 
						|
static void check_all_nodes(struct impl *impl)
 | 
						|
{
 | 
						|
	/*
 | 
						|
	 * Check if the nodes we have emitted are in sync with connected devices.
 | 
						|
	 */
 | 
						|
 | 
						|
	GList *chrs = get_all_valid_chr(impl);
 | 
						|
 | 
						|
	for (GList *p = chrs; p; p = p->next)
 | 
						|
		check_chr_node(impl, MIDI_ENUM_CHARACTERISTIC_PROXY(p->data));
 | 
						|
 | 
						|
	g_list_free_full(chrs, g_object_unref);
 | 
						|
}
 | 
						|
 | 
						|
static void manager_register_application_reply(GObject *source_object, GAsyncResult *res, gpointer user_data)
 | 
						|
{
 | 
						|
	MidiEnumManagerProxy *manager = MIDI_ENUM_MANAGER_PROXY(source_object);
 | 
						|
	struct impl *impl = user_data;
 | 
						|
	GError *err = NULL;
 | 
						|
 | 
						|
	bluez5_gatt_manager1_call_register_application_finish(
 | 
						|
		BLUEZ5_GATT_MANAGER1(source_object), res, &err);
 | 
						|
 | 
						|
	if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
 | 
						|
		/* Operation canceled: user_data may be invalid by now */
 | 
						|
		g_error_free(err);
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
	if (err) {
 | 
						|
		spa_log_error(impl->log, "%s.RegisterApplication() failed: %s",
 | 
						|
				BLUEZ_GATT_MANAGER_INTERFACE,
 | 
						|
				err->message);
 | 
						|
		g_error_free(err);
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
 | 
						|
	manager->registered = true;
 | 
						|
 | 
						|
done:
 | 
						|
	g_clear_object(&manager->register_call);
 | 
						|
}
 | 
						|
 | 
						|
static int manager_register_application(struct impl *impl, MidiEnumManagerProxy *manager)
 | 
						|
{
 | 
						|
	GVariantBuilder builder;
 | 
						|
	GVariant *options;
 | 
						|
 | 
						|
	if (manager->registered)
 | 
						|
		return 0;
 | 
						|
	if (manager->register_call)
 | 
						|
		return -EBUSY;
 | 
						|
 | 
						|
	spa_log_debug(impl->log, "%s.RegisterApplication(%s) on %s",
 | 
						|
			BLUEZ_GATT_MANAGER_INTERFACE,
 | 
						|
			g_dbus_object_manager_get_object_path(G_DBUS_OBJECT_MANAGER(impl->manager)),
 | 
						|
			g_dbus_proxy_get_object_path(G_DBUS_PROXY(manager)));
 | 
						|
 | 
						|
	manager->register_call = g_cancellable_new();
 | 
						|
 | 
						|
	g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
 | 
						|
	options = g_variant_builder_end(&builder);
 | 
						|
 | 
						|
	bluez5_gatt_manager1_call_register_application(BLUEZ5_GATT_MANAGER1(manager),
 | 
						|
			g_dbus_object_manager_get_object_path(G_DBUS_OBJECT_MANAGER(impl->manager)),
 | 
						|
			options,
 | 
						|
			manager->register_call,
 | 
						|
			manager_register_application_reply,
 | 
						|
			impl);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * DBus monitoring (Glib)
 | 
						|
 */
 | 
						|
 | 
						|
static void midi_enum_characteristic_proxy_init(MidiEnumCharacteristicProxy *chr)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
static void midi_enum_characteristic_proxy_finalize(GObject *object)
 | 
						|
{
 | 
						|
	MidiEnumCharacteristicProxy *chr = MIDI_ENUM_CHARACTERISTIC_PROXY(object);
 | 
						|
 | 
						|
	g_cancellable_cancel(chr->read_call);
 | 
						|
	g_clear_object(&chr->read_call);
 | 
						|
 | 
						|
	g_cancellable_cancel(chr->dsc_call);
 | 
						|
	g_clear_object(&chr->dsc_call);
 | 
						|
 | 
						|
	if (chr->impl && chr->node_emitted)
 | 
						|
		remove_chr_node(chr->impl, chr);
 | 
						|
 | 
						|
	chr->impl = NULL;
 | 
						|
 | 
						|
	g_free(chr->description);
 | 
						|
	chr->description = NULL;
 | 
						|
}
 | 
						|
 | 
						|
static void midi_enum_characteristic_proxy_class_init(MidiEnumCharacteristicProxyClass *klass)
 | 
						|
{
 | 
						|
	GObjectClass *object_class = (GObjectClass *) klass;
 | 
						|
 | 
						|
	object_class->finalize = midi_enum_characteristic_proxy_finalize;
 | 
						|
}
 | 
						|
 | 
						|
static void midi_enum_manager_proxy_init(MidiEnumManagerProxy *manager)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
static void midi_enum_manager_proxy_finalize(GObject *object)
 | 
						|
{
 | 
						|
	MidiEnumManagerProxy *manager = MIDI_ENUM_MANAGER_PROXY(object);
 | 
						|
 | 
						|
	g_cancellable_cancel(manager->register_call);
 | 
						|
	g_clear_object(&manager->register_call);
 | 
						|
}
 | 
						|
 | 
						|
static void midi_enum_manager_proxy_class_init(MidiEnumManagerProxyClass *klass)
 | 
						|
{
 | 
						|
	GObjectClass *object_class = (GObjectClass *) klass;
 | 
						|
 | 
						|
	object_class->finalize = midi_enum_manager_proxy_finalize;
 | 
						|
}
 | 
						|
 | 
						|
static void manager_update(struct dbus_monitor *monitor, GDBusInterface *iface)
 | 
						|
{
 | 
						|
	struct impl *impl = SPA_CONTAINER_OF(monitor, struct impl, monitor);
 | 
						|
 | 
						|
	manager_register_application(impl, MIDI_ENUM_MANAGER_PROXY(iface));
 | 
						|
}
 | 
						|
 | 
						|
static void manager_clear(struct dbus_monitor *monitor, GDBusInterface *iface)
 | 
						|
{
 | 
						|
	midi_enum_manager_proxy_finalize(G_OBJECT(iface));
 | 
						|
}
 | 
						|
 | 
						|
static void device_update(struct dbus_monitor *monitor, GDBusInterface *iface)
 | 
						|
{
 | 
						|
	struct impl *impl = SPA_CONTAINER_OF(monitor, struct impl, monitor);
 | 
						|
 | 
						|
	check_all_nodes(impl);
 | 
						|
}
 | 
						|
 | 
						|
static void service_update(struct dbus_monitor *monitor, GDBusInterface *iface)
 | 
						|
{
 | 
						|
	struct impl *impl = SPA_CONTAINER_OF(monitor, struct impl, monitor);
 | 
						|
	Bluez5GattService1 *service = BLUEZ5_GATT_SERVICE1(iface);
 | 
						|
 | 
						|
	if (!spa_streq(bluez5_gatt_service1_get_uuid(service), BT_MIDI_SERVICE_UUID))
 | 
						|
		return;
 | 
						|
 | 
						|
	check_all_nodes(impl);
 | 
						|
}
 | 
						|
 | 
						|
static void chr_update(struct dbus_monitor *monitor, GDBusInterface *iface)
 | 
						|
{
 | 
						|
	struct impl *impl = SPA_CONTAINER_OF(monitor, struct impl, monitor);
 | 
						|
	MidiEnumCharacteristicProxy *chr = MIDI_ENUM_CHARACTERISTIC_PROXY(iface);
 | 
						|
 | 
						|
	if (!spa_streq(bluez5_gatt_characteristic1_get_uuid(BLUEZ5_GATT_CHARACTERISTIC1(chr)),
 | 
						|
					BT_MIDI_CHR_UUID))
 | 
						|
		return;
 | 
						|
 | 
						|
	if (chr->impl == NULL) {
 | 
						|
		chr->impl = impl;
 | 
						|
		chr->id = ++impl->id;
 | 
						|
	}
 | 
						|
 | 
						|
	check_chr_node(impl, chr);
 | 
						|
}
 | 
						|
 | 
						|
static void chr_clear(struct dbus_monitor *monitor, GDBusInterface *iface)
 | 
						|
{
 | 
						|
	midi_enum_characteristic_proxy_finalize(G_OBJECT(iface));
 | 
						|
}
 | 
						|
 | 
						|
static void monitor_start(struct impl *impl)
 | 
						|
{
 | 
						|
	struct dbus_monitor_proxy_type proxy_types[] = {
 | 
						|
		{ BLUEZ_DEVICE_INTERFACE, BLUEZ5_TYPE_DEVICE1_PROXY, device_update, NULL },
 | 
						|
		{ BLUEZ_GATT_MANAGER_INTERFACE, MIDI_ENUM_TYPE_MANAGER_PROXY, manager_update, manager_clear },
 | 
						|
		{ BLUEZ_GATT_SERVICE_INTERFACE, BLUEZ5_TYPE_GATT_SERVICE1_PROXY, service_update, NULL },
 | 
						|
		{ BLUEZ_GATT_CHR_INTERFACE, MIDI_ENUM_TYPE_CHARACTERISTIC_PROXY, chr_update, chr_clear },
 | 
						|
		{ BLUEZ_GATT_DSC_INTERFACE, BLUEZ5_TYPE_GATT_DESCRIPTOR1_PROXY, NULL, NULL },
 | 
						|
		{ NULL, BLUEZ5_TYPE_OBJECT_PROXY, NULL, NULL },
 | 
						|
		{ NULL, G_TYPE_INVALID, NULL, NULL }
 | 
						|
	};
 | 
						|
 | 
						|
	SPA_STATIC_ASSERT(SPA_N_ELEMENTS(proxy_types) <= DBUS_MONITOR_MAX_TYPES);
 | 
						|
 | 
						|
	dbus_monitor_init(&impl->monitor, BLUEZ5_TYPE_OBJECT_MANAGER_CLIENT,
 | 
						|
			impl->log, impl->conn, BLUEZ_SERVICE, "/", proxy_types, NULL);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * DBus GATT profile, to enable BlueZ autoconnect
 | 
						|
 */
 | 
						|
 | 
						|
static gboolean profile_handle_release(Bluez5GattProfile1 *iface, GDBusMethodInvocation *invocation)
 | 
						|
{
 | 
						|
	bluez5_gatt_profile1_complete_release(iface, invocation);
 | 
						|
	return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static int export_profile(struct impl *impl)
 | 
						|
{
 | 
						|
	static const char *uuids[] = { BT_MIDI_SERVICE_UUID, NULL };
 | 
						|
	GDBusObjectSkeleton *skeleton = NULL;
 | 
						|
	Bluez5GattProfile1 *iface = NULL;
 | 
						|
	int res = -ENOMEM;
 | 
						|
 | 
						|
	iface = bluez5_gatt_profile1_skeleton_new();
 | 
						|
	if (!iface)
 | 
						|
		goto done;
 | 
						|
 | 
						|
	skeleton = g_dbus_object_skeleton_new(MIDI_PROFILE_PATH);
 | 
						|
	if (!skeleton)
 | 
						|
		goto done;
 | 
						|
	g_dbus_object_skeleton_add_interface(skeleton, G_DBUS_INTERFACE_SKELETON(iface));
 | 
						|
 | 
						|
	bluez5_gatt_profile1_set_uuids(iface, uuids);
 | 
						|
	g_signal_connect(iface, "handle-release", G_CALLBACK(profile_handle_release), NULL);
 | 
						|
 | 
						|
	g_dbus_object_manager_server_export(impl->manager, skeleton);
 | 
						|
 | 
						|
	spa_log_debug(impl->log, "MIDI GATT Profile exported, path=%s",
 | 
						|
			g_dbus_object_get_object_path(G_DBUS_OBJECT(skeleton)));
 | 
						|
 | 
						|
	res = 0;
 | 
						|
 | 
						|
done:
 | 
						|
	g_clear_object(&iface);
 | 
						|
	g_clear_object(&skeleton);
 | 
						|
	return res;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Monitor impl
 | 
						|
 */
 | 
						|
 | 
						|
static int impl_device_add_listener(void *object, struct spa_hook *listener,
 | 
						|
		const struct spa_device_events *events, void *data)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
	struct spa_hook_list save;
 | 
						|
	GList *chrs;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(events != NULL, -EINVAL);
 | 
						|
 | 
						|
	chrs = get_all_valid_chr(this);
 | 
						|
 | 
						|
	spa_hook_list_isolate(&this->hooks, &save, listener, events, data);
 | 
						|
 | 
						|
	for (GList *p = g_list_first(chrs); p; p = p->next) {
 | 
						|
		MidiEnumCharacteristicProxy *chr = MIDI_ENUM_CHARACTERISTIC_PROXY(p->data);
 | 
						|
		Bluez5Device1 *device;
 | 
						|
		Bluez5GattService1 *service;
 | 
						|
 | 
						|
		if (!chr->node_emitted)
 | 
						|
			continue;
 | 
						|
 | 
						|
		lookup_chr_node(this, chr, &service, &device);
 | 
						|
		if (device)
 | 
						|
			emit_chr_node(this, chr, device);
 | 
						|
	}
 | 
						|
	g_list_free_full(chrs, g_object_unref);
 | 
						|
 | 
						|
	spa_hook_list_join(&this->hooks, &save);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static const struct spa_device_methods impl_device = {
 | 
						|
	SPA_VERSION_DEVICE_METHODS,
 | 
						|
	.add_listener = impl_device_add_listener,
 | 
						|
};
 | 
						|
 | 
						|
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
 | 
						|
{
 | 
						|
	struct impl *this;
 | 
						|
 | 
						|
	spa_return_val_if_fail(handle != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(interface != NULL, -EINVAL);
 | 
						|
 | 
						|
	this = (struct impl *) handle;
 | 
						|
 | 
						|
	if (spa_streq(type, SPA_TYPE_INTERFACE_Device))
 | 
						|
		*interface = &this->device;
 | 
						|
	else
 | 
						|
		return -ENOENT;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_clear(struct spa_handle *handle)
 | 
						|
{
 | 
						|
	struct impl *this;
 | 
						|
 | 
						|
	this = (struct impl *) handle;
 | 
						|
 | 
						|
	dbus_monitor_clear(&this->monitor);
 | 
						|
	g_clear_object(&this->manager);
 | 
						|
	g_clear_object(&this->conn);
 | 
						|
 | 
						|
	spa_zero(*this);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static size_t
 | 
						|
impl_get_size(const struct spa_handle_factory *factory,
 | 
						|
		const struct spa_dict *params)
 | 
						|
{
 | 
						|
	return sizeof(struct impl);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
impl_init(const struct spa_handle_factory *factory,
 | 
						|
		struct spa_handle *handle,
 | 
						|
		const struct spa_dict *info,
 | 
						|
		const struct spa_support *support,
 | 
						|
		uint32_t n_support)
 | 
						|
{
 | 
						|
	struct impl *this;
 | 
						|
	GError *error = NULL;
 | 
						|
	int res = 0;
 | 
						|
 | 
						|
	spa_return_val_if_fail(factory != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(handle != NULL, -EINVAL);
 | 
						|
 | 
						|
	handle->get_interface = impl_get_interface;
 | 
						|
	handle->clear = impl_clear;
 | 
						|
 | 
						|
	this = (struct impl *) handle;
 | 
						|
 | 
						|
	this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
 | 
						|
 | 
						|
	if (this->log == NULL)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	spa_log_topic_init(this->log, &log_topic);
 | 
						|
 | 
						|
	if (!(info && spa_atob(spa_dict_lookup(info, SPA_KEY_API_GLIB_MAINLOOP)))) {
 | 
						|
		spa_log_error(this->log, "Glib mainloop is not usable: %s not set",
 | 
						|
				SPA_KEY_API_GLIB_MAINLOOP);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	spa_hook_list_init(&this->hooks);
 | 
						|
 | 
						|
	this->device.iface = SPA_INTERFACE_INIT(
 | 
						|
		SPA_TYPE_INTERFACE_Device,
 | 
						|
				SPA_VERSION_DEVICE,
 | 
						|
				&impl_device, this);
 | 
						|
 | 
						|
	this->conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
 | 
						|
	if (!this->conn) {
 | 
						|
		spa_log_error(this->log, "Creating GDBus connection failed: %s",
 | 
						|
				error->message);
 | 
						|
		g_error_free(error);
 | 
						|
		goto fail;
 | 
						|
	}
 | 
						|
 | 
						|
	g_dbus_connection_set_exit_on_close(this->conn, FALSE);
 | 
						|
 | 
						|
	this->manager = g_dbus_object_manager_server_new(MIDI_OBJECT_PATH);
 | 
						|
	if (!this->manager){
 | 
						|
		spa_log_error(this->log, "Creating GDBus object manager failed");
 | 
						|
		goto fail;
 | 
						|
	}
 | 
						|
 | 
						|
	if ((res = export_profile(this)) < 0)
 | 
						|
		goto fail;
 | 
						|
 | 
						|
	g_dbus_object_manager_server_set_connection(this->manager, this->conn);
 | 
						|
 | 
						|
	monitor_start(this);
 | 
						|
 | 
						|
	return 0;
 | 
						|
 | 
						|
fail:
 | 
						|
	res = (res < 0) ? res : ((errno > 0) ? -errno : -EIO);
 | 
						|
	impl_clear(handle);
 | 
						|
	return res;
 | 
						|
}
 | 
						|
 | 
						|
static const struct spa_interface_info impl_interfaces[] = {
 | 
						|
	{SPA_TYPE_INTERFACE_Device,},
 | 
						|
};
 | 
						|
 | 
						|
static int
 | 
						|
impl_enum_interface_info(const struct spa_handle_factory *factory,
 | 
						|
		const struct spa_interface_info **info,
 | 
						|
		uint32_t *index)
 | 
						|
{
 | 
						|
	spa_return_val_if_fail(factory != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(info != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(index != NULL, -EINVAL);
 | 
						|
 | 
						|
	if (*index >= SPA_N_ELEMENTS(impl_interfaces))
 | 
						|
		return 0;
 | 
						|
 | 
						|
	*info = &impl_interfaces[(*index)++];
 | 
						|
 | 
						|
	return 1;
 | 
						|
}
 | 
						|
 | 
						|
static const struct spa_dict_item info_items[] = {
 | 
						|
	{ SPA_KEY_FACTORY_AUTHOR, "Pauli Virtanen <pav@iki.fi>" },
 | 
						|
	{ SPA_KEY_FACTORY_DESCRIPTION, "Bluez5 MIDI connection" },
 | 
						|
};
 | 
						|
 | 
						|
static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items);
 | 
						|
 | 
						|
const struct spa_handle_factory spa_bluez5_midi_enum_factory = {
 | 
						|
	SPA_VERSION_HANDLE_FACTORY,
 | 
						|
	SPA_NAME_API_BLUEZ5_MIDI_ENUM,
 | 
						|
	&info,
 | 
						|
	impl_get_size,
 | 
						|
	impl_init,
 | 
						|
	impl_enum_interface_info,
 | 
						|
};
 |