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 struct pa_bluetooth_discovery
{
68 pa_dbus_connection
*connection
;
69 PA_LLIST_HEAD(pa_dbus_pending
, pending
);
71 pa_hashmap
*transports
;
72 pa_hook hooks
[PA_BLUETOOTH_HOOK_MAX
];
73 pa_bool_t filter_added
;
76 static void get_properties_reply(DBusPendingCall
*pending
, void *userdata
);
77 static pa_dbus_pending
* send_and_add_to_pending(pa_bluetooth_discovery
*y
, DBusMessage
*m
, DBusPendingCallNotifyFunction func
, void *call_data
);
78 static void found_adapter(pa_bluetooth_discovery
*y
, const char *path
);
79 static pa_bluetooth_device
*found_device(pa_bluetooth_discovery
*y
, const char* path
);
81 static pa_bt_audio_state_t
audio_state_from_string(const char* value
) {
84 if (pa_streq(value
, "disconnected"))
85 return PA_BT_AUDIO_STATE_DISCONNECTED
;
86 else if (pa_streq(value
, "connecting"))
87 return PA_BT_AUDIO_STATE_CONNECTING
;
88 else if (pa_streq(value
, "connected"))
89 return PA_BT_AUDIO_STATE_CONNECTED
;
90 else if (pa_streq(value
, "playing"))
91 return PA_BT_AUDIO_STATE_PLAYING
;
93 return PA_BT_AUDIO_STATE_INVALID
;
96 const char *pa_bt_profile_to_string(enum profile profile
) {
100 case PROFILE_A2DP_SOURCE
:
101 return "a2dp_source";
107 pa_assert_not_reached();
110 pa_assert_not_reached();
113 static int profile_from_interface(const char *interface
, enum profile
*p
) {
114 pa_assert(interface
);
117 if (pa_streq(interface
, "org.bluez.AudioSink")) {
120 } else if (pa_streq(interface
, "org.bluez.AudioSource")) {
121 *p
= PROFILE_A2DP_SOURCE
;
123 } else if (pa_streq(interface
, "org.bluez.Headset")) {
126 } else if (pa_streq(interface
, "org.bluez.HandsfreeGateway")) {
134 static pa_bluetooth_transport_state_t
audio_state_to_transport_state(pa_bt_audio_state_t state
) {
136 case PA_BT_AUDIO_STATE_INVALID
: /* Typically if state hasn't been received yet */
137 case PA_BT_AUDIO_STATE_DISCONNECTED
:
138 case PA_BT_AUDIO_STATE_CONNECTING
:
139 return PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
;
140 case PA_BT_AUDIO_STATE_CONNECTED
:
141 return PA_BLUETOOTH_TRANSPORT_STATE_IDLE
;
142 case PA_BT_AUDIO_STATE_PLAYING
:
143 return PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
;
146 pa_assert_not_reached();
149 static pa_bluetooth_uuid
*uuid_new(const char *uuid
) {
150 pa_bluetooth_uuid
*u
;
152 u
= pa_xnew(pa_bluetooth_uuid
, 1);
153 u
->uuid
= pa_xstrdup(uuid
);
154 PA_LLIST_INIT(pa_bluetooth_uuid
, u
);
159 static void uuid_free(pa_bluetooth_uuid
*u
) {
166 static pa_bluetooth_device
* device_new(pa_bluetooth_discovery
*discovery
, const char *path
) {
167 pa_bluetooth_device
*d
;
170 pa_assert(discovery
);
173 d
= pa_xnew0(pa_bluetooth_device
, 1);
175 d
->discovery
= discovery
;
178 d
->device_info_valid
= 0;
181 d
->path
= pa_xstrdup(path
);
184 PA_LLIST_HEAD_INIT(pa_bluetooth_uuid
, d
->uuids
);
189 d
->audio_state
= PA_BT_AUDIO_STATE_INVALID
;
191 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++)
192 d
->profile_state
[i
] = PA_BT_AUDIO_STATE_INVALID
;
197 static void transport_free(pa_bluetooth_transport
*t
) {
206 static void device_free(pa_bluetooth_device
*d
) {
207 pa_bluetooth_uuid
*u
;
208 pa_bluetooth_transport
*t
;
213 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++) {
214 if (!(t
= d
->transports
[i
]))
217 d
->transports
[i
] = NULL
;
218 pa_hashmap_remove(d
->discovery
->transports
, t
->path
);
219 t
->state
= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
;
220 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED
], t
);
224 while ((u
= d
->uuids
)) {
225 PA_LLIST_REMOVE(pa_bluetooth_uuid
, d
->uuids
, u
);
232 pa_xfree(d
->address
);
236 static pa_bool_t
device_is_audio_ready(const pa_bluetooth_device
*d
) {
241 if (!d
->device_info_valid
|| d
->audio_state
== PA_BT_AUDIO_STATE_INVALID
)
244 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++)
245 if (d
->profile_state
[i
] != PA_BT_AUDIO_STATE_INVALID
)
251 static const char *check_variant_property(DBusMessageIter
*i
) {
256 if (dbus_message_iter_get_arg_type(i
) != DBUS_TYPE_STRING
) {
257 pa_log("Property name not a string.");
261 dbus_message_iter_get_basic(i
, &key
);
263 if (!dbus_message_iter_next(i
)) {
264 pa_log("Property value missing");
268 if (dbus_message_iter_get_arg_type(i
) != DBUS_TYPE_VARIANT
) {
269 pa_log("Property value not a variant.");
276 static int parse_manager_property(pa_bluetooth_discovery
*y
, DBusMessageIter
*i
) {
278 DBusMessageIter variant_i
;
282 key
= check_variant_property(i
);
286 dbus_message_iter_recurse(i
, &variant_i
);
288 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
290 case DBUS_TYPE_ARRAY
: {
293 dbus_message_iter_recurse(&variant_i
, &ai
);
295 if (dbus_message_iter_get_arg_type(&ai
) == DBUS_TYPE_OBJECT_PATH
&&
296 pa_streq(key
, "Adapters")) {
298 while (dbus_message_iter_get_arg_type(&ai
) != DBUS_TYPE_INVALID
) {
301 dbus_message_iter_get_basic(&ai
, &value
);
303 found_adapter(y
, value
);
305 dbus_message_iter_next(&ai
);
316 static int parse_adapter_property(pa_bluetooth_discovery
*y
, DBusMessageIter
*i
) {
318 DBusMessageIter variant_i
;
322 key
= check_variant_property(i
);
326 dbus_message_iter_recurse(i
, &variant_i
);
328 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
330 case DBUS_TYPE_ARRAY
: {
333 dbus_message_iter_recurse(&variant_i
, &ai
);
335 if (dbus_message_iter_get_arg_type(&ai
) == DBUS_TYPE_OBJECT_PATH
&&
336 pa_streq(key
, "Devices")) {
338 while (dbus_message_iter_get_arg_type(&ai
) != DBUS_TYPE_INVALID
) {
341 dbus_message_iter_get_basic(&ai
, &value
);
343 found_device(y
, value
);
345 dbus_message_iter_next(&ai
);
356 static int parse_device_property(pa_bluetooth_device
*d
, DBusMessageIter
*i
) {
358 DBusMessageIter variant_i
;
362 key
= check_variant_property(i
);
366 dbus_message_iter_recurse(i
, &variant_i
);
368 /* pa_log_debug("Parsing property org.bluez.Device.%s", key); */
370 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
372 case DBUS_TYPE_STRING
: {
375 dbus_message_iter_get_basic(&variant_i
, &value
);
377 if (pa_streq(key
, "Name")) {
379 d
->name
= pa_xstrdup(value
);
380 } else if (pa_streq(key
, "Alias")) {
382 d
->alias
= pa_xstrdup(value
);
383 } else if (pa_streq(key
, "Address")) {
384 pa_xfree(d
->address
);
385 d
->address
= pa_xstrdup(value
);
388 /* pa_log_debug("Value %s", value); */
393 case DBUS_TYPE_BOOLEAN
: {
396 dbus_message_iter_get_basic(&variant_i
, &value
);
398 if (pa_streq(key
, "Paired"))
400 else if (pa_streq(key
, "Trusted"))
401 d
->trusted
= !!value
;
403 /* pa_log_debug("Value %s", pa_yes_no(value)); */
408 case DBUS_TYPE_UINT32
: {
411 dbus_message_iter_get_basic(&variant_i
, &value
);
413 if (pa_streq(key
, "Class"))
414 d
->class = (int) value
;
416 /* pa_log_debug("Value %u", (unsigned) value); */
421 case DBUS_TYPE_ARRAY
: {
424 dbus_message_iter_recurse(&variant_i
, &ai
);
426 if (dbus_message_iter_get_arg_type(&ai
) == DBUS_TYPE_STRING
&&
427 pa_streq(key
, "UUIDs")) {
429 pa_bool_t has_audio
= FALSE
;
431 while (dbus_message_iter_get_arg_type(&ai
) != DBUS_TYPE_INVALID
) {
432 pa_bluetooth_uuid
*node
;
434 struct pa_bluetooth_hook_uuid_data uuiddata
;
436 dbus_message_iter_get_basic(&ai
, &value
);
438 if (pa_bluetooth_uuid_has(d
->uuids
, value
)) {
439 dbus_message_iter_next(&ai
);
443 node
= uuid_new(value
);
444 PA_LLIST_PREPEND(pa_bluetooth_uuid
, d
->uuids
, node
);
447 uuiddata
.uuid
= value
;
448 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED
], &uuiddata
);
450 /* Vudentz said the interfaces are here when the UUIDs are announced */
451 if (strcasecmp(HSP_AG_UUID
, value
) == 0 || strcasecmp(HFP_AG_UUID
, value
) == 0) {
452 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.HandsfreeGateway", "GetProperties"));
453 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
455 } else if (strcasecmp(HSP_HS_UUID
, value
) == 0 || strcasecmp(HFP_HS_UUID
, value
) == 0) {
456 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.Headset", "GetProperties"));
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", "GetProperties"));
461 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
463 } else if (strcasecmp(A2DP_SOURCE_UUID
, value
) == 0) {
464 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.AudioSource", "GetProperties"));
465 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
469 dbus_message_iter_next(&ai
);
472 /* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */
474 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.Audio", "GetProperties"));
475 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
486 static const char *transport_state_to_string(pa_bluetooth_transport_state_t state
) {
488 case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
:
489 return "disconnected";
490 case PA_BLUETOOTH_TRANSPORT_STATE_IDLE
:
492 case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
:
496 pa_assert_not_reached();
499 static int parse_audio_property(pa_bluetooth_device
*d
, const char *interface
, DBusMessageIter
*i
) {
500 pa_bluetooth_transport
*transport
;
502 DBusMessageIter variant_i
;
503 bool is_audio_interface
;
504 enum profile p
= PROFILE_OFF
;
507 pa_assert(interface
);
510 if (!(is_audio_interface
= pa_streq(interface
, "org.bluez.Audio")))
511 if (profile_from_interface(interface
, &p
) < 0)
512 return 0; /* Interface not known so silently ignore property */
514 key
= check_variant_property(i
);
518 transport
= p
== PROFILE_OFF
? NULL
: d
->transports
[p
];
520 dbus_message_iter_recurse(i
, &variant_i
);
522 /* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
524 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
526 case DBUS_TYPE_STRING
: {
529 dbus_message_iter_get_basic(&variant_i
, &value
);
531 if (pa_streq(key
, "State")) {
532 pa_bt_audio_state_t state
= audio_state_from_string(value
);
533 pa_bluetooth_transport_state_t old_state
;
535 pa_log_debug("Device %s interface %s property 'State' changed to value '%s'", d
->path
, interface
, value
);
537 if (state
== PA_BT_AUDIO_STATE_INVALID
)
540 if (is_audio_interface
) {
541 d
->audio_state
= state
;
545 pa_assert(p
!= PROFILE_OFF
);
547 d
->profile_state
[p
] = state
;
552 old_state
= transport
->state
;
553 transport
->state
= audio_state_to_transport_state(state
);
555 if (transport
->state
!= old_state
) {
556 pa_log_debug("Transport %s (profile %s) changed state from %s to %s.", transport
->path
,
557 pa_bt_profile_to_string(transport
->profile
), transport_state_to_string(old_state
),
558 transport_state_to_string(transport
->state
));
560 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED
], transport
);
567 case DBUS_TYPE_UINT16
: {
570 dbus_message_iter_get_basic(&variant_i
, &value
);
572 if (pa_streq(key
, "MicrophoneGain")) {
575 pa_log_debug("dbus: property '%s' changed to value '%u'", key
, value
);
578 pa_log("Volume change does not have an associated transport");
582 if ((gain
= PA_MIN(value
, HSP_MAX_GAIN
)) == transport
->microphone_gain
)
585 transport
->microphone_gain
= gain
;
586 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED
], transport
);
587 } else if (pa_streq(key
, "SpeakerGain")) {
590 pa_log_debug("dbus: property '%s' changed to value '%u'", key
, value
);
593 pa_log("Volume change does not have an associated transport");
597 if ((gain
= PA_MIN(value
, HSP_MAX_GAIN
)) == transport
->speaker_gain
)
600 transport
->speaker_gain
= gain
;
601 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED
], transport
);
611 static void run_callback(pa_bluetooth_device
*d
, pa_bool_t dead
) {
614 if (!device_is_audio_ready(d
))
618 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED
], d
);
621 static void remove_all_devices(pa_bluetooth_discovery
*y
) {
622 pa_bluetooth_device
*d
;
626 while ((d
= pa_hashmap_steal_first(y
->devices
))) {
627 run_callback(d
, TRUE
);
632 static pa_bluetooth_device
*found_device(pa_bluetooth_discovery
*y
, const char* path
) {
634 pa_bluetooth_device
*d
;
639 d
= pa_hashmap_get(y
->devices
, path
);
643 d
= device_new(y
, path
);
645 pa_hashmap_put(y
->devices
, d
->path
, d
);
647 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Device", "GetProperties"));
648 send_and_add_to_pending(y
, m
, get_properties_reply
, d
);
650 /* Before we read the other properties (Audio, AudioSink, AudioSource,
651 * Headset) we wait that the UUID is read */
655 static void get_properties_reply(DBusPendingCall
*pending
, void *userdata
) {
657 DBusMessageIter arg_i
, element_i
;
659 pa_bluetooth_device
*d
;
660 pa_bluetooth_discovery
*y
;
662 bool old_any_connected
;
664 pa_assert_se(p
= userdata
);
665 pa_assert_se(y
= p
->context_data
);
666 pa_assert_se(r
= dbus_pending_call_steal_reply(pending
));
668 /* pa_log_debug("Got %s.GetProperties response for %s", */
669 /* dbus_message_get_interface(p->message), */
670 /* dbus_message_get_path(p->message)); */
672 /* We don't use p->call_data here right-away since the device
673 * might already be invalidated at this point */
675 if (dbus_message_has_interface(p
->message
, "org.bluez.Manager") ||
676 dbus_message_has_interface(p
->message
, "org.bluez.Adapter"))
679 d
= pa_hashmap_get(y
->devices
, dbus_message_get_path(p
->message
));
681 pa_assert(p
->call_data
== d
);
684 old_any_connected
= pa_bluetooth_device_any_audio_connected(d
);
686 valid
= dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
? -1 : 1;
688 if (dbus_message_is_method_call(p
->message
, "org.bluez.Device", "GetProperties"))
689 d
->device_info_valid
= valid
;
691 if (dbus_message_is_error(r
, DBUS_ERROR_SERVICE_UNKNOWN
)) {
692 pa_log_debug("Bluetooth daemon is apparently not available.");
693 remove_all_devices(y
);
697 if (dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
) {
698 pa_log("%s.GetProperties() failed: %s: %s", dbus_message_get_interface(p
->message
), dbus_message_get_error_name(r
), pa_dbus_get_error_message(r
));
702 if (!dbus_message_iter_init(r
, &arg_i
)) {
703 pa_log("GetProperties reply has no arguments.");
707 if (dbus_message_iter_get_arg_type(&arg_i
) != DBUS_TYPE_ARRAY
) {
708 pa_log("GetProperties argument is not an array.");
712 dbus_message_iter_recurse(&arg_i
, &element_i
);
713 while (dbus_message_iter_get_arg_type(&element_i
) != DBUS_TYPE_INVALID
) {
715 if (dbus_message_iter_get_arg_type(&element_i
) == DBUS_TYPE_DICT_ENTRY
) {
716 DBusMessageIter dict_i
;
718 dbus_message_iter_recurse(&element_i
, &dict_i
);
720 if (dbus_message_has_interface(p
->message
, "org.bluez.Manager")) {
721 if (parse_manager_property(y
, &dict_i
) < 0)
724 } else if (dbus_message_has_interface(p
->message
, "org.bluez.Adapter")) {
725 if (parse_adapter_property(y
, &dict_i
) < 0)
728 } else if (dbus_message_has_interface(p
->message
, "org.bluez.Device")) {
729 if (parse_device_property(d
, &dict_i
) < 0)
732 } else if (parse_audio_property(d
, dbus_message_get_interface(p
->message
), &dict_i
) < 0)
737 dbus_message_iter_next(&element_i
);
741 if (d
!= NULL
&& old_any_connected
!= pa_bluetooth_device_any_audio_connected(d
))
742 run_callback(d
, FALSE
);
745 dbus_message_unref(r
);
747 PA_LLIST_REMOVE(pa_dbus_pending
, y
->pending
, p
);
748 pa_dbus_pending_free(p
);
751 static pa_dbus_pending
* send_and_add_to_pending(pa_bluetooth_discovery
*y
, DBusMessage
*m
, DBusPendingCallNotifyFunction func
, void *call_data
) {
753 DBusPendingCall
*call
;
758 pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y
->connection
), m
, &call
, -1));
760 p
= pa_dbus_pending_new(pa_dbus_connection_get(y
->connection
), m
, call
, y
, call_data
);
761 PA_LLIST_PREPEND(pa_dbus_pending
, y
->pending
, p
);
762 dbus_pending_call_set_notify(call
, func
, p
, NULL
);
767 static void register_endpoint_reply(DBusPendingCall
*pending
, void *userdata
) {
771 pa_bluetooth_discovery
*y
;
778 pa_assert_se(p
= userdata
);
779 pa_assert_se(y
= p
->context_data
);
780 pa_assert_se(endpoint
= p
->call_data
);
781 pa_assert_se(r
= dbus_pending_call_steal_reply(pending
));
783 if (dbus_message_is_error(r
, DBUS_ERROR_SERVICE_UNKNOWN
)) {
784 pa_log_debug("Bluetooth daemon is apparently not available.");
785 remove_all_devices(y
);
789 if (dbus_message_is_error(r
, PA_BLUETOOTH_ERROR_NOT_SUPPORTED
)) {
790 pa_log_info("Couldn't register endpoint %s, because BlueZ is configured to disable the endpoint type.", endpoint
);
794 if (dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
) {
795 pa_log("org.bluez.Media.RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r
), pa_dbus_get_error_message(r
));
800 dbus_message_unref(r
);
802 PA_LLIST_REMOVE(pa_dbus_pending
, y
->pending
, p
);
803 pa_dbus_pending_free(p
);
808 static void register_endpoint(pa_bluetooth_discovery
*y
, const char *path
, const char *endpoint
, const char *uuid
) {
810 DBusMessageIter i
, d
;
813 pa_log_debug("Registering %s on adapter %s.", endpoint
, path
);
815 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Media", "RegisterEndpoint"));
817 dbus_message_iter_init_append(m
, &i
);
819 dbus_message_iter_append_basic(&i
, DBUS_TYPE_OBJECT_PATH
, &endpoint
);
821 dbus_message_iter_open_container(&i
, DBUS_TYPE_ARRAY
, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
822 DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING
,
825 pa_dbus_append_basic_variant_dict_entry(&d
, "UUID", DBUS_TYPE_STRING
, &uuid
);
827 pa_dbus_append_basic_variant_dict_entry(&d
, "Codec", DBUS_TYPE_BYTE
, &codec
);
829 if (pa_streq(uuid
, HFP_AG_UUID
) || pa_streq(uuid
, HFP_HS_UUID
)) {
830 uint8_t capability
= 0;
831 pa_dbus_append_basic_array_variant_dict_entry(&d
, "Capabilities", DBUS_TYPE_BYTE
, &capability
, 1);
833 a2dp_sbc_t capabilities
;
835 capabilities
.channel_mode
= SBC_CHANNEL_MODE_MONO
| SBC_CHANNEL_MODE_DUAL_CHANNEL
|
836 SBC_CHANNEL_MODE_STEREO
| SBC_CHANNEL_MODE_JOINT_STEREO
;
837 capabilities
.frequency
= SBC_SAMPLING_FREQ_16000
| SBC_SAMPLING_FREQ_32000
|
838 SBC_SAMPLING_FREQ_44100
| SBC_SAMPLING_FREQ_48000
;
839 capabilities
.allocation_method
= SBC_ALLOCATION_SNR
| SBC_ALLOCATION_LOUDNESS
;
840 capabilities
.subbands
= SBC_SUBBANDS_4
| SBC_SUBBANDS_8
;
841 capabilities
.block_length
= SBC_BLOCK_LENGTH_4
| SBC_BLOCK_LENGTH_8
|
842 SBC_BLOCK_LENGTH_12
| SBC_BLOCK_LENGTH_16
;
843 capabilities
.min_bitpool
= MIN_BITPOOL
;
844 capabilities
.max_bitpool
= MAX_BITPOOL
;
846 pa_dbus_append_basic_array_variant_dict_entry(&d
, "Capabilities", DBUS_TYPE_BYTE
, &capabilities
, sizeof(capabilities
));
849 dbus_message_iter_close_container(&i
, &d
);
851 send_and_add_to_pending(y
, m
, register_endpoint_reply
, pa_xstrdup(endpoint
));
854 static void found_adapter(pa_bluetooth_discovery
*y
, const char *path
) {
857 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Adapter", "GetProperties"));
858 send_and_add_to_pending(y
, m
, get_properties_reply
, NULL
);
860 register_endpoint(y
, path
, HFP_AG_ENDPOINT
, HFP_AG_UUID
);
861 register_endpoint(y
, path
, HFP_HS_ENDPOINT
, HFP_HS_UUID
);
862 register_endpoint(y
, path
, A2DP_SOURCE_ENDPOINT
, A2DP_SOURCE_UUID
);
863 register_endpoint(y
, path
, A2DP_SINK_ENDPOINT
, A2DP_SINK_UUID
);
866 static void list_adapters(pa_bluetooth_discovery
*y
) {
870 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "GetProperties"));
871 send_and_add_to_pending(y
, m
, get_properties_reply
, NULL
);
874 static int transport_parse_property(pa_bluetooth_transport
*t
, DBusMessageIter
*i
) {
876 DBusMessageIter variant_i
;
878 key
= check_variant_property(i
);
882 dbus_message_iter_recurse(i
, &variant_i
);
884 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
886 case DBUS_TYPE_BOOLEAN
: {
889 dbus_message_iter_get_basic(&variant_i
, &value
);
891 if (pa_streq(key
, "NREC") && t
->nrec
!= value
) {
893 pa_log_debug("Transport %s: Property 'NREC' changed to %s.", t
->path
, t
->nrec
? "True" : "False");
894 pa_hook_fire(&t
->device
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED
], t
);
904 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*m
, void *userdata
) {
906 pa_bluetooth_discovery
*y
;
911 pa_assert_se(y
= userdata
);
913 dbus_error_init(&err
);
915 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
916 dbus_message_get_interface(m
),
917 dbus_message_get_path(m
),
918 dbus_message_get_member(m
));
920 if (dbus_message_is_signal(m
, "org.bluez.Adapter", "DeviceRemoved")) {
922 pa_bluetooth_device
*d
;
924 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
925 pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err
.message
);
929 pa_log_debug("Device %s removed", path
);
931 if ((d
= pa_hashmap_remove(y
->devices
, path
))) {
932 run_callback(d
, TRUE
);
936 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
938 } else if (dbus_message_is_signal(m
, "org.bluez.Adapter", "DeviceCreated")) {
941 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
942 pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err
.message
);
946 pa_log_debug("Device %s created", path
);
948 found_device(y
, path
);
949 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
951 } else if (dbus_message_is_signal(m
, "org.bluez.Manager", "AdapterAdded")) {
954 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
955 pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err
.message
);
959 pa_log_debug("Adapter %s created", path
);
961 found_adapter(y
, path
);
962 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
964 } else if (dbus_message_is_signal(m
, "org.bluez.Audio", "PropertyChanged") ||
965 dbus_message_is_signal(m
, "org.bluez.Headset", "PropertyChanged") ||
966 dbus_message_is_signal(m
, "org.bluez.AudioSink", "PropertyChanged") ||
967 dbus_message_is_signal(m
, "org.bluez.AudioSource", "PropertyChanged") ||
968 dbus_message_is_signal(m
, "org.bluez.HandsfreeGateway", "PropertyChanged") ||
969 dbus_message_is_signal(m
, "org.bluez.Device", "PropertyChanged")) {
971 pa_bluetooth_device
*d
;
973 if ((d
= pa_hashmap_get(y
->devices
, dbus_message_get_path(m
)))) {
974 DBusMessageIter arg_i
;
975 bool old_any_connected
= pa_bluetooth_device_any_audio_connected(d
);
977 if (!dbus_message_iter_init(m
, &arg_i
)) {
978 pa_log("Failed to parse PropertyChanged: %s", err
.message
);
982 if (dbus_message_has_interface(m
, "org.bluez.Device")) {
983 if (parse_device_property(d
, &arg_i
) < 0)
986 } else if (parse_audio_property(d
, dbus_message_get_interface(m
), &arg_i
) < 0)
989 if (old_any_connected
!= pa_bluetooth_device_any_audio_connected(d
))
990 run_callback(d
, FALSE
);
993 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
995 } else if (dbus_message_is_signal(m
, "org.freedesktop.DBus", "NameOwnerChanged")) {
996 const char *name
, *old_owner
, *new_owner
;
998 if (!dbus_message_get_args(m
, &err
,
999 DBUS_TYPE_STRING
, &name
,
1000 DBUS_TYPE_STRING
, &old_owner
,
1001 DBUS_TYPE_STRING
, &new_owner
,
1002 DBUS_TYPE_INVALID
)) {
1003 pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err
.message
);
1007 if (pa_streq(name
, "org.bluez")) {
1008 if (old_owner
&& *old_owner
) {
1009 pa_log_debug("Bluetooth daemon disappeared.");
1010 remove_all_devices(y
);
1013 if (new_owner
&& *new_owner
) {
1014 pa_log_debug("Bluetooth daemon appeared.");
1019 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1020 } else if (dbus_message_is_signal(m
, "org.bluez.MediaTransport", "PropertyChanged")) {
1021 pa_bluetooth_transport
*t
;
1022 DBusMessageIter arg_i
;
1024 if (!(t
= pa_hashmap_get(y
->transports
, dbus_message_get_path(m
))))
1027 if (!dbus_message_iter_init(m
, &arg_i
)) {
1028 pa_log("Failed to parse PropertyChanged: %s", err
.message
);
1032 if (transport_parse_property(t
, &arg_i
) < 0)
1035 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1039 dbus_error_free(&err
);
1041 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1044 pa_bluetooth_device
* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery
*y
, const char* address
) {
1045 pa_bluetooth_device
*d
;
1049 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1052 if (!pa_hook_is_firing(&y
->hooks
[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED
]))
1053 pa_bluetooth_discovery_sync(y
);
1055 while ((d
= pa_hashmap_iterate(y
->devices
, &state
, NULL
)))
1056 if (pa_streq(d
->address
, address
))
1057 return device_is_audio_ready(d
) ? d
: NULL
;
1062 pa_bluetooth_device
* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery
*y
, const char* path
) {
1063 pa_bluetooth_device
*d
;
1066 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1069 if (!pa_hook_is_firing(&y
->hooks
[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED
]))
1070 pa_bluetooth_discovery_sync(y
);
1072 if ((d
= pa_hashmap_get(y
->devices
, path
)))
1073 if (device_is_audio_ready(d
))
1079 bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device
*d
) {
1084 if (d
->dead
|| !device_is_audio_ready(d
))
1087 /* Make sure audio_state is *not* in CONNECTING state before we fire the hook
1088 * to report the new device state. This is actually very important in order to
1089 * make module-card-restore work well with headsets: if the headset
1090 * supports both HSP and A2DP, one of those profiles is connected first and
1091 * then the other, and lastly the Audio interface becomes connected.
1092 * Checking only audio_state means that this function will return false at
1093 * the time when only the first connection has been made. This is good,
1094 * because otherwise, if the first connection is for HSP and we would
1095 * already load a new device module instance, and module-card-restore tries
1096 * to restore the A2DP profile, that would fail because A2DP is not yet
1097 * connected. Waiting until the Audio interface gets connected means that
1098 * both headset profiles will be connected when the device module is
1100 if (d
->audio_state
== PA_BT_AUDIO_STATE_CONNECTING
)
1103 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++)
1104 if (d
->transports
[i
])
1110 int pa_bluetooth_transport_acquire(pa_bluetooth_transport
*t
, bool optional
, size_t *imtu
, size_t *omtu
) {
1111 const char *accesstype
= "rw";
1118 pa_assert(t
->device
);
1119 pa_assert(t
->device
->discovery
);
1122 /* FIXME: we are trying to acquire the transport only if the stream is
1123 playing, without actually initiating the stream request from our side
1124 (which is typically undesireable specially for hfgw use-cases.
1125 However this approach is racy, since the stream could have been
1126 suspended in the meantime, so we can't really guarantee that the
1127 stream will not be requested until BlueZ's API supports this
1129 if (t
->state
< PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
) {
1130 pa_log_info("Failed optional acquire of transport %s", t
->path
);
1135 dbus_error_init(&err
);
1137 pa_assert_se(m
= dbus_message_new_method_call(t
->owner
, t
->path
, "org.bluez.MediaTransport", "Acquire"));
1138 pa_assert_se(dbus_message_append_args(m
, DBUS_TYPE_STRING
, &accesstype
, DBUS_TYPE_INVALID
));
1139 r
= dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t
->device
->discovery
->connection
), m
, -1, &err
);
1141 if (dbus_error_is_set(&err
) || !r
) {
1142 dbus_error_free(&err
);
1146 if (!dbus_message_get_args(r
, &err
, DBUS_TYPE_UNIX_FD
, &ret
, DBUS_TYPE_UINT16
, &i
, DBUS_TYPE_UINT16
, &o
, DBUS_TYPE_INVALID
)) {
1147 pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err
.message
);
1149 dbus_error_free(&err
);
1160 dbus_message_unref(r
);
1164 void pa_bluetooth_transport_release(pa_bluetooth_transport
*t
) {
1165 const char *accesstype
= "rw";
1170 pa_assert(t
->device
);
1171 pa_assert(t
->device
->discovery
);
1173 dbus_error_init(&err
);
1175 pa_assert_se(m
= dbus_message_new_method_call(t
->owner
, t
->path
, "org.bluez.MediaTransport", "Release"));
1176 pa_assert_se(dbus_message_append_args(m
, DBUS_TYPE_STRING
, &accesstype
, DBUS_TYPE_INVALID
));
1177 dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t
->device
->discovery
->connection
), m
, -1, &err
);
1179 if (dbus_error_is_set(&err
)) {
1180 pa_log("Failed to release transport %s: %s", t
->path
, err
.message
);
1181 dbus_error_free(&err
);
1183 pa_log_info("Transport %s released", t
->path
);
1186 static void set_property(pa_bluetooth_discovery
*y
, const char *bus
, const char *path
, const char *interface
,
1187 const char *prop_name
, int prop_type
, void *prop_value
) {
1193 pa_assert(interface
);
1194 pa_assert(prop_name
);
1196 pa_assert_se(m
= dbus_message_new_method_call(bus
, path
, interface
, "SetProperty"));
1197 dbus_message_iter_init_append(m
, &i
);
1198 dbus_message_iter_append_basic(&i
, DBUS_TYPE_STRING
, &prop_name
);
1199 pa_dbus_append_basic_variant(&i
, prop_type
, prop_value
);
1201 dbus_message_set_no_reply(m
, true);
1202 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y
->connection
), m
, NULL
));
1203 dbus_message_unref(m
);
1206 void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport
*t
, uint16_t value
) {
1207 dbus_uint16_t gain
= PA_MIN(value
, HSP_MAX_GAIN
);
1210 pa_assert(t
->profile
== PROFILE_HSP
);
1212 set_property(t
->device
->discovery
, "org.bluez", t
->device
->path
, "org.bluez.Headset",
1213 "MicrophoneGain", DBUS_TYPE_UINT16
, &gain
);
1216 void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport
*t
, uint16_t value
) {
1217 dbus_uint16_t gain
= PA_MIN(value
, HSP_MAX_GAIN
);
1220 pa_assert(t
->profile
== PROFILE_HSP
);
1222 set_property(t
->device
->discovery
, "org.bluez", t
->device
->path
, "org.bluez.Headset",
1223 "SpeakerGain", DBUS_TYPE_UINT16
, &gain
);
1226 static int setup_dbus(pa_bluetooth_discovery
*y
) {
1229 dbus_error_init(&err
);
1231 y
->connection
= pa_dbus_bus_get(y
->core
, DBUS_BUS_SYSTEM
, &err
);
1233 if (dbus_error_is_set(&err
) || !y
->connection
) {
1234 pa_log("Failed to get D-Bus connection: %s", err
.message
);
1235 dbus_error_free(&err
);
1242 static pa_bluetooth_transport
*transport_new(pa_bluetooth_device
*d
, const char *owner
, const char *path
, enum profile p
,
1243 const uint8_t *config
, int size
) {
1244 pa_bluetooth_transport
*t
;
1246 t
= pa_xnew0(pa_bluetooth_transport
, 1);
1248 t
->owner
= pa_xstrdup(owner
);
1249 t
->path
= pa_xstrdup(path
);
1251 t
->config_size
= size
;
1254 t
->config
= pa_xnew(uint8_t, size
);
1255 memcpy(t
->config
, config
, size
);
1258 t
->state
= audio_state_to_transport_state(d
->profile_state
[p
]);
1263 static DBusMessage
*endpoint_set_configuration(DBusConnection
*conn
, DBusMessage
*m
, void *userdata
) {
1264 pa_bluetooth_discovery
*y
= userdata
;
1265 pa_bluetooth_device
*d
;
1266 pa_bluetooth_transport
*t
;
1267 const char *sender
, *path
, *dev_path
= NULL
, *uuid
= NULL
;
1268 uint8_t *config
= NULL
;
1270 pa_bool_t nrec
= FALSE
;
1272 DBusMessageIter args
, props
;
1274 bool old_any_connected
;
1276 dbus_message_iter_init(m
, &args
);
1278 dbus_message_iter_get_basic(&args
, &path
);
1280 if (pa_hashmap_get(y
->transports
, path
)) {
1281 pa_log("org.bluez.MediaEndpoint.SetConfiguration: Transport %s is already configured.", path
);
1285 if (!dbus_message_iter_next(&args
))
1288 dbus_message_iter_recurse(&args
, &props
);
1289 if (dbus_message_iter_get_arg_type(&props
) != DBUS_TYPE_DICT_ENTRY
)
1292 /* Read transport properties */
1293 while (dbus_message_iter_get_arg_type(&props
) == DBUS_TYPE_DICT_ENTRY
) {
1295 DBusMessageIter value
, entry
;
1298 dbus_message_iter_recurse(&props
, &entry
);
1299 dbus_message_iter_get_basic(&entry
, &key
);
1301 dbus_message_iter_next(&entry
);
1302 dbus_message_iter_recurse(&entry
, &value
);
1304 var
= dbus_message_iter_get_arg_type(&value
);
1305 if (strcasecmp(key
, "UUID") == 0) {
1306 if (var
!= DBUS_TYPE_STRING
)
1308 dbus_message_iter_get_basic(&value
, &uuid
);
1309 } else if (strcasecmp(key
, "Device") == 0) {
1310 if (var
!= DBUS_TYPE_OBJECT_PATH
)
1312 dbus_message_iter_get_basic(&value
, &dev_path
);
1313 } else if (strcasecmp(key
, "NREC") == 0) {
1314 dbus_bool_t tmp_boolean
;
1315 if (var
!= DBUS_TYPE_BOOLEAN
)
1317 dbus_message_iter_get_basic(&value
, &tmp_boolean
);
1319 } else if (strcasecmp(key
, "Configuration") == 0) {
1320 DBusMessageIter array
;
1321 if (var
!= DBUS_TYPE_ARRAY
)
1323 dbus_message_iter_recurse(&value
, &array
);
1324 dbus_message_iter_get_fixed_array(&array
, &config
, &size
);
1327 dbus_message_iter_next(&props
);
1330 d
= found_device(y
, dev_path
);
1334 if (dbus_message_has_path(m
, HFP_AG_ENDPOINT
))
1336 else if (dbus_message_has_path(m
, HFP_HS_ENDPOINT
))
1338 else if (dbus_message_has_path(m
, A2DP_SOURCE_ENDPOINT
))
1341 p
= PROFILE_A2DP_SOURCE
;
1343 if (d
->transports
[p
] != NULL
) {
1344 pa_log("Cannot configure transport %s because profile %d is already used", path
, p
);
1348 old_any_connected
= pa_bluetooth_device_any_audio_connected(d
);
1350 sender
= dbus_message_get_sender(m
);
1352 t
= transport_new(d
, sender
, path
, p
, config
, size
);
1356 d
->transports
[p
] = t
;
1357 pa_assert_se(pa_hashmap_put(y
->transports
, t
->path
, t
) >= 0);
1359 pa_log_debug("Transport %s profile %d available", t
->path
, t
->profile
);
1361 pa_assert_se(r
= dbus_message_new_method_return(m
));
1363 if (old_any_connected
!= pa_bluetooth_device_any_audio_connected(d
))
1364 run_callback(d
, FALSE
);
1369 pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments");
1370 pa_assert_se(r
= (dbus_message_new_error(m
, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1371 "Unable to set configuration")));
1375 static DBusMessage
*endpoint_clear_configuration(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
1376 pa_bluetooth_discovery
*y
= userdata
;
1377 pa_bluetooth_transport
*t
;
1382 dbus_error_init(&e
);
1384 if (!dbus_message_get_args(m
, &e
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
1385 pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e
.message
);
1386 dbus_error_free(&e
);
1390 if ((t
= pa_hashmap_get(y
->transports
, path
))) {
1391 bool old_any_connected
= t
->device
? pa_bluetooth_device_any_audio_connected(t
->device
) : false;
1393 pa_log_debug("Clearing transport %s profile %d", t
->path
, t
->profile
);
1394 t
->device
->transports
[t
->profile
] = NULL
;
1395 pa_hashmap_remove(y
->transports
, t
->path
);
1396 t
->state
= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
;
1397 pa_hook_fire(&y
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED
], t
);
1400 if (t
->device
&& old_any_connected
!= pa_bluetooth_device_any_audio_connected(t
->device
))
1401 run_callback(t
->device
, FALSE
);
1404 pa_assert_se(r
= dbus_message_new_method_return(m
));
1409 pa_assert_se(r
= (dbus_message_new_error(m
, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1410 "Unable to clear configuration")));
1414 static uint8_t a2dp_default_bitpool(uint8_t freq
, uint8_t mode
) {
1417 case SBC_SAMPLING_FREQ_16000
:
1418 case SBC_SAMPLING_FREQ_32000
:
1421 case SBC_SAMPLING_FREQ_44100
:
1424 case SBC_CHANNEL_MODE_MONO
:
1425 case SBC_CHANNEL_MODE_DUAL_CHANNEL
:
1428 case SBC_CHANNEL_MODE_STEREO
:
1429 case SBC_CHANNEL_MODE_JOINT_STEREO
:
1433 pa_log_warn("Invalid channel mode %u", mode
);
1437 case SBC_SAMPLING_FREQ_48000
:
1440 case SBC_CHANNEL_MODE_MONO
:
1441 case SBC_CHANNEL_MODE_DUAL_CHANNEL
:
1444 case SBC_CHANNEL_MODE_STEREO
:
1445 case SBC_CHANNEL_MODE_JOINT_STEREO
:
1449 pa_log_warn("Invalid channel mode %u", mode
);
1454 pa_log_warn("Invalid sampling freq %u", freq
);
1459 static DBusMessage
*endpoint_select_configuration(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
1460 pa_bluetooth_discovery
*y
= userdata
;
1461 a2dp_sbc_t
*cap
, config
;
1462 uint8_t *pconf
= (uint8_t *) &config
;
1467 static const struct {
1471 { 16000U, SBC_SAMPLING_FREQ_16000
},
1472 { 32000U, SBC_SAMPLING_FREQ_32000
},
1473 { 44100U, SBC_SAMPLING_FREQ_44100
},
1474 { 48000U, SBC_SAMPLING_FREQ_48000
}
1477 dbus_error_init(&e
);
1479 if (!dbus_message_get_args(m
, &e
, DBUS_TYPE_ARRAY
, DBUS_TYPE_BYTE
, &cap
, &size
, DBUS_TYPE_INVALID
)) {
1480 pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e
.message
);
1481 dbus_error_free(&e
);
1485 if (dbus_message_has_path(m
, HFP_AG_ENDPOINT
) || dbus_message_has_path(m
, HFP_HS_ENDPOINT
))
1488 pa_assert(size
== sizeof(config
));
1490 memset(&config
, 0, sizeof(config
));
1492 /* Find the lowest freq that is at least as high as the requested
1494 for (i
= 0; (unsigned) i
< PA_ELEMENTSOF(freq_table
); i
++)
1495 if (freq_table
[i
].rate
>= y
->core
->default_sample_spec
.rate
&& (cap
->frequency
& freq_table
[i
].cap
)) {
1496 config
.frequency
= freq_table
[i
].cap
;
1500 if ((unsigned) i
== PA_ELEMENTSOF(freq_table
)) {
1501 for (--i
; i
>= 0; i
--) {
1502 if (cap
->frequency
& freq_table
[i
].cap
) {
1503 config
.frequency
= freq_table
[i
].cap
;
1509 pa_log("Not suitable sample rate");
1514 pa_assert((unsigned) i
< PA_ELEMENTSOF(freq_table
));
1516 if (y
->core
->default_sample_spec
.channels
<= 1) {
1517 if (cap
->channel_mode
& SBC_CHANNEL_MODE_MONO
)
1518 config
.channel_mode
= SBC_CHANNEL_MODE_MONO
;
1521 if (y
->core
->default_sample_spec
.channels
>= 2) {
1522 if (cap
->channel_mode
& SBC_CHANNEL_MODE_JOINT_STEREO
)
1523 config
.channel_mode
= SBC_CHANNEL_MODE_JOINT_STEREO
;
1524 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_STEREO
)
1525 config
.channel_mode
= SBC_CHANNEL_MODE_STEREO
;
1526 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_DUAL_CHANNEL
)
1527 config
.channel_mode
= SBC_CHANNEL_MODE_DUAL_CHANNEL
;
1528 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_MONO
) {
1529 config
.channel_mode
= SBC_CHANNEL_MODE_MONO
;
1531 pa_log("No supported channel modes");
1536 if (cap
->block_length
& SBC_BLOCK_LENGTH_16
)
1537 config
.block_length
= SBC_BLOCK_LENGTH_16
;
1538 else if (cap
->block_length
& SBC_BLOCK_LENGTH_12
)
1539 config
.block_length
= SBC_BLOCK_LENGTH_12
;
1540 else if (cap
->block_length
& SBC_BLOCK_LENGTH_8
)
1541 config
.block_length
= SBC_BLOCK_LENGTH_8
;
1542 else if (cap
->block_length
& SBC_BLOCK_LENGTH_4
)
1543 config
.block_length
= SBC_BLOCK_LENGTH_4
;
1545 pa_log_error("No supported block lengths");
1549 if (cap
->subbands
& SBC_SUBBANDS_8
)
1550 config
.subbands
= SBC_SUBBANDS_8
;
1551 else if (cap
->subbands
& SBC_SUBBANDS_4
)
1552 config
.subbands
= SBC_SUBBANDS_4
;
1554 pa_log_error("No supported subbands");
1558 if (cap
->allocation_method
& SBC_ALLOCATION_LOUDNESS
)
1559 config
.allocation_method
= SBC_ALLOCATION_LOUDNESS
;
1560 else if (cap
->allocation_method
& SBC_ALLOCATION_SNR
)
1561 config
.allocation_method
= SBC_ALLOCATION_SNR
;
1563 config
.min_bitpool
= (uint8_t) PA_MAX(MIN_BITPOOL
, cap
->min_bitpool
);
1564 config
.max_bitpool
= (uint8_t) PA_MIN(a2dp_default_bitpool(config
.frequency
, config
.channel_mode
), cap
->max_bitpool
);
1567 pa_assert_se(r
= dbus_message_new_method_return(m
));
1569 pa_assert_se(dbus_message_append_args(
1571 DBUS_TYPE_ARRAY
, DBUS_TYPE_BYTE
, &pconf
, size
,
1572 DBUS_TYPE_INVALID
));
1577 pa_assert_se(r
= (dbus_message_new_error(m
, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1578 "Unable to select configuration")));
1582 static DBusHandlerResult
endpoint_handler(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
1583 struct pa_bluetooth_discovery
*y
= userdata
;
1584 DBusMessage
*r
= NULL
;
1590 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
1591 dbus_message_get_interface(m
),
1592 dbus_message_get_path(m
),
1593 dbus_message_get_member(m
));
1595 path
= dbus_message_get_path(m
);
1596 dbus_error_init(&e
);
1598 if (!pa_streq(path
, A2DP_SOURCE_ENDPOINT
) && !pa_streq(path
, A2DP_SINK_ENDPOINT
) && !pa_streq(path
, HFP_AG_ENDPOINT
) && !pa_streq(path
, HFP_HS_ENDPOINT
))
1599 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1601 if (dbus_message_is_method_call(m
, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1602 const char *xml
= ENDPOINT_INTROSPECT_XML
;
1604 pa_assert_se(r
= dbus_message_new_method_return(m
));
1605 pa_assert_se(dbus_message_append_args(
1607 DBUS_TYPE_STRING
, &xml
,
1608 DBUS_TYPE_INVALID
));
1610 } else if (dbus_message_is_method_call(m
, "org.bluez.MediaEndpoint", "SetConfiguration")) {
1611 r
= endpoint_set_configuration(c
, m
, userdata
);
1612 } else if (dbus_message_is_method_call(m
, "org.bluez.MediaEndpoint", "SelectConfiguration")) {
1613 r
= endpoint_select_configuration(c
, m
, userdata
);
1614 } else if (dbus_message_is_method_call(m
, "org.bluez.MediaEndpoint", "ClearConfiguration"))
1615 r
= endpoint_clear_configuration(c
, m
, userdata
);
1617 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1620 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y
->connection
), r
, NULL
));
1621 dbus_message_unref(r
);
1624 return DBUS_HANDLER_RESULT_HANDLED
;
1627 pa_bluetooth_discovery
* pa_bluetooth_discovery_get(pa_core
*c
) {
1629 pa_bluetooth_discovery
*y
;
1631 static const DBusObjectPathVTable vtable_endpoint
= {
1632 .message_function
= endpoint_handler
,
1637 dbus_error_init(&err
);
1639 if ((y
= pa_shared_get(c
, "bluetooth-discovery")))
1640 return pa_bluetooth_discovery_ref(y
);
1642 y
= pa_xnew0(pa_bluetooth_discovery
, 1);
1645 y
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
1646 y
->transports
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
1647 PA_LLIST_HEAD_INIT(pa_dbus_pending
, y
->pending
);
1649 for (i
= 0; i
< PA_BLUETOOTH_HOOK_MAX
; i
++)
1650 pa_hook_init(&y
->hooks
[i
], y
);
1652 pa_shared_set(c
, "bluetooth-discovery", y
);
1654 if (setup_dbus(y
) < 0)
1657 /* dynamic detection of bluetooth audio devices */
1658 if (!dbus_connection_add_filter(pa_dbus_connection_get(y
->connection
), filter_cb
, y
, NULL
)) {
1659 pa_log_error("Failed to add filter function");
1662 y
->filter_added
= TRUE
;
1664 if (pa_dbus_add_matches(
1665 pa_dbus_connection_get(y
->connection
), &err
,
1666 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
1667 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1668 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1669 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1670 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1671 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1672 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1673 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1674 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1675 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1676 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1678 pa_log("Failed to add D-Bus matches: %s", err
.message
);
1682 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y
->connection
), HFP_AG_ENDPOINT
, &vtable_endpoint
, y
));
1683 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y
->connection
), HFP_HS_ENDPOINT
, &vtable_endpoint
, y
));
1684 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SOURCE_ENDPOINT
, &vtable_endpoint
, y
));
1685 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SINK_ENDPOINT
, &vtable_endpoint
, y
));
1694 pa_bluetooth_discovery_unref(y
);
1696 dbus_error_free(&err
);
1701 pa_bluetooth_discovery
* pa_bluetooth_discovery_ref(pa_bluetooth_discovery
*y
) {
1703 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1710 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery
*y
) {
1714 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1716 if (PA_REFCNT_DEC(y
) > 0)
1719 pa_dbus_free_pending_list(&y
->pending
);
1722 remove_all_devices(y
);
1723 pa_hashmap_free(y
->devices
, NULL
, NULL
);
1726 if (y
->transports
) {
1727 pa_assert(pa_hashmap_isempty(y
->transports
));
1728 pa_hashmap_free(y
->transports
, NULL
, NULL
);
1731 if (y
->connection
) {
1732 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), HFP_AG_ENDPOINT
);
1733 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), HFP_HS_ENDPOINT
);
1734 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SOURCE_ENDPOINT
);
1735 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SINK_ENDPOINT
);
1736 pa_dbus_remove_matches(pa_dbus_connection_get(y
->connection
),
1737 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
1738 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1739 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
1740 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1741 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1742 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1743 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1744 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1745 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1746 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1747 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1748 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1751 if (y
->filter_added
)
1752 dbus_connection_remove_filter(pa_dbus_connection_get(y
->connection
), filter_cb
, y
);
1754 pa_dbus_connection_unref(y
->connection
);
1757 for (i
= 0; i
< PA_BLUETOOTH_HOOK_MAX
; i
++)
1758 pa_hook_done(&y
->hooks
[i
]);
1761 pa_shared_remove(y
->core
, "bluetooth-discovery");
1766 void pa_bluetooth_discovery_sync(pa_bluetooth_discovery
*y
) {
1768 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1770 pa_dbus_sync_pending_list(&y
->pending
);
1773 pa_hook
* pa_bluetooth_discovery_hook(pa_bluetooth_discovery
*y
, pa_bluetooth_hook_t hook
) {
1775 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1777 return &y
->hooks
[hook
];
1780 const char*pa_bluetooth_get_form_factor(uint32_t class) {
1784 static const char * const table
[] = {
1795 if (((class >> 8) & 31) != 4)
1798 if ((i
= (class >> 2) & 63) > PA_ELEMENTSOF(table
))
1804 pa_log_debug("Unknown Bluetooth minor device class %u", i
);
1809 char *pa_bluetooth_cleanup_name(const char *name
) {
1811 pa_bool_t space
= FALSE
;
1815 while ((*name
>= 1 && *name
<= 32) || *name
>= 127)
1818 t
= pa_xstrdup(name
);
1820 for (s
= d
= t
; *s
; s
++) {
1822 if (*s
<= 32 || *s
>= 127 || *s
== '_') {
1840 pa_bool_t
pa_bluetooth_uuid_has(pa_bluetooth_uuid
*uuids
, const char *uuid
) {
1844 if (strcasecmp(uuids
->uuid
, uuid
) == 0)
1847 uuids
= uuids
->next
;