From 249a12ee69418b68b6ec146e9a4c9fb274f06cdb Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 27 Nov 2018 17:08:36 +0100 Subject: [PATCH] bluez: work on the bluez device Add HSP and HFP profiles Wait for profiles to connect before exposing the device Implement SCO and RFCOMM connection handling --- spa/plugins/bluez5/bluez5-device.c | 52 +- spa/plugins/bluez5/bluez5-monitor.c | 868 ++++++++++++++++++++++++++-- spa/plugins/bluez5/defs.h | 58 +- 3 files changed, 899 insertions(+), 79 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 8259913b3..2247f690c 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -36,6 +36,8 @@ #include #include +#include "defs.h" + #define NAME "bluez5-device" #define MAX_DEVICES 64 @@ -65,21 +67,43 @@ struct impl { struct props props; - struct spa_bt_transport *transport; + struct spa_bt_device *bt_dev; }; -static int emit_devices(struct impl *this) +static int emit_nodes(struct impl *this) { struct spa_dict_item items[1]; - char transport[16]; + struct spa_bt_transport *t; + struct spa_bt_device *device = this->bt_dev; + enum spa_bt_profile profile; - snprintf(transport, 16, "%p", this->transport); + if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) + profile = SPA_BT_PROFILE_A2DP_SINK; + else if (device->connected_profiles & SPA_BT_PROFILE_HSP_AG) + profile = SPA_BT_PROFILE_HSP_AG; + else if (device->connected_profiles & SPA_BT_PROFILE_HFP_AG) + profile = SPA_BT_PROFILE_HFP_AG; + else { + spa_log_warn(this->log, "no profile available"); + return -ENODEV; + } + + + spa_list_for_each(t, &device->transport_list, device_link) { + if (t->profile == profile) { + char transport[16]; + + snprintf(transport, 16, "%p", t); + items[0] = SPA_DICT_ITEM_INIT("bluez5.transport", transport); + + this->callbacks->add(this->callbacks_data, 0, + &spa_a2dp_sink_factory, + SPA_TYPE_INTERFACE_Node, + &SPA_DICT_INIT(items, 1)); + break; + } + } - items[0] = SPA_DICT_ITEM_INIT("bluez5.transport", transport); - this->callbacks->add(this->callbacks_data, 0, - &spa_a2dp_sink_factory, - SPA_TYPE_INTERFACE_Node, - &SPA_DICT_INIT(items, 1)); return 0; } @@ -105,7 +129,7 @@ static int impl_set_callbacks(struct spa_device *device, callbacks->info(data, &SPA_DICT_INIT_ARRAY(info_items)); if (this->callbacks->add) - emit_devices(this); + emit_nodes(this); } return 0; @@ -194,11 +218,11 @@ impl_init(const struct spa_handle_factory *factory, } for (i = 0; info && i < info->n_items; i++) { - if (strcmp(info->items[i].key, "bluez5.transport") == 0) - sscanf(info->items[i].value, "%p", &this->transport); + if (strcmp(info->items[i].key, "bluez5.device") == 0) + sscanf(info->items[i].value, "%p", &this->bt_dev); } - if (this->transport == NULL) { - spa_log_error(this->log, "a transport is needed"); + if (this->bt_dev == NULL) { + spa_log_error(this->log, "a device is needed"); return -EINVAL; } this->device = impl_device; diff --git a/spa/plugins/bluez5/bluez5-monitor.c b/spa/plugins/bluez5/bluez5-monitor.c index a66d71fd9..d7df3bff4 100644 --- a/spa/plugins/bluez5/bluez5-monitor.c +++ b/spa/plugins/bluez5/bluez5-monitor.c @@ -30,8 +30,13 @@ #include #include #include +#include +#include #include +#include +#include + #include #include @@ -54,6 +59,7 @@ struct spa_bt_monitor { struct spa_monitor monitor; struct spa_log *log; + struct spa_loop *main_loop; struct spa_dbus *dbus; struct spa_dbus_connection *dbus_connection; DBusConnection *conn; @@ -61,8 +67,6 @@ struct spa_bt_monitor { const struct spa_monitor_callbacks *callbacks; void *callbacks_data; - uint32_t index; - uint32_t count; struct spa_list adapter_list; @@ -70,6 +74,11 @@ struct spa_bt_monitor { struct spa_list transport_list; }; +struct transport_data { + struct spa_source rfcomm; + struct spa_source sco; +}; + struct spa_handle_factory spa_bluez5_device_factory; static inline void add_dict(struct spa_pod_builder *builder, const char *key, const char *val) @@ -78,17 +87,17 @@ static inline void add_dict(struct spa_pod_builder *builder, const char *key, co spa_pod_builder_string(builder, val); } -static void fill_item(struct spa_bt_monitor *this, struct spa_bt_transport *transport, +static void fill_item(struct spa_bt_monitor *this, struct spa_bt_device *device, struct spa_pod **result, struct spa_pod_builder *builder) { - char trans[16]; + char dev[16]; spa_pod_builder_push_object(builder, SPA_TYPE_OBJECT_MonitorItem, 0); spa_pod_builder_props(builder, - SPA_MONITOR_ITEM_id, &SPA_POD_Stringv(transport->path), + SPA_MONITOR_ITEM_id, &SPA_POD_Stringv(device->path), SPA_MONITOR_ITEM_flags, &SPA_POD_Id(SPA_MONITOR_ITEM_FLAG_NONE), SPA_MONITOR_ITEM_state, &SPA_POD_Id(SPA_MONITOR_ITEM_STATE_Available), - SPA_MONITOR_ITEM_name, &SPA_POD_Stringv(transport->path), + SPA_MONITOR_ITEM_name, &SPA_POD_Stringv(device->name), SPA_MONITOR_ITEM_class, &SPA_POD_Stringc("Adapter/Bluetooth"), SPA_MONITOR_ITEM_factory, &SPA_POD_Pointer(SPA_TYPE_INTERFACE_HandleFactory, &spa_bluez5_device_factory), @@ -97,13 +106,14 @@ static void fill_item(struct spa_bt_monitor *this, struct spa_bt_transport *tran spa_pod_builder_prop(builder, SPA_MONITOR_ITEM_info, 0); spa_pod_builder_push_struct(builder); - snprintf(trans, sizeof(trans), "%p", transport); + snprintf(dev, sizeof(dev), "%p", device); add_dict(builder, "device.api", "bluez5"); - add_dict(builder, "device.name", transport->device->name); - add_dict(builder, "device.icon", transport->device->icon); - add_dict(builder, "device.bluez5.address", transport->device->address); - add_dict(builder, "bluez5.transport", trans); + add_dict(builder, "device.name", device->name); + add_dict(builder, "device.alias", device->alias); + add_dict(builder, "device.icon", device->icon); + add_dict(builder, "device.bluez5.address", device->address); + add_dict(builder, "bluez5.device", dev); spa_pod_builder_pop(builder); *result = spa_pod_builder_pop(builder); @@ -482,17 +492,182 @@ static struct spa_bt_device *device_create(struct spa_bt_monitor *monitor, const d->monitor = monitor; d->path = strdup(path); + spa_list_init(&d->transport_list); spa_list_prepend(&monitor->device_list, &d->link); return d; } +#if 0 +static int device_free(struct spa_bt_device *device) +{ + struct spa_bt_transport *t; + struct spa_bt_monitor *monitor = device->monitor; + + spa_log_debug(monitor->log, "%p", device); + + spa_list_for_each(t, &device->transport_list, device_link) { + if (t->device == device) { + spa_list_remove(&t->device_link); + t->device = NULL; + } + } + spa_list_remove(&device->link); + free(device->path); + free(device); + return 0; +} +#endif + +static int device_add(struct spa_bt_monitor *monitor, struct spa_bt_device *device) +{ + struct spa_event *event; + struct spa_pod_builder b = { NULL, }; + uint8_t buffer[4096]; + struct spa_pod *item; + + if (device->added) + return 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + event = spa_pod_builder_object(&b, SPA_TYPE_EVENT_Monitor, SPA_MONITOR_EVENT_Added, 0); + fill_item(monitor, device, &item, &b); + + device->added = true; + monitor->callbacks->event(monitor->callbacks_data, event); + + return 0; +} + +static int device_remove(struct spa_bt_monitor *monitor, struct spa_bt_device *device) +{ + struct spa_event *event; + struct spa_pod_builder b = { NULL, }; + uint8_t buffer[4096]; + struct spa_pod *item; + + if (!device->added) + return 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + event = spa_pod_builder_object(&b, SPA_TYPE_EVENT_Monitor, SPA_MONITOR_EVENT_Removed, 0); + fill_item(monitor, device, &item, &b); + + device->added = false; + monitor->callbacks->event(monitor->callbacks_data, event); + + return 0; +} + + +#define DEVICE_PROFILE_TIMEOUT_SEC 3 + +static void device_timer_event(struct spa_source *source) +{ + struct spa_bt_device *device = source->data; + struct spa_bt_monitor *monitor = device->monitor; + uint64_t exp; + + if (read(source->fd, &exp, sizeof(uint64_t)) != sizeof(uint64_t)) + spa_log_warn(monitor->log, "error reading timerfd: %s", strerror(errno)); + + spa_log_debug(monitor->log, "timeout %08x %08x", device->profiles, device->connected_profiles); + + device_add(device->monitor, device); +} + +static int device_start_timer(struct spa_bt_device *device) +{ + struct spa_bt_monitor *monitor = device->monitor; + struct itimerspec ts; + + spa_log_debug(monitor->log, "start timer"); + if (device->timer.data == NULL) { + device->timer.data = device; + device->timer.func = device_timer_event; + device->timer.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); + device->timer.mask = SPA_IO_IN; + device->timer.rmask = 0; + spa_loop_add_source(monitor->main_loop, &device->timer); + } + ts.it_value.tv_sec = DEVICE_PROFILE_TIMEOUT_SEC; + ts.it_value.tv_nsec = 0; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + timerfd_settime(device->timer.fd, 0, &ts, NULL); + return 0; +} + +static int device_stop_timer(struct spa_bt_device *device) +{ + struct spa_bt_monitor *monitor = device->monitor; + struct itimerspec ts; + + if (device->timer.data == NULL) + return 0; + + spa_log_debug(monitor->log, "stop timer"); + spa_loop_remove_source(monitor->main_loop, &device->timer); + ts.it_value.tv_sec = 0; + ts.it_value.tv_nsec = 0; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + timerfd_settime(device->timer.fd, 0, &ts, NULL); + close(device->timer.fd); + device->timer.data = NULL; + return 0; +} + +static int check_profiles(struct spa_bt_device *device) +{ + struct spa_bt_monitor *monitor = device->monitor; + uint32_t connected_profiles = device->connected_profiles; + + if (connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) + connected_profiles |= SPA_BT_PROFILE_HEADSET_HEAD_UNIT; + if (connected_profiles & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) + connected_profiles |= SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; + + spa_log_debug(monitor->log, "profiles %08x %08x %d", + device->profiles, connected_profiles, device->added); + + if (connected_profiles == 0) { + if (device->added) { + device_stop_timer(device); + device_remove(monitor, device); + } + } + else if ((device->profiles & connected_profiles) == device->profiles) { + device_stop_timer(device); + device_add(monitor, device); + } else { + device_start_timer(device); + } + return 0; +} + static void device_set_connected(struct spa_bt_device *device, int connected) { + if (device->connected && !connected) + device->connected_profiles = 0; + device->connected = connected; + + if (connected) + check_profiles(device); + else + device_stop_timer(device); } +static int device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile) +{ + device->connected_profiles |= profile; + check_profiles(device); + return 0; +} + + static int device_update_props(struct spa_bt_device *device, DBusMessageIter *props_iter, DBusMessageIter *invalidated_iter) @@ -533,6 +708,11 @@ static int device_update_props(struct spa_bt_device *device, else if (strcmp(key, "Adapter") == 0) { free(device->adapter_path); device->adapter_path = strdup(value); + + device->adapter = adapter_find(monitor, value); + if (device->adapter == NULL) { + spa_log_warn(monitor->log, "unknown adapter %s", value); + } } else if (strcmp(key, "Icon") == 0) { free(device->icon); @@ -627,26 +807,33 @@ static struct spa_bt_transport *transport_find(struct spa_bt_monitor *monitor, c return NULL; } -static struct spa_bt_transport *transport_create(struct spa_bt_monitor *monitor, const char *path) +static struct spa_bt_transport *transport_create(struct spa_bt_monitor *monitor, char *path, size_t extra) { struct spa_bt_transport *t; - t = calloc(1, sizeof(struct spa_bt_transport)); + t = calloc(1, sizeof(struct spa_bt_transport) + extra); if (t == NULL) return NULL; t->monitor = monitor; - t->path = strdup(path); + t->path = path; t->fd = -1; + t->user_data = SPA_MEMBER(t, sizeof(struct spa_bt_transport), void); - spa_list_prepend(&monitor->transport_list, &t->link); + spa_list_append(&monitor->transport_list, &t->link); return t; } static void transport_free(struct spa_bt_transport *transport) { + if (transport->destroy) + transport->destroy(transport); spa_list_remove(&transport->link); + if (transport->device) { + transport->device->connected_profiles &= ~transport->profile; + spa_list_remove(&transport->device_link); + } free(transport->path); free(transport); } @@ -677,7 +864,17 @@ static int transport_update_props(struct spa_bt_transport *transport, spa_log_debug(monitor->log, "transport %p: %s=%s", transport, key, value); if (strcmp(key, "UUID") == 0) { - transport->profile = spa_bt_profile_from_uuid(value); + switch (spa_bt_profile_from_uuid(value)) { + case SPA_BT_PROFILE_A2DP_SOURCE: + transport->profile = SPA_BT_PROFILE_A2DP_SINK; + break; + case SPA_BT_PROFILE_A2DP_SINK: + transport->profile = SPA_BT_PROFILE_A2DP_SOURCE; + break; + default: + spa_log_warn(monitor->log, "unknown profile %s", value); + break; + } } else if (strcmp(key, "State") == 0) { transport->state = spa_bt_transport_state_from_string(value); @@ -729,38 +926,6 @@ static int transport_update_props(struct spa_bt_transport *transport, return 0; } -static struct spa_bt_node *node_create(struct spa_bt_monitor *monitor, struct spa_bt_transport *transport) -{ - struct spa_event *event; - struct spa_pod_builder b = { NULL, }; - uint8_t buffer[4096]; - struct spa_pod *item; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - event = spa_pod_builder_object(&b, SPA_TYPE_EVENT_Monitor, SPA_MONITOR_EVENT_Added, 0); - fill_item(monitor, transport, &item, &b); - - monitor->callbacks->event(monitor->callbacks_data, event); - - return NULL; -} - -static struct spa_bt_node *node_destroy(struct spa_bt_monitor *monitor, struct spa_bt_transport *transport) -{ - struct spa_event *event; - struct spa_pod_builder b = { NULL, }; - uint8_t buffer[4096]; - struct spa_pod *item; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - event = spa_pod_builder_object(&b, SPA_TYPE_EVENT_Monitor, SPA_MONITOR_EVENT_Removed, 0); - fill_item(monitor, transport, &item, &b); - - monitor->callbacks->event(monitor->callbacks_data, event); - - return NULL; -} - static int transport_acquire(struct spa_bt_transport *transport, bool optional) { struct spa_bt_monitor *monitor = transport->monitor; @@ -888,7 +1053,7 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, is_new = transport == NULL; if (is_new) { - transport = transport_create(monitor, transport_path); + transport = transport_create(monitor, strdup(transport_path), 0); if (transport == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; @@ -897,13 +1062,13 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, } transport_update_props(transport, &it[1], NULL); - if (is_new) - node_create(monitor, transport); - if (transport->device == NULL) { spa_log_warn(monitor->log, "no device found for transport"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } + spa_list_append(&transport->device->transport_list, &transport->device_link); + + device_connect_profile(transport->device, transport->profile); if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; @@ -935,8 +1100,13 @@ static DBusHandlerResult endpoint_clear_configuration(DBusConnection *conn, DBus } transport = transport_find(monitor, transport_path); - if (transport != NULL) - node_destroy(monitor, transport); + + if (transport != NULL) { + struct spa_bt_device *device = transport->device; + transport_free(transport); + if (device != NULL) + check_profiles(device); + } if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; @@ -1137,12 +1307,579 @@ static int adapter_register_endpoints(struct spa_bt_adapter *a) return 0; } +static DBusHandlerResult profile_release(DBusConnection *conn, DBusMessage *m, void *userdata) +{ + DBusMessage *r; + + r = dbus_message_new_error(m, BLUEZ_PROFILE_INTERFACE ".Error.NotImplemented", + "Method not implemented"); + if (r == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + if (!dbus_connection_send(conn, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + dbus_message_unref(r); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void rfcomm_event(struct spa_source *source) +{ + struct spa_bt_transport *t = source->data; + struct spa_bt_monitor *monitor = t->monitor; + + if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { + spa_log_info(monitor->log, "lost RFCOMM connection."); + if (source->loop) + spa_loop_remove_source(source->loop, source); + goto fail; + } + + if (source->rmask & SPA_IO_IN) { + char buf[512]; + ssize_t len; + int gain, dummy; + bool do_reply = false; + + len = read(source->fd, buf, 511); + if (len < 0) { + spa_log_error(monitor->log, "RFCOMM read error: %s", strerror(errno)); + goto fail; + } + buf[len] = 0; + spa_log_debug(monitor->log, "RFCOMM << %s", buf); + + /* There are only four HSP AT commands: + * AT+VGS=value: value between 0 and 15, sent by the HS to AG to set the speaker gain. + * +VGS=value is sent by AG to HS as a response to an AT+VGS command or when the gain + * is changed on the AG side. + * AT+VGM=value: value between 0 and 15, sent by the HS to AG to set the microphone gain. + * +VGM=value is sent by AG to HS as a response to an AT+VGM command or when the gain + * is changed on the AG side. + * AT+CKPD=200: Sent by HS when headset button is pressed. + * RING: Sent by AG to HS to notify of an incoming call. It can safely be ignored because + * it does not expect a reply. */ + if (sscanf(buf, "AT+VGS=%d", &gain) == 1 || + sscanf(buf, "\r\n+VGM=%d\r\n", &gain) == 1) { +// t->speaker_gain = gain; + do_reply = true; + } else if (sscanf(buf, "AT+VGM=%d", &gain) == 1 || + sscanf(buf, "\r\n+VGS=%d\r\n", &gain) == 1) { +// t->microphone_gain = gain; + do_reply = true; + } else if (sscanf(buf, "AT+CKPD=%d", &dummy) == 1) { + do_reply = true; + } else { + do_reply = false; + } + + if (do_reply) { + spa_log_debug(monitor->log, "RFCOMM >> OK"); + + len = write(source->fd, "\r\nOK\r\n", 6); + + /* we ignore any errors, it's not critical and real errors should + * be caught with the HANGUP and ERROR events handled above */ + if (len < 0) + spa_log_error(monitor->log, "RFCOMM write error: %s", strerror(errno)); + } + } +fail: + return; +} + +static int sco_do_accept(struct spa_bt_transport *t) +{ + struct transport_data *td = t->user_data; + struct spa_bt_monitor *monitor = t->monitor; + struct sockaddr_sco addr; + socklen_t optlen; + int sock; + + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + + spa_log_info(monitor->log, "doing accept"); + sock = accept(td->sco.fd, (struct sockaddr *) &addr, &optlen); + if (sock < 0) { + if (errno != EAGAIN) + spa_log_error(monitor->log, "accept(): %s", strerror(errno)); + goto fail; + } + return sock; +fail: + return -1; +} + +static int sco_do_connect(struct spa_bt_transport *t) +{ + struct spa_bt_monitor *monitor = t->monitor; + struct spa_bt_device *d = t->device; + struct sockaddr_sco addr; + socklen_t len; + int err, i; + int sock; + bdaddr_t src; + bdaddr_t dst; + const char *src_addr, *dst_addr; + + if (d->adapter == NULL) + return -EIO; + + src_addr = d->adapter->address; + dst_addr = d->address; + + /* don't use ba2str to avoid -lbluetooth */ + for (i = 5; i >= 0; i--, src_addr += 3) + src.b[i] = strtol(src_addr, NULL, 16); + for (i = 5; i >= 0; i--, dst_addr += 3) + dst.b[i] = strtol(dst_addr, NULL, 16); + + sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); + if (sock < 0) { + spa_log_error(monitor->log, "socket(SEQPACKET, SCO) %s", strerror(errno)); + return -errno; + } + + len = sizeof(addr); + memset(&addr, 0, len); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, &src); + + if (bind(sock, (struct sockaddr *) &addr, len) < 0) { + spa_log_error(monitor->log, "bind(): %s", strerror(errno)); + goto fail_close; + } + + memset(&addr, 0, len); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, &dst); + + spa_log_info(monitor->log, "doing connect"); + err = connect(sock, (struct sockaddr *) &addr, len); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) { + spa_log_error(monitor->log, "connect(): %s", strerror(errno)); + goto fail_close; + } + + return sock; + +fail_close: + close(sock); + return -1; +} + + +static int sco_acquire_cb(struct spa_bt_transport *t, bool optional) +{ + struct spa_bt_monitor *monitor = t->monitor; + int sock; + socklen_t len; + + if (optional) + sock = sco_do_accept(t); + else + sock = sco_do_connect(t); + + if (sock < 0) + goto fail; + + t->read_mtu = 48; + t->write_mtu = 48; + + if (true) { + struct sco_options sco_opt; + + len = sizeof(sco_opt); + memset(&sco_opt, 0, len); + + if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) + spa_log_warn(monitor->log, "getsockopt(SCO_OPTIONS) failed, loading defaults"); + else { + spa_log_debug(monitor->log, "autodetected mtu = %u", sco_opt.mtu); + t->read_mtu = sco_opt.mtu; + t->write_mtu = sco_opt.mtu; + } + } + return sock; +fail: + return -1; +} + +static int sco_release_cb(struct spa_bt_transport *t) +{ + struct spa_bt_monitor *monitor = t->monitor; + spa_log_info(monitor->log, "Transport %s released", t->path); + /* device will close the SCO socket for us */ + return 0; +} + +static void sco_event(struct spa_source *source) +{ + struct spa_bt_transport *t = source->data; + struct spa_bt_monitor *monitor = t->monitor; + + if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { + spa_log_error(monitor->log, "error listening SCO connection: %s", strerror(errno)); + goto fail; + } + +#if 0 + if (t->state != PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) { + spa_log_info(monitor->log, "SCO incoming connection: changing state to PLAYING"); + pa_bluetooth_transport_set_state (t, PA_BLUETOOTH_TRANSPORT_STATE_PLAYING); + } +#endif + +fail: + return; +} + +static int sco_listen(struct spa_bt_transport *t) +{ + struct spa_bt_monitor *monitor = t->monitor; + struct transport_data *td = t->user_data; + struct sockaddr_sco addr; + int sock, i; + bdaddr_t src; + const char *src_addr; + + if (t->device->adapter == NULL) + return -EIO; + + sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, BTPROTO_SCO); + if (sock < 0) { + spa_log_error(monitor->log, "socket(SEQPACKET, SCO) %m"); + return -errno; + } + + src_addr = t->device->adapter->address; + + /* don't use ba2str to avoid -lbluetooth */ + for (i = 5; i >= 0; i--, src_addr += 3) + src.b[i] = strtol(src_addr, NULL, 16); + + /* Bind to local address */ + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, &src); + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + spa_log_error(monitor->log, "bind(): %m"); + goto fail_close; + } + + spa_log_info(monitor->log, "doing listen"); + if (listen(sock, 1) < 0) { + spa_log_error(monitor->log, "listen(): %m"); + goto fail_close; + } + + td->sco.func = sco_event; + td->sco.data = t; + td->sco.fd = sock; + td->sco.mask = SPA_IO_IN; + td->sco.rmask = 0; + spa_loop_add_source(monitor->main_loop, &td->sco); + + return sock; + +fail_close: + close(sock); + return -1; +} + +static int sco_destroy_cb(struct spa_bt_transport *trans) +{ + struct transport_data *td = trans->user_data; + + if (td->sco.data) { + if (td->sco.loop) + spa_loop_remove_source(td->sco.loop, &td->sco); + shutdown(td->sco.fd, SHUT_RDWR); + close (td->sco.fd); + } + if (td->rfcomm.data) { + if (td->rfcomm.loop) + spa_loop_remove_source(td->rfcomm.loop, &td->rfcomm); + shutdown(td->rfcomm.fd, SHUT_RDWR); + close (td->rfcomm.fd); + } + return 0; +} + +static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessage *m, void *userdata) +{ + struct spa_bt_monitor *monitor = userdata; + DBusMessage *r; + DBusMessageIter it[5]; + const char *handler, *path; + char *pathfd; + enum spa_bt_profile profile; + struct spa_bt_device *d; + struct spa_bt_transport *t; + struct transport_data *td; + int fd; + + if (!dbus_message_has_signature(m, "oha{sv}")) { + spa_log_warn(monitor->log, "invalid NewConnection() signature"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + handler = dbus_message_get_path(m); + if (strcmp(handler, PROFILE_HSP_AG) == 0) + profile = SPA_BT_PROFILE_HSP_HS; + else if (strcmp(handler, PROFILE_HSP_HS) == 0) + profile = SPA_BT_PROFILE_HSP_AG; + else if (strcmp(handler, PROFILE_HFP_HS) == 0) + profile = SPA_BT_PROFILE_HFP_AG; + else if (strcmp(handler, PROFILE_HFP_AG) == 0) + profile = SPA_BT_PROFILE_HFP_HF; + else { + spa_log_warn(monitor->log, "invalid handler %s", handler); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + dbus_message_iter_init(m, &it[0]); + dbus_message_iter_get_basic(&it[0], &path); + + d = device_find(monitor, path); + if (d == NULL) { + spa_log_warn(monitor->log, "unknown device for path %s", path); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + dbus_message_iter_next(&it[0]); + dbus_message_iter_get_basic(&it[0], &fd); + + spa_log_debug(monitor->log, "NewConnection path=%s, fd=%d, profile %s", path, fd, handler); + + asprintf(&pathfd, "%s/fd%d", path, fd); + t = transport_create(monitor, pathfd, sizeof(struct transport_data)); + + t->acquire = sco_acquire_cb; + t->release = sco_release_cb; + t->destroy = sco_destroy_cb; + t->device = d; + spa_list_append(&t->device->transport_list, &t->device_link); + t->profile = profile; + + td = t->user_data; + td->rfcomm.func = rfcomm_event; + td->rfcomm.data = t; + td->rfcomm.fd = fd; + td->rfcomm.mask = SPA_IO_IN; + td->rfcomm.rmask = 0; + spa_loop_add_source(monitor->main_loop, &td->rfcomm); + + device_connect_profile(t->device, profile); + + sco_listen(t); + + if ((r = dbus_message_new_method_return(m)) == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + if (!dbus_connection_send(conn, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + dbus_message_unref(r); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult profile_request_disconnection(DBusConnection *conn, DBusMessage *m, void *userdata) +{ + struct spa_bt_monitor *monitor = userdata; + DBusMessage *r; + const char *handler, *path; + struct spa_bt_device *d; + struct spa_bt_transport *t, *tmp; + enum spa_bt_profile profile; + DBusMessageIter it[5]; + + if (!dbus_message_has_signature(m, "o")) { + spa_log_warn(monitor->log, "invalid RequestDisconnection() signature"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + handler = dbus_message_get_path(m); + if (strcmp(handler, PROFILE_HSP_AG) == 0) + profile = SPA_BT_PROFILE_HSP_HS; + else if (strcmp(handler, PROFILE_HSP_HS) == 0) + profile = SPA_BT_PROFILE_HSP_AG; + else if (strcmp(handler, PROFILE_HFP_HS) == 0) + profile = SPA_BT_PROFILE_HFP_AG; + else if (strcmp(handler, PROFILE_HFP_AG) == 0) + profile = SPA_BT_PROFILE_HFP_HF; + else { + spa_log_warn(monitor->log, "invalid handler %s", handler); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + dbus_message_iter_init(m, &it[0]); + dbus_message_iter_get_basic(&it[0], &path); + + d = device_find(monitor, path); + if (d == NULL) { + spa_log_warn(monitor->log, "unknown device for path %s", path); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + spa_list_for_each_safe(t, tmp, &d->transport_list, device_link) { + if (t->profile == profile) + transport_free(t); + } + check_profiles(d); + + if ((r = dbus_message_new_method_return(m)) == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + if (!dbus_connection_send(conn, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + dbus_message_unref(r); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult profile_handler(DBusConnection *c, DBusMessage *m, void *userdata) +{ + struct spa_bt_monitor *monitor = userdata; + const char *path, *interface, *member; + DBusMessage *r; + DBusHandlerResult res; + + path = dbus_message_get_path(m); + interface = dbus_message_get_interface(m); + member = dbus_message_get_member(m); + + spa_log_debug(monitor->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); + + if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + const char *xml = PROFILE_INTROSPECT_XML; + + if ((r = dbus_message_new_method_return(m)) == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + if (!dbus_connection_send(monitor->conn, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + dbus_message_unref(r); + res = DBUS_HANDLER_RESULT_HANDLED; + } + else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, "Release")) + res = profile_release(c, m, userdata); + else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, "RequestDisconnection")) + res = profile_request_disconnection(c, m, userdata); + else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, "NewConnection")) + res = profile_new_connection(c, m, userdata); + else + res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + return res; +} + +static void register_profile_reply(DBusPendingCall *pending, void *user_data) +{ + struct spa_bt_monitor *monitor = user_data; + DBusMessage *r; + + r = dbus_pending_call_steal_reply(pending); + if (r == NULL) + return; + + if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) { + spa_log_warn(monitor->log, "Register profile not supported"); + goto finish; + } + if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { + spa_log_warn(monitor->log, "Error registering profile"); + goto finish; + } + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(monitor->log, "RegisterProfile() failed: %s", + dbus_message_get_error_name(r)); + goto finish; + } + + finish: + dbus_message_unref(r); + dbus_pending_call_unref(pending); +} + +static int register_profile(struct spa_bt_monitor *monitor, const char *profile, const char *uuid) +{ + static const DBusObjectPathVTable vtable_profile = { + .message_function = profile_handler, + }; + DBusMessage *m; + DBusMessageIter it[4]; + dbus_bool_t autoconnect; + dbus_uint16_t version, chan; + char *str; + DBusPendingCall *call; + + spa_log_debug(monitor->log, "Registering Profile %s %s", profile, uuid); + + if (!dbus_connection_register_object_path(monitor->conn, + profile, + &vtable_profile, monitor)) + return -EIO; + + m = dbus_message_new_method_call(BLUEZ_SERVICE, "/org/bluez", + BLUEZ_PROFILE_MANAGER_INTERFACE, "RegisterProfile"); + if (m == NULL) + return -ENOMEM; + + dbus_message_iter_init_append(m, &it[0]); + dbus_message_iter_append_basic(&it[0], DBUS_TYPE_OBJECT_PATH, &profile); + dbus_message_iter_append_basic(&it[0], DBUS_TYPE_STRING, &uuid); + dbus_message_iter_open_container(&it[0], DBUS_TYPE_ARRAY, "{sv}", &it[1]); + + if (strcmp(uuid, SPA_BT_UUID_HSP_HS) == 0 || + strcmp(uuid, SPA_BT_UUID_HSP_HS_ALT) == 0) { + + /* In the headset role, the connection will only be initiated from the remote side */ + str = "AutoConnect"; + autoconnect = 0; + dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); + dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); + dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "b", &it[3]); + dbus_message_iter_append_basic(&it[3], DBUS_TYPE_BOOLEAN, &autoconnect); + dbus_message_iter_close_container(&it[2], &it[3]); + dbus_message_iter_close_container(&it[1], &it[2]); + + str = "Channel"; + chan = HSP_HS_DEFAULT_CHANNEL; + dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); + dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); + dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); + dbus_message_iter_append_basic(&it[3], DBUS_TYPE_UINT16, &chan); + dbus_message_iter_close_container(&it[2], &it[3]); + dbus_message_iter_close_container(&it[1], &it[2]); + + /* HSP version 1.2 */ + str = "Version"; + version = 0x0102; + dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); + dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); + dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); + dbus_message_iter_append_basic(&it[3], DBUS_TYPE_UINT16, &version); + dbus_message_iter_close_container(&it[2], &it[3]); + dbus_message_iter_close_container(&it[1], &it[2]); + } + dbus_message_iter_close_container(&it[0], &it[1]); + + dbus_connection_send_with_reply(monitor->conn, m, &call, -1); + dbus_pending_call_set_notify(call, register_profile_reply, monitor, NULL); + dbus_message_unref(m); + return 0; +} + static void interface_added(struct spa_bt_monitor *monitor, DBusConnection *conn, const char *object_path, const char *interface_name, DBusMessageIter *props_iter) { + spa_log_debug(monitor->log, "Found object %s, interface %s", object_path, interface_name); + if (strcmp(interface_name, BLUEZ_ADAPTER_INTERFACE) == 0) { struct spa_bt_adapter *a; @@ -1157,6 +1894,12 @@ static void interface_added(struct spa_bt_monitor *monitor, adapter_update_props(a, props_iter, NULL); adapter_register_endpoints(a); } + else if (strcmp(interface_name, BLUEZ_PROFILE_MANAGER_INTERFACE) == 0) { + register_profile(monitor, PROFILE_HSP_AG, SPA_BT_UUID_HSP_AG); + register_profile(monitor, PROFILE_HSP_HS, SPA_BT_UUID_HSP_HS); + register_profile(monitor, PROFILE_HFP_AG, SPA_BT_UUID_HFP_AG); + register_profile(monitor, PROFILE_HFP_HS, SPA_BT_UUID_HFP_HF); + } else if (strcmp(interface_name, BLUEZ_DEVICE_INTERFACE) == 0) { struct spa_bt_device *d; @@ -1170,8 +1913,6 @@ static void interface_added(struct spa_bt_monitor *monitor, } device_update_props(d, props_iter, NULL); } - else - spa_log_debug(monitor->log, "Unknown interface %s found, skipping", interface_name); } static void get_managed_objects_reply(DBusPendingCall *pending, void *user_data) @@ -1263,7 +2004,9 @@ impl_monitor_set_callbacks(struct spa_monitor *monitor, this->callbacks = callbacks; this->callbacks_data = data; - get_managed_objects(this); + if (callbacks) { + get_managed_objects(this); + } return 0; } @@ -1321,10 +2064,17 @@ impl_init(const struct spa_handle_factory *factory, this = (struct spa_bt_monitor *) handle; for (i = 0; i < n_support; i++) { - if (support[i].type == SPA_TYPE_INTERFACE_Log) + switch (support[i].type) { + case SPA_TYPE_INTERFACE_Log: this->log = support[i].data; - else if (support[i].type == SPA_TYPE_INTERFACE_DBus) + break; + case SPA_TYPE_INTERFACE_DBus: this->dbus = support[i].data; + break; + case SPA_TYPE_INTERFACE_MainLoop: + this->main_loop = support[i].data; + break; + } } if (this->dbus == NULL) { spa_log_error(this->log, "a dbus is needed"); diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 8d0075dd9..d3042e325 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -30,13 +30,16 @@ extern "C" { #endif #define BLUEZ_SERVICE "org.bluez" +#define BLUEZ_PROFILE_MANAGER_INTERFACE BLUEZ_SERVICE ".ProfileManager1" +#define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1" #define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1" #define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1" #define BLUEZ_MEDIA_INTERFACE BLUEZ_SERVICE ".Media1" #define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1" #define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1" -#define ENDPOINT_INTROSPECT_XML \ + +#define ENDPOINT_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "" \ " " \ @@ -61,24 +64,56 @@ extern "C" { " " \ "" +#define PROFILE_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "" \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + " " \ + "" #define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported" #define SPA_BT_UUID_A2DP_SOURCE "0000110A-0000-1000-8000-00805F9B34FB" #define SPA_BT_UUID_A2DP_SINK "0000110B-0000-1000-8000-00805F9B34FB" #define SPA_BT_UUID_HSP_HS "00001108-0000-1000-8000-00805F9B34FB" +#define SPA_BT_UUID_HSP_HS_ALT "00001131-0000-1000-8000-00805F9B34FB" #define SPA_BT_UUID_HSP_AG "00001112-0000-1000-8000-00805F9B34FB" #define SPA_BT_UUID_HFP_HF "0000111E-0000-1000-8000-00805F9B34FB" #define SPA_BT_UUID_HFP_AG "0000111F-0000-1000-8000-00805F9B34FB" +#define PROFILE_HSP_AG "/Profile/HSPAG" +#define PROFILE_HSP_HS "/Profile/HSPHS" +#define PROFILE_HFP_AG "/Profile/HFPAG" +#define PROFILE_HFP_HS "/Profile/HFPHS" + +#define HSP_HS_DEFAULT_CHANNEL 3 + enum spa_bt_profile { - SPA_BT_PROFILE_NULL = 0, + SPA_BT_PROFILE_NULL = 0, SPA_BT_PROFILE_A2DP_SOURCE = (1 << 0), SPA_BT_PROFILE_A2DP_SINK = (1 << 1), - SPA_BT_PROFILE_HSP_HS = (1 << 2), - SPA_BT_PROFILE_HSP_AG = (1 << 3), - SPA_BT_PROFILE_HFP_HF = (1 << 4), - SPA_BT_PROFILE_HFP_AG = (1 << 5), + SPA_BT_PROFILE_HSP_HS = (1 << 2), + SPA_BT_PROFILE_HSP_AG = (1 << 3), + SPA_BT_PROFILE_HFP_HF = (1 << 4), + SPA_BT_PROFILE_HFP_AG = (1 << 5), + + SPA_BT_PROFILE_HEADSET_HEAD_UNIT = (SPA_BT_PROFILE_HSP_HS | SPA_BT_PROFILE_HFP_HF), + SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY = (SPA_BT_PROFILE_HSP_AG | SPA_BT_PROFILE_HFP_AG), }; static inline enum spa_bt_profile spa_bt_profile_from_uuid(const char *uuid) @@ -89,6 +124,8 @@ static inline enum spa_bt_profile spa_bt_profile_from_uuid(const char *uuid) return SPA_BT_PROFILE_A2DP_SINK; else if (strcasecmp(uuid, SPA_BT_UUID_HSP_HS) == 0) return SPA_BT_PROFILE_HSP_HS; + else if (strcasecmp(uuid, SPA_BT_UUID_HSP_HS_ALT) == 0) + return SPA_BT_PROFILE_HSP_HS; else if (strcasecmp(uuid, SPA_BT_UUID_HSP_AG) == 0) return SPA_BT_PROFILE_HSP_AG; else if (strcasecmp(uuid, SPA_BT_UUID_HFP_HF) == 0) @@ -116,6 +153,7 @@ struct spa_bt_adapter { struct spa_bt_device { struct spa_list link; struct spa_bt_monitor *monitor; + struct spa_bt_adapter *adapter; char *path; char *alias; char *address; @@ -130,6 +168,10 @@ struct spa_bt_device { int connected; int blocked; uint32_t profiles; + uint32_t connected_profiles; + struct spa_source timer; + struct spa_list transport_list; + bool added; }; enum spa_bt_transport_state { @@ -143,6 +185,7 @@ struct spa_bt_transport { struct spa_bt_monitor *monitor; char *path; struct spa_bt_device *device; + struct spa_list device_link; enum spa_bt_profile profile; enum spa_bt_transport_state state; int codec; @@ -153,10 +196,13 @@ struct spa_bt_transport { int fd; uint16_t read_mtu; uint16_t write_mtu; + void *user_data; int (*acquire) (struct spa_bt_transport *trans, bool optional); int (*release) (struct spa_bt_transport *trans); + + int (*destroy) (struct spa_bt_transport *trans); }; static inline enum spa_bt_transport_state spa_bt_transport_state_from_string(const char *value)