diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 677cf4b1c..dc68d3f59 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -623,8 +623,11 @@ static void device_set_connected(struct spa_bt_device *device, int connected) int spa_bt_device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile) { + uint32_t prev_connected = device->connected_profiles; device->connected_profiles |= profile; spa_bt_device_check_profiles(device, false); + if (device->connected_profiles != prev_connected) + spa_bt_device_emit_profiles_changed(device, device->profiles, prev_connected); return 0; } @@ -736,6 +739,7 @@ static int device_update_props(struct spa_bt_device *device, } else if (strcmp(key, "UUIDs") == 0) { DBusMessageIter iter; + uint32_t prev_profiles = device->profiles; if (!check_iter_signature(&it[1], "as")) goto next; @@ -755,6 +759,10 @@ static int device_update_props(struct spa_bt_device *device, } dbus_message_iter_next(&iter); } + + if (device->profiles != prev_profiles) + spa_bt_device_emit_profiles_changed( + device, prev_profiles, device->connected_profiles); } else spa_log_debug(monitor->log, "device %p: unhandled key %s type %d", device, key, type); @@ -1092,6 +1100,8 @@ static void transport_set_state(struct spa_bt_transport *transport, enum spa_bt_ void spa_bt_transport_free(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; + struct spa_bt_device *device = transport->device; + uint32_t prev_connected = 0; spa_log_debug(monitor->log, "transport %p: free %s", transport, transport->path); @@ -1116,11 +1126,15 @@ void spa_bt_transport_free(struct spa_bt_transport *transport) spa_list_remove(&transport->link); if (transport->device) { + prev_connected = transport->device->connected_profiles; transport->device->connected_profiles &= ~transport->profile; spa_list_remove(&transport->device_link); } free(transport->path); free(transport); + + if (device && device->connected_profiles != prev_connected) + spa_bt_device_emit_profiles_changed(device, device->profiles, prev_connected); } int spa_bt_transport_acquire(struct spa_bt_transport *transport, bool optional) diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index b8d3b0ba2..1b142a9cc 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -96,6 +96,8 @@ struct impl { uint32_t profile; const struct a2dp_codec *selected_a2dp_codec; /**< Codec wanted. NULL means any. */ + unsigned int switching_codec:1; + uint32_t prev_bt_connected_profiles; const struct a2dp_codec **supported_codecs; size_t supported_codec_count; @@ -269,6 +271,9 @@ static int set_profile(struct impl *this, uint32_t profile, const struct a2dp_co codecs = codec_list; } + this->switching_codec = true; + this->prev_bt_connected_profiles = this->bt_dev->connected_profiles; + ret = spa_bt_device_ensure_a2dp_codec(this->bt_dev, codecs); if (ret < 0) spa_log_error(this->log, NAME": failed to switch codec (%d), setting basic profile", ret); @@ -295,6 +300,8 @@ static void codec_switched(void *userdata, int status) spa_log_debug(this->log, NAME": codec switched (status %d)", status); + this->switching_codec = false; + if (status < 0) { /* Failed to switch: return to a fallback profile */ spa_log_error(this->log, NAME": failed to switch codec (%d), setting fallback profile", status); @@ -309,15 +316,66 @@ static void codec_switched(void *userdata, int status) emit_nodes(this); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; + if (this->prev_bt_connected_profiles != this->bt_dev->connected_profiles) + this->params[IDX_EnumProfile].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL; emit_info(this, false); } +static void profiles_changed(void *userdata, uint32_t prev_profiles, uint32_t prev_connected_profiles) +{ + struct impl *this = userdata; + uint32_t connected_change; + bool nodes_changed = false; + + connected_change = (this->bt_dev->connected_profiles ^ prev_connected_profiles); + + /* Profiles changed. We have to re-emit device information. */ + spa_log_info(this->log, NAME": profiles changed to %08x %08x (prev %08x %08x, change %08x)" + " switching_codec:%d", + this->bt_dev->profiles, this->bt_dev->connected_profiles, + prev_profiles, prev_connected_profiles, connected_change, + this->switching_codec); + + if (this->switching_codec) + return; + + if (this->profile == 0) { + /* Noop */ + nodes_changed = false; + } else if (this->profile == 2) { + /* HFP/HSP */ + nodes_changed = (connected_change & (SPA_BT_PROFILE_HEADSET_HEAD_UNIT | + SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)); + spa_log_debug(this->log, NAME": profiles changed: HSP/HFP nodes changed: %d", + nodes_changed); + } else { + nodes_changed = (connected_change & (SPA_BT_PROFILE_A2DP_SINK | + SPA_BT_PROFILE_A2DP_SOURCE)); + spa_log_debug(this->log, NAME": profiles changed: A2DP nodes changed: %d", + nodes_changed); + } + + if (nodes_changed) { + emit_remove_nodes(this); + emit_nodes(this); + + this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; + this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL; + } + + this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; + this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL; + this->params[IDX_EnumProfile].flags ^= SPA_PARAM_INFO_SERIAL; + emit_info(this, false); +} + static const struct spa_bt_device_events bt_dev_events = { SPA_VERSION_BT_DEVICE_EVENTS, .codec_switched = codec_switched, + .profiles_changed = profiles_changed, }; static int impl_add_listener(void *object, diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index b5f764e55..01d66ac42 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -335,6 +335,9 @@ struct spa_bt_device_events { /** Codec switching completed */ void (*codec_switched) (void *data, int status); + + /** Profile configuration changed */ + void (*profiles_changed) (void *data, uint32_t prev_profiles, uint32_t prev_connected); }; struct spa_bt_device { @@ -381,6 +384,7 @@ const struct a2dp_codec **spa_bt_device_get_supported_a2dp_codecs(struct spa_bt_ struct spa_bt_device_events, \ m, v, ##__VA_ARGS__) #define spa_bt_device_emit_codec_switched(d,...) spa_bt_device_emit(d, codec_switched, 0, __VA_ARGS__) +#define spa_bt_device_emit_profiles_changed(d,...) spa_bt_device_emit(d, profiles_changed, 0, __VA_ARGS__) #define spa_bt_device_add_listener(d,listener,events,data) \ spa_hook_list_append(&(d)->listener_list, listener, events, data)