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
= pa_bluetooth_device_any_audio_connected(t
->device
);
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
);
1399 if (old_any_connected
!= pa_bluetooth_device_any_audio_connected(t
->device
))
1400 run_callback(t
->device
, FALSE
);
1405 pa_assert_se(r
= dbus_message_new_method_return(m
));
1410 pa_assert_se(r
= (dbus_message_new_error(m
, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1411 "Unable to clear configuration")));
1415 static uint8_t a2dp_default_bitpool(uint8_t freq
, uint8_t mode
) {
1418 case SBC_SAMPLING_FREQ_16000
:
1419 case SBC_SAMPLING_FREQ_32000
:
1422 case SBC_SAMPLING_FREQ_44100
:
1425 case SBC_CHANNEL_MODE_MONO
:
1426 case SBC_CHANNEL_MODE_DUAL_CHANNEL
:
1429 case SBC_CHANNEL_MODE_STEREO
:
1430 case SBC_CHANNEL_MODE_JOINT_STEREO
:
1434 pa_log_warn("Invalid channel mode %u", mode
);
1438 case SBC_SAMPLING_FREQ_48000
:
1441 case SBC_CHANNEL_MODE_MONO
:
1442 case SBC_CHANNEL_MODE_DUAL_CHANNEL
:
1445 case SBC_CHANNEL_MODE_STEREO
:
1446 case SBC_CHANNEL_MODE_JOINT_STEREO
:
1450 pa_log_warn("Invalid channel mode %u", mode
);
1455 pa_log_warn("Invalid sampling freq %u", freq
);
1460 static DBusMessage
*endpoint_select_configuration(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
1461 pa_bluetooth_discovery
*y
= userdata
;
1462 a2dp_sbc_t
*cap
, config
;
1463 uint8_t *pconf
= (uint8_t *) &config
;
1468 static const struct {
1472 { 16000U, SBC_SAMPLING_FREQ_16000
},
1473 { 32000U, SBC_SAMPLING_FREQ_32000
},
1474 { 44100U, SBC_SAMPLING_FREQ_44100
},
1475 { 48000U, SBC_SAMPLING_FREQ_48000
}
1478 dbus_error_init(&e
);
1480 if (!dbus_message_get_args(m
, &e
, DBUS_TYPE_ARRAY
, DBUS_TYPE_BYTE
, &cap
, &size
, DBUS_TYPE_INVALID
)) {
1481 pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e
.message
);
1482 dbus_error_free(&e
);
1486 if (dbus_message_has_path(m
, HFP_AG_ENDPOINT
) || dbus_message_has_path(m
, HFP_HS_ENDPOINT
))
1489 pa_assert(size
== sizeof(config
));
1491 memset(&config
, 0, sizeof(config
));
1493 /* Find the lowest freq that is at least as high as the requested
1495 for (i
= 0; (unsigned) i
< PA_ELEMENTSOF(freq_table
); i
++)
1496 if (freq_table
[i
].rate
>= y
->core
->default_sample_spec
.rate
&& (cap
->frequency
& freq_table
[i
].cap
)) {
1497 config
.frequency
= freq_table
[i
].cap
;
1501 if ((unsigned) i
== PA_ELEMENTSOF(freq_table
)) {
1502 for (--i
; i
>= 0; i
--) {
1503 if (cap
->frequency
& freq_table
[i
].cap
) {
1504 config
.frequency
= freq_table
[i
].cap
;
1510 pa_log("Not suitable sample rate");
1515 pa_assert((unsigned) i
< PA_ELEMENTSOF(freq_table
));
1517 if (y
->core
->default_sample_spec
.channels
<= 1) {
1518 if (cap
->channel_mode
& SBC_CHANNEL_MODE_MONO
)
1519 config
.channel_mode
= SBC_CHANNEL_MODE_MONO
;
1522 if (y
->core
->default_sample_spec
.channels
>= 2) {
1523 if (cap
->channel_mode
& SBC_CHANNEL_MODE_JOINT_STEREO
)
1524 config
.channel_mode
= SBC_CHANNEL_MODE_JOINT_STEREO
;
1525 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_STEREO
)
1526 config
.channel_mode
= SBC_CHANNEL_MODE_STEREO
;
1527 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_DUAL_CHANNEL
)
1528 config
.channel_mode
= SBC_CHANNEL_MODE_DUAL_CHANNEL
;
1529 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_MONO
) {
1530 config
.channel_mode
= SBC_CHANNEL_MODE_MONO
;
1532 pa_log("No supported channel modes");
1537 if (cap
->block_length
& SBC_BLOCK_LENGTH_16
)
1538 config
.block_length
= SBC_BLOCK_LENGTH_16
;
1539 else if (cap
->block_length
& SBC_BLOCK_LENGTH_12
)
1540 config
.block_length
= SBC_BLOCK_LENGTH_12
;
1541 else if (cap
->block_length
& SBC_BLOCK_LENGTH_8
)
1542 config
.block_length
= SBC_BLOCK_LENGTH_8
;
1543 else if (cap
->block_length
& SBC_BLOCK_LENGTH_4
)
1544 config
.block_length
= SBC_BLOCK_LENGTH_4
;
1546 pa_log_error("No supported block lengths");
1550 if (cap
->subbands
& SBC_SUBBANDS_8
)
1551 config
.subbands
= SBC_SUBBANDS_8
;
1552 else if (cap
->subbands
& SBC_SUBBANDS_4
)
1553 config
.subbands
= SBC_SUBBANDS_4
;
1555 pa_log_error("No supported subbands");
1559 if (cap
->allocation_method
& SBC_ALLOCATION_LOUDNESS
)
1560 config
.allocation_method
= SBC_ALLOCATION_LOUDNESS
;
1561 else if (cap
->allocation_method
& SBC_ALLOCATION_SNR
)
1562 config
.allocation_method
= SBC_ALLOCATION_SNR
;
1564 config
.min_bitpool
= (uint8_t) PA_MAX(MIN_BITPOOL
, cap
->min_bitpool
);
1565 config
.max_bitpool
= (uint8_t) PA_MIN(a2dp_default_bitpool(config
.frequency
, config
.channel_mode
), cap
->max_bitpool
);
1568 pa_assert_se(r
= dbus_message_new_method_return(m
));
1570 pa_assert_se(dbus_message_append_args(
1572 DBUS_TYPE_ARRAY
, DBUS_TYPE_BYTE
, &pconf
, size
,
1573 DBUS_TYPE_INVALID
));
1578 pa_assert_se(r
= (dbus_message_new_error(m
, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1579 "Unable to select configuration")));
1583 static DBusHandlerResult
endpoint_handler(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
1584 struct pa_bluetooth_discovery
*y
= userdata
;
1585 DBusMessage
*r
= NULL
;
1591 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
1592 dbus_message_get_interface(m
),
1593 dbus_message_get_path(m
),
1594 dbus_message_get_member(m
));
1596 path
= dbus_message_get_path(m
);
1597 dbus_error_init(&e
);
1599 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
))
1600 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1602 if (dbus_message_is_method_call(m
, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1603 const char *xml
= ENDPOINT_INTROSPECT_XML
;
1605 pa_assert_se(r
= dbus_message_new_method_return(m
));
1606 pa_assert_se(dbus_message_append_args(
1608 DBUS_TYPE_STRING
, &xml
,
1609 DBUS_TYPE_INVALID
));
1611 } else if (dbus_message_is_method_call(m
, "org.bluez.MediaEndpoint", "SetConfiguration")) {
1612 r
= endpoint_set_configuration(c
, m
, userdata
);
1613 } else if (dbus_message_is_method_call(m
, "org.bluez.MediaEndpoint", "SelectConfiguration")) {
1614 r
= endpoint_select_configuration(c
, m
, userdata
);
1615 } else if (dbus_message_is_method_call(m
, "org.bluez.MediaEndpoint", "ClearConfiguration"))
1616 r
= endpoint_clear_configuration(c
, m
, userdata
);
1618 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1621 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y
->connection
), r
, NULL
));
1622 dbus_message_unref(r
);
1625 return DBUS_HANDLER_RESULT_HANDLED
;
1628 pa_bluetooth_discovery
* pa_bluetooth_discovery_get(pa_core
*c
) {
1630 pa_bluetooth_discovery
*y
;
1632 static const DBusObjectPathVTable vtable_endpoint
= {
1633 .message_function
= endpoint_handler
,
1638 dbus_error_init(&err
);
1640 if ((y
= pa_shared_get(c
, "bluetooth-discovery")))
1641 return pa_bluetooth_discovery_ref(y
);
1643 y
= pa_xnew0(pa_bluetooth_discovery
, 1);
1646 y
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
1647 y
->transports
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
1648 PA_LLIST_HEAD_INIT(pa_dbus_pending
, y
->pending
);
1650 for (i
= 0; i
< PA_BLUETOOTH_HOOK_MAX
; i
++)
1651 pa_hook_init(&y
->hooks
[i
], y
);
1653 pa_shared_set(c
, "bluetooth-discovery", y
);
1655 if (setup_dbus(y
) < 0)
1658 /* dynamic detection of bluetooth audio devices */
1659 if (!dbus_connection_add_filter(pa_dbus_connection_get(y
->connection
), filter_cb
, y
, NULL
)) {
1660 pa_log_error("Failed to add filter function");
1663 y
->filter_added
= TRUE
;
1665 if (pa_dbus_add_matches(
1666 pa_dbus_connection_get(y
->connection
), &err
,
1667 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
1668 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1669 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1670 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1671 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1672 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1673 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1674 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1675 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1676 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1677 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1679 pa_log("Failed to add D-Bus matches: %s", err
.message
);
1683 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y
->connection
), HFP_AG_ENDPOINT
, &vtable_endpoint
, y
));
1684 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y
->connection
), HFP_HS_ENDPOINT
, &vtable_endpoint
, y
));
1685 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SOURCE_ENDPOINT
, &vtable_endpoint
, y
));
1686 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SINK_ENDPOINT
, &vtable_endpoint
, y
));
1695 pa_bluetooth_discovery_unref(y
);
1697 dbus_error_free(&err
);
1702 pa_bluetooth_discovery
* pa_bluetooth_discovery_ref(pa_bluetooth_discovery
*y
) {
1704 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1711 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery
*y
) {
1715 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1717 if (PA_REFCNT_DEC(y
) > 0)
1720 pa_dbus_free_pending_list(&y
->pending
);
1723 remove_all_devices(y
);
1724 pa_hashmap_free(y
->devices
, NULL
, NULL
);
1727 if (y
->transports
) {
1728 pa_assert(pa_hashmap_isempty(y
->transports
));
1729 pa_hashmap_free(y
->transports
, NULL
, NULL
);
1732 if (y
->connection
) {
1733 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), HFP_AG_ENDPOINT
);
1734 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), HFP_HS_ENDPOINT
);
1735 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SOURCE_ENDPOINT
);
1736 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SINK_ENDPOINT
);
1737 pa_dbus_remove_matches(pa_dbus_connection_get(y
->connection
),
1738 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
1739 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1740 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
1741 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1742 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1743 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1744 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1745 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1746 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1747 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1748 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1749 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1752 if (y
->filter_added
)
1753 dbus_connection_remove_filter(pa_dbus_connection_get(y
->connection
), filter_cb
, y
);
1755 pa_dbus_connection_unref(y
->connection
);
1758 for (i
= 0; i
< PA_BLUETOOTH_HOOK_MAX
; i
++)
1759 pa_hook_done(&y
->hooks
[i
]);
1762 pa_shared_remove(y
->core
, "bluetooth-discovery");
1767 void pa_bluetooth_discovery_sync(pa_bluetooth_discovery
*y
) {
1769 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1771 pa_dbus_sync_pending_list(&y
->pending
);
1774 pa_hook
* pa_bluetooth_discovery_hook(pa_bluetooth_discovery
*y
, pa_bluetooth_hook_t hook
) {
1776 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1778 return &y
->hooks
[hook
];
1781 const char*pa_bluetooth_get_form_factor(uint32_t class) {
1785 static const char * const table
[] = {
1796 if (((class >> 8) & 31) != 4)
1799 if ((i
= (class >> 2) & 63) > PA_ELEMENTSOF(table
))
1805 pa_log_debug("Unknown Bluetooth minor device class %u", i
);
1810 char *pa_bluetooth_cleanup_name(const char *name
) {
1812 pa_bool_t space
= FALSE
;
1816 while ((*name
>= 1 && *name
<= 32) || *name
>= 127)
1819 t
= pa_xstrdup(name
);
1821 for (s
= d
= t
; *s
; s
++) {
1823 if (*s
<= 32 || *s
>= 127 || *s
== '_') {
1841 pa_bool_t
pa_bluetooth_uuid_has(pa_bluetooth_uuid
*uuids
, const char *uuid
) {
1845 if (strcasecmp(uuids
->uuid
, uuid
) == 0)
1848 uuids
= uuids
->next
;