From f46852908494c64c659fab9a31b0249ae4bfa159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Tue, 25 Nov 2025 16:51:52 +0100 Subject: [PATCH 1/2] spa: bluez: backend-native: Fix audio connection policy for HSP/HFP --- spa/plugins/bluez5/backend-native.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 297334754..b7661fd5b 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -2942,7 +2942,11 @@ static void sco_listen_event(struct spa_source *source) /* Find transport for local and remote address */ spa_list_for_each(rfcomm, &backend->rfcomm_list, link) { - if ((rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) && + /* Audio connection is allowed from both side with legacy peer, i.e. HSP or codec negotion not supported + * (except if PTS workaround has been enabled in which case audio coonection is allowed as for HSP), + * or only from the HFP Audio Gateway. */ + if ((((!rfcomm->codec_negotiation_supported || backend->pts) && (rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO)) || + (rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) && rfcomm->transport && spa_streq(rfcomm->device->address, remote_address) && spa_streq(rfcomm->device->adapter->address, local_address)) { @@ -2956,7 +2960,7 @@ static void sco_listen_event(struct spa_source *source) return; } - spa_assert(t->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); + spa_assert(t->profile & SPA_BT_PROFILE_HEADSET_AUDIO); if (rfcomm->telephony_ag && rfcomm->telephony_ag->transport.rejectSCO) { spa_log_info(backend->log, "rejecting SCO, AudioGatewayTransport1.RejectSCO=true"); From e15e50c5ee9c0653c9976cc3c141fba41887e50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Tue, 16 Dec 2025 13:29:18 +0100 Subject: [PATCH 2/2] spa: bluez: backend-native: Prevent HSP/HFP connection in both directions The HSP and HFP profiles expect that a device function only as an audio gateway or as an headset, which is the normal behavior for a headset, a hands-free car unit or a phone. In case of a desktop, it can perform both functionalities, but there's no interest to get them at the same time as the bidirectional audio is already supported. --- spa/plugins/bluez5/backend-native.c | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index b7661fd5b..5c4a2b785 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -3401,6 +3401,43 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag } spa_bt_device_add_profile(d, profile); + /* Prevent to connect HSP/HFP in both directions, i.e. HS->AG and AG->HS. + * This may only occur when connecting to a device which provides both + * HS and AG which should not be the case with headsets and phones. */ + spa_list_for_each(rfcomm, &backend->rfcomm_list, link) { + if (spa_streq(rfcomm->device->address, d->address) && + spa_streq(rfcomm->device->adapter->address, d->adapter->address)) { + bool connected = false; + + switch (profile) { + case SPA_BT_PROFILE_HFP_HF: + if (rfcomm->profile == SPA_BT_PROFILE_HFP_AG) + connected = true; + break; + case SPA_BT_PROFILE_HFP_AG: + if (rfcomm->profile == SPA_BT_PROFILE_HFP_HF) + connected = true; + break; + case SPA_BT_PROFILE_HSP_HS: + if (rfcomm->profile == SPA_BT_PROFILE_HSP_AG) + connected = true; + break; + case SPA_BT_PROFILE_HSP_AG: + if (rfcomm->profile == SPA_BT_PROFILE_HSP_HS) + connected = true; + break; + default: + spa_log_warn(backend->log, "Unsupported profile: %s", handler); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (connected) { + spa_log_debug(backend->log, "Already connected in the opposite direction"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + } + dbus_message_iter_next(&it); dbus_message_iter_get_basic(&it, &fd);