1
0
Fork 0
pipewire/spa/plugins/bluez5/midi-enum.c
Daniel Baumann 6b016a712f
Adding upstream version 1.4.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 21:40:42 +02:00

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,
};