From 87fcb3d5925cc030e957f55399f8c3e96c66cbb5 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Fri, 27 Mar 2009 21:48:04 +0200 Subject: bluetooth: use new audio State properties --- src/modules/bluetooth/bluetooth-util.c | 109 +++++++++++++++------- src/modules/bluetooth/bluetooth-util.h | 25 +++-- src/modules/bluetooth/module-bluetooth-device.c | 13 ++- src/modules/bluetooth/module-bluetooth-discover.c | 14 ++- 4 files changed, 112 insertions(+), 49 deletions(-) (limited to 'src/modules') diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c index dfebf663..771afff5 100644 --- a/src/modules/bluetooth/bluetooth-util.c +++ b/src/modules/bluetooth/bluetooth-util.c @@ -42,6 +42,22 @@ struct pa_bluetooth_discovery { static void get_properties_reply(DBusPendingCall *pending, void *userdata); static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func); +static enum pa_bt_audio_state pa_bt_audio_state_from_string(const char* value) { + pa_assert(value); + + if (pa_streq(value, "disconnected")) { + return PA_BT_AUDIO_STATE_DISCONNECTED; + } else if (pa_streq(value, "connecting")) { + return PA_BT_AUDIO_STATE_CONNECTING; + } else if (pa_streq(value, "connected")) { + return PA_BT_AUDIO_STATE_CONNECTED; + } else if (pa_streq(value, "playing")) { + return PA_BT_AUDIO_STATE_PLAYING; + } + + return PA_BT_AUDIO_STATE_INVALID; +} + static pa_bluetooth_uuid *uuid_new(const char *uuid) { pa_bluetooth_uuid *u; @@ -66,7 +82,7 @@ static pa_bluetooth_device* device_new(const char *path) { d->dead = FALSE; - d->device_info_valid = d->audio_sink_info_valid = d->headset_info_valid = 0; + d->device_info_valid = 0; d->name = NULL; d->path = pa_xstrdup(path); @@ -78,9 +94,9 @@ static pa_bluetooth_device* device_new(const char *path) { d->class = -1; d->trusted = -1; - d->audio_sink_connected = -1; - - d->headset_connected = -1; + d->audio_state = PA_BT_AUDIO_STATE_INVALID; + d->audio_sink_state = PA_BT_AUDIO_STATE_INVALID; + d->headset_state = PA_BT_AUDIO_STATE_INVALID; return d; } @@ -102,25 +118,14 @@ static void device_free(pa_bluetooth_device *d) { pa_xfree(d); } -static pa_bool_t device_is_loaded(pa_bluetooth_device *d) { - pa_assert(d); - - return - d->device_info_valid && - d->audio_sink_info_valid && - d->headset_info_valid; -} - static pa_bool_t device_is_audio(pa_bluetooth_device *d) { pa_assert(d); - pa_assert(d->device_info_valid); - pa_assert(d->audio_sink_info_valid); - pa_assert(d->headset_info_valid); - return - d->device_info_valid > 0 && - (d->audio_sink_info_valid > 0 || d->headset_info_valid > 0); + d->device_info_valid && + (d->audio_state != PA_BT_AUDIO_STATE_INVALID || + d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID || + d->headset_state != PA_BT_AUDIO_STATE_INVALID); } static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) { @@ -222,6 +227,11 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device node = uuid_new(value); PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node); + /* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */ + pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties")); + send_and_add_to_pending(y, d, m, get_properties_reply); + + /* Vudentz said the interfaces are here when the UUIDs are announced */ if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) { pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset", "GetProperties")); send_and_add_to_pending(y, d, m, get_properties_reply); @@ -242,12 +252,12 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device return 0; } -static int parse_audio_property(pa_bluetooth_discovery *u, int *connected, DBusMessageIter *i) { +static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessageIter *i) { const char *key; DBusMessageIter variant_i; pa_assert(u); - pa_assert(connected); + pa_assert(state); pa_assert(i); if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { @@ -269,17 +279,27 @@ static int parse_audio_property(pa_bluetooth_discovery *u, int *connected, DBusM dbus_message_iter_recurse(i, &variant_i); -/* pa_log_debug("Parsing property org.bluez.{AudioSink|Headset}.%s", key); */ +/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|Headset}.%s", key); */ switch (dbus_message_iter_get_arg_type(&variant_i)) { + case DBUS_TYPE_STRING: { + + const char *value; + dbus_message_iter_get_basic(&variant_i, &value); + + if (pa_streq(key, "State")) + *state = pa_bt_audio_state_from_string(value); +/* pa_log_debug("Value %s", value); */ + } + case DBUS_TYPE_BOOLEAN: { dbus_bool_t value; dbus_message_iter_get_basic(&variant_i, &value); - if (pa_streq(key, "Connected")) - *connected = !!value; + /* if (pa_streq(key, "Connected")) */ + /* *connected = !!value; */ /* pa_log_debug("Value %s", pa_yes_no(value)); */ @@ -294,9 +314,6 @@ static void run_callback(pa_bluetooth_discovery *y, pa_bluetooth_device *d, pa_b pa_assert(y); pa_assert(d); - if (!device_is_loaded(d)) - return; - if (!device_is_audio(d)) return; @@ -326,10 +343,6 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) { if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties")) d->device_info_valid = valid; - else if (dbus_message_is_method_call(p->message, "org.bluez.Headset", "GetProperties")) - d->headset_info_valid = valid; - else if (dbus_message_is_method_call(p->message, "org.bluez.AudioSink", "GetProperties")) - d->audio_sink_info_valid = valid; if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { @@ -361,12 +374,16 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) { if (parse_device_property(y, d, &dict_i) < 0) goto finish; + } else if (dbus_message_has_interface(p->message, "org.bluez.Audio")) { + if (parse_audio_property(y, &d->audio_state, &dict_i) < 0) + goto finish; + } else if (dbus_message_has_interface(p->message, "org.bluez.Headset")) { - if (parse_audio_property(y, &d->headset_connected, &dict_i) < 0) + if (parse_audio_property(y, &d->headset_state, &dict_i) < 0) goto finish; } else if (dbus_message_has_interface(p->message, "org.bluez.AudioSink")) { - if (parse_audio_property(y, &d->audio_sink_connected, &dict_i) < 0) + if (parse_audio_property(y, &d->audio_sink_state, &dict_i) < 0) goto finish; } } @@ -572,7 +589,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us found_adapter(y, path); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - } else if (dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") || + } else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") || + dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") || dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") || dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) { @@ -590,12 +608,16 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us if (parse_device_property(y, d, &arg_i) < 0) goto fail; + } else if (dbus_message_has_interface(m, "org.bluez.Audio")) { + if (parse_audio_property(y, &d->audio_state, &arg_i) < 0) + goto fail; + } else if (dbus_message_has_interface(m, "org.bluez.Headset")) { - if (parse_audio_property(y, &d->headset_connected, &arg_i) < 0) + if (parse_audio_property(y, &d->headset_state, &arg_i) < 0) goto fail; } else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) { - if (parse_audio_property(y, &d->audio_sink_connected, &arg_i) < 0) + if (parse_audio_property(y, &d->audio_sink_state, &arg_i) < 0) goto fail; } @@ -690,6 +712,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) { "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'", "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'", "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'", "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'", "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL) < 0) { pa_log("Failed to add D-Bus matches: %s", err.message); @@ -746,6 +769,7 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) { "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'", "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'", "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'", "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'", "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL); @@ -833,3 +857,16 @@ char *pa_bluetooth_cleanup_name(const char *name) { return t; } + +pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) { + pa_assert(uuid); + + while (uuids) { + if (strcasecmp(uuids->uuid, uuid) == 0) + return TRUE; + + uuids = uuids->next; + } + + return FALSE; +} diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h index 57f11725..54114738 100644 --- a/src/modules/bluetooth/bluetooth-util.h +++ b/src/modules/bluetooth/bluetooth-util.h @@ -53,12 +53,20 @@ struct pa_bluetooth_uuid { PA_LLIST_FIELDS(pa_bluetooth_uuid); }; +/* This enum is shared among Audio, Headset, and AudioSink, although not all values are acceptable in all profiles */ +enum pa_bt_audio_state { + PA_BT_AUDIO_STATE_INVALID = -1, + PA_BT_AUDIO_STATE_DISCONNECTED, + PA_BT_AUDIO_STATE_CONNECTING, + PA_BT_AUDIO_STATE_CONNECTED, + PA_BT_AUDIO_STATE_PLAYING, + PA_BT_AUDIO_STATE_LAST +}; + struct pa_bluetooth_device { pa_bool_t dead; int device_info_valid; /* 0: no results yet; 1: good results; -1: bad results ... */ - int audio_sink_info_valid; /* ... same here ... */ - int headset_info_valid; /* ... and here */ /* Device information */ char *name; @@ -71,11 +79,14 @@ struct pa_bluetooth_device { int class; int trusted; - /* AudioSink information */ - int audio_sink_connected; + /* Audio state */ + int audio_state; - /* Headset information */ - int headset_connected; + /* AudioSink state */ + int audio_sink_state; + + /* Headset state */ + int headset_state; }; pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core); @@ -93,4 +104,6 @@ const char* pa_bluetooth_get_form_factor(uint32_t class); char *pa_bluetooth_cleanup_name(const char *name); +pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid); + #endif diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index 2c4f29c8..9fc1531e 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -1736,11 +1736,11 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { d = PA_CARD_PROFILE_DATA(new_profile); - if (u->device->headset_connected <= 0 && *d == PROFILE_HSP) { + if (u->device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) { pa_log_warn("HSP is not connected, refused to switch profile"); return -1; } - else if (u->device->audio_sink_connected <= 0 && *d == PROFILE_A2DP) { + else if (u->device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP) { pa_log_warn("A2DP is not connected, refused to switch profile"); return -1; } @@ -1821,7 +1821,11 @@ static int add_card(struct userdata *u, const char *default_profile, const pa_bl data.profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - if (device->audio_sink_info_valid > 0) { + /* we base hsp/a2dp availability on UUIDs. + Ideally, it would be based on "Connected" state, but + we can't afford to wait for this information when + we are loaded with profile="hsp", for instance */ + if (pa_bluetooth_uuid_has(device->uuids, A2DP_SINK_UUID)) { p = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP)"), sizeof(enum profile)); p->priority = 10; p->n_sinks = 1; @@ -1835,7 +1839,8 @@ static int add_card(struct userdata *u, const char *default_profile, const pa_bl pa_hashmap_put(data.profiles, p->name, p); } - if (device->headset_info_valid > 0) { + if (pa_bluetooth_uuid_has(device->uuids, HSP_HS_UUID) || + pa_bluetooth_uuid_has(device->uuids, HFP_HS_UUID)) { p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile)); p->priority = 20; p->n_sinks = 1; diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c index 49c7a800..6f3dba12 100644 --- a/src/modules/bluetooth/module-bluetooth-discover.c +++ b/src/modules/bluetooth/module-bluetooth-discover.c @@ -84,8 +84,7 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const mi = pa_hashmap_get(u->hashmap, d->path); if (!d->dead && - d->device_connected > 0 && - (d->audio_sink_connected > 0 || d->headset_connected > 0)) { + d->device_connected > 0 && d->audio_state >= PA_BT_AUDIO_STATE_CONNECTED) { if (!mi) { pa_module *m = NULL; @@ -93,7 +92,16 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const /* Oh, awesome, a new device has shown up and been connected! */ - args = pa_sprintf_malloc("address=\"%s\" path=\"%s\" profile=\"%s\"", d->address, d->path, d->headset_connected > 0 ? "hsp" : "a2dp"); + args = pa_sprintf_malloc("address=\"%s\" path=\"%s\"", d->address, d->path); +#if 0 + /* This is in case we have to use hsp immediately, without waiting for .Audio.State = Connected */ + if (d->headset_state >= PA_BT_AUDIO_STATE_CONNECTED && somecondition) { + char *tmp; + tmp = pa_sprintf_malloc("%s profile=\"hsp\"", args); + pa_xfree(args); + args = tmp; + } +#endif #ifdef NOKIA if (pa_modargs_get_value(u->modargs, "sco_sink", NULL) && -- cgit