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);
1538 if (t->state <= PA_BLUETOOTH_TRANSPORT_STATE_IDLE) {
1539 pa_log_info("Transport %s auto-released by BlueZ or already released", t->path);
1543 pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport1", "Release"));
1546 dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
1548 if (dbus_error_is_set(&err)) {
1549 pa_log("Failed to release transport %s: %s", t->path, err.message);
1550 dbus_error_free(&err);
1552 pa_log_info("Transport %s released", t->path);
1555 static void set_property(pa_bluetooth_discovery *y, const char *bus, const char *path, const char *interface,
1556 const char *prop_name, int prop_type, void *prop_value) {
1562 pa_assert(interface);
1563 pa_assert(prop_name);
1565 pa_assert_se(m = dbus_message_new_method_call(bus, path, interface, "SetProperty"));
1566 dbus_message_iter_init_append(m, &i);
1567 dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &prop_name);
1568 pa_dbus_append_basic_variant(&i, prop_type, prop_value);
1570 dbus_message_set_no_reply(m, true);
1571 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), m, NULL));
1572 dbus_message_unref(m);
1575 void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value) {
1576 dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
1579 pa_assert(t->profile == PROFILE_HSP);
1581 set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
1582 "MicrophoneGain", DBUS_TYPE_UINT16, &gain);
1585 void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value) {
1586 dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
1589 pa_assert(t->profile == PROFILE_HSP);
1591 set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
1592 "SpeakerGain", DBUS_TYPE_UINT16, &gain);
1595 static int setup_dbus(pa_bluetooth_discovery *y) {
1598 dbus_error_init(&err);
1600 if (!(y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err))) {
1601 pa_log("Failed to get D-Bus connection: %s", err.message);
1602 dbus_error_free(&err);
1609 static pa_bluetooth_transport *transport_new(pa_bluetooth_device *d, const char *owner, const char *path, enum profile p,
1610 const uint8_t *config, int size) {
1611 pa_bluetooth_transport *t;
1613 t = pa_xnew0(pa_bluetooth_transport, 1);
1615 t->owner = pa_xstrdup(owner);
1616 t->path = pa_xstrdup(path);
1618 t->config_size = size;
1621 t->config = pa_xnew(uint8_t, size);
1622 memcpy(t->config, config, size);
1625 if (d->discovery->version == BLUEZ_VERSION_4)
1626 t->state = audio_state_to_transport_state(d->profile_state[p]);
1628 t->state = PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
1633 static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
1634 pa_bluetooth_discovery *y = userdata;
1635 pa_bluetooth_device *d;
1636 pa_bluetooth_transport *t;
1637 const char *sender, *path, *dev_path = NULL, *uuid = NULL;
1638 uint8_t *config = NULL;
1642 DBusMessageIter args, props;
1644 bool old_any_connected;
1646 if (!dbus_message_iter_init(m, &args) || !pa_streq(dbus_message_get_signature(m), "oa{sv}")) {
1647 pa_log("Invalid signature for method SetConfiguration");
1651 dbus_message_iter_get_basic(&args, &path);
1653 if (pa_hashmap_get(y->transports, path)) {
1654 pa_log("Endpoint SetConfiguration: Transport %s is already configured.", path);
1658 pa_assert_se(dbus_message_iter_next(&args));
1660 dbus_message_iter_recurse(&args, &props);
1661 if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
1664 /* Read transport properties */
1665 while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) {
1667 DBusMessageIter value, entry;
1670 dbus_message_iter_recurse(&props, &entry);
1671 dbus_message_iter_get_basic(&entry, &key);
1673 dbus_message_iter_next(&entry);
1674 dbus_message_iter_recurse(&entry, &value);
1676 var = dbus_message_iter_get_arg_type(&value);
1678 if (strcasecmp(key, "UUID") == 0) {
1679 if (var != DBUS_TYPE_STRING)
1682 dbus_message_iter_get_basic(&value, &uuid);
1683 } else if (strcasecmp(key, "Device") == 0) {
1684 if (var != DBUS_TYPE_OBJECT_PATH)
1687 dbus_message_iter_get_basic(&value, &dev_path);
1688 } else if (strcasecmp(key, "NREC") == 0) {
1689 dbus_bool_t tmp_boolean;
1690 if (var != DBUS_TYPE_BOOLEAN)
1693 dbus_message_iter_get_basic(&value, &tmp_boolean);
1695 } else if (strcasecmp(key, "Configuration") == 0) {
1696 DBusMessageIter array;
1697 if (var != DBUS_TYPE_ARRAY)
1700 dbus_message_iter_recurse(&value, &array);
1701 dbus_message_iter_get_fixed_array(&array, &config, &size);
1704 dbus_message_iter_next(&props);
1707 d = found_device(y, dev_path);
1711 if (dbus_message_has_path(m, HFP_AG_ENDPOINT))
1713 else if (dbus_message_has_path(m, HFP_HS_ENDPOINT))
1715 else if (dbus_message_has_path(m, A2DP_SOURCE_ENDPOINT))
1718 p = PROFILE_A2DP_SOURCE;
1720 if (d->transports[p] != NULL) {
1721 pa_log("Cannot configure transport %s because profile %d is already used", path, p);
1725 old_any_connected = pa_bluetooth_device_any_audio_connected(d);
1727 sender = dbus_message_get_sender(m);
1729 t = transport_new(d, sender, path, p, config, size);
1733 d->transports[p] = t;
1734 pa_assert_se(pa_hashmap_put(y->transports, t->path, t) >= 0);
1736 pa_log_debug("Transport %s profile %d available", t->path, t->profile);
1738 pa_assert_se(r = dbus_message_new_method_return(m));
1739 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
1740 dbus_message_unref(r);
1742 if (old_any_connected != pa_bluetooth_device_any_audio_connected(d))
1743 run_callback(d, false);
1748 pa_log("Endpoint SetConfiguration: invalid arguments");
1751 pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to set configuration"));
1755 static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
1756 pa_bluetooth_discovery *y = userdata;
1757 pa_bluetooth_transport *t;
1762 dbus_error_init(&e);
1764 if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
1765 pa_log("Endpoint ClearConfiguration: %s", e.message);
1766 dbus_error_free(&e);
1770 if ((t = pa_hashmap_get(y->transports, path))) {
1771 bool old_any_connected = pa_bluetooth_device_any_audio_connected(t->device);
1773 pa_log_debug("Clearing transport %s profile %d", t->path, t->profile);
1774 t->device->transports[t->profile] = NULL;
1775 pa_hashmap_remove(y->transports, t->path);
1776 t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
1777 pa_hook_fire(&y->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
1779 if (old_any_connected != pa_bluetooth_device_any_audio_connected(t->device))
1780 run_callback(t->device, false);
1785 pa_assert_se(r = dbus_message_new_method_return(m));
1790 pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to clear configuration"));
1794 static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
1797 case SBC_SAMPLING_FREQ_16000:
1798 case SBC_SAMPLING_FREQ_32000:
1801 case SBC_SAMPLING_FREQ_44100:
1804 case SBC_CHANNEL_MODE_MONO:
1805 case SBC_CHANNEL_MODE_DUAL_CHANNEL:
1808 case SBC_CHANNEL_MODE_STEREO:
1809 case SBC_CHANNEL_MODE_JOINT_STEREO:
1813 pa_log_warn("Invalid channel mode %u", mode);
1817 case SBC_SAMPLING_FREQ_48000:
1820 case SBC_CHANNEL_MODE_MONO:
1821 case SBC_CHANNEL_MODE_DUAL_CHANNEL:
1824 case SBC_CHANNEL_MODE_STEREO:
1825 case SBC_CHANNEL_MODE_JOINT_STEREO:
1829 pa_log_warn("Invalid channel mode %u", mode);
1834 pa_log_warn("Invalid sampling freq %u", freq);
1839 static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
1840 pa_bluetooth_discovery *y = userdata;
1841 a2dp_sbc_t *cap, config;
1842 uint8_t *pconf = (uint8_t *) &config;
1847 static const struct {
1851 { 16000U, SBC_SAMPLING_FREQ_16000 },
1852 { 32000U, SBC_SAMPLING_FREQ_32000 },
1853 { 44100U, SBC_SAMPLING_FREQ_44100 },
1854 { 48000U, SBC_SAMPLING_FREQ_48000 }
1857 dbus_error_init(&e);
1859 if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
1860 pa_log("Endpoint SelectConfiguration: %s", e.message);
1861 dbus_error_free(&e);
1865 if (dbus_message_has_path(m, HFP_AG_ENDPOINT) || dbus_message_has_path(m, HFP_HS_ENDPOINT))
1868 pa_assert(size == sizeof(config));
1870 memset(&config, 0, sizeof(config));
1872 /* Find the lowest freq that is at least as high as the requested
1874 for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
1875 if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
1876 config.frequency = freq_table[i].cap;
1880 if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
1881 for (--i; i >= 0; i--) {
1882 if (cap->frequency & freq_table[i].cap) {
1883 config.frequency = freq_table[i].cap;
1889 pa_log("Not suitable sample rate");
1894 pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
1896 if (y->core->default_sample_spec.channels <= 1) {
1897 if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
1898 config.channel_mode = SBC_CHANNEL_MODE_MONO;
1901 if (y->core->default_sample_spec.channels >= 2) {
1902 if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
1903 config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
1904 else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
1905 config.channel_mode = SBC_CHANNEL_MODE_STEREO;
1906 else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
1907 config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
1908 else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) {
1909 config.channel_mode = SBC_CHANNEL_MODE_MONO;
1911 pa_log("No supported channel modes");
1916 if (cap->block_length & SBC_BLOCK_LENGTH_16)
1917 config.block_length = SBC_BLOCK_LENGTH_16;
1918 else if (cap->block_length & SBC_BLOCK_LENGTH_12)
1919 config.block_length = SBC_BLOCK_LENGTH_12;
1920 else if (cap->block_length & SBC_BLOCK_LENGTH_8)
1921 config.block_length = SBC_BLOCK_LENGTH_8;
1922 else if (cap->block_length & SBC_BLOCK_LENGTH_4)
1923 config.block_length = SBC_BLOCK_LENGTH_4;
1925 pa_log_error("No supported block lengths");
1929 if (cap->subbands & SBC_SUBBANDS_8)
1930 config.subbands = SBC_SUBBANDS_8;
1931 else if (cap->subbands & SBC_SUBBANDS_4)
1932 config.subbands = SBC_SUBBANDS_4;
1934 pa_log_error("No supported subbands");
1938 if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS)
1939 config.allocation_method = SBC_ALLOCATION_LOUDNESS;
1940 else if (cap->allocation_method & SBC_ALLOCATION_SNR)
1941 config.allocation_method = SBC_ALLOCATION_SNR;
1943 config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool);
1944 config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool);
1947 pa_assert_se(r = dbus_message_new_method_return(m));
1949 pa_assert_se(dbus_message_append_args(
1951 DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size,
1952 DBUS_TYPE_INVALID));
1957 pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to select configuration"));
1961 static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
1962 struct pa_bluetooth_discovery *y = userdata;
1963 DBusMessage *r = NULL;
1965 const char *path, *interface, *member;
1969 path = dbus_message_get_path(m);
1970 interface = dbus_message_get_interface(m);
1971 member = dbus_message_get_member(m);
1973 pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
1975 dbus_error_init(&e);
1977 if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT) &&
1978 !pa_streq(path, HFP_HS_ENDPOINT))
1979 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1981 interface = y->version == BLUEZ_VERSION_4 ? "org.bluez.MediaEndpoint" : "org.bluez.MediaEndpoint1";
1983 if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1984 const char *xml = y->version == BLUEZ_VERSION_4 ? ENDPOINT_INTROSPECT_XML : MEDIA_ENDPOINT_1_INTROSPECT_XML;
1986 pa_assert_se(r = dbus_message_new_method_return(m));
1987 pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
1989 } else if (dbus_message_is_method_call(m, interface, "SetConfiguration"))
1990 r = endpoint_set_configuration(c, m, userdata);
1991 else if (dbus_message_is_method_call(m, interface, "SelectConfiguration"))
1992 r = endpoint_select_configuration(c, m, userdata);
1993 else if (dbus_message_is_method_call(m, interface, "ClearConfiguration"))
1994 r = endpoint_clear_configuration(c, m, userdata);
1996 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1999 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
2000 dbus_message_unref(r);
2003 return DBUS_HANDLER_RESULT_HANDLED;
2006 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
2008 pa_bluetooth_discovery *y;
2009 DBusConnection *conn;
2011 static const DBusObjectPathVTable vtable_endpoint = {
2012 .message_function = endpoint_handler,
2017 dbus_error_init(&err);
2019 if ((y = pa_shared_get(c, "bluetooth-discovery")))
2020 return pa_bluetooth_discovery_ref(y);
2022 y = pa_xnew0(pa_bluetooth_discovery, 1);
2025 y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
2026 y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
2027 PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
2029 for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
2030 pa_hook_init(&y->hooks[i], y);
2032 pa_shared_set(c, "bluetooth-discovery", y);
2034 if (setup_dbus(y) < 0)
2037 conn = pa_dbus_connection_get(y->connection);
2039 /* dynamic detection of bluetooth audio devices */
2040 if (!dbus_connection_add_filter(conn, filter_cb, y, NULL)) {
2041 pa_log_error("Failed to add filter function");
2045 y->filter_added = true;
2047 if (pa_dbus_add_matches(
2049 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
2050 ",arg0='org.bluez'",
2051 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
2052 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
2053 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
2054 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
2055 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
2056 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
2057 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
2058 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
2059 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
2060 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
2061 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
2062 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'",
2063 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
2064 ",arg0='org.bluez.Device1'",
2065 "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
2066 ",arg0='org.bluez.MediaTransport1'",
2068 pa_log("Failed to add D-Bus matches: %s", err.message);
2072 pa_assert_se(dbus_connection_register_object_path(conn, HFP_AG_ENDPOINT, &vtable_endpoint, y));
2073 pa_assert_se(dbus_connection_register_object_path(conn, HFP_HS_ENDPOINT, &vtable_endpoint, y));
2074 pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y));
2075 pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SINK_ENDPOINT, &vtable_endpoint, y));
2083 pa_bluetooth_discovery_unref(y);
2085 dbus_error_free(&err);
2090 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
2092 pa_assert(PA_REFCNT_VALUE(y) > 0);
2099 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
2103 pa_assert(PA_REFCNT_VALUE(y) > 0);
2105 if (PA_REFCNT_DEC(y) > 0)
2108 pa_dbus_free_pending_list(&y->pending);
2111 remove_all_devices(y);
2112 pa_hashmap_free(y->devices, NULL);
2115 if (y->transports) {
2116 pa_assert(pa_hashmap_isempty(y->transports));
2117 pa_hashmap_free(y->transports, NULL);
2120 if (y->connection) {
2121 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT);
2122 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_HS_ENDPOINT);
2123 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
2124 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
2125 pa_dbus_remove_matches(
2126 pa_dbus_connection_get(y->connection),
2127 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
2128 ",arg0='org.bluez'",
2129 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
2130 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
2131 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
2132 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
2133 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
2134 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
2135 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
2136 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
2137 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
2138 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
2139 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
2140 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
2141 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'",
2142 "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
2143 ",arg0='org.bluez.Device1'",
2144 "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
2145 ",arg0='org.bluez.MediaTransport1'",
2148 if (y->filter_added)
2149 dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
2151 pa_dbus_connection_unref(y->connection);
2154 for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
2155 pa_hook_done(&y->hooks[i]);
2158 pa_shared_remove(y->core, "bluetooth-discovery");
2163 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) {
2165 pa_assert(PA_REFCNT_VALUE(y) > 0);
2167 return &y->hooks[hook];
2170 pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class) {
2171 unsigned major, minor;
2172 pa_bt_form_factor_t r;
2174 static const pa_bt_form_factor_t table[] = {
2175 [1] = PA_BT_FORM_FACTOR_HEADSET,
2176 [2] = PA_BT_FORM_FACTOR_HANDSFREE,
2177 [4] = PA_BT_FORM_FACTOR_MICROPHONE,
2178 [5] = PA_BT_FORM_FACTOR_SPEAKER,
2179 [6] = PA_BT_FORM_FACTOR_HEADPHONE,
2180 [7] = PA_BT_FORM_FACTOR_PORTABLE,
2181 [8] = PA_BT_FORM_FACTOR_CAR,
2182 [10] = PA_BT_FORM_FACTOR_HIFI
2186 * See Bluetooth Assigned Numbers:
2187 * https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
2189 major = (class >> 8) & 0x1F;
2190 minor = (class >> 2) & 0x3F;
2194 return PA_BT_FORM_FACTOR_PHONE;
2198 pa_log_debug("Unknown Bluetooth major device class %u", major);
2199 return PA_BT_FORM_FACTOR_UNKNOWN;
2202 r = minor < PA_ELEMENTSOF(table) ? table[minor] : PA_BT_FORM_FACTOR_UNKNOWN;
2205 pa_log_debug("Unknown Bluetooth minor device class %u", minor);
2210 const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff) {
2212 case PA_BT_FORM_FACTOR_UNKNOWN:
2214 case PA_BT_FORM_FACTOR_HEADSET:
2216 case PA_BT_FORM_FACTOR_HANDSFREE:
2217 return "hands-free";
2218 case PA_BT_FORM_FACTOR_MICROPHONE:
2219 return "microphone";
2220 case PA_BT_FORM_FACTOR_SPEAKER:
2222 case PA_BT_FORM_FACTOR_HEADPHONE:
2224 case PA_BT_FORM_FACTOR_PORTABLE:
2226 case PA_BT_FORM_FACTOR_CAR:
2228 case PA_BT_FORM_FACTOR_HIFI:
2230 case PA_BT_FORM_FACTOR_PHONE:
2234 pa_assert_not_reached();
2237 char *pa_bluetooth_cleanup_name(const char *name) {
2243 while ((*name >= 1 && *name <= 32) || *name >= 127)
2246 t = pa_xstrdup(name);
2248 for (s = d = t; *s; s++) {
2250 if (*s <= 32 || *s >= 127 || *s == '_') {
2268 bool pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
2272 if (strcasecmp(uuids->uuid, uuid) == 0)
2275 uuids = uuids->next;