mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
bluez5: autoconnect device profiles on startup
Auto-connect all paired & trusted devices on startup. Since devices that already connected or powering off would reject the connecting requests, it should be fine with this behavior. Also reconnect remaining profiles if only partial profiles are connected.
This commit is contained in:
parent
4112b34f4d
commit
34c9f24d2a
5 changed files with 168 additions and 86 deletions
|
|
@ -1619,38 +1619,17 @@ static int backend_native_free(void *data)
|
|||
static int parse_headset_roles(struct impl *backend, const struct spa_dict *info)
|
||||
{
|
||||
const char *str;
|
||||
struct spa_json it, it_array;
|
||||
char role_name[256];
|
||||
enum spa_bt_profile profiles = SPA_BT_PROFILE_NULL;
|
||||
int profiles = SPA_BT_PROFILE_NULL;
|
||||
|
||||
if (info == NULL ||
|
||||
(str = spa_dict_lookup(info, PROP_KEY_HEADSET_ROLES)) == NULL)
|
||||
goto fallback;
|
||||
|
||||
spa_json_init(&it, str, strlen(str));
|
||||
|
||||
if (spa_json_enter_array(&it, &it_array) <= 0) {
|
||||
spa_log_error(backend->log,
|
||||
NAME": property "PROP_KEY_HEADSET_ROLES" '%s' is not an array", str);
|
||||
profiles = spa_bt_profiles_from_json_array(str);
|
||||
if (profiles < 0)
|
||||
goto fallback;
|
||||
}
|
||||
|
||||
while (spa_json_get_string(&it_array, role_name, sizeof(role_name)) > 0) {
|
||||
if (strcmp(role_name, "hsp_hs") == 0) {
|
||||
profiles |= SPA_BT_PROFILE_HSP_HS;
|
||||
} else if (strcmp(role_name, "hsp_ag") == 0) {
|
||||
profiles |= SPA_BT_PROFILE_HSP_AG;
|
||||
} else if (strcmp(role_name, "hfp_hf") == 0) {
|
||||
profiles |= SPA_BT_PROFILE_HFP_HF;
|
||||
} else if (strcmp(role_name, "hfp_ag") == 0) {
|
||||
profiles |= SPA_BT_PROFILE_HFP_AG;
|
||||
} else {
|
||||
spa_log_warn(backend->log,
|
||||
NAME": unknown role name '%s' in "PROP_KEY_HEADSET_ROLES, role_name);
|
||||
}
|
||||
}
|
||||
|
||||
backend->enabled_profiles = profiles;
|
||||
backend->enabled_profiles = profiles & SPA_BT_PROFILE_HEADSET_AUDIO;
|
||||
return 0;
|
||||
fallback:
|
||||
backend->enabled_profiles = DEFAULT_ENABLED_PROFILES;
|
||||
|
|
|
|||
|
|
@ -769,20 +769,27 @@ static int device_connected_old(struct spa_bt_monitor *monitor, struct spa_bt_de
|
|||
return 0;
|
||||
}
|
||||
|
||||
enum {
|
||||
BT_DEVICE_RECONNECT_INIT = 0,
|
||||
BT_DEVICE_RECONNECT_PROFILE,
|
||||
BT_DEVICE_RECONNECT_STOP
|
||||
};
|
||||
|
||||
static int device_connected(struct spa_bt_monitor *monitor, struct spa_bt_device *device, int status)
|
||||
{
|
||||
struct spa_device_object_info info;
|
||||
char dev[32], name[128], class[16];
|
||||
struct spa_dict_item items[20];
|
||||
uint32_t n_items = 0;
|
||||
bool connection_changed, init;
|
||||
bool connection_changed, init = status == BT_DEVICE_INIT;
|
||||
status = init ? 0 : status;
|
||||
|
||||
device->reconnect_state = status ? BT_DEVICE_RECONNECT_STOP : BT_DEVICE_RECONNECT_PROFILE;
|
||||
|
||||
if (!monitor->connection_info_supported) {
|
||||
return device_connected_old(monitor, device, status);
|
||||
}
|
||||
|
||||
init = status == BT_DEVICE_INIT;
|
||||
status = init ? 0 : status;
|
||||
connection_changed = status ^ device->connected;
|
||||
device->connected = status;
|
||||
|
||||
|
|
@ -841,8 +848,78 @@ static int device_connected(struct spa_bt_monitor *monitor, struct spa_bt_device
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int device_try_connect_profile(struct spa_bt_device *device,
|
||||
const char *profile_uuid)
|
||||
{
|
||||
struct spa_bt_monitor *monitor = device->monitor;
|
||||
DBusMessage *m;
|
||||
|
||||
spa_log_info(monitor->log, "device %p %s: profile %s not connected; try ConnectProfile()",
|
||||
device, device->path, profile_uuid);
|
||||
|
||||
/* Call org.bluez.Device1.ConnectProfile() on device, ignoring result */
|
||||
|
||||
m = dbus_message_new_method_call(BLUEZ_SERVICE,
|
||||
device->path,
|
||||
BLUEZ_DEVICE_INTERFACE,
|
||||
"ConnectProfile");
|
||||
if (m == NULL)
|
||||
return -ENOMEM;
|
||||
dbus_message_append_args(m, DBUS_TYPE_STRING, &profile_uuid, DBUS_TYPE_INVALID);
|
||||
if (!dbus_connection_send(monitor->conn, m, NULL)) {
|
||||
dbus_message_unref(m);
|
||||
return -EIO;
|
||||
}
|
||||
dbus_message_unref(m);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int reconnect_device_profiles(struct spa_bt_device *device)
|
||||
{
|
||||
uint32_t reconnect = device->profiles
|
||||
& device->reconnect_profiles
|
||||
& (device->connected_profiles ^ device->profiles);
|
||||
|
||||
if (!(device->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)) {
|
||||
if (reconnect & SPA_BT_PROFILE_HFP_HF) {
|
||||
SPA_FLAG_CLEAR(reconnect, SPA_BT_PROFILE_HSP_HS);
|
||||
} else if (reconnect & SPA_BT_PROFILE_HSP_HS) {
|
||||
SPA_FLAG_CLEAR(reconnect, SPA_BT_PROFILE_HFP_HF);
|
||||
}
|
||||
} else
|
||||
SPA_FLAG_CLEAR(reconnect, SPA_BT_PROFILE_HEADSET_HEAD_UNIT);
|
||||
|
||||
if (!(device->connected_profiles & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) {
|
||||
if (reconnect & SPA_BT_PROFILE_HFP_AG)
|
||||
SPA_FLAG_CLEAR(reconnect, SPA_BT_PROFILE_HSP_AG);
|
||||
else if (reconnect & SPA_BT_PROFILE_HSP_AG)
|
||||
SPA_FLAG_CLEAR(reconnect, SPA_BT_PROFILE_HFP_AG);
|
||||
} else
|
||||
SPA_FLAG_CLEAR(reconnect, SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY);
|
||||
|
||||
if (reconnect & SPA_BT_PROFILE_HFP_HF)
|
||||
device_try_connect_profile(device, SPA_BT_UUID_HFP_HF);
|
||||
if (reconnect & SPA_BT_PROFILE_HSP_HS)
|
||||
device_try_connect_profile(device, SPA_BT_UUID_HSP_HS);
|
||||
if (reconnect & SPA_BT_PROFILE_HFP_AG)
|
||||
device_try_connect_profile(device, SPA_BT_UUID_HFP_AG);
|
||||
if (reconnect & SPA_BT_PROFILE_HSP_AG)
|
||||
device_try_connect_profile(device, SPA_BT_UUID_HSP_AG);
|
||||
if (reconnect & SPA_BT_PROFILE_A2DP_SINK)
|
||||
device_try_connect_profile(device, SPA_BT_UUID_A2DP_SINK);
|
||||
if (reconnect & SPA_BT_PROFILE_A2DP_SOURCE)
|
||||
device_try_connect_profile(device, SPA_BT_UUID_A2DP_SOURCE);
|
||||
|
||||
return reconnect;
|
||||
}
|
||||
|
||||
#define DEVICE_RECONNECT_TIMEOUT_SEC 2
|
||||
#define DEVICE_PROFILE_TIMEOUT_SEC 3
|
||||
|
||||
static int device_start_timer(struct spa_bt_device *device);
|
||||
static int device_stop_timer(struct spa_bt_device *device);
|
||||
|
||||
static void device_timer_event(struct spa_source *source)
|
||||
{
|
||||
struct spa_bt_device *device = source->data;
|
||||
|
|
@ -854,8 +931,21 @@ static void device_timer_event(struct spa_source *source)
|
|||
|
||||
spa_log_debug(monitor->log, "device %p: timeout %08x %08x",
|
||||
device, device->profiles, device->connected_profiles);
|
||||
|
||||
device_connected(device->monitor, device, BT_DEVICE_CONNECTED);
|
||||
device_stop_timer(device);
|
||||
if (BT_DEVICE_RECONNECT_STOP != device->reconnect_state) {
|
||||
device->reconnect_state = BT_DEVICE_RECONNECT_STOP;
|
||||
if (device->paired
|
||||
&& device->trusted
|
||||
&& !device->blocked
|
||||
&& device->reconnect_profiles != 0
|
||||
&& reconnect_device_profiles(device))
|
||||
{
|
||||
device_start_timer(device);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (device->connected_profiles)
|
||||
device_connected(device->monitor, device, BT_DEVICE_CONNECTED);
|
||||
}
|
||||
|
||||
static int device_start_timer(struct spa_bt_device *device)
|
||||
|
|
@ -873,7 +963,9 @@ static int device_start_timer(struct spa_bt_device *device)
|
|||
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_sec = device->reconnect_state == BT_DEVICE_RECONNECT_STOP
|
||||
? DEVICE_PROFILE_TIMEOUT_SEC
|
||||
: DEVICE_RECONNECT_TIMEOUT_SEC;
|
||||
ts.it_value.tv_nsec = 0;
|
||||
ts.it_interval.tv_sec = 0;
|
||||
ts.it_interval.tv_nsec = 0;
|
||||
|
|
@ -921,6 +1013,10 @@ int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force)
|
|||
device_stop_timer(device);
|
||||
device_connected(monitor, device, BT_DEVICE_CONNECTED);
|
||||
} else {
|
||||
/* The initial reconnect event has not been triggred,
|
||||
* the connecting is triggred by bluez. */
|
||||
if (device->reconnect_state == BT_DEVICE_RECONNECT_INIT)
|
||||
device->reconnect_state = BT_DEVICE_RECONNECT_PROFILE;
|
||||
device_start_timer(device);
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -936,8 +1032,9 @@ static void device_set_connected(struct spa_bt_device *device, int connected)
|
|||
if (connected)
|
||||
spa_bt_device_check_profiles(device, false);
|
||||
else {
|
||||
device_stop_timer(device);
|
||||
device_connected(monitor, device, connected);
|
||||
if (device->reconnect_state != BT_DEVICE_RECONNECT_INIT)
|
||||
device_stop_timer(device);
|
||||
device_connected(monitor, device, BT_DEVICE_DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1150,53 +1247,6 @@ const struct a2dp_codec **spa_bt_device_get_supported_a2dp_codecs(struct spa_bt_
|
|||
return supported_codecs;
|
||||
}
|
||||
|
||||
static int device_try_connect_profile(struct spa_bt_device *device,
|
||||
enum spa_bt_profile profile,
|
||||
const char *profile_uuid)
|
||||
{
|
||||
struct spa_bt_monitor *monitor = device->monitor;
|
||||
DBusMessage *m;
|
||||
|
||||
if (!(device->profiles & profile) || (device->connected_profiles & profile))
|
||||
return 0;
|
||||
|
||||
spa_log_info(monitor->log, "device %p %s: A2DP profile %s not connected; try ConnectProfile()",
|
||||
device, device->path, profile_uuid);
|
||||
|
||||
/* Call org.bluez.Device1.ConnectProfile() on device, ignoring result */
|
||||
|
||||
m = dbus_message_new_method_call(BLUEZ_SERVICE,
|
||||
device->path,
|
||||
BLUEZ_DEVICE_INTERFACE,
|
||||
"ConnectProfile");
|
||||
if (m == NULL)
|
||||
return -ENOMEM;
|
||||
dbus_message_append_args(m, DBUS_TYPE_STRING, &profile_uuid, DBUS_TYPE_INVALID);
|
||||
if (!dbus_connection_send(monitor->conn, m, NULL)) {
|
||||
dbus_message_unref(m);
|
||||
return -EIO;
|
||||
}
|
||||
dbus_message_unref(m);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void reconnect_device_profiles(struct spa_bt_monitor *monitor)
|
||||
{
|
||||
struct spa_bt_device *device;
|
||||
|
||||
/* Call org.bluez.Device1.ConnectProfile() on connected devices, it they have A2DP but the
|
||||
* profile is not yet connected.
|
||||
*/
|
||||
|
||||
spa_list_for_each(device, &monitor->device_list, link) {
|
||||
if (device->connected) {
|
||||
device_try_connect_profile(device, SPA_BT_PROFILE_A2DP_SINK, SPA_BT_UUID_A2DP_SINK);
|
||||
device_try_connect_profile(device, SPA_BT_PROFILE_A2DP_SOURCE, SPA_BT_UUID_A2DP_SOURCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct spa_bt_remote_endpoint *device_remote_endpoint_find(struct spa_bt_device *device, const char *path)
|
||||
{
|
||||
struct spa_bt_remote_endpoint *ep;
|
||||
|
|
@ -2696,9 +2746,6 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
|
|||
if (!dbus_connection_send(monitor->conn, r, NULL))
|
||||
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
||||
res = DBUS_HANDLER_RESULT_HANDLED;
|
||||
|
||||
/* Reconnect A2DP profiles for existing connected devices */
|
||||
reconnect_device_profiles(monitor);
|
||||
}
|
||||
else
|
||||
res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
|
|
@ -2872,6 +2919,8 @@ static void interface_added(struct spa_bt_monitor *monitor,
|
|||
/* Trigger bluez device creation before bluez profile negotiation started so that
|
||||
* profile connection handlers can receive per-device settings during profile negotiation. */
|
||||
device_connected(monitor, d, BT_DEVICE_INIT);
|
||||
d->reconnect_state = BT_DEVICE_RECONNECT_INIT;
|
||||
device_start_timer(d);
|
||||
}
|
||||
else if (strcmp(interface_name, BLUEZ_MEDIA_ENDPOINT_INTERFACE) == 0) {
|
||||
struct spa_bt_remote_endpoint *ep;
|
||||
|
|
@ -3376,6 +3425,36 @@ impl_get_size(const struct spa_handle_factory *factory,
|
|||
return sizeof(struct spa_bt_monitor);
|
||||
}
|
||||
|
||||
int spa_bt_profiles_from_json_array(const char *str)
|
||||
{
|
||||
struct spa_json it, it_array;
|
||||
char role_name[256];
|
||||
enum spa_bt_profile profiles = SPA_BT_PROFILE_NULL;
|
||||
|
||||
spa_json_init(&it, str, strlen(str));
|
||||
|
||||
if (spa_json_enter_array(&it, &it_array) <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
while (spa_json_get_string(&it_array, role_name, sizeof(role_name)) > 0) {
|
||||
if (strcmp(role_name, "hsp_hs") == 0) {
|
||||
profiles |= SPA_BT_PROFILE_HSP_HS;
|
||||
} else if (strcmp(role_name, "hsp_ag") == 0) {
|
||||
profiles |= SPA_BT_PROFILE_HSP_AG;
|
||||
} else if (strcmp(role_name, "hfp_hf") == 0) {
|
||||
profiles |= SPA_BT_PROFILE_HFP_HF;
|
||||
} else if (strcmp(role_name, "hfp_ag") == 0) {
|
||||
profiles |= SPA_BT_PROFILE_HFP_AG;
|
||||
} else if (strcmp(role_name, "a2dp_sink") == 0) {
|
||||
profiles |= SPA_BT_PROFILE_A2DP_SINK;
|
||||
} else if (strcmp(role_name, "a2dp_source") == 0) {
|
||||
profiles |= SPA_BT_PROFILE_A2DP_SOURCE;
|
||||
}
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict *info)
|
||||
{
|
||||
const char *str;
|
||||
|
|
|
|||
|
|
@ -1504,7 +1504,15 @@ impl_init(const struct spa_handle_factory *factory,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
this->bt_dev->settings = filter_bluez_device_setting(this, info);
|
||||
if (info) {
|
||||
int profiles;
|
||||
this->bt_dev->settings = filter_bluez_device_setting(this, info);
|
||||
if ((str = spa_dict_lookup(info, "bluez5.reconnect-profiles")) != NULL)
|
||||
profiles = spa_bt_profiles_from_json_array(str);
|
||||
if (str == NULL || profiles < 0)
|
||||
profiles = SPA_BT_PROFILE_NULL;
|
||||
this->bt_dev->reconnect_profiles = profiles;
|
||||
}
|
||||
|
||||
this->device.iface = SPA_INTERFACE_INIT(
|
||||
SPA_TYPE_INTERFACE_Device,
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@ static inline enum spa_bt_profile spa_bt_profile_from_uuid(const char *uuid)
|
|||
else
|
||||
return 0;
|
||||
}
|
||||
int spa_bt_profiles_from_json_array(const char *str);
|
||||
|
||||
enum spa_bt_hfp_ag_feature {
|
||||
SPA_BT_HFP_AG_FEATURE_NONE = (0),
|
||||
|
|
@ -406,6 +407,8 @@ struct spa_bt_device {
|
|||
int blocked;
|
||||
uint32_t profiles;
|
||||
uint32_t connected_profiles;
|
||||
uint32_t reconnect_profiles;
|
||||
int reconnect_state;
|
||||
struct spa_source timer;
|
||||
struct spa_list remote_endpoint_list;
|
||||
struct spa_list transport_list;
|
||||
|
|
|
|||
|
|
@ -33,15 +33,28 @@ rules = [
|
|||
actions = {
|
||||
# Actions can update properties on the matched object.
|
||||
update-props = {
|
||||
|
||||
# Autoconnect device profiles, disabled by default
|
||||
# if the property is not specified.
|
||||
#bluez5.reconnect-profiles = [
|
||||
# hfp_hf
|
||||
# hsp_hs
|
||||
# a2dp_sink
|
||||
# hfp_ag
|
||||
# hsp_ag
|
||||
# a2dp_source
|
||||
#]
|
||||
bluez5.reconnect-profiles = [ hfp_hf hsp_hs a2dp_sink ]
|
||||
|
||||
# MSBC is not expected to work on all headset + adapter combinations.
|
||||
#bluez5.msbc-support = false
|
||||
#bluez5.msbc-support = false
|
||||
|
||||
# LDAC encoding quality
|
||||
# Available values: auto (Adaptive Bitrate, default)
|
||||
# hq (High Quality, 990/909kbps)
|
||||
# sq (Standard Quality, 660/606kbps)
|
||||
# mq (Mobile use Quality, 330/303kbps)
|
||||
#bluez5.a2dp.ldac.quality = auto
|
||||
#bluez5.a2dp.ldac.quality = auto
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue