2 This file is part of PulseAudio.
4 Copyright 2008-2009 Joao Paulo Rechi Vita
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2.1 of the
9 License, or (at your option) any later version.
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
26 #include <pulse/xmalloc.h>
28 #include <pulsecore/core-util.h>
29 #include <pulsecore/shared.h>
30 #include <pulsecore/dbus-shared.h>
32 #include "bluetooth-util.h"
33 #include "a2dp-codecs.h"
35 #define HFP_AG_ENDPOINT "/MediaEndpoint/HFPAG"
36 #define HFP_HS_ENDPOINT "/MediaEndpoint/HFPHS"
37 #define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
38 #define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
40 #define ENDPOINT_INTROSPECT_XML \
41 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
43 " <interface name=\"org.bluez.MediaEndpoint\">" \
44 " <method name=\"SetConfiguration\">" \
45 " <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \
46 " <arg name=\"configuration\" direction=\"in\" type=\"ay\"/>" \
48 " <method name=\"SelectConfiguration\">" \
49 " <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>" \
50 " <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>" \
52 " <method name=\"ClearConfiguration\">" \
54 " <method name=\"Release\">" \
57 " <interface name=\"org.freedesktop.DBus.Introspectable\">" \
58 " <method name=\"Introspect\">" \
59 " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
64 #define MEDIA_ENDPOINT_1_INTROSPECT_XML \
65 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
67 " <interface name=\"org.bluez.MediaEndpoint1\">" \
68 " <method name=\"SetConfiguration\">" \
69 " <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \
70 " <arg name=\"configuration\" direction=\"in\" type=\"ay\"/>" \
72 " <method name=\"SelectConfiguration\">" \
73 " <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>" \
74 " <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>" \
76 " <method name=\"ClearConfiguration\">" \
78 " <method name=\"Release\">" \
81 " <interface name=\"org.freedesktop.DBus.Introspectable\">" \
82 " <method name=\"Introspect\">" \
83 " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
88 typedef enum pa_bluez_version {
89 BLUEZ_VERSION_UNKNOWN,
94 struct pa_bluetooth_discovery {
98 pa_dbus_connection *connection;
99 PA_LLIST_HEAD(pa_dbus_pending, pending);
100 pa_bluez_version_t version;
101 bool adapters_listed;
103 pa_hashmap *transports;
104 pa_hook hooks[PA_BLUETOOTH_HOOK_MAX];
108 static void get_properties_reply(DBusPendingCall *pending, void *userdata);
109 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func,
111 static void found_adapter(pa_bluetooth_discovery *y, const char *path);
112 static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path);
114 static pa_bt_audio_state_t audio_state_from_string(const char* value) {
117 if (pa_streq(value, "disconnected"))
118 return PA_BT_AUDIO_STATE_DISCONNECTED;
119 else if (pa_streq(value, "connecting"))
120 return PA_BT_AUDIO_STATE_CONNECTING;
121 else if (pa_streq(value, "connected"))
122 return PA_BT_AUDIO_STATE_CONNECTED;
123 else if (pa_streq(value, "playing"))
124 return PA_BT_AUDIO_STATE_PLAYING;
126 return PA_BT_AUDIO_STATE_INVALID;
129 static int transport_state_from_string(const char* value, pa_bluetooth_transport_state_t *state) {
133 if (pa_streq(value, "idle"))
134 *state = PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
135 else if (pa_streq(value, "pending") || pa_streq(value, "active")) /* We don't need such a separation */
136 *state = PA_BLUETOOTH_TRANSPORT_STATE_PLAYING;
143 const char *pa_bt_profile_to_string(enum profile profile) {
147 case PROFILE_A2DP_SOURCE:
148 return "a2dp_source";
154 pa_assert_not_reached();
157 pa_assert_not_reached();
160 static int profile_from_interface(const char *interface, enum profile *p) {
161 pa_assert(interface);
164 if (pa_streq(interface, "org.bluez.AudioSink")) {
167 } else if (pa_streq(interface, "org.bluez.AudioSource")) {
168 *p = PROFILE_A2DP_SOURCE;
170 } else if (pa_streq(interface, "org.bluez.Headset")) {
173 } else if (pa_streq(interface, "org.bluez.HandsfreeGateway")) {
181 static pa_bluetooth_transport_state_t audio_state_to_transport_state(pa_bt_audio_state_t state) {
183 case PA_BT_AUDIO_STATE_INVALID: /* Typically if state hasn't been received yet */
184 case PA_BT_AUDIO_STATE_DISCONNECTED:
185 case PA_BT_AUDIO_STATE_CONNECTING:
186 return PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
187 case PA_BT_AUDIO_STATE_CONNECTED:
188 return PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
189 case PA_BT_AUDIO_STATE_PLAYING:
190 return PA_BLUETOOTH_TRANSPORT_STATE_PLAYING;
193 pa_assert_not_reached();
196 static pa_bluetooth_uuid *uuid_new(const char *uuid) {
197 pa_bluetooth_uuid *u;
199 u = pa_xnew(pa_bluetooth_uuid, 1);
200 u->uuid = pa_xstrdup(uuid);
201 PA_LLIST_INIT(pa_bluetooth_uuid, u);
206 static void uuid_free(pa_bluetooth_uuid *u) {
213 static pa_bluetooth_device* device_new(pa_bluetooth_discovery *discovery, const char *path) {
214 pa_bluetooth_device *d;
217 pa_assert(discovery);
220 d = pa_xnew0(pa_bluetooth_device, 1);
222 d->discovery = discovery;
225 d->device_info_valid = 0;
228 d->path = pa_xstrdup(path);
231 PA_LLIST_HEAD_INIT(pa_bluetooth_uuid, d->uuids);
236 d->audio_state = PA_BT_AUDIO_STATE_INVALID;
238 for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
239 d->profile_state[i] = PA_BT_AUDIO_STATE_INVALID;
244 static void transport_free(pa_bluetooth_transport *t) {
253 static void device_free(pa_bluetooth_device *d) {
254 pa_bluetooth_uuid *u;
255 pa_bluetooth_transport *t;
260 for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
261 if (!(t = d->transports[i]))
264 d->transports[i] = NULL;
265 pa_hashmap_remove(d->discovery->transports, t->path);
266 t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
267 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
271 while ((u = d->uuids)) {
272 PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u);
279 pa_xfree(d->address);
283 static const char *check_variant_property(DBusMessageIter *i) {
288 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
289 pa_log("Property name not a string.");
293 dbus_message_iter_get_basic(i, &key);
295 if (!dbus_message_iter_next(i)) {
296 pa_log("Property value missing");
300 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
301 pa_log("Property value not a variant.");
308 static int parse_manager_property(pa_bluetooth_discovery *y, DBusMessageIter *i, bool is_property_change) {
310 DBusMessageIter variant_i;
314 key = check_variant_property(i);
318 dbus_message_iter_recurse(i, &variant_i);
320 switch (dbus_message_iter_get_arg_type(&variant_i)) {
322 case DBUS_TYPE_ARRAY: {
325 dbus_message_iter_recurse(&variant_i, &ai);
327 if (pa_streq(key, "Adapters")) {
328 y->adapters_listed = true;
330 if (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_OBJECT_PATH)
333 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
336 dbus_message_iter_get_basic(&ai, &value);
338 found_adapter(y, value);
340 dbus_message_iter_next(&ai);
351 static int parse_adapter_property(pa_bluetooth_discovery *y, DBusMessageIter *i, bool is_property_change) {
353 DBusMessageIter variant_i;
357 key = check_variant_property(i);
361 dbus_message_iter_recurse(i, &variant_i);
363 switch (dbus_message_iter_get_arg_type(&variant_i)) {
365 case DBUS_TYPE_ARRAY: {
368 dbus_message_iter_recurse(&variant_i, &ai);
370 if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_OBJECT_PATH &&
371 pa_streq(key, "Devices")) {
373 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
376 dbus_message_iter_get_basic(&ai, &value);
378 found_device(y, value);
380 dbus_message_iter_next(&ai);
391 static int parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) {
393 DBusMessageIter variant_i;
397 key = check_variant_property(i);
401 dbus_message_iter_recurse(i, &variant_i);
403 switch (dbus_message_iter_get_arg_type(&variant_i)) {
405 case DBUS_TYPE_STRING: {
408 dbus_message_iter_get_basic(&variant_i, &value);
410 if (pa_streq(key, "Name")) {
412 d->name = pa_xstrdup(value);
413 } else if (pa_streq(key, "Alias")) {
415 d->alias = pa_xstrdup(value);
416 } else if (pa_streq(key, "Address")) {
417 if (is_property_change) {
418 pa_log("Device property 'Address' expected to be constant but changed for %s", d->path);
423 pa_log("Device %s: Received a duplicate Address property.", d->path);
427 d->address = pa_xstrdup(value);
430 /* pa_log_debug("Value %s", value); */
435 case DBUS_TYPE_BOOLEAN: {
438 dbus_message_iter_get_basic(&variant_i, &value);
440 if (pa_streq(key, "Paired"))
442 else if (pa_streq(key, "Trusted"))
443 d->trusted = !!value;
445 /* pa_log_debug("Value %s", pa_yes_no(value)); */
450 case DBUS_TYPE_UINT32: {
453 dbus_message_iter_get_basic(&variant_i, &value);
455 if (pa_streq(key, "Class"))
456 d->class = (int) value;
458 /* pa_log_debug("Value %u", (unsigned) value); */
463 case DBUS_TYPE_ARRAY: {
466 dbus_message_iter_recurse(&variant_i, &ai);
468 if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING && pa_streq(key, "UUIDs")) {
470 bool has_audio = false;
472 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
473 pa_bluetooth_uuid *node;
475 struct pa_bluetooth_hook_uuid_data uuiddata;
477 dbus_message_iter_get_basic(&ai, &value);
479 if (pa_bluetooth_uuid_has(d->uuids, value)) {
480 dbus_message_iter_next(&ai);
484 node = uuid_new(value);
485 PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
488 uuiddata.uuid = value;
489 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED], &uuiddata);
491 if (d->discovery->version >= BLUEZ_VERSION_5) {
492 dbus_message_iter_next(&ai);
496 /* Vudentz said the interfaces are here when the UUIDs are announced */
497 if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) {
498 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.HandsfreeGateway",
500 send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
502 } else if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) {
503 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset",
505 send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
507 } else if (strcasecmp(A2DP_SINK_UUID, value) == 0) {
508 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink",
510 send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
512 } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) {
513 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource",
515 send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
519 dbus_message_iter_next(&ai);
522 /* this might eventually be racy if .Audio is not there yet, but
523 the State change will come anyway later, so this call is for
524 cold-detection mostly */
526 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
527 send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
538 static const char *transport_state_to_string(pa_bluetooth_transport_state_t state) {
540 case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
541 return "disconnected";
542 case PA_BLUETOOTH_TRANSPORT_STATE_IDLE:
544 case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
548 pa_assert_not_reached();
551 static int parse_audio_property(pa_bluetooth_device *d, const char *interface, DBusMessageIter *i, bool is_property_change) {
552 pa_bluetooth_transport *transport;
554 DBusMessageIter variant_i;
555 bool is_audio_interface;
556 enum profile p = PROFILE_OFF;
559 pa_assert(interface);
562 if (!(is_audio_interface = pa_streq(interface, "org.bluez.Audio")))
563 if (profile_from_interface(interface, &p) < 0)
564 return 0; /* Interface not known so silently ignore property */
566 key = check_variant_property(i);
570 transport = p == PROFILE_OFF ? NULL : d->transports[p];
572 dbus_message_iter_recurse(i, &variant_i);
574 /* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
576 switch (dbus_message_iter_get_arg_type(&variant_i)) {
578 case DBUS_TYPE_STRING: {
581 dbus_message_iter_get_basic(&variant_i, &value);
583 if (pa_streq(key, "State")) {
584 pa_bt_audio_state_t state = audio_state_from_string(value);
585 pa_bluetooth_transport_state_t old_state;
587 pa_log_debug("Device %s interface %s property 'State' changed to value '%s'", d->path, interface, value);
589 if (state == PA_BT_AUDIO_STATE_INVALID)
592 if (is_audio_interface) {
593 d->audio_state = state;
597 pa_assert(p != PROFILE_OFF);
599 d->profile_state[p] = state;
604 old_state = transport->state;
605 transport->state = audio_state_to_transport_state(state);
607 if (transport->state != old_state) {
608 pa_log_debug("Transport %s (profile %s) changed state from %s to %s.", transport->path,
609 pa_bt_profile_to_string(transport->profile), transport_state_to_string(old_state),
610 transport_state_to_string(transport->state));
612 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], transport);
619 case DBUS_TYPE_UINT16: {
622 dbus_message_iter_get_basic(&variant_i, &value);
624 if (pa_streq(key, "MicrophoneGain")) {
627 pa_log_debug("dbus: property '%s' changed to value '%u'", key, value);
630 pa_log("Volume change does not have an associated transport");
634 if ((gain = PA_MIN(value, HSP_MAX_GAIN)) == transport->microphone_gain)
637 transport->microphone_gain = gain;
638 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED], transport);
639 } else if (pa_streq(key, "SpeakerGain")) {
642 pa_log_debug("dbus: property '%s' changed to value '%u'", key, value);
645 pa_log("Volume change does not have an associated transport");
649 if ((gain = PA_MIN(value, HSP_MAX_GAIN)) == transport->speaker_gain)
652 transport->speaker_gain = gain;
653 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED], transport);
663 static void run_callback(pa_bluetooth_device *d, bool dead) {
666 if (d->device_info_valid != 1)
670 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], d);
673 static void remove_all_devices(pa_bluetooth_discovery *y) {
674 pa_bluetooth_device *d;
678 while ((d = pa_hashmap_steal_first(y->devices))) {
679 run_callback(d, true);
684 static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path) {
686 pa_bluetooth_device *d;
691 d = pa_hashmap_get(y->devices, path);
695 d = device_new(y, path);
697 pa_hashmap_put(y->devices, d->path, d);
699 pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties"));
700 send_and_add_to_pending(y, m, get_properties_reply, d);
702 /* Before we read the other properties (Audio, AudioSink, AudioSource,
703 * Headset) we wait that the UUID is read */
707 static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
709 DBusMessageIter arg_i, element_i;
711 pa_bluetooth_device *d;
712 pa_bluetooth_discovery *y;
714 bool old_any_connected;
716 pa_assert_se(p = userdata);
717 pa_assert_se(y = p->context_data);
718 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
720 /* pa_log_debug("Got %s.GetProperties response for %s", */
721 /* dbus_message_get_interface(p->message), */
722 /* dbus_message_get_path(p->message)); */
724 /* We don't use p->call_data here right-away since the device
725 * might already be invalidated at this point */
727 if (dbus_message_has_interface(p->message, "org.bluez.Manager") ||
728 dbus_message_has_interface(p->message, "org.bluez.Adapter"))
730 else if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(p->message)))) {
731 pa_log_warn("Received GetProperties() reply from unknown device: %s (device removed?)", dbus_message_get_path(p->message));
735 pa_assert(p->call_data == d);
738 old_any_connected = pa_bluetooth_device_any_audio_connected(d);
740 valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1;
742 if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties"))
743 d->device_info_valid = valid;
745 if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
746 pa_log_debug("Bluetooth daemon is apparently not available.");
747 remove_all_devices(y);
751 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
752 pa_log("%s.GetProperties() failed: %s: %s", dbus_message_get_interface(p->message), dbus_message_get_error_name(r),
753 pa_dbus_get_error_message(r));
757 if (!dbus_message_iter_init(r, &arg_i)) {
758 pa_log("GetProperties reply has no arguments.");
762 if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
763 pa_log("GetProperties argument is not an array.");
767 dbus_message_iter_recurse(&arg_i, &element_i);
768 while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) {
770 if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
771 DBusMessageIter dict_i;
773 dbus_message_iter_recurse(&element_i, &dict_i);
775 if (dbus_message_has_interface(p->message, "org.bluez.Manager")) {
776 if (parse_manager_property(y, &dict_i, false) < 0)
779 } else if (dbus_message_has_interface(p->message, "org.bluez.Adapter")) {
780 if (parse_adapter_property(y, &dict_i, false) < 0)
783 } else if (dbus_message_has_interface(p->message, "org.bluez.Device")) {
784 if (parse_device_property(d, &dict_i, false) < 0)
787 } else if (parse_audio_property(d, dbus_message_get_interface(p->message), &dict_i, false) < 0)
792 dbus_message_iter_next(&element_i);
796 if (d != NULL && old_any_connected != pa_bluetooth_device_any_audio_connected(d))
797 run_callback(d, false);
800 dbus_message_unref(r);
802 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
803 pa_dbus_pending_free(p);
806 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func,
809 DBusPendingCall *call;
814 pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
816 p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, call_data);
817 PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);
818 dbus_pending_call_set_notify(call, func, p, NULL);
823 static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
826 pa_bluetooth_discovery *y;
830 pa_assert_se(p = userdata);
831 pa_assert_se(y = p->context_data);
832 pa_assert_se(endpoint = p->call_data);
833 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
835 if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
836 pa_log_debug("Bluetooth daemon is apparently not available.");
837 remove_all_devices(y);
841 if (dbus_message_is_error(r, PA_BLUETOOTH_ERROR_NOT_SUPPORTED)) {
842 pa_log_info("Couldn't register endpoint %s, because BlueZ is configured to disable the endpoint type.", endpoint);
846 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
847 pa_log("RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
852 dbus_message_unref(r);
854 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
855 pa_dbus_pending_free(p);
860 static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
862 DBusMessageIter i, d;
864 const char *interface = y->version == BLUEZ_VERSION_4 ? "org.bluez.Media" : "org.bluez.Media1";
866 pa_log_debug("Registering %s on adapter %s.", endpoint, path);
868 pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, interface, "RegisterEndpoint"));
870 dbus_message_iter_init_append(m, &i);
872 dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint);
874 dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
875 DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
878 pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid);
880 pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec);
882 if (pa_streq(uuid, HFP_AG_UUID) || pa_streq(uuid, HFP_HS_UUID)) {
883 uint8_t capability = 0;
884 pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capability, 1);
886 a2dp_sbc_t capabilities;
888 capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL |
889 SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO;
890 capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 |
891 SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000;
892 capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS;
893 capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8;
894 capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 |
895 SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16;
896 capabilities.min_bitpool = MIN_BITPOOL;
897 capabilities.max_bitpool = MAX_BITPOOL;
899 pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
902 dbus_message_iter_close_container(&i, &d);
904 send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
907 static void register_adapter_endpoints(pa_bluetooth_discovery *y, const char *path) {
908 register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID);
909 register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID);
911 /* For BlueZ 5, only A2DP is registered in the Media API */
912 if (y->version >= BLUEZ_VERSION_5)
915 register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID);
916 register_endpoint(y, path, HFP_HS_ENDPOINT, HFP_HS_UUID);
919 static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
922 pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "GetProperties"));
923 send_and_add_to_pending(y, m, get_properties_reply, NULL);
925 register_adapter_endpoints(y, path);
928 static void list_adapters(pa_bluetooth_discovery *y) {
932 pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "GetProperties"));
933 send_and_add_to_pending(y, m, get_properties_reply, NULL);
936 static int parse_device_properties(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) {
937 DBusMessageIter element_i;
940 dbus_message_iter_recurse(i, &element_i);
942 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
943 DBusMessageIter dict_i;
945 dbus_message_iter_recurse(&element_i, &dict_i);
947 if (parse_device_property(d, &dict_i, is_property_change) < 0)
950 dbus_message_iter_next(&element_i);
953 if (!d->address || !d->alias || d->paired < 0 || d->trusted < 0) {
954 pa_log_error("Non-optional information missing for device %s", d->path);
955 d->device_info_valid = -1;
959 d->device_info_valid = 1;
963 static int parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
964 DBusMessageIter element_i;
967 pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_OBJECT_PATH);
968 dbus_message_iter_get_basic(dict_i, &path);
970 pa_assert_se(dbus_message_iter_next(dict_i));
971 pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_ARRAY);
973 dbus_message_iter_recurse(dict_i, &element_i);
975 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
976 DBusMessageIter iface_i;
977 const char *interface;
979 dbus_message_iter_recurse(&element_i, &iface_i);
981 pa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_STRING);
982 dbus_message_iter_get_basic(&iface_i, &interface);
984 pa_assert_se(dbus_message_iter_next(&iface_i));
985 pa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY);
987 if (pa_streq(interface, "org.bluez.Adapter1")) {
988 pa_log_debug("Adapter %s found", path);
989 register_adapter_endpoints(y, path);
990 } else if (pa_streq(interface, "org.bluez.Device1")) {
991 pa_bluetooth_device *d;
993 if (pa_hashmap_get(y->devices, path)) {
994 pa_log("Found duplicated D-Bus path for device %s", path);
998 pa_log_debug("Device %s found", path);
1000 d = device_new(y, path);
1001 pa_hashmap_put(y->devices, d->path, d);
1003 /* FIXME: BlueZ 5 doesn't support the old Audio interface, and thus
1004 it's not possible to know if any audio profile is about to be
1005 connected. This can introduce regressions with modules such as
1006 module-card-restore */
1007 d->audio_state = PA_BT_AUDIO_STATE_DISCONNECTED;
1009 if (parse_device_properties(d, &iface_i, false) < 0)
1013 dbus_message_iter_next(&element_i);
1019 static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata) {
1022 pa_bluetooth_discovery *y;
1023 DBusMessageIter arg_i, element_i;
1025 pa_assert_se(p = userdata);
1026 pa_assert_se(y = p->context_data);
1027 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
1029 if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
1030 pa_log_info("D-Bus ObjectManager not detected so falling back to BlueZ version 4 API.");
1031 y->version = BLUEZ_VERSION_4;
1036 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
1037 pa_log("GetManagedObjects() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
1041 pa_log_info("D-Bus ObjectManager detected so assuming BlueZ version 5.");
1042 y->version = BLUEZ_VERSION_5;
1044 if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{oa{sa{sv}}}")) {
1045 pa_log("Invalid reply signature for GetManagedObjects().");
1049 dbus_message_iter_recurse(&arg_i, &element_i);
1050 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
1051 DBusMessageIter dict_i;
1053 dbus_message_iter_recurse(&element_i, &dict_i);
1055 /* Ignore errors here and proceed with next object */
1056 parse_interfaces_and_properties(y, &dict_i);
1058 dbus_message_iter_next(&element_i);
1062 dbus_message_unref(r);
1064 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
1065 pa_dbus_pending_free(p);
1068 static void init_bluez(pa_bluetooth_discovery *y) {
1072 pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.freedesktop.DBus.ObjectManager",
1073 "GetManagedObjects"));
1074 send_and_add_to_pending(y, m, get_managed_objects_reply, NULL);
1077 static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *i) {
1079 DBusMessageIter variant_i;
1081 key = check_variant_property(i);
1085 dbus_message_iter_recurse(i, &variant_i);
1087 switch (dbus_message_iter_get_arg_type(&variant_i)) {
1089 case DBUS_TYPE_BOOLEAN: {
1092 dbus_message_iter_get_basic(&variant_i, &value);
1094 if (pa_streq(key, "NREC") && t->nrec != value) {
1096 pa_log_debug("Transport %s: Property 'NREC' changed to %s.", t->path, t->nrec ? "True" : "False");
1097 pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED], t);
1103 case DBUS_TYPE_STRING: {
1106 dbus_message_iter_get_basic(&variant_i, &value);
1108 if (pa_streq(key, "State")) { /* Added in BlueZ 5.0 */
1109 bool old_any_connected = pa_bluetooth_device_any_audio_connected(t->device);
1111 if (transport_state_from_string(value, &t->state) < 0) {
1112 pa_log("Transport %s has an invalid state: '%s'", t->path, value);
1116 pa_log_debug("dbus: transport %s set to state '%s'", t->path, value);
1117 pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
1119 if (old_any_connected != pa_bluetooth_device_any_audio_connected(t->device))
1120 run_callback(t->device, old_any_connected);
1130 static int parse_transport_properties(pa_bluetooth_transport *t, DBusMessageIter *i) {
1131 DBusMessageIter element_i;
1133 dbus_message_iter_recurse(i, &element_i);
1135 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
1136 DBusMessageIter dict_i;
1138 dbus_message_iter_recurse(&element_i, &dict_i);
1140 transport_parse_property(t, &dict_i);
1142 dbus_message_iter_next(&element_i);
1148 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
1150 pa_bluetooth_discovery *y;
1155 pa_assert_se(y = userdata);
1157 dbus_error_init(&err);
1159 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
1160 dbus_message_get_interface(m),
1161 dbus_message_get_path(m),
1162 dbus_message_get_member(m));
1164 if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) {
1166 pa_bluetooth_device *d;
1168 if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
1169 pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err.message);
1173 pa_log_debug("Device %s removed", path);
1175 if ((d = pa_hashmap_remove(y->devices, path))) {
1176 run_callback(d, true);
1180 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1182 } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceCreated")) {
1185 if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
1186 pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err.message);
1190 pa_log_debug("Device %s created", path);
1192 found_device(y, path);
1193 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1195 } else if (dbus_message_is_signal(m, "org.bluez.Manager", "AdapterAdded")) {
1198 if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
1199 pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err.message);
1203 if (!y->adapters_listed) {
1204 pa_log_debug("Ignoring 'AdapterAdded' because initial adapter list has not been received yet.");
1205 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1208 pa_log_debug("Adapter %s created", path);
1210 found_adapter(y, path);
1211 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1213 } else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") ||
1214 dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
1215 dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") ||
1216 dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged") ||
1217 dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged") ||
1218 dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
1220 pa_bluetooth_device *d;
1222 if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
1223 DBusMessageIter arg_i;
1224 bool old_any_connected = pa_bluetooth_device_any_audio_connected(d);
1226 if (!dbus_message_iter_init(m, &arg_i)) {
1227 pa_log("Failed to parse PropertyChanged for device %s", d->path);
1231 if (dbus_message_has_interface(m, "org.bluez.Device")) {
1232 if (parse_device_property(d, &arg_i, true) < 0)
1235 } else if (parse_audio_property(d, dbus_message_get_interface(m), &arg_i, true) < 0)
1238 if (old_any_connected != pa_bluetooth_device_any_audio_connected(d))
1239 run_callback(d, false);
1242 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1244 } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
1245 const char *name, *old_owner, *new_owner;
1247 if (!dbus_message_get_args(m, &err,
1248 DBUS_TYPE_STRING, &name,
1249 DBUS_TYPE_STRING, &old_owner,
1250 DBUS_TYPE_STRING, &new_owner,
1251 DBUS_TYPE_INVALID)) {
1252 pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
1256 if (pa_streq(name, "org.bluez")) {
1257 if (old_owner && *old_owner) {
1258 pa_log_debug("Bluetooth daemon disappeared.");
1259 remove_all_devices(y);
1260 y->adapters_listed = false;
1261 y->version = BLUEZ_VERSION_UNKNOWN;
1264 if (new_owner && *new_owner) {
1265 pa_log_debug("Bluetooth daemon appeared.");
1270 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1271 } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) {
1272 pa_bluetooth_transport *t;
1273 DBusMessageIter arg_i;
1275 if (!(t = pa_hashmap_get(y->transports, dbus_message_get_path(m))))
1278 if (!dbus_message_iter_init(m, &arg_i)) {
1279 pa_log("Failed to parse PropertyChanged for transport %s", t->path);
1283 if (transport_parse_property(t, &arg_i) < 0)
1286 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1287 } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")) {
1288 DBusMessageIter arg_i;
1290 if (y->version != BLUEZ_VERSION_5)
1291 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
1293 if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oa{sa{sv}}")) {
1294 pa_log("Invalid signature found in InterfacesAdded");
1298 if (parse_interfaces_and_properties(y, &arg_i) < 0)
1301 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1302 } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")) {
1304 DBusMessageIter arg_i;
1305 DBusMessageIter element_i;
1307 if (y->version != BLUEZ_VERSION_5)
1308 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
1310 if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oas")) {
1311 pa_log("Invalid signature found in InterfacesRemoved");
1315 dbus_message_iter_get_basic(&arg_i, &path);
1317 pa_assert_se(dbus_message_iter_next(&arg_i));
1318 pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
1320 dbus_message_iter_recurse(&arg_i, &element_i);
1322 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) {
1323 const char *interface;
1325 dbus_message_iter_get_basic(&element_i, &interface);
1327 if (pa_streq(interface, "org.bluez.Device1")) {
1328 pa_bluetooth_device *d;
1330 if (!(d = pa_hashmap_remove(y->devices, path)))
1331 pa_log_warn("Unknown device removed %s", path);
1333 pa_log_debug("Device %s removed", path);
1334 run_callback(d, true);
1339 dbus_message_iter_next(&element_i);
1342 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1343 } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.Properties", "PropertiesChanged")) {
1344 DBusMessageIter arg_i;
1345 const char *interface;
1347 if (y->version != BLUEZ_VERSION_5)
1348 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
1350 if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "sa{sv}as")) {
1351 pa_log("Invalid signature found in PropertiesChanged");
1355 dbus_message_iter_get_basic(&arg_i, &interface);
1357 pa_assert_se(dbus_message_iter_next(&arg_i));
1358 pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
1360 if (pa_streq(interface, "org.bluez.Device1")) {
1361 pa_bluetooth_device *d;
1363 if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
1364 pa_log_warn("Property change in unknown device %s", dbus_message_get_path(m));
1365 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1368 parse_device_properties(d, &arg_i, true);
1369 } else if (pa_streq(interface, "org.bluez.MediaTransport1")) {
1370 pa_bluetooth_transport *t;
1372 if (!(t = pa_hashmap_get(y->transports, dbus_message_get_path(m))))
1373 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1375 parse_transport_properties(t, &arg_i);
1378 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1382 dbus_error_free(&err);
1384 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1387 pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) {
1388 pa_bluetooth_device *d;
1392 pa_assert(PA_REFCNT_VALUE(y) > 0);
1395 while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
1396 if (pa_streq(d->address, address))
1397 return d->device_info_valid == 1 ? d : NULL;
1402 pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) {
1403 pa_bluetooth_device *d;
1406 pa_assert(PA_REFCNT_VALUE(y) > 0);
1409 if ((d = pa_hashmap_get(y->devices, path)))
1410 if (d->device_info_valid == 1)
1416 bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d) {
1421 if (d->dead || d->device_info_valid != 1)
1424 if (d->audio_state == PA_BT_AUDIO_STATE_INVALID)
1427 /* Make sure audio_state is *not* in CONNECTING state before we fire the
1428 * hook to report the new device state. This is actually very important in
1429 * order to make module-card-restore work well with headsets: if the headset
1430 * supports both HSP and A2DP, one of those profiles is connected first and
1431 * then the other, and lastly the Audio interface becomes connected.
1432 * Checking only audio_state means that this function will return false at
1433 * the time when only the first connection has been made. This is good,
1434 * because otherwise, if the first connection is for HSP and we would
1435 * already load a new device module instance, and module-card-restore tries
1436 * to restore the A2DP profile, that would fail because A2DP is not yet
1437 * connected. Waiting until the Audio interface gets connected means that
1438 * both headset profiles will be connected when the device module is
1440 if (d->audio_state == PA_BT_AUDIO_STATE_CONNECTING)
1443 for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
1444 if (d->transports[i] && d->transports[i]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
1450 int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
1458 pa_assert(t->device);
1459 pa_assert(t->device->discovery);
1461 dbus_error_init(&err);
1463 if (t->device->discovery->version == BLUEZ_VERSION_4) {
1464 const char *accesstype = "rw";
1467 /* We are trying to acquire the transport only if the stream is
1468 playing, without actually initiating the stream request from our side
1469 (which is typically undesireable specially for hfgw use-cases.
1470 However this approach is racy, since the stream could have been
1471 suspended in the meantime, so we can't really guarantee that the
1472 stream will not be requested with the API in BlueZ 4.x */
1473 if (t->state < PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) {
1474 pa_log_info("Failed optional acquire of unavailable transport %s", t->path);
1480 pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", method));
1481 pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
1483 pa_assert(t->device->discovery->version == BLUEZ_VERSION_5);
1485 method = optional ? "TryAcquire" : "Acquire";
1486 pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport1", method));
1489 r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
1492 if (optional && pa_streq(err.name, "org.bluez.Error.NotAvailable"))
1493 pa_log_info("Failed optional acquire of unavailable transport %s", t->path);
1495 pa_log("Transport %s() failed for transport %s (%s)", method, t->path, err.message);
1497 dbus_error_free(&err);
1501 if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_UINT16, &i, DBUS_TYPE_UINT16, &o,
1502 DBUS_TYPE_INVALID)) {
1503 pa_log("Failed to parse the media transport Acquire() reply: %s", err.message);
1505 dbus_error_free(&err);
1516 dbus_message_unref(r);
1520 void pa_bluetooth_transport_release(pa_bluetooth_transport *t) {
1525 pa_assert(t->device);
1526 pa_assert(t->device->discovery);
1528 dbus_error_init(&err);
1530 if (t->device->discovery->version == BLUEZ_VERSION_4) {
1531 const char *accesstype = "rw";
1533 pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Release"));
1534 pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
1536 pa_assert(t->device->discovery->version == BLUEZ_VERSION_5);
1537 pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport1", "Release"));
1540 dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
1542 if (dbus_error_is_set(&err)) {
1543 pa_log("Failed to release transport %s: %s", t->path, err.message);
1544 dbus_error_free(&err);
1546 pa_log_info("Transport %s released", t->path);
1549 static void set_property(pa_bluetooth_discovery *y, const char *bus, const char *path, const char *interface,
1550 const char *prop_name, int prop_type, void *prop_value) {
1556 pa_assert(interface);
1557 pa_assert(prop_name);
1559 pa_assert_se(m = dbus_message_new_method_call(bus, path, interface, "SetProperty"));
1560 dbus_message_iter_init_append(m, &i);
1561 dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &prop_name);
1562 pa_dbus_append_basic_variant(&i, prop_type, prop_value);
1564 dbus_message_set_no_reply(m, true);
1565 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), m, NULL));
1566 dbus_message_unref(m);
1569 void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value) {
1570 dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
1573 pa_assert(t->profile == PROFILE_HSP);
1575 set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
1576 "MicrophoneGain", DBUS_TYPE_UINT16, &gain);
1579 void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value) {
1580 dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
1583 pa_assert(t->profile == PROFILE_HSP);
1585 set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
1586 "SpeakerGain", DBUS_TYPE_UINT16, &gain);
1589 static int setup_dbus(pa_bluetooth_discovery *y) {
1592 dbus_error_init(&err);
1594 if (!(y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err))) {
1595 pa_log("Failed to get D-Bus connection: %s", err.message);
1596 dbus_error_free(&err);
1603 static pa_bluetooth_transport *transport_new(pa_bluetooth_device *d, const char *owner, const char *path, enum profile p,
1604 const uint8_t *config, int size) {
1605 pa_bluetooth_transport *t;
1607 t = pa_xnew0(pa_bluetooth_transport, 1);
1609 t->owner = pa_xstrdup(owner);
1610 t->path = pa_xstrdup(path);
1612 t->config_size = size;
1615 t->config = pa_xnew(uint8_t, size);
1616 memcpy(t->config, config, size);
1619 if (d->discovery->version == BLUEZ_VERSION_4)
1620 t->state = audio_state_to_transport_state(d->profile_state[p]);
1622 t->state = PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
1627 static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
1628 pa_bluetooth_discovery *y = userdata;
1629 pa_bluetooth_device *d;
1630 pa_bluetooth_transport *t;
1631 const char *sender, *path, *dev_path = NULL, *uuid = NULL;
1632 uint8_t *config = NULL;
1636 DBusMessageIter args, props;
1638 bool old_any_connected;
1640 if (!dbus_message_iter_init(m, &args) || !pa_streq(dbus_message_get_signature(m), "oa{sv}")) {
1641 pa_log("Invalid signature for method SetConfiguration");
1645 dbus_message_iter_get_basic(&args, &path);
1647 if (pa_hashmap_get(y->transports, path)) {
1648 pa_log("Endpoint SetConfiguration: Transport %s is already configured.", path);
1652 pa_assert_se(dbus_message_iter_next(&args));
1654 dbus_message_iter_recurse(&args, &props);
1655 if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
1658 /* Read transport properties */
1659 while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) {
1661 DBusMessageIter value, entry;
1664 dbus_message_iter_recurse(&props, &entry);
1665 dbus_message_iter_get_basic(&entry, &key);
1667 dbus_message_iter_next(&entry);
1668 dbus_message_iter_recurse(&entry, &value);
1670 var = dbus_message_iter_get_arg_type(&value);
1672 if (strcasecmp(key, "UUID") == 0) {
1673 if (var != DBUS_TYPE_STRING)
1676 dbus_message_iter_get_basic(&value, &uuid);
1677 } else if (strcasecmp(key, "Device") == 0) {
1678 if (var != DBUS_TYPE_OBJECT_PATH)
1681 dbus_message_iter_get_basic(&value, &dev_path);
1682 } else if (strcasecmp(key, "NREC") == 0) {
1683 dbus_bool_t tmp_boolean;
1684 if (var != DBUS_TYPE_BOOLEAN)
1687 dbus_message_iter_get_basic(&value, &tmp_boolean);
1689 } else if (strcasecmp(key, "Configuration") == 0) {
1690 DBusMessageIter array;
1691 if (var != DBUS_TYPE_ARRAY)
1694 dbus_message_iter_recurse(&value, &array);
1695 dbus_message_iter_get_fixed_array(&array, &config, &size);
1698 dbus_message_iter_next(&props);
1701 d = found_device(y, dev_path);
1705 if (dbus_message_has_path(m, HFP_AG_ENDPOINT))
1707 else if (dbus_message_has_path(m, HFP_HS_ENDPOINT))
1709 else if (dbus_message_has_path(m, A2DP_SOURCE_ENDPOINT))
1712 p = PROFILE_A2DP_SOURCE;
1714 if (d->transports[p] != NULL) {
1715 pa_log("Cannot configure transport %s because profile %d is already used", path, p);
1719 old_any_connected = pa_bluetooth_device_any_audio_connected(d);
1721 sender = dbus_message_get_sender(m);
1723 t = transport_new(d, sender, path, p, config, size);
1727 d->transports[p] = t;
1728 pa_assert_se(pa_hashmap_put(y->transports, t->path, t) >= 0);
1730 pa_log_debug("Transport %s profile %d available", t->path, t->profile);
1732 pa_assert_se(r = dbus_message_new_method_return(m));
1733 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
1734 dbus_message_unref(r);
1736 if (old_any_connected != pa_bluetooth_device_any_audio_connected(d))
1737 run_callback(d, false);
1742 pa_log("Endpoint SetConfiguration: invalid arguments");
1745 pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to set configuration"));
1749 static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
1750 pa_bluetooth_discovery *y = userdata;
1751 pa_bluetooth_transport *t;
1756 dbus_error_init(&e);
1758 if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
1759 pa_log("Endpoint ClearConfiguration: %s", e.message);
1760 dbus_error_free(&e);
1764 if ((t = pa_hashmap_get(y->transports, path))) {
1765 bool old_any_connected = pa_bluetooth_device_any_audio_connected(t->device);
1767 pa_log_debug("Clearing transport %s profile %d", t->path, t->profile);
1768 t->device->transports[t->profile] = NULL;
1769 pa_hashmap_remove(y->transports, t->path);
1770 t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
1771 pa_hook_fire(&y->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
1773 if (old_any_connected != pa_bluetooth_device_any_audio_connected(t->device))
1774 run_callback(t->device, false);
1779 pa_assert_se(r = dbus_message_new_method_return(m));
1784 pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to clear configuration"));
1788 static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
1791 case SBC_SAMPLING_FREQ_16000:
1792 case SBC_SAMPLING_FREQ_32000:
1795 case SBC_SAMPLING_FREQ_44100:
1798 case SBC_CHANNEL_MODE_MONO:
1799 case SBC_CHANNEL_MODE_DUAL_CHANNEL:
1802 case SBC_CHANNEL_MODE_STEREO:
1803 case SBC_CHANNEL_MODE_JOINT_STEREO:
1807 pa_log_warn("Invalid channel mode %u", mode);
1811 case SBC_SAMPLING_FREQ_48000:
1814 case SBC_CHANNEL_MODE_MONO:
1815 case SBC_CHANNEL_MODE_DUAL_CHANNEL:
1818 case SBC_CHANNEL_MODE_STEREO:
1819 case SBC_CHANNEL_MODE_JOINT_STEREO:
1823 pa_log_warn("Invalid channel mode %u", mode);
1828 pa_log_warn("Invalid sampling freq %u", freq);
1833 static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
1834 pa_bluetooth_discovery *y = userdata;
1835 a2dp_sbc_t *cap, config;
1836 uint8_t *pconf = (uint8_t *) &config;
1841 static const struct {
1845 { 16000U, SBC_SAMPLING_FREQ_16000 },
1846 { 32000U, SBC_SAMPLING_FREQ_32000 },
1847 { 44100U, SBC_SAMPLING_FREQ_44100 },
1848 { 48000U, SBC_SAMPLING_FREQ_48000 }
1851 dbus_error_init(&e);
1853 if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
1854 pa_log("Endpoint SelectConfiguration: %s", e.message);
1855 dbus_error_free(&e);
1859 if (dbus_message_has_path(m, HFP_AG_ENDPOINT) || dbus_message_has_path(m, HFP_HS_ENDPOINT))
1862 pa_assert(size == sizeof(config));
1864 memset(&config, 0, sizeof(config));
1866 /* Find the lowest freq that is at least as high as the requested
1868 for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
1869 if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
1870 config.frequency = freq_table[i].cap;
1874 if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
1875 for (--i; i >= 0; i--) {
1876 if (cap->frequency & freq_table[i].cap) {
1877 config.frequency = freq_table[i].cap;
1883 pa_log("Not suitable sample rate");
1888 pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
1890 if (y->core->default_sample_spec.channels <= 1) {
1891 if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
1892 config.channel_mode = SBC_CHANNEL_MODE_MONO;
1895 if (y->core->default_sample_spec.channels >= 2) {
1896 if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
1897 config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
1898 else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
1899 config.channel_mode = SBC_CHANNEL_MODE_STEREO;
1900 else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
1901 config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
1902 else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) {
1903 config.channel_mode = SBC_CHANNEL_MODE_MONO;
1905 pa_log("No supported channel modes");
1910 if (cap->block_length & SBC_BLOCK_LENGTH_16)
1911 config.block_length = SBC_BLOCK_LENGTH_16;
1912 else if (cap->block_length & SBC_BLOCK_LENGTH_12)
1913 config.block_length = SBC_BLOCK_LENGTH_12;
1914 else if (cap->block_length & SBC_BLOCK_LENGTH_8)
1915 config.block_length = SBC_BLOCK_LENGTH_8;
1916 else if (cap->block_length & SBC_BLOCK_LENGTH_4)
1917 config.block_length = SBC_BLOCK_LENGTH_4;
1919 pa_log_error("No supported block lengths");
1923 if (cap->subbands & SBC_SUBBANDS_8)
1924 config.subbands = SBC_SUBBANDS_8;
1925 else if (cap->subbands & SBC_SUBBANDS_4)
1926 config.subbands = SBC_SUBBANDS_4;
1928 pa_log_error("No supported subbands");
1932 if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS)
1933 config.allocation_method = SBC_ALLOCATION_LOUDNESS;
1934 else if (cap->allocation_method & SBC_ALLOCATION_SNR)
1935 config.allocation_method = SBC_ALLOCATION_SNR;
1937 config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool);
1938 config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool);
1941 pa_assert_se(r = dbus_message_new_method_return(m));
1943 pa_assert_se(dbus_message_append_args(
1945 DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size,
1946 DBUS_TYPE_INVALID));
1951 pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to select configuration"));
1955 static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
1956 struct pa_bluetooth_discovery *y = userdata;
1957 DBusMessage *r = NULL;
1959 const char *path, *interface, *member;
1963 path = dbus_message_get_path(m);
1964 interface = dbus_message_get_interface(m);
1965 member = dbus_message_get_member(m);
1967 pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
1969 dbus_error_init(&e);
1971 if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT) &&
1972 !pa_streq(path, HFP_HS_ENDPOINT))
1973 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1975 interface = y->version == BLUEZ_VERSION_4 ? "org.bluez.MediaEndpoint" : "org.bluez.MediaEndpoint1";
1977 if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1978 const char *xml = y->version == BLUEZ_VERSION_4 ? ENDPOINT_INTROSPECT_XML : MEDIA_ENDPOINT_1_INTROSPECT_XML;
1980 pa_assert_se(r = dbus_message_new_method_return(m));
1981 pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
1983 } else if (dbus_message_is_method_call(m, interface, "SetConfiguration"))
1984 r = endpoint_set_configuration(c, m, userdata);
1985 else if (dbus_message_is_method_call(m, interface, "SelectConfiguration"))
1986 r = endpoint_select_configuration(c, m, userdata);
1987 else if (dbus_message_is_method_call(m, interface, "ClearConfiguration"))
1988 r = endpoint_clear_configuration(c, m, userdata);
1990 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1993 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
1994 dbus_message_unref(r);
1997 return DBUS_HANDLER_RESULT_HANDLED;
2000 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
2002 pa_bluetooth_discovery *y;
2003 DBusConnection *conn;
2005 static const DBusObjectPathVTable vtable_endpoint = {
2006 .message_function = endpoint_handler,
2011 dbus_error_init(&err);
2013 if ((y = pa_shared_get(c, "bluetooth-discovery")))
2014 return pa_bluetooth_discovery_ref(y);
2016 y = pa_xnew0(pa_bluetooth_discovery, 1);
2019 y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
2020 y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
2021 PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
2023 for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
2024 pa_hook_init(&y->hooks[i], y);
2026 pa_shared_set(c, "bluetooth-discovery", y);
2028 if (setup_dbus(y) < 0)
2031 conn = pa_dbus_connection_get(y->connection);
2033 /* dynamic detection of bluetooth audio devices */
2034 if (!dbus_connection_add_filter(conn, filter_cb, y, NULL)) {
2035 pa_log_error("Failed to add filter function");
2039 y->filter_added = true;
2041 if (pa_dbus_add_matches(
2043 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
2044 ",arg0='org.bluez'",
2045 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
2046 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
2047 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
2048 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
2049 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
2050 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
2051 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
2052 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
2053 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
2054 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
2055 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
2056 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'",
2057 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
2058 ",arg0='org.bluez.Device1'",
2059 "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
2060 ",arg0='org.bluez.MediaTransport1'",
2062 pa_log("Failed to add D-Bus matches: %s", err.message);
2066 pa_assert_se(dbus_connection_register_object_path(conn, HFP_AG_ENDPOINT, &vtable_endpoint, y));
2067 pa_assert_se(dbus_connection_register_object_path(conn, HFP_HS_ENDPOINT, &vtable_endpoint, y));
2068 pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y));
2069 pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SINK_ENDPOINT, &vtable_endpoint, y));
2077 pa_bluetooth_discovery_unref(y);
2079 dbus_error_free(&err);
2084 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
2086 pa_assert(PA_REFCNT_VALUE(y) > 0);
2093 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
2097 pa_assert(PA_REFCNT_VALUE(y) > 0);
2099 if (PA_REFCNT_DEC(y) > 0)
2102 pa_dbus_free_pending_list(&y->pending);
2105 remove_all_devices(y);
2106 pa_hashmap_free(y->devices);
2109 if (y->transports) {
2110 pa_assert(pa_hashmap_isempty(y->transports));
2111 pa_hashmap_free(y->transports);
2114 if (y->connection) {
2115 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT);
2116 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_HS_ENDPOINT);
2117 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
2118 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
2119 pa_dbus_remove_matches(
2120 pa_dbus_connection_get(y->connection),
2121 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
2122 ",arg0='org.bluez'",
2123 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
2124 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
2125 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
2126 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
2127 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
2128 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
2129 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
2130 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
2131 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
2132 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
2133 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
2134 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
2135 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'",
2136 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
2137 ",arg0='org.bluez.Device1'",
2138 "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
2139 ",arg0='org.bluez.MediaTransport1'",
2142 if (y->filter_added)
2143 dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
2145 pa_dbus_connection_unref(y->connection);
2148 for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
2149 pa_hook_done(&y->hooks[i]);
2152 pa_shared_remove(y->core, "bluetooth-discovery");
2157 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) {
2159 pa_assert(PA_REFCNT_VALUE(y) > 0);
2161 return &y->hooks[hook];
2164 pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class) {
2165 unsigned major, minor;
2166 pa_bt_form_factor_t r;
2168 static const pa_bt_form_factor_t table[] = {
2169 [1] = PA_BT_FORM_FACTOR_HEADSET,
2170 [2] = PA_BT_FORM_FACTOR_HANDSFREE,
2171 [4] = PA_BT_FORM_FACTOR_MICROPHONE,
2172 [5] = PA_BT_FORM_FACTOR_SPEAKER,
2173 [6] = PA_BT_FORM_FACTOR_HEADPHONE,
2174 [7] = PA_BT_FORM_FACTOR_PORTABLE,
2175 [8] = PA_BT_FORM_FACTOR_CAR,
2176 [10] = PA_BT_FORM_FACTOR_HIFI
2180 * See Bluetooth Assigned Numbers:
2181 * https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
2183 major = (class >> 8) & 0x1F;
2184 minor = (class >> 2) & 0x3F;
2188 return PA_BT_FORM_FACTOR_PHONE;
2192 pa_log_debug("Unknown Bluetooth major device class %u", major);
2193 return PA_BT_FORM_FACTOR_UNKNOWN;
2196 r = minor < PA_ELEMENTSOF(table) ? table[minor] : PA_BT_FORM_FACTOR_UNKNOWN;
2199 pa_log_debug("Unknown Bluetooth minor device class %u", minor);
2204 const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff) {
2206 case PA_BT_FORM_FACTOR_UNKNOWN:
2208 case PA_BT_FORM_FACTOR_HEADSET:
2210 case PA_BT_FORM_FACTOR_HANDSFREE:
2211 return "hands-free";
2212 case PA_BT_FORM_FACTOR_MICROPHONE:
2213 return "microphone";
2214 case PA_BT_FORM_FACTOR_SPEAKER:
2216 case PA_BT_FORM_FACTOR_HEADPHONE:
2218 case PA_BT_FORM_FACTOR_PORTABLE:
2220 case PA_BT_FORM_FACTOR_CAR:
2222 case PA_BT_FORM_FACTOR_HIFI:
2224 case PA_BT_FORM_FACTOR_PHONE:
2228 pa_assert_not_reached();
2231 char *pa_bluetooth_cleanup_name(const char *name) {
2237 while ((*name >= 1 && *name <= 32) || *name >= 127)
2240 t = pa_xstrdup(name);
2242 for (s = d = t; *s; s++) {
2244 if (*s <= 32 || *s >= 127 || *s == '_') {
2262 bool pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
2266 if (strcasecmp(uuids->uuid, uuid) == 0)
2269 uuids = uuids->next;