From 06a6e11806f243a09ad1f004e63c0a6d9a68d4f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Fri, 17 Jul 2020 15:18:10 +0200 Subject: [PATCH] bluez5: Create backend for native HSP support --- spa/plugins/bluez5/backend-hsp-native.c | 669 ++++++++++++++++++++++++ spa/plugins/bluez5/bluez5-dbus.c | 636 +--------------------- spa/plugins/bluez5/defs.h | 15 + spa/plugins/bluez5/meson.build | 3 +- 4 files changed, 714 insertions(+), 609 deletions(-) create mode 100644 spa/plugins/bluez5/backend-hsp-native.c diff --git a/spa/plugins/bluez5/backend-hsp-native.c b/spa/plugins/bluez5/backend-hsp-native.c new file mode 100644 index 000000000..f792df390 --- /dev/null +++ b/spa/plugins/bluez5/backend-hsp-native.c @@ -0,0 +1,669 @@ +/* Spa HSP native backend + * + * Copyright © 2018 Wim Taymans + * + * 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 +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "defs.h" + +struct spa_bt_backend { + struct spa_bt_monitor *monitor; + + struct spa_log *log; + struct spa_loop *main_loop; + struct spa_dbus *dbus; + struct spa_dbus_connection *dbus_connection; + DBusConnection *conn; +}; + +struct transport_data { + struct spa_source rfcomm; + struct spa_source sco; +}; + +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_backend *backend = t->backend; + + if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { + spa_log_info(backend->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(backend->log, "RFCOMM read error: %s", strerror(errno)); + goto fail; + } + buf[len] = 0; + spa_log_debug(backend->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(backend->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(backend->log, "RFCOMM write error: %s", strerror(errno)); + } + } + return; + +fail: + spa_bt_transport_free(t); + return; +} + +static int sco_do_accept(struct spa_bt_transport *t) +{ + struct transport_data *td = t->user_data; + struct spa_bt_backend *backend = t->backend; + struct sockaddr_sco addr; + socklen_t optlen; + int sock; + + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + + spa_log_debug(backend->log, "transport %p: doing accept", t); + sock = accept(td->sco.fd, (struct sockaddr *) &addr, &optlen); + if (sock < 0) { + if (errno != EAGAIN) + spa_log_error(backend->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_backend *backend = t->backend; + 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(backend->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(backend->log, "bind(): %s", strerror(errno)); + goto fail_close; + } + + memset(&addr, 0, len); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, &dst); + + spa_log_debug(backend->log, "transport %p: doing connect", t); + err = connect(sock, (struct sockaddr *) &addr, len); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) { + spa_log_error(backend->log, "connect(): %s", strerror(errno)); + goto fail_close; + } + + return sock; + +fail_close: + close(sock); + return -1; +} + +static int sco_acquire_cb(void *data, bool optional) +{ + struct spa_bt_transport *t = data; + struct spa_bt_backend *backend = t->backend; + 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(backend->log, "getsockopt(SCO_OPTIONS) failed, loading defaults"); + else { + spa_log_debug(backend->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(void *data) +{ + struct spa_bt_transport *t = data; + struct spa_bt_backend *backend = t->backend; + spa_log_info(backend->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_backend *backend = t->backend; + + if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { + spa_log_error(backend->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_backend *backend = t->backend; + 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(backend->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(backend->log, "bind(): %m"); + goto fail_close; + } + + spa_log_debug(backend->log, "transport %p: doing listen", t); + if (listen(sock, 1) < 0) { + spa_log_error(backend->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(backend->main_loop, &td->sco); + + return sock; + +fail_close: + close(sock); + return -1; +} + +static int sco_destroy_cb(void *data) +{ + struct spa_bt_transport *trans = data; + 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); + td->sco.fd = -1; + } + 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); + td->rfcomm.fd = -1; + } + return 0; +} + +static const struct spa_bt_transport_implementation sco_transport_impl = { + SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION, + .acquire = sco_acquire_cb, + .release = sco_release_cb, + .destroy = sco_destroy_cb, +}; + +static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessage *m, void *userdata) +{ + struct spa_bt_backend *backend = 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(backend->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 { + spa_log_warn(backend->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 = spa_bt_device_find(backend->monitor, path); + if (d == NULL) { + spa_log_warn(backend->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(backend->log, "NewConnection path=%s, fd=%d, profile %s", path, fd, handler); + + if ((pathfd = spa_aprintf("%s/fd%d", path, fd)) == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + t = spa_bt_transport_create(backend->monitor, pathfd, sizeof(struct transport_data)); + if (t == NULL) { + spa_log_warn(backend->log, "can't create transport: %m"); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + spa_bt_transport_set_implementation(t, &sco_transport_impl, t); + + t->device = d; + spa_list_append(&t->device->transport_list, &t->device_link); + t->profile = profile; + t->backend = backend; + + 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(backend->main_loop, &td->rfcomm); + + spa_bt_device_connect_profile(t->device, profile); + + sco_listen(t); + + spa_log_debug(backend->log, "Transport %s available for profile %s", t->path, handler); + + 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_backend *backend = 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(backend->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 { + spa_log_warn(backend->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 = spa_bt_device_find(backend->monitor, path); + if (d == NULL) { + spa_log_warn(backend->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) + spa_bt_transport_free(t); + } + spa_bt_device_check_profiles(d, false); + + 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_backend *backend = 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(backend->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(backend->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_backend *backend = 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(backend->log, "Register profile not supported"); + goto finish; + } + if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { + spa_log_warn(backend->log, "Error registering profile"); + goto finish; + } + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(backend->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_backend *backend, 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(backend->log, "Registering Profile %s %s", profile, uuid); + + if (!dbus_connection_register_object_path(backend->conn, + profile, + &vtable_profile, backend)) + 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(backend->conn, m, &call, -1); + dbus_pending_call_set_notify(call, register_profile_reply, backend, NULL); + dbus_message_unref(m); + return 0; +} + +void backend_hsp_native_register_profiles(struct spa_bt_backend *backend) +{ + register_profile(backend, PROFILE_HSP_AG, SPA_BT_UUID_HSP_AG); + register_profile(backend, PROFILE_HSP_HS, SPA_BT_UUID_HSP_HS); +} + +void backend_hsp_native_free(struct spa_bt_backend *backend) +{ + free(backend); +} + +struct spa_bt_backend *backend_hsp_native_new(struct spa_bt_monitor *monitor, + const struct spa_support *support, + uint32_t n_support) +{ + struct spa_bt_backend *backend; + + backend = calloc(1, sizeof(struct spa_bt_backend)); + if (backend == NULL) + return NULL; + + backend->monitor = monitor; + backend->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + backend->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus); + backend->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); + + backend->dbus_connection = spa_dbus_get_connection(backend->dbus, SPA_DBUS_TYPE_SYSTEM); + if (backend->dbus_connection == NULL) { + spa_log_error(backend->log, "no dbus connection"); + free(backend); + return NULL; + } + backend->conn = spa_dbus_connection_get(backend->dbus_connection); + + return backend; +} \ No newline at end of file diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 761a7a724..6ad519859 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -32,7 +32,6 @@ #include #include -#include #include @@ -73,11 +72,8 @@ struct spa_bt_monitor { struct spa_list transport_list; unsigned int filters_added:1; -}; -struct transport_data { - struct spa_source rfcomm; - struct spa_source sco; + struct spa_bt_backend *backend_hsp_native; }; static inline void add_dict(struct spa_pod_builder *builder, const char *key, const char *val) @@ -456,7 +452,7 @@ static void adapter_free(struct spa_bt_adapter *adapter) free(adapter); } -static struct spa_bt_device *device_find(struct spa_bt_monitor *monitor, const char *path) +struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_device *d; spa_list_for_each(d, &monitor->device_list, link) @@ -616,7 +612,7 @@ static int device_stop_timer(struct spa_bt_device *device) return 0; } -static int device_check_profiles(struct spa_bt_device *device, bool force) +int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force) { struct spa_bt_monitor *monitor = device->monitor; uint32_t connected_profiles = device->connected_profiles; @@ -652,15 +648,15 @@ static void device_set_connected(struct spa_bt_device *device, int connected) device->connected = connected; if (connected) - device_check_profiles(device, false); + spa_bt_device_check_profiles(device, false); else device_stop_timer(device); } -static int device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile) +int spa_bt_device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile) { device->connected_profiles |= profile; - device_check_profiles(device, false); + spa_bt_device_check_profiles(device, false); return 0; } @@ -767,7 +763,7 @@ static int device_update_props(struct spa_bt_device *device, } else if (strcmp(key, "ServicesResolved") == 0) { if (value) - device_check_profiles(device, true); + spa_bt_device_check_profiles(device, true); } } else if (strcmp(key, "UUIDs") == 0) { @@ -810,7 +806,7 @@ 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, char *path, size_t extra) +struct spa_bt_transport *spa_bt_transport_create(struct spa_bt_monitor *monitor, char *path, size_t extra) { struct spa_bt_transport *t; @@ -841,7 +837,7 @@ static void transport_set_state(struct spa_bt_transport *transport, enum spa_bt_ } } -static void transport_free(struct spa_bt_transport *transport) +void spa_bt_transport_free(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; @@ -904,7 +900,7 @@ static int transport_update_props(struct spa_bt_transport *transport, transport_set_state(transport, spa_bt_transport_state_from_string(value)); } else if (strcmp(key, "Device") == 0) { - transport->device = device_find(monitor, value); + transport->device = spa_bt_device_find(monitor, value); if (transport->device == NULL) spa_log_warn(monitor->log, "could not find device %s", value); } @@ -1085,7 +1081,7 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, is_new = transport == NULL; if (is_new) { - transport = transport_create(monitor, strdup(transport_path), 0); + transport = spa_bt_transport_create(monitor, strdup(transport_path), 0); if (transport == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; @@ -1100,7 +1096,7 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, if (is_new) spa_list_append(&transport->device->transport_list, &transport->device_link); - device_connect_profile(transport->device, transport->profile); + spa_bt_device_connect_profile(transport->device, transport->profile); if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; @@ -1139,9 +1135,9 @@ static DBusHandlerResult endpoint_clear_configuration(DBusConnection *conn, DBus spa_log_debug(monitor->log, "transport %p: free %s", transport, transport->path); - transport_free(transport); + spa_bt_transport_free(transport); if (device != NULL) - device_check_profiles(device, false); + spa_bt_device_check_profiles(device, false); } if ((r = dbus_message_new_method_return(m)) == NULL) @@ -1375,591 +1371,6 @@ 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)); - } - } - return; - -fail: - transport_free(t); - 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_debug(monitor->log, "transport %p: doing accept", t); - 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_debug(monitor->log, "transport %p: doing connect", t); - 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(void *data, bool optional) -{ - struct spa_bt_transport *t = data; - 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(void *data) -{ - struct spa_bt_transport *t = data; - 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_debug(monitor->log, "transport %p: doing listen", t); - 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(void *data) -{ - struct spa_bt_transport *trans = data; - 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); - td->sco.fd = -1; - } - 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); - td->rfcomm.fd = -1; - } - return 0; -} - -static const struct spa_bt_transport_implementation sco_transport_impl = { - SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION, - .acquire = sco_acquire_cb, - .release = sco_release_cb, - .destroy = sco_destroy_cb, -}; - -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); - - if ((pathfd = spa_aprintf("%s/fd%d", path, fd)) == NULL) - return DBUS_HANDLER_RESULT_NEED_MEMORY; - - t = transport_create(monitor, pathfd, sizeof(struct transport_data)); - if (t == NULL) { - spa_log_warn(monitor->log, "can't create transport: %m"); - return DBUS_HANDLER_RESULT_NEED_MEMORY; - } - spa_bt_transport_set_implementation(t, &sco_transport_impl, t); - - 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); - - spa_log_debug(monitor->log, "Transport %s available for profile %s", t->path, handler); - - 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); - } - device_check_profiles(d, false); - - 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, @@ -1984,15 +1395,17 @@ static void interface_added(struct spa_bt_monitor *monitor, 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); + backend_hsp_native_register_profiles(monitor->backend_hsp_native); + /* HFP Profiles should be managed by an external program */ + /* 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; - d = device_find(monitor, object_path); + d = spa_bt_device_find(monitor, object_path); if (d == NULL) { d = device_create(monitor, object_path); if (d == NULL) { @@ -2123,7 +1536,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us else if (strcmp(iface, BLUEZ_DEVICE_INTERFACE) == 0) { struct spa_bt_device *d; - d = device_find(monitor, path); + d = spa_bt_device_find(monitor, path); if (d == NULL) { spa_log_warn(monitor->log, "Properties changed in unknown device %s", path); @@ -2250,12 +1663,17 @@ static int impl_clear(struct spa_handle *handle) monitor = (struct spa_bt_monitor *) handle; spa_list_consume(t, &monitor->transport_list, link) - transport_free(t); + spa_bt_transport_free(t); spa_list_consume(d, &monitor->device_list, link) device_free(d); spa_list_consume(a, &monitor->adapter_list, link) adapter_free(a); + if (monitor->backend_hsp_native) { + backend_hsp_native_free(monitor->backend_hsp_native); + monitor->backend_hsp_native = NULL; + } + return 0; } @@ -2311,6 +1729,8 @@ impl_init(const struct spa_handle_factory *factory, spa_list_init(&this->device_list); spa_list_init(&this->transport_list); + this->backend_hsp_native = backend_hsp_native_new(this, support, n_support); + return 0; } diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index ee419d287..bb998f573 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -161,6 +161,7 @@ static inline const char *spa_bt_profile_name (enum spa_bt_profile profile) { } struct spa_bt_monitor; +struct spa_bt_backend; struct spa_bt_adapter { struct spa_list link; @@ -199,6 +200,10 @@ struct spa_bt_device { bool added; }; +struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path); +int spa_bt_device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile); +int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force); + enum spa_bt_transport_state { SPA_BT_TRANSPORT_STATE_IDLE, SPA_BT_TRANSPORT_STATE_PENDING, @@ -226,6 +231,7 @@ struct spa_bt_transport_implementation { struct spa_bt_transport { struct spa_list link; struct spa_bt_monitor *monitor; + struct spa_bt_backend *backend; char *path; struct spa_bt_device *device; struct spa_list device_link; @@ -245,6 +251,9 @@ struct spa_bt_transport { struct spa_callbacks impl; }; +struct spa_bt_transport *spa_bt_transport_create(struct spa_bt_monitor *monitor, char *path, size_t extra); +void spa_bt_transport_free(struct spa_bt_transport *transport); + #define spa_bt_transport_emit(t,m,v,...) spa_hook_list_call(&(t)->listener_list, \ struct spa_bt_transport_events, \ m, v, ##__VA_ARGS__) @@ -283,6 +292,12 @@ static inline enum spa_bt_transport_state spa_bt_transport_state_from_string(con } +struct spa_bt_backend *backend_hsp_native_new(struct spa_bt_monitor *monitor, + const struct spa_support *support, + uint32_t n_support); +void backend_hsp_native_free(struct spa_bt_backend *backend); +void backend_hsp_native_register_profiles(struct spa_bt_backend *backend); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 83bef6df0..ca2490206 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -6,7 +6,8 @@ bluez5_sources = ['plugin.c', 'sco-sink.c', 'sco-source.c', 'bluez5-device.c', - 'bluez5-dbus.c'] + 'bluez5-dbus.c', + 'backend-hsp-native.c'] bluez5lib = shared_library('spa-bluez5', bluez5_sources,