2 This file is part of PulseAudio.
4 Copyright 2008-2013 João 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 struct pa_bluetooth_discovery
{
68 pa_dbus_connection
*connection
;
69 PA_LLIST_HEAD(pa_dbus_pending
, pending
);
72 pa_hashmap
*transports
;
73 pa_hook hooks
[PA_BLUETOOTH_HOOK_MAX
];
77 static void get_properties_reply(DBusPendingCall
*pending
, void *userdata
);
78 static pa_dbus_pending
* send_and_add_to_pending(pa_bluetooth_discovery
*y
, DBusMessage
*m
, DBusPendingCallNotifyFunction func
,
80 static void found_adapter(pa_bluetooth_discovery
*y
, const char *path
);
81 static pa_bluetooth_device
*found_device(pa_bluetooth_discovery
*y
, const char* path
);
83 static pa_bt_audio_state_t
audio_state_from_string(const char* value
) {
86 if (pa_streq(value
, "disconnected"))
87 return PA_BT_AUDIO_STATE_DISCONNECTED
;
88 else if (pa_streq(value
, "connecting"))
89 return PA_BT_AUDIO_STATE_CONNECTING
;
90 else if (pa_streq(value
, "connected"))
91 return PA_BT_AUDIO_STATE_CONNECTED
;
92 else if (pa_streq(value
, "playing"))
93 return PA_BT_AUDIO_STATE_PLAYING
;
95 return PA_BT_AUDIO_STATE_INVALID
;
98 const char *pa_bt_profile_to_string(enum profile profile
) {
102 case PROFILE_A2DP_SOURCE
:
103 return "a2dp_source";
109 pa_assert_not_reached();
112 pa_assert_not_reached();
115 static int profile_from_interface(const char *interface
, enum profile
*p
) {
116 pa_assert(interface
);
119 if (pa_streq(interface
, "org.bluez.AudioSink")) {
122 } else if (pa_streq(interface
, "org.bluez.AudioSource")) {
123 *p
= PROFILE_A2DP_SOURCE
;
125 } else if (pa_streq(interface
, "org.bluez.Headset")) {
128 } else if (pa_streq(interface
, "org.bluez.HandsfreeGateway")) {
136 static pa_bluetooth_transport_state_t
audio_state_to_transport_state(pa_bt_audio_state_t state
) {
138 case PA_BT_AUDIO_STATE_INVALID
: /* Typically if state hasn't been received yet */
139 case PA_BT_AUDIO_STATE_DISCONNECTED
:
140 case PA_BT_AUDIO_STATE_CONNECTING
:
141 return PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
;
142 case PA_BT_AUDIO_STATE_CONNECTED
:
143 return PA_BLUETOOTH_TRANSPORT_STATE_IDLE
;
144 case PA_BT_AUDIO_STATE_PLAYING
:
145 return PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
;
148 pa_assert_not_reached();
151 static pa_bluetooth_uuid
*uuid_new(const char *uuid
) {
152 pa_bluetooth_uuid
*u
;
154 u
= pa_xnew(pa_bluetooth_uuid
, 1);
155 u
->uuid
= pa_xstrdup(uuid
);
156 PA_LLIST_INIT(pa_bluetooth_uuid
, u
);
161 static void uuid_free(pa_bluetooth_uuid
*u
) {
168 static pa_bluetooth_device
* device_new(pa_bluetooth_discovery
*discovery
, const char *path
) {
169 pa_bluetooth_device
*d
;
172 pa_assert(discovery
);
175 d
= pa_xnew0(pa_bluetooth_device
, 1);
177 d
->discovery
= discovery
;
180 d
->device_info_valid
= 0;
183 d
->path
= pa_xstrdup(path
);
186 PA_LLIST_HEAD_INIT(pa_bluetooth_uuid
, d
->uuids
);
191 d
->audio_state
= PA_BT_AUDIO_STATE_INVALID
;
193 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++)
194 d
->profile_state
[i
] = PA_BT_AUDIO_STATE_INVALID
;
199 static void transport_free(pa_bluetooth_transport
*t
) {
208 static void device_free(pa_bluetooth_device
*d
) {
209 pa_bluetooth_uuid
*u
;
210 pa_bluetooth_transport
*t
;
215 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++) {
216 if (!(t
= d
->transports
[i
]))
219 d
->transports
[i
] = NULL
;
220 pa_hashmap_remove(d
->discovery
->transports
, t
->path
);
221 t
->state
= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
;
222 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED
], t
);
226 while ((u
= d
->uuids
)) {
227 PA_LLIST_REMOVE(pa_bluetooth_uuid
, d
->uuids
, u
);
234 pa_xfree(d
->address
);
238 static const char *check_variant_property(DBusMessageIter
*i
) {
243 if (dbus_message_iter_get_arg_type(i
) != DBUS_TYPE_STRING
) {
244 pa_log("Property name not a string.");
248 dbus_message_iter_get_basic(i
, &key
);
250 if (!dbus_message_iter_next(i
)) {
251 pa_log("Property value missing");
255 if (dbus_message_iter_get_arg_type(i
) != DBUS_TYPE_VARIANT
) {
256 pa_log("Property value not a variant.");
263 static int parse_manager_property(pa_bluetooth_discovery
*y
, DBusMessageIter
*i
, bool is_property_change
) {
265 DBusMessageIter variant_i
;
269 key
= check_variant_property(i
);
273 dbus_message_iter_recurse(i
, &variant_i
);
275 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
277 case DBUS_TYPE_ARRAY
: {
280 dbus_message_iter_recurse(&variant_i
, &ai
);
282 if (pa_streq(key
, "Adapters")) {
283 y
->adapters_listed
= true;
285 if (dbus_message_iter_get_arg_type(&ai
) != DBUS_TYPE_OBJECT_PATH
)
288 while (dbus_message_iter_get_arg_type(&ai
) != DBUS_TYPE_INVALID
) {
291 dbus_message_iter_get_basic(&ai
, &value
);
293 found_adapter(y
, value
);
295 dbus_message_iter_next(&ai
);
306 static int parse_adapter_property(pa_bluetooth_discovery
*y
, DBusMessageIter
*i
, bool is_property_change
) {
308 DBusMessageIter variant_i
;
312 key
= check_variant_property(i
);
316 dbus_message_iter_recurse(i
, &variant_i
);
318 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
320 case DBUS_TYPE_ARRAY
: {
323 dbus_message_iter_recurse(&variant_i
, &ai
);
325 if (dbus_message_iter_get_arg_type(&ai
) == DBUS_TYPE_OBJECT_PATH
&&
326 pa_streq(key
, "Devices")) {
328 while (dbus_message_iter_get_arg_type(&ai
) != DBUS_TYPE_INVALID
) {
331 dbus_message_iter_get_basic(&ai
, &value
);
333 found_device(y
, value
);
335 dbus_message_iter_next(&ai
);
346 static int parse_device_property(pa_bluetooth_device
*d
, DBusMessageIter
*i
, bool is_property_change
) {
348 DBusMessageIter variant_i
;
352 key
= check_variant_property(i
);
356 dbus_message_iter_recurse(i
, &variant_i
);
358 /* pa_log_debug("Parsing property org.bluez.Device.%s", key); */
360 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
362 case DBUS_TYPE_STRING
: {
365 dbus_message_iter_get_basic(&variant_i
, &value
);
367 if (pa_streq(key
, "Name")) {
369 d
->name
= pa_xstrdup(value
);
370 } else if (pa_streq(key
, "Alias")) {
372 d
->alias
= pa_xstrdup(value
);
373 } else if (pa_streq(key
, "Address")) {
374 if (is_property_change
) {
375 pa_log("Device property 'Address' expected to be constant but changed for %s", d
->path
);
380 pa_log("Device %s: Received a duplicate Address property.", d
->path
);
384 d
->address
= pa_xstrdup(value
);
387 /* pa_log_debug("Value %s", value); */
392 case DBUS_TYPE_BOOLEAN
: {
395 dbus_message_iter_get_basic(&variant_i
, &value
);
397 if (pa_streq(key
, "Paired"))
399 else if (pa_streq(key
, "Trusted"))
400 d
->trusted
= !!value
;
402 /* pa_log_debug("Value %s", pa_yes_no(value)); */
407 case DBUS_TYPE_UINT32
: {
410 dbus_message_iter_get_basic(&variant_i
, &value
);
412 if (pa_streq(key
, "Class"))
413 d
->class = (int) value
;
415 /* pa_log_debug("Value %u", (unsigned) value); */
420 case DBUS_TYPE_ARRAY
: {
423 dbus_message_iter_recurse(&variant_i
, &ai
);
425 if (dbus_message_iter_get_arg_type(&ai
) == DBUS_TYPE_STRING
&& pa_streq(key
, "UUIDs")) {
427 bool has_audio
= false;
429 while (dbus_message_iter_get_arg_type(&ai
) != DBUS_TYPE_INVALID
) {
430 pa_bluetooth_uuid
*node
;
432 struct pa_bluetooth_hook_uuid_data uuiddata
;
434 dbus_message_iter_get_basic(&ai
, &value
);
436 if (pa_bluetooth_uuid_has(d
->uuids
, value
)) {
437 dbus_message_iter_next(&ai
);
441 node
= uuid_new(value
);
442 PA_LLIST_PREPEND(pa_bluetooth_uuid
, d
->uuids
, node
);
445 uuiddata
.uuid
= value
;
446 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED
], &uuiddata
);
448 /* Vudentz said the interfaces are here when the UUIDs are announced */
449 if (strcasecmp(HSP_AG_UUID
, value
) == 0 || strcasecmp(HFP_AG_UUID
, value
) == 0) {
450 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.HandsfreeGateway",
452 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
454 } else if (strcasecmp(HSP_HS_UUID
, value
) == 0 || strcasecmp(HFP_HS_UUID
, value
) == 0) {
455 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.Headset",
457 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
459 } else if (strcasecmp(A2DP_SINK_UUID
, value
) == 0) {
460 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.AudioSink",
462 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
464 } else if (strcasecmp(A2DP_SOURCE_UUID
, value
) == 0) {
465 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.AudioSource",
467 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
471 dbus_message_iter_next(&ai
);
474 /* this might eventually be racy if .Audio is not there yet, but
475 the State change will come anyway later, so this call is for
476 cold-detection mostly */
478 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.Audio", "GetProperties"));
479 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
490 static const char *transport_state_to_string(pa_bluetooth_transport_state_t state
) {
492 case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
:
493 return "disconnected";
494 case PA_BLUETOOTH_TRANSPORT_STATE_IDLE
:
496 case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
:
500 pa_assert_not_reached();
503 static int parse_audio_property(pa_bluetooth_device
*d
, const char *interface
, DBusMessageIter
*i
, bool is_property_change
) {
504 pa_bluetooth_transport
*transport
;
506 DBusMessageIter variant_i
;
507 bool is_audio_interface
;
508 enum profile p
= PROFILE_OFF
;
511 pa_assert(interface
);
514 if (!(is_audio_interface
= pa_streq(interface
, "org.bluez.Audio")))
515 if (profile_from_interface(interface
, &p
) < 0)
516 return 0; /* Interface not known so silently ignore property */
518 key
= check_variant_property(i
);
522 transport
= p
== PROFILE_OFF
? NULL
: d
->transports
[p
];
524 dbus_message_iter_recurse(i
, &variant_i
);
526 /* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
528 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
530 case DBUS_TYPE_STRING
: {
533 dbus_message_iter_get_basic(&variant_i
, &value
);
535 if (pa_streq(key
, "State")) {
536 pa_bt_audio_state_t state
= audio_state_from_string(value
);
537 pa_bluetooth_transport_state_t old_state
;
539 pa_log_debug("Device %s interface %s property 'State' changed to value '%s'", d
->path
, interface
, value
);
541 if (state
== PA_BT_AUDIO_STATE_INVALID
)
544 if (is_audio_interface
) {
545 d
->audio_state
= state
;
549 pa_assert(p
!= PROFILE_OFF
);
551 d
->profile_state
[p
] = state
;
556 old_state
= transport
->state
;
557 transport
->state
= audio_state_to_transport_state(state
);
559 if (transport
->state
!= old_state
) {
560 pa_log_debug("Transport %s (profile %s) changed state from %s to %s.", transport
->path
,
561 pa_bt_profile_to_string(transport
->profile
), transport_state_to_string(old_state
),
562 transport_state_to_string(transport
->state
));
564 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED
], transport
);
571 case DBUS_TYPE_UINT16
: {
574 dbus_message_iter_get_basic(&variant_i
, &value
);
576 if (pa_streq(key
, "MicrophoneGain")) {
579 pa_log_debug("dbus: property '%s' changed to value '%u'", key
, value
);
582 pa_log("Volume change does not have an associated transport");
586 if ((gain
= PA_MIN(value
, HSP_MAX_GAIN
)) == transport
->microphone_gain
)
589 transport
->microphone_gain
= gain
;
590 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED
], transport
);
591 } else if (pa_streq(key
, "SpeakerGain")) {
594 pa_log_debug("dbus: property '%s' changed to value '%u'", key
, value
);
597 pa_log("Volume change does not have an associated transport");
601 if ((gain
= PA_MIN(value
, HSP_MAX_GAIN
)) == transport
->speaker_gain
)
604 transport
->speaker_gain
= gain
;
605 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED
], transport
);
615 static void run_callback(pa_bluetooth_device
*d
, bool dead
) {
618 if (d
->device_info_valid
!= 1)
622 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED
], d
);
625 static void remove_all_devices(pa_bluetooth_discovery
*y
) {
626 pa_bluetooth_device
*d
;
630 while ((d
= pa_hashmap_steal_first(y
->devices
))) {
631 run_callback(d
, true);
636 static pa_bluetooth_device
*found_device(pa_bluetooth_discovery
*y
, const char* path
) {
638 pa_bluetooth_device
*d
;
643 d
= pa_hashmap_get(y
->devices
, path
);
647 d
= device_new(y
, path
);
649 pa_hashmap_put(y
->devices
, d
->path
, d
);
651 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Device", "GetProperties"));
652 send_and_add_to_pending(y
, m
, get_properties_reply
, d
);
654 /* Before we read the other properties (Audio, AudioSink, AudioSource,
655 * Headset) we wait that the UUID is read */
659 static void get_properties_reply(DBusPendingCall
*pending
, void *userdata
) {
661 DBusMessageIter arg_i
, element_i
;
663 pa_bluetooth_device
*d
;
664 pa_bluetooth_discovery
*y
;
666 bool old_any_connected
;
668 pa_assert_se(p
= userdata
);
669 pa_assert_se(y
= p
->context_data
);
670 pa_assert_se(r
= dbus_pending_call_steal_reply(pending
));
672 /* pa_log_debug("Got %s.GetProperties response for %s", */
673 /* dbus_message_get_interface(p->message), */
674 /* dbus_message_get_path(p->message)); */
676 /* We don't use p->call_data here right-away since the device
677 * might already be invalidated at this point */
679 if (dbus_message_has_interface(p
->message
, "org.bluez.Manager") ||
680 dbus_message_has_interface(p
->message
, "org.bluez.Adapter"))
682 else if (!(d
= pa_hashmap_get(y
->devices
, dbus_message_get_path(p
->message
)))) {
683 pa_log_warn("Received GetProperties() reply from unknown device: %s (device removed?)", dbus_message_get_path(p
->message
));
687 pa_assert(p
->call_data
== d
);
690 old_any_connected
= pa_bluetooth_device_any_audio_connected(d
);
692 valid
= dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
? -1 : 1;
694 if (dbus_message_is_method_call(p
->message
, "org.bluez.Device", "GetProperties"))
695 d
->device_info_valid
= valid
;
697 if (dbus_message_is_error(r
, DBUS_ERROR_SERVICE_UNKNOWN
)) {
698 pa_log_debug("Bluetooth daemon is apparently not available.");
699 remove_all_devices(y
);
703 if (dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
) {
704 pa_log("%s.GetProperties() failed: %s: %s", dbus_message_get_interface(p
->message
), dbus_message_get_error_name(r
),
705 pa_dbus_get_error_message(r
));
709 if (!dbus_message_iter_init(r
, &arg_i
)) {
710 pa_log("GetProperties reply has no arguments.");
714 if (dbus_message_iter_get_arg_type(&arg_i
) != DBUS_TYPE_ARRAY
) {
715 pa_log("GetProperties argument is not an array.");
719 dbus_message_iter_recurse(&arg_i
, &element_i
);
720 while (dbus_message_iter_get_arg_type(&element_i
) != DBUS_TYPE_INVALID
) {
722 if (dbus_message_iter_get_arg_type(&element_i
) == DBUS_TYPE_DICT_ENTRY
) {
723 DBusMessageIter dict_i
;
725 dbus_message_iter_recurse(&element_i
, &dict_i
);
727 if (dbus_message_has_interface(p
->message
, "org.bluez.Manager")) {
728 if (parse_manager_property(y
, &dict_i
, false) < 0)
731 } else if (dbus_message_has_interface(p
->message
, "org.bluez.Adapter")) {
732 if (parse_adapter_property(y
, &dict_i
, false) < 0)
735 } else if (dbus_message_has_interface(p
->message
, "org.bluez.Device")) {
736 if (parse_device_property(d
, &dict_i
, false) < 0)
739 } else if (parse_audio_property(d
, dbus_message_get_interface(p
->message
), &dict_i
, false) < 0)
744 dbus_message_iter_next(&element_i
);
748 if (d
!= NULL
&& old_any_connected
!= pa_bluetooth_device_any_audio_connected(d
))
749 run_callback(d
, false);
752 dbus_message_unref(r
);
754 PA_LLIST_REMOVE(pa_dbus_pending
, y
->pending
, p
);
755 pa_dbus_pending_free(p
);
758 static pa_dbus_pending
* send_and_add_to_pending(pa_bluetooth_discovery
*y
, DBusMessage
*m
, DBusPendingCallNotifyFunction func
,
761 DBusPendingCall
*call
;
766 pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y
->connection
), m
, &call
, -1));
768 p
= pa_dbus_pending_new(pa_dbus_connection_get(y
->connection
), m
, call
, y
, call_data
);
769 PA_LLIST_PREPEND(pa_dbus_pending
, y
->pending
, p
);
770 dbus_pending_call_set_notify(call
, func
, p
, NULL
);
775 static void register_endpoint_reply(DBusPendingCall
*pending
, void *userdata
) {
778 pa_bluetooth_discovery
*y
;
782 pa_assert_se(p
= userdata
);
783 pa_assert_se(y
= p
->context_data
);
784 pa_assert_se(endpoint
= p
->call_data
);
785 pa_assert_se(r
= dbus_pending_call_steal_reply(pending
));
787 if (dbus_message_is_error(r
, DBUS_ERROR_SERVICE_UNKNOWN
)) {
788 pa_log_debug("Bluetooth daemon is apparently not available.");
789 remove_all_devices(y
);
793 if (dbus_message_is_error(r
, PA_BLUETOOTH_ERROR_NOT_SUPPORTED
)) {
794 pa_log_info("Couldn't register endpoint %s, because BlueZ is configured to disable the endpoint type.", endpoint
);
798 if (dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
) {
799 pa_log("org.bluez.Media.RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r
),
800 pa_dbus_get_error_message(r
));
805 dbus_message_unref(r
);
807 PA_LLIST_REMOVE(pa_dbus_pending
, y
->pending
, p
);
808 pa_dbus_pending_free(p
);
813 static void register_endpoint(pa_bluetooth_discovery
*y
, const char *path
, const char *endpoint
, const char *uuid
) {
815 DBusMessageIter i
, d
;
818 pa_log_debug("Registering %s on adapter %s.", endpoint
, path
);
820 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Media", "RegisterEndpoint"));
822 dbus_message_iter_init_append(m
, &i
);
824 dbus_message_iter_append_basic(&i
, DBUS_TYPE_OBJECT_PATH
, &endpoint
);
826 dbus_message_iter_open_container(&i
, DBUS_TYPE_ARRAY
, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
827 DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING
,
830 pa_dbus_append_basic_variant_dict_entry(&d
, "UUID", DBUS_TYPE_STRING
, &uuid
);
832 pa_dbus_append_basic_variant_dict_entry(&d
, "Codec", DBUS_TYPE_BYTE
, &codec
);
834 if (pa_streq(uuid
, HFP_AG_UUID
) || pa_streq(uuid
, HFP_HS_UUID
)) {
835 uint8_t capability
= 0;
836 pa_dbus_append_basic_array_variant_dict_entry(&d
, "Capabilities", DBUS_TYPE_BYTE
, &capability
, 1);
838 a2dp_sbc_t capabilities
;
840 capabilities
.channel_mode
= SBC_CHANNEL_MODE_MONO
| SBC_CHANNEL_MODE_DUAL_CHANNEL
|
841 SBC_CHANNEL_MODE_STEREO
| SBC_CHANNEL_MODE_JOINT_STEREO
;
842 capabilities
.frequency
= SBC_SAMPLING_FREQ_16000
| SBC_SAMPLING_FREQ_32000
|
843 SBC_SAMPLING_FREQ_44100
| SBC_SAMPLING_FREQ_48000
;
844 capabilities
.allocation_method
= SBC_ALLOCATION_SNR
| SBC_ALLOCATION_LOUDNESS
;
845 capabilities
.subbands
= SBC_SUBBANDS_4
| SBC_SUBBANDS_8
;
846 capabilities
.block_length
= SBC_BLOCK_LENGTH_4
| SBC_BLOCK_LENGTH_8
|
847 SBC_BLOCK_LENGTH_12
| SBC_BLOCK_LENGTH_16
;
848 capabilities
.min_bitpool
= MIN_BITPOOL
;
849 capabilities
.max_bitpool
= MAX_BITPOOL
;
851 pa_dbus_append_basic_array_variant_dict_entry(&d
, "Capabilities", DBUS_TYPE_BYTE
, &capabilities
, sizeof(capabilities
));
854 dbus_message_iter_close_container(&i
, &d
);
856 send_and_add_to_pending(y
, m
, register_endpoint_reply
, pa_xstrdup(endpoint
));
859 static void found_adapter(pa_bluetooth_discovery
*y
, const char *path
) {
862 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Adapter", "GetProperties"));
863 send_and_add_to_pending(y
, m
, get_properties_reply
, NULL
);
865 register_endpoint(y
, path
, HFP_AG_ENDPOINT
, HFP_AG_UUID
);
866 register_endpoint(y
, path
, HFP_HS_ENDPOINT
, HFP_HS_UUID
);
867 register_endpoint(y
, path
, A2DP_SOURCE_ENDPOINT
, A2DP_SOURCE_UUID
);
868 register_endpoint(y
, path
, A2DP_SINK_ENDPOINT
, A2DP_SINK_UUID
);
871 static void list_adapters(pa_bluetooth_discovery
*y
) {
875 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "GetProperties"));
876 send_and_add_to_pending(y
, m
, get_properties_reply
, NULL
);
879 static int transport_parse_property(pa_bluetooth_transport
*t
, DBusMessageIter
*i
) {
881 DBusMessageIter variant_i
;
883 key
= check_variant_property(i
);
887 dbus_message_iter_recurse(i
, &variant_i
);
889 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
891 case DBUS_TYPE_BOOLEAN
: {
894 dbus_message_iter_get_basic(&variant_i
, &value
);
896 if (pa_streq(key
, "NREC") && t
->nrec
!= value
) {
898 pa_log_debug("Transport %s: Property 'NREC' changed to %s.", t
->path
, t
->nrec
? "True" : "False");
899 pa_hook_fire(&t
->device
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED
], t
);
909 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*m
, void *userdata
) {
911 pa_bluetooth_discovery
*y
;
916 pa_assert_se(y
= userdata
);
918 dbus_error_init(&err
);
920 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
921 dbus_message_get_interface(m
),
922 dbus_message_get_path(m
),
923 dbus_message_get_member(m
));
925 if (dbus_message_is_signal(m
, "org.bluez.Adapter", "DeviceRemoved")) {
927 pa_bluetooth_device
*d
;
929 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
930 pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err
.message
);
934 pa_log_debug("Device %s removed", path
);
936 if ((d
= pa_hashmap_remove(y
->devices
, path
))) {
937 run_callback(d
, true);
941 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
943 } else if (dbus_message_is_signal(m
, "org.bluez.Adapter", "DeviceCreated")) {
946 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
947 pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err
.message
);
951 pa_log_debug("Device %s created", path
);
953 found_device(y
, path
);
954 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
956 } else if (dbus_message_is_signal(m
, "org.bluez.Manager", "AdapterAdded")) {
959 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
960 pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err
.message
);
964 if (!y
->adapters_listed
) {
965 pa_log_debug("Ignoring 'AdapterAdded' because initial adapter list has not been received yet.");
966 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
969 pa_log_debug("Adapter %s created", path
);
971 found_adapter(y
, path
);
972 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
974 } else if (dbus_message_is_signal(m
, "org.bluez.Audio", "PropertyChanged") ||
975 dbus_message_is_signal(m
, "org.bluez.Headset", "PropertyChanged") ||
976 dbus_message_is_signal(m
, "org.bluez.AudioSink", "PropertyChanged") ||
977 dbus_message_is_signal(m
, "org.bluez.AudioSource", "PropertyChanged") ||
978 dbus_message_is_signal(m
, "org.bluez.HandsfreeGateway", "PropertyChanged") ||
979 dbus_message_is_signal(m
, "org.bluez.Device", "PropertyChanged")) {
981 pa_bluetooth_device
*d
;
983 if ((d
= pa_hashmap_get(y
->devices
, dbus_message_get_path(m
)))) {
984 DBusMessageIter arg_i
;
985 bool old_any_connected
= pa_bluetooth_device_any_audio_connected(d
);
987 if (!dbus_message_iter_init(m
, &arg_i
)) {
988 pa_log("Failed to parse PropertyChanged for device %s", d
->path
);
992 if (dbus_message_has_interface(m
, "org.bluez.Device")) {
993 if (parse_device_property(d
, &arg_i
, true) < 0)
996 } else if (parse_audio_property(d
, dbus_message_get_interface(m
), &arg_i
, true) < 0)
999 if (old_any_connected
!= pa_bluetooth_device_any_audio_connected(d
))
1000 run_callback(d
, false);
1003 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1005 } else if (dbus_message_is_signal(m
, "org.freedesktop.DBus", "NameOwnerChanged")) {
1006 const char *name
, *old_owner
, *new_owner
;
1008 if (!dbus_message_get_args(m
, &err
,
1009 DBUS_TYPE_STRING
, &name
,
1010 DBUS_TYPE_STRING
, &old_owner
,
1011 DBUS_TYPE_STRING
, &new_owner
,
1012 DBUS_TYPE_INVALID
)) {
1013 pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err
.message
);
1017 if (pa_streq(name
, "org.bluez")) {
1018 if (old_owner
&& *old_owner
) {
1019 pa_log_debug("Bluetooth daemon disappeared.");
1020 remove_all_devices(y
);
1021 y
->adapters_listed
= false;
1024 if (new_owner
&& *new_owner
) {
1025 pa_log_debug("Bluetooth daemon appeared.");
1030 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1031 } else if (dbus_message_is_signal(m
, "org.bluez.MediaTransport", "PropertyChanged")) {
1032 pa_bluetooth_transport
*t
;
1033 DBusMessageIter arg_i
;
1035 if (!(t
= pa_hashmap_get(y
->transports
, dbus_message_get_path(m
))))
1038 if (!dbus_message_iter_init(m
, &arg_i
)) {
1039 pa_log("Failed to parse PropertyChanged for transport %s", t
->path
);
1043 if (transport_parse_property(t
, &arg_i
) < 0)
1046 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1050 dbus_error_free(&err
);
1052 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1055 pa_bluetooth_device
* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery
*y
, const char* address
) {
1056 pa_bluetooth_device
*d
;
1060 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1063 while ((d
= pa_hashmap_iterate(y
->devices
, &state
, NULL
)))
1064 if (pa_streq(d
->address
, address
))
1065 return d
->device_info_valid
== 1 ? d
: NULL
;
1070 pa_bluetooth_device
* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery
*y
, const char* path
) {
1071 pa_bluetooth_device
*d
;
1074 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1077 if ((d
= pa_hashmap_get(y
->devices
, path
)))
1078 if (d
->device_info_valid
== 1)
1084 bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device
*d
) {
1089 if (d
->dead
|| d
->device_info_valid
!= 1)
1092 if (d
->audio_state
== PA_BT_AUDIO_STATE_INVALID
)
1095 /* Make sure audio_state is *not* in CONNECTING state before we fire the
1096 * hook to report the new device state. This is actually very important in
1097 * order to make module-card-restore work well with headsets: if the headset
1098 * supports both HSP and A2DP, one of those profiles is connected first and
1099 * then the other, and lastly the Audio interface becomes connected.
1100 * Checking only audio_state means that this function will return false at
1101 * the time when only the first connection has been made. This is good,
1102 * because otherwise, if the first connection is for HSP and we would
1103 * already load a new device module instance, and module-card-restore tries
1104 * to restore the A2DP profile, that would fail because A2DP is not yet
1105 * connected. Waiting until the Audio interface gets connected means that
1106 * both headset profiles will be connected when the device module is
1108 if (d
->audio_state
== PA_BT_AUDIO_STATE_CONNECTING
)
1111 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++)
1112 if (d
->transports
[i
] && d
->transports
[i
]->state
!= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
)
1118 int pa_bluetooth_transport_acquire(pa_bluetooth_transport
*t
, bool optional
, size_t *imtu
, size_t *omtu
) {
1119 const char *accesstype
= "rw";
1126 pa_assert(t
->device
);
1127 pa_assert(t
->device
->discovery
);
1130 /* FIXME: we are trying to acquire the transport only if the stream is
1131 playing, without actually initiating the stream request from our side
1132 (which is typically undesireable specially for hfgw use-cases.
1133 However this approach is racy, since the stream could have been
1134 suspended in the meantime, so we can't really guarantee that the
1135 stream will not be requested until BlueZ's API supports this
1137 if (t
->state
< PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
) {
1138 pa_log_info("Failed optional acquire of transport %s", t
->path
);
1143 dbus_error_init(&err
);
1145 pa_assert_se(m
= dbus_message_new_method_call(t
->owner
, t
->path
, "org.bluez.MediaTransport", "Acquire"));
1146 pa_assert_se(dbus_message_append_args(m
, DBUS_TYPE_STRING
, &accesstype
, DBUS_TYPE_INVALID
));
1147 r
= dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t
->device
->discovery
->connection
), m
, -1, &err
);
1150 dbus_error_free(&err
);
1154 if (!dbus_message_get_args(r
, &err
, DBUS_TYPE_UNIX_FD
, &ret
, DBUS_TYPE_UINT16
, &i
, DBUS_TYPE_UINT16
, &o
,
1155 DBUS_TYPE_INVALID
)) {
1156 pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err
.message
);
1158 dbus_error_free(&err
);
1169 dbus_message_unref(r
);
1173 void pa_bluetooth_transport_release(pa_bluetooth_transport
*t
) {
1174 const char *accesstype
= "rw";
1179 pa_assert(t
->device
);
1180 pa_assert(t
->device
->discovery
);
1182 dbus_error_init(&err
);
1184 pa_assert_se(m
= dbus_message_new_method_call(t
->owner
, t
->path
, "org.bluez.MediaTransport", "Release"));
1185 pa_assert_se(dbus_message_append_args(m
, DBUS_TYPE_STRING
, &accesstype
, DBUS_TYPE_INVALID
));
1186 dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t
->device
->discovery
->connection
), m
, -1, &err
);
1188 if (dbus_error_is_set(&err
)) {
1189 pa_log("Failed to release transport %s: %s", t
->path
, err
.message
);
1190 dbus_error_free(&err
);
1192 pa_log_info("Transport %s released", t
->path
);
1195 static void set_property(pa_bluetooth_discovery
*y
, const char *bus
, const char *path
, const char *interface
,
1196 const char *prop_name
, int prop_type
, void *prop_value
) {
1202 pa_assert(interface
);
1203 pa_assert(prop_name
);
1205 pa_assert_se(m
= dbus_message_new_method_call(bus
, path
, interface
, "SetProperty"));
1206 dbus_message_iter_init_append(m
, &i
);
1207 dbus_message_iter_append_basic(&i
, DBUS_TYPE_STRING
, &prop_name
);
1208 pa_dbus_append_basic_variant(&i
, prop_type
, prop_value
);
1210 dbus_message_set_no_reply(m
, true);
1211 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y
->connection
), m
, NULL
));
1212 dbus_message_unref(m
);
1215 void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport
*t
, uint16_t value
) {
1216 dbus_uint16_t gain
= PA_MIN(value
, HSP_MAX_GAIN
);
1219 pa_assert(t
->profile
== PROFILE_HSP
);
1221 set_property(t
->device
->discovery
, "org.bluez", t
->device
->path
, "org.bluez.Headset",
1222 "MicrophoneGain", DBUS_TYPE_UINT16
, &gain
);
1225 void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport
*t
, uint16_t value
) {
1226 dbus_uint16_t gain
= PA_MIN(value
, HSP_MAX_GAIN
);
1229 pa_assert(t
->profile
== PROFILE_HSP
);
1231 set_property(t
->device
->discovery
, "org.bluez", t
->device
->path
, "org.bluez.Headset",
1232 "SpeakerGain", DBUS_TYPE_UINT16
, &gain
);
1235 static int setup_dbus(pa_bluetooth_discovery
*y
) {
1238 dbus_error_init(&err
);
1240 if (!(y
->connection
= pa_dbus_bus_get(y
->core
, DBUS_BUS_SYSTEM
, &err
))) {
1241 pa_log("Failed to get D-Bus connection: %s", err
.message
);
1242 dbus_error_free(&err
);
1249 static pa_bluetooth_transport
*transport_new(pa_bluetooth_device
*d
, const char *owner
, const char *path
, enum profile p
,
1250 const uint8_t *config
, int size
) {
1251 pa_bluetooth_transport
*t
;
1253 t
= pa_xnew0(pa_bluetooth_transport
, 1);
1255 t
->owner
= pa_xstrdup(owner
);
1256 t
->path
= pa_xstrdup(path
);
1258 t
->config_size
= size
;
1261 t
->config
= pa_xnew(uint8_t, size
);
1262 memcpy(t
->config
, config
, size
);
1265 t
->state
= audio_state_to_transport_state(d
->profile_state
[p
]);
1270 static DBusMessage
*endpoint_set_configuration(DBusConnection
*conn
, DBusMessage
*m
, void *userdata
) {
1271 pa_bluetooth_discovery
*y
= userdata
;
1272 pa_bluetooth_device
*d
;
1273 pa_bluetooth_transport
*t
;
1274 const char *sender
, *path
, *dev_path
= NULL
, *uuid
= NULL
;
1275 uint8_t *config
= NULL
;
1279 DBusMessageIter args
, props
;
1281 bool old_any_connected
;
1283 if (!dbus_message_iter_init(m
, &args
) || !pa_streq(dbus_message_get_signature(m
), "oa{sv}")) {
1284 pa_log("Invalid signature for method SetConfiguration");
1288 dbus_message_iter_get_basic(&args
, &path
);
1290 if (pa_hashmap_get(y
->transports
, path
)) {
1291 pa_log("org.bluez.MediaEndpoint.SetConfiguration: Transport %s is already configured.", path
);
1295 pa_assert_se(dbus_message_iter_next(&args
));
1297 dbus_message_iter_recurse(&args
, &props
);
1298 if (dbus_message_iter_get_arg_type(&props
) != DBUS_TYPE_DICT_ENTRY
)
1301 /* Read transport properties */
1302 while (dbus_message_iter_get_arg_type(&props
) == DBUS_TYPE_DICT_ENTRY
) {
1304 DBusMessageIter value
, entry
;
1307 dbus_message_iter_recurse(&props
, &entry
);
1308 dbus_message_iter_get_basic(&entry
, &key
);
1310 dbus_message_iter_next(&entry
);
1311 dbus_message_iter_recurse(&entry
, &value
);
1313 var
= dbus_message_iter_get_arg_type(&value
);
1315 if (strcasecmp(key
, "UUID") == 0) {
1316 if (var
!= DBUS_TYPE_STRING
)
1319 dbus_message_iter_get_basic(&value
, &uuid
);
1320 } else if (strcasecmp(key
, "Device") == 0) {
1321 if (var
!= DBUS_TYPE_OBJECT_PATH
)
1324 dbus_message_iter_get_basic(&value
, &dev_path
);
1325 } else if (strcasecmp(key
, "NREC") == 0) {
1326 dbus_bool_t tmp_boolean
;
1327 if (var
!= DBUS_TYPE_BOOLEAN
)
1330 dbus_message_iter_get_basic(&value
, &tmp_boolean
);
1332 } else if (strcasecmp(key
, "Configuration") == 0) {
1333 DBusMessageIter array
;
1334 if (var
!= DBUS_TYPE_ARRAY
)
1337 dbus_message_iter_recurse(&value
, &array
);
1338 dbus_message_iter_get_fixed_array(&array
, &config
, &size
);
1341 dbus_message_iter_next(&props
);
1344 d
= found_device(y
, dev_path
);
1348 if (dbus_message_has_path(m
, HFP_AG_ENDPOINT
))
1350 else if (dbus_message_has_path(m
, HFP_HS_ENDPOINT
))
1352 else if (dbus_message_has_path(m
, A2DP_SOURCE_ENDPOINT
))
1355 p
= PROFILE_A2DP_SOURCE
;
1357 if (d
->transports
[p
] != NULL
) {
1358 pa_log("Cannot configure transport %s because profile %d is already used", path
, p
);
1362 old_any_connected
= pa_bluetooth_device_any_audio_connected(d
);
1364 sender
= dbus_message_get_sender(m
);
1366 t
= transport_new(d
, sender
, path
, p
, config
, size
);
1370 d
->transports
[p
] = t
;
1371 pa_assert_se(pa_hashmap_put(y
->transports
, t
->path
, t
) >= 0);
1373 pa_log_debug("Transport %s profile %d available", t
->path
, t
->profile
);
1375 pa_assert_se(r
= dbus_message_new_method_return(m
));
1376 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y
->connection
), r
, NULL
));
1377 dbus_message_unref(r
);
1379 if (old_any_connected
!= pa_bluetooth_device_any_audio_connected(d
))
1380 run_callback(d
, false);
1385 pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments");
1388 pa_assert_se(r
= dbus_message_new_error(m
, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1389 "Unable to set configuration"));
1393 static DBusMessage
*endpoint_clear_configuration(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
1394 pa_bluetooth_discovery
*y
= userdata
;
1395 pa_bluetooth_transport
*t
;
1400 dbus_error_init(&e
);
1402 if (!dbus_message_get_args(m
, &e
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
1403 pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e
.message
);
1404 dbus_error_free(&e
);
1408 if ((t
= pa_hashmap_get(y
->transports
, path
))) {
1409 bool old_any_connected
= pa_bluetooth_device_any_audio_connected(t
->device
);
1411 pa_log_debug("Clearing transport %s profile %d", t
->path
, t
->profile
);
1412 t
->device
->transports
[t
->profile
] = NULL
;
1413 pa_hashmap_remove(y
->transports
, t
->path
);
1414 t
->state
= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
;
1415 pa_hook_fire(&y
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED
], t
);
1417 if (old_any_connected
!= pa_bluetooth_device_any_audio_connected(t
->device
))
1418 run_callback(t
->device
, false);
1423 pa_assert_se(r
= dbus_message_new_method_return(m
));
1428 pa_assert_se(r
= dbus_message_new_error(m
, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1429 "Unable to clear configuration"));
1433 static uint8_t a2dp_default_bitpool(uint8_t freq
, uint8_t mode
) {
1436 case SBC_SAMPLING_FREQ_16000
:
1437 case SBC_SAMPLING_FREQ_32000
:
1440 case SBC_SAMPLING_FREQ_44100
:
1443 case SBC_CHANNEL_MODE_MONO
:
1444 case SBC_CHANNEL_MODE_DUAL_CHANNEL
:
1447 case SBC_CHANNEL_MODE_STEREO
:
1448 case SBC_CHANNEL_MODE_JOINT_STEREO
:
1452 pa_log_warn("Invalid channel mode %u", mode
);
1456 case SBC_SAMPLING_FREQ_48000
:
1459 case SBC_CHANNEL_MODE_MONO
:
1460 case SBC_CHANNEL_MODE_DUAL_CHANNEL
:
1463 case SBC_CHANNEL_MODE_STEREO
:
1464 case SBC_CHANNEL_MODE_JOINT_STEREO
:
1468 pa_log_warn("Invalid channel mode %u", mode
);
1473 pa_log_warn("Invalid sampling freq %u", freq
);
1478 static DBusMessage
*endpoint_select_configuration(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
1479 pa_bluetooth_discovery
*y
= userdata
;
1480 a2dp_sbc_t
*cap
, config
;
1481 uint8_t *pconf
= (uint8_t *) &config
;
1486 static const struct {
1490 { 16000U, SBC_SAMPLING_FREQ_16000
},
1491 { 32000U, SBC_SAMPLING_FREQ_32000
},
1492 { 44100U, SBC_SAMPLING_FREQ_44100
},
1493 { 48000U, SBC_SAMPLING_FREQ_48000
}
1496 dbus_error_init(&e
);
1498 if (!dbus_message_get_args(m
, &e
, DBUS_TYPE_ARRAY
, DBUS_TYPE_BYTE
, &cap
, &size
, DBUS_TYPE_INVALID
)) {
1499 pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e
.message
);
1500 dbus_error_free(&e
);
1504 if (dbus_message_has_path(m
, HFP_AG_ENDPOINT
) || dbus_message_has_path(m
, HFP_HS_ENDPOINT
))
1507 pa_assert(size
== sizeof(config
));
1509 memset(&config
, 0, sizeof(config
));
1511 /* Find the lowest freq that is at least as high as the requested
1513 for (i
= 0; (unsigned) i
< PA_ELEMENTSOF(freq_table
); i
++)
1514 if (freq_table
[i
].rate
>= y
->core
->default_sample_spec
.rate
&& (cap
->frequency
& freq_table
[i
].cap
)) {
1515 config
.frequency
= freq_table
[i
].cap
;
1519 if ((unsigned) i
== PA_ELEMENTSOF(freq_table
)) {
1520 for (--i
; i
>= 0; i
--) {
1521 if (cap
->frequency
& freq_table
[i
].cap
) {
1522 config
.frequency
= freq_table
[i
].cap
;
1528 pa_log("Not suitable sample rate");
1533 pa_assert((unsigned) i
< PA_ELEMENTSOF(freq_table
));
1535 if (y
->core
->default_sample_spec
.channels
<= 1) {
1536 if (cap
->channel_mode
& SBC_CHANNEL_MODE_MONO
)
1537 config
.channel_mode
= SBC_CHANNEL_MODE_MONO
;
1540 if (y
->core
->default_sample_spec
.channels
>= 2) {
1541 if (cap
->channel_mode
& SBC_CHANNEL_MODE_JOINT_STEREO
)
1542 config
.channel_mode
= SBC_CHANNEL_MODE_JOINT_STEREO
;
1543 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_STEREO
)
1544 config
.channel_mode
= SBC_CHANNEL_MODE_STEREO
;
1545 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_DUAL_CHANNEL
)
1546 config
.channel_mode
= SBC_CHANNEL_MODE_DUAL_CHANNEL
;
1547 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_MONO
) {
1548 config
.channel_mode
= SBC_CHANNEL_MODE_MONO
;
1550 pa_log("No supported channel modes");
1555 if (cap
->block_length
& SBC_BLOCK_LENGTH_16
)
1556 config
.block_length
= SBC_BLOCK_LENGTH_16
;
1557 else if (cap
->block_length
& SBC_BLOCK_LENGTH_12
)
1558 config
.block_length
= SBC_BLOCK_LENGTH_12
;
1559 else if (cap
->block_length
& SBC_BLOCK_LENGTH_8
)
1560 config
.block_length
= SBC_BLOCK_LENGTH_8
;
1561 else if (cap
->block_length
& SBC_BLOCK_LENGTH_4
)
1562 config
.block_length
= SBC_BLOCK_LENGTH_4
;
1564 pa_log_error("No supported block lengths");
1568 if (cap
->subbands
& SBC_SUBBANDS_8
)
1569 config
.subbands
= SBC_SUBBANDS_8
;
1570 else if (cap
->subbands
& SBC_SUBBANDS_4
)
1571 config
.subbands
= SBC_SUBBANDS_4
;
1573 pa_log_error("No supported subbands");
1577 if (cap
->allocation_method
& SBC_ALLOCATION_LOUDNESS
)
1578 config
.allocation_method
= SBC_ALLOCATION_LOUDNESS
;
1579 else if (cap
->allocation_method
& SBC_ALLOCATION_SNR
)
1580 config
.allocation_method
= SBC_ALLOCATION_SNR
;
1582 config
.min_bitpool
= (uint8_t) PA_MAX(MIN_BITPOOL
, cap
->min_bitpool
);
1583 config
.max_bitpool
= (uint8_t) PA_MIN(a2dp_default_bitpool(config
.frequency
, config
.channel_mode
), cap
->max_bitpool
);
1586 pa_assert_se(r
= dbus_message_new_method_return(m
));
1588 pa_assert_se(dbus_message_append_args(
1590 DBUS_TYPE_ARRAY
, DBUS_TYPE_BYTE
, &pconf
, size
,
1591 DBUS_TYPE_INVALID
));
1596 pa_assert_se(r
= dbus_message_new_error(m
, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1597 "Unable to select configuration"));
1601 static DBusHandlerResult
endpoint_handler(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
1602 struct pa_bluetooth_discovery
*y
= userdata
;
1603 DBusMessage
*r
= NULL
;
1605 const char *path
, *interface
, *member
;
1609 path
= dbus_message_get_path(m
);
1610 interface
= dbus_message_get_interface(m
);
1611 member
= dbus_message_get_member(m
);
1613 pa_log_debug("dbus: path=%s, interface=%s, member=%s", path
, interface
, member
);
1615 dbus_error_init(&e
);
1617 if (!pa_streq(path
, A2DP_SOURCE_ENDPOINT
) && !pa_streq(path
, A2DP_SINK_ENDPOINT
) && !pa_streq(path
, HFP_AG_ENDPOINT
) &&
1618 !pa_streq(path
, HFP_HS_ENDPOINT
))
1619 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1621 if (dbus_message_is_method_call(m
, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1622 const char *xml
= ENDPOINT_INTROSPECT_XML
;
1624 pa_assert_se(r
= dbus_message_new_method_return(m
));
1625 pa_assert_se(dbus_message_append_args(r
, DBUS_TYPE_STRING
, &xml
, DBUS_TYPE_INVALID
));
1627 } else if (dbus_message_is_method_call(m
, "org.bluez.MediaEndpoint", "SetConfiguration"))
1628 r
= endpoint_set_configuration(c
, m
, userdata
);
1629 else if (dbus_message_is_method_call(m
, "org.bluez.MediaEndpoint", "SelectConfiguration"))
1630 r
= endpoint_select_configuration(c
, m
, userdata
);
1631 else if (dbus_message_is_method_call(m
, "org.bluez.MediaEndpoint", "ClearConfiguration"))
1632 r
= endpoint_clear_configuration(c
, m
, userdata
);
1634 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1637 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y
->connection
), r
, NULL
));
1638 dbus_message_unref(r
);
1641 return DBUS_HANDLER_RESULT_HANDLED
;
1644 pa_bluetooth_discovery
* pa_bluetooth_discovery_get(pa_core
*c
) {
1646 pa_bluetooth_discovery
*y
;
1647 DBusConnection
*conn
;
1649 static const DBusObjectPathVTable vtable_endpoint
= {
1650 .message_function
= endpoint_handler
,
1655 dbus_error_init(&err
);
1657 if ((y
= pa_shared_get(c
, "bluetooth-discovery")))
1658 return pa_bluetooth_discovery_ref(y
);
1660 y
= pa_xnew0(pa_bluetooth_discovery
, 1);
1663 y
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
1664 y
->transports
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
1665 PA_LLIST_HEAD_INIT(pa_dbus_pending
, y
->pending
);
1667 for (i
= 0; i
< PA_BLUETOOTH_HOOK_MAX
; i
++)
1668 pa_hook_init(&y
->hooks
[i
], y
);
1670 pa_shared_set(c
, "bluetooth-discovery", y
);
1672 if (setup_dbus(y
) < 0)
1675 conn
= pa_dbus_connection_get(y
->connection
);
1677 /* dynamic detection of bluetooth audio devices */
1678 if (!dbus_connection_add_filter(conn
, filter_cb
, y
, NULL
)) {
1679 pa_log_error("Failed to add filter function");
1683 y
->filter_added
= true;
1685 if (pa_dbus_add_matches(
1687 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
1688 ",arg0='org.bluez'",
1689 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1690 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1691 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1692 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1693 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1694 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1695 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1696 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1697 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1698 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1700 pa_log("Failed to add D-Bus matches: %s", err
.message
);
1704 pa_assert_se(dbus_connection_register_object_path(conn
, HFP_AG_ENDPOINT
, &vtable_endpoint
, y
));
1705 pa_assert_se(dbus_connection_register_object_path(conn
, HFP_HS_ENDPOINT
, &vtable_endpoint
, y
));
1706 pa_assert_se(dbus_connection_register_object_path(conn
, A2DP_SOURCE_ENDPOINT
, &vtable_endpoint
, y
));
1707 pa_assert_se(dbus_connection_register_object_path(conn
, A2DP_SINK_ENDPOINT
, &vtable_endpoint
, y
));
1715 pa_bluetooth_discovery_unref(y
);
1717 dbus_error_free(&err
);
1722 pa_bluetooth_discovery
* pa_bluetooth_discovery_ref(pa_bluetooth_discovery
*y
) {
1724 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1731 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery
*y
) {
1735 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1737 if (PA_REFCNT_DEC(y
) > 0)
1740 pa_dbus_free_pending_list(&y
->pending
);
1743 remove_all_devices(y
);
1744 pa_hashmap_free(y
->devices
);
1747 if (y
->transports
) {
1748 pa_assert(pa_hashmap_isempty(y
->transports
));
1749 pa_hashmap_free(y
->transports
);
1752 if (y
->connection
) {
1753 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), HFP_AG_ENDPOINT
);
1754 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), HFP_HS_ENDPOINT
);
1755 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SOURCE_ENDPOINT
);
1756 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SINK_ENDPOINT
);
1757 pa_dbus_remove_matches(
1758 pa_dbus_connection_get(y
->connection
),
1759 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
1760 ",arg0='org.bluez'",
1761 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1762 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
1763 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1764 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1765 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1766 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1767 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1768 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1769 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1770 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1771 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1774 if (y
->filter_added
)
1775 dbus_connection_remove_filter(pa_dbus_connection_get(y
->connection
), filter_cb
, y
);
1777 pa_dbus_connection_unref(y
->connection
);
1780 for (i
= 0; i
< PA_BLUETOOTH_HOOK_MAX
; i
++)
1781 pa_hook_done(&y
->hooks
[i
]);
1784 pa_shared_remove(y
->core
, "bluetooth-discovery");
1789 pa_hook
* pa_bluetooth_discovery_hook(pa_bluetooth_discovery
*y
, pa_bluetooth_hook_t hook
) {
1791 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1793 return &y
->hooks
[hook
];
1796 pa_bt_form_factor_t
pa_bluetooth_get_form_factor(uint32_t class) {
1797 unsigned major
, minor
;
1798 pa_bt_form_factor_t r
;
1800 static const pa_bt_form_factor_t table
[] = {
1801 [1] = PA_BT_FORM_FACTOR_HEADSET
,
1802 [2] = PA_BT_FORM_FACTOR_HANDSFREE
,
1803 [4] = PA_BT_FORM_FACTOR_MICROPHONE
,
1804 [5] = PA_BT_FORM_FACTOR_SPEAKER
,
1805 [6] = PA_BT_FORM_FACTOR_HEADPHONE
,
1806 [7] = PA_BT_FORM_FACTOR_PORTABLE
,
1807 [8] = PA_BT_FORM_FACTOR_CAR
,
1808 [10] = PA_BT_FORM_FACTOR_HIFI
1812 * See Bluetooth Assigned Numbers:
1813 * https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
1815 major
= (class >> 8) & 0x1F;
1816 minor
= (class >> 2) & 0x3F;
1820 return PA_BT_FORM_FACTOR_PHONE
;
1824 pa_log_debug("Unknown Bluetooth major device class %u", major
);
1825 return PA_BT_FORM_FACTOR_UNKNOWN
;
1828 r
= minor
< PA_ELEMENTSOF(table
) ? table
[minor
] : PA_BT_FORM_FACTOR_UNKNOWN
;
1831 pa_log_debug("Unknown Bluetooth minor device class %u", minor
);
1836 const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff
) {
1838 case PA_BT_FORM_FACTOR_UNKNOWN
:
1840 case PA_BT_FORM_FACTOR_HEADSET
:
1842 case PA_BT_FORM_FACTOR_HANDSFREE
:
1843 return "hands-free";
1844 case PA_BT_FORM_FACTOR_MICROPHONE
:
1845 return "microphone";
1846 case PA_BT_FORM_FACTOR_SPEAKER
:
1848 case PA_BT_FORM_FACTOR_HEADPHONE
:
1850 case PA_BT_FORM_FACTOR_PORTABLE
:
1852 case PA_BT_FORM_FACTOR_CAR
:
1854 case PA_BT_FORM_FACTOR_HIFI
:
1856 case PA_BT_FORM_FACTOR_PHONE
:
1860 pa_assert_not_reached();
1863 char *pa_bluetooth_cleanup_name(const char *name
) {
1869 while ((*name
>= 1 && *name
<= 32) || *name
>= 127)
1872 t
= pa_xstrdup(name
);
1874 for (s
= d
= t
; *s
; s
++) {
1876 if (*s
<= 32 || *s
>= 127 || *s
== '_') {
1894 bool pa_bluetooth_uuid_has(pa_bluetooth_uuid
*uuids
, const char *uuid
) {
1898 if (strcasecmp(uuids
->uuid
, uuid
) == 0)
1901 uuids
= uuids
->next
;