diff options
Diffstat (limited to 'spa/plugins/bluez5/midi-enum.c')
-rw-r--r-- | spa/plugins/bluez5/midi-enum.c | 887 |
1 files changed, 887 insertions, 0 deletions
diff --git a/spa/plugins/bluez5/midi-enum.c b/spa/plugins/bluez5/midi-enum.c new file mode 100644 index 0000000..a966591 --- /dev/null +++ b/spa/plugins/bluez5/midi-enum.c @@ -0,0 +1,887 @@ +/* Spa midi 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 <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" + +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "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; +} + +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; + } + + 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, +}; |