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
,
79 static void found_adapter(pa_bluetooth_discovery
*y
, const char *path
);
80 static pa_bluetooth_device
*found_device(pa_bluetooth_discovery
*y
, const char* path
);
82 static pa_bt_audio_state_t
audio_state_from_string(const char* value
) {
85 if (pa_streq(value
, "disconnected"))
86 return PA_BT_AUDIO_STATE_DISCONNECTED
;
87 else if (pa_streq(value
, "connecting"))
88 return PA_BT_AUDIO_STATE_CONNECTING
;
89 else if (pa_streq(value
, "connected"))
90 return PA_BT_AUDIO_STATE_CONNECTED
;
91 else if (pa_streq(value
, "playing"))
92 return PA_BT_AUDIO_STATE_PLAYING
;
94 return PA_BT_AUDIO_STATE_INVALID
;
97 const char *pa_bt_profile_to_string(enum profile profile
) {
101 case PROFILE_A2DP_SOURCE
:
102 return "a2dp_source";
108 pa_assert_not_reached();
111 pa_assert_not_reached();
114 static int profile_from_interface(const char *interface
, enum profile
*p
) {
115 pa_assert(interface
);
118 if (pa_streq(interface
, "org.bluez.AudioSink")) {
121 } else if (pa_streq(interface
, "org.bluez.AudioSource")) {
122 *p
= PROFILE_A2DP_SOURCE
;
124 } else if (pa_streq(interface
, "org.bluez.Headset")) {
127 } else if (pa_streq(interface
, "org.bluez.HandsfreeGateway")) {
135 static pa_bluetooth_transport_state_t
audio_state_to_transport_state(pa_bt_audio_state_t state
) {
137 case PA_BT_AUDIO_STATE_INVALID
: /* Typically if state hasn't been received yet */
138 case PA_BT_AUDIO_STATE_DISCONNECTED
:
139 case PA_BT_AUDIO_STATE_CONNECTING
:
140 return PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
;
141 case PA_BT_AUDIO_STATE_CONNECTED
:
142 return PA_BLUETOOTH_TRANSPORT_STATE_IDLE
;
143 case PA_BT_AUDIO_STATE_PLAYING
:
144 return PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
;
147 pa_assert_not_reached();
150 static pa_bluetooth_uuid
*uuid_new(const char *uuid
) {
151 pa_bluetooth_uuid
*u
;
153 u
= pa_xnew(pa_bluetooth_uuid
, 1);
154 u
->uuid
= pa_xstrdup(uuid
);
155 PA_LLIST_INIT(pa_bluetooth_uuid
, u
);
160 static void uuid_free(pa_bluetooth_uuid
*u
) {
167 static pa_bluetooth_device
* device_new(pa_bluetooth_discovery
*discovery
, const char *path
) {
168 pa_bluetooth_device
*d
;
171 pa_assert(discovery
);
174 d
= pa_xnew0(pa_bluetooth_device
, 1);
176 d
->discovery
= discovery
;
179 d
->device_info_valid
= 0;
182 d
->path
= pa_xstrdup(path
);
185 PA_LLIST_HEAD_INIT(pa_bluetooth_uuid
, d
->uuids
);
190 d
->audio_state
= PA_BT_AUDIO_STATE_INVALID
;
192 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++)
193 d
->profile_state
[i
] = PA_BT_AUDIO_STATE_INVALID
;
198 static void transport_free(pa_bluetooth_transport
*t
) {
207 static void device_free(pa_bluetooth_device
*d
) {
208 pa_bluetooth_uuid
*u
;
209 pa_bluetooth_transport
*t
;
214 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++) {
215 if (!(t
= d
->transports
[i
]))
218 d
->transports
[i
] = NULL
;
219 pa_hashmap_remove(d
->discovery
->transports
, t
->path
);
220 t
->state
= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
;
221 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED
], t
);
225 while ((u
= d
->uuids
)) {
226 PA_LLIST_REMOVE(pa_bluetooth_uuid
, d
->uuids
, u
);
233 pa_xfree(d
->address
);
237 static const char *check_variant_property(DBusMessageIter
*i
) {
242 if (dbus_message_iter_get_arg_type(i
) != DBUS_TYPE_STRING
) {
243 pa_log("Property name not a string.");
247 dbus_message_iter_get_basic(i
, &key
);
249 if (!dbus_message_iter_next(i
)) {
250 pa_log("Property value missing");
254 if (dbus_message_iter_get_arg_type(i
) != DBUS_TYPE_VARIANT
) {
255 pa_log("Property value not a variant.");
262 static int parse_manager_property(pa_bluetooth_discovery
*y
, DBusMessageIter
*i
) {
264 DBusMessageIter variant_i
;
268 key
= check_variant_property(i
);
272 dbus_message_iter_recurse(i
, &variant_i
);
274 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
276 case DBUS_TYPE_ARRAY
: {
279 dbus_message_iter_recurse(&variant_i
, &ai
);
281 if (dbus_message_iter_get_arg_type(&ai
) == DBUS_TYPE_OBJECT_PATH
&&
282 pa_streq(key
, "Adapters")) {
284 while (dbus_message_iter_get_arg_type(&ai
) != DBUS_TYPE_INVALID
) {
287 dbus_message_iter_get_basic(&ai
, &value
);
289 found_adapter(y
, value
);
291 dbus_message_iter_next(&ai
);
302 static int parse_adapter_property(pa_bluetooth_discovery
*y
, DBusMessageIter
*i
) {
304 DBusMessageIter variant_i
;
308 key
= check_variant_property(i
);
312 dbus_message_iter_recurse(i
, &variant_i
);
314 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
316 case DBUS_TYPE_ARRAY
: {
319 dbus_message_iter_recurse(&variant_i
, &ai
);
321 if (dbus_message_iter_get_arg_type(&ai
) == DBUS_TYPE_OBJECT_PATH
&&
322 pa_streq(key
, "Devices")) {
324 while (dbus_message_iter_get_arg_type(&ai
) != DBUS_TYPE_INVALID
) {
327 dbus_message_iter_get_basic(&ai
, &value
);
329 found_device(y
, value
);
331 dbus_message_iter_next(&ai
);
342 static int parse_device_property(pa_bluetooth_device
*d
, DBusMessageIter
*i
) {
344 DBusMessageIter variant_i
;
348 key
= check_variant_property(i
);
352 dbus_message_iter_recurse(i
, &variant_i
);
354 /* pa_log_debug("Parsing property org.bluez.Device.%s", key); */
356 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
358 case DBUS_TYPE_STRING
: {
361 dbus_message_iter_get_basic(&variant_i
, &value
);
363 if (pa_streq(key
, "Name")) {
365 d
->name
= pa_xstrdup(value
);
366 } else if (pa_streq(key
, "Alias")) {
368 d
->alias
= pa_xstrdup(value
);
369 } else if (pa_streq(key
, "Address")) {
370 pa_xfree(d
->address
);
371 d
->address
= pa_xstrdup(value
);
374 /* pa_log_debug("Value %s", value); */
379 case DBUS_TYPE_BOOLEAN
: {
382 dbus_message_iter_get_basic(&variant_i
, &value
);
384 if (pa_streq(key
, "Paired"))
386 else if (pa_streq(key
, "Trusted"))
387 d
->trusted
= !!value
;
389 /* pa_log_debug("Value %s", pa_yes_no(value)); */
394 case DBUS_TYPE_UINT32
: {
397 dbus_message_iter_get_basic(&variant_i
, &value
);
399 if (pa_streq(key
, "Class"))
400 d
->class = (int) value
;
402 /* pa_log_debug("Value %u", (unsigned) value); */
407 case DBUS_TYPE_ARRAY
: {
410 dbus_message_iter_recurse(&variant_i
, &ai
);
412 if (dbus_message_iter_get_arg_type(&ai
) == DBUS_TYPE_STRING
&& pa_streq(key
, "UUIDs")) {
414 pa_bool_t has_audio
= false;
416 while (dbus_message_iter_get_arg_type(&ai
) != DBUS_TYPE_INVALID
) {
417 pa_bluetooth_uuid
*node
;
419 struct pa_bluetooth_hook_uuid_data uuiddata
;
421 dbus_message_iter_get_basic(&ai
, &value
);
423 if (pa_bluetooth_uuid_has(d
->uuids
, value
)) {
424 dbus_message_iter_next(&ai
);
428 node
= uuid_new(value
);
429 PA_LLIST_PREPEND(pa_bluetooth_uuid
, d
->uuids
, node
);
432 uuiddata
.uuid
= value
;
433 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED
], &uuiddata
);
435 /* Vudentz said the interfaces are here when the UUIDs are announced */
436 if (strcasecmp(HSP_AG_UUID
, value
) == 0 || strcasecmp(HFP_AG_UUID
, value
) == 0) {
437 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.HandsfreeGateway",
439 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
441 } else if (strcasecmp(HSP_HS_UUID
, value
) == 0 || strcasecmp(HFP_HS_UUID
, value
) == 0) {
442 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.Headset",
444 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
446 } else if (strcasecmp(A2DP_SINK_UUID
, value
) == 0) {
447 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.AudioSink",
449 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
451 } else if (strcasecmp(A2DP_SOURCE_UUID
, value
) == 0) {
452 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.AudioSource",
454 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
458 dbus_message_iter_next(&ai
);
461 /* this might eventually be racy if .Audio is not there yet, but
462 the State change will come anyway later, so this call is for
463 cold-detection mostly */
465 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", d
->path
, "org.bluez.Audio", "GetProperties"));
466 send_and_add_to_pending(d
->discovery
, m
, get_properties_reply
, d
);
477 static const char *transport_state_to_string(pa_bluetooth_transport_state_t state
) {
479 case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
:
480 return "disconnected";
481 case PA_BLUETOOTH_TRANSPORT_STATE_IDLE
:
483 case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
:
487 pa_assert_not_reached();
490 static int parse_audio_property(pa_bluetooth_device
*d
, const char *interface
, DBusMessageIter
*i
) {
491 pa_bluetooth_transport
*transport
;
493 DBusMessageIter variant_i
;
494 bool is_audio_interface
;
495 enum profile p
= PROFILE_OFF
;
498 pa_assert(interface
);
501 if (!(is_audio_interface
= pa_streq(interface
, "org.bluez.Audio")))
502 if (profile_from_interface(interface
, &p
) < 0)
503 return 0; /* Interface not known so silently ignore property */
505 key
= check_variant_property(i
);
509 transport
= p
== PROFILE_OFF
? NULL
: d
->transports
[p
];
511 dbus_message_iter_recurse(i
, &variant_i
);
513 /* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
515 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
517 case DBUS_TYPE_STRING
: {
520 dbus_message_iter_get_basic(&variant_i
, &value
);
522 if (pa_streq(key
, "State")) {
523 pa_bt_audio_state_t state
= audio_state_from_string(value
);
524 pa_bluetooth_transport_state_t old_state
;
526 pa_log_debug("Device %s interface %s property 'State' changed to value '%s'", d
->path
, interface
, value
);
528 if (state
== PA_BT_AUDIO_STATE_INVALID
)
531 if (is_audio_interface
) {
532 d
->audio_state
= state
;
536 pa_assert(p
!= PROFILE_OFF
);
538 d
->profile_state
[p
] = state
;
543 old_state
= transport
->state
;
544 transport
->state
= audio_state_to_transport_state(state
);
546 if (transport
->state
!= old_state
) {
547 pa_log_debug("Transport %s (profile %s) changed state from %s to %s.", transport
->path
,
548 pa_bt_profile_to_string(transport
->profile
), transport_state_to_string(old_state
),
549 transport_state_to_string(transport
->state
));
551 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED
], transport
);
558 case DBUS_TYPE_UINT16
: {
561 dbus_message_iter_get_basic(&variant_i
, &value
);
563 if (pa_streq(key
, "MicrophoneGain")) {
566 pa_log_debug("dbus: property '%s' changed to value '%u'", key
, value
);
569 pa_log("Volume change does not have an associated transport");
573 if ((gain
= PA_MIN(value
, HSP_MAX_GAIN
)) == transport
->microphone_gain
)
576 transport
->microphone_gain
= gain
;
577 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED
], transport
);
578 } else if (pa_streq(key
, "SpeakerGain")) {
581 pa_log_debug("dbus: property '%s' changed to value '%u'", key
, value
);
584 pa_log("Volume change does not have an associated transport");
588 if ((gain
= PA_MIN(value
, HSP_MAX_GAIN
)) == transport
->speaker_gain
)
591 transport
->speaker_gain
= gain
;
592 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED
], transport
);
602 static void run_callback(pa_bluetooth_device
*d
, pa_bool_t dead
) {
605 if (d
->device_info_valid
!= 1)
609 pa_hook_fire(&d
->discovery
->hooks
[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED
], d
);
612 static void remove_all_devices(pa_bluetooth_discovery
*y
) {
613 pa_bluetooth_device
*d
;
617 while ((d
= pa_hashmap_steal_first(y
->devices
))) {
618 run_callback(d
, true);
623 static pa_bluetooth_device
*found_device(pa_bluetooth_discovery
*y
, const char* path
) {
625 pa_bluetooth_device
*d
;
630 d
= pa_hashmap_get(y
->devices
, path
);
634 d
= device_new(y
, path
);
636 pa_hashmap_put(y
->devices
, d
->path
, d
);
638 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Device", "GetProperties"));
639 send_and_add_to_pending(y
, m
, get_properties_reply
, d
);
641 /* Before we read the other properties (Audio, AudioSink, AudioSource,
642 * Headset) we wait that the UUID is read */
646 static void get_properties_reply(DBusPendingCall
*pending
, void *userdata
) {
648 DBusMessageIter arg_i
, element_i
;
650 pa_bluetooth_device
*d
;
651 pa_bluetooth_discovery
*y
;
653 bool old_any_connected
;
655 pa_assert_se(p
= userdata
);
656 pa_assert_se(y
= p
->context_data
);
657 pa_assert_se(r
= dbus_pending_call_steal_reply(pending
));
659 /* pa_log_debug("Got %s.GetProperties response for %s", */
660 /* dbus_message_get_interface(p->message), */
661 /* dbus_message_get_path(p->message)); */
663 /* We don't use p->call_data here right-away since the device
664 * might already be invalidated at this point */
666 if (dbus_message_has_interface(p
->message
, "org.bluez.Manager") ||
667 dbus_message_has_interface(p
->message
, "org.bluez.Adapter"))
670 d
= pa_hashmap_get(y
->devices
, dbus_message_get_path(p
->message
));
672 pa_assert(p
->call_data
== d
);
675 old_any_connected
= pa_bluetooth_device_any_audio_connected(d
);
677 valid
= dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
? -1 : 1;
679 if (dbus_message_is_method_call(p
->message
, "org.bluez.Device", "GetProperties"))
680 d
->device_info_valid
= valid
;
682 if (dbus_message_is_error(r
, DBUS_ERROR_SERVICE_UNKNOWN
)) {
683 pa_log_debug("Bluetooth daemon is apparently not available.");
684 remove_all_devices(y
);
688 if (dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
) {
689 pa_log("%s.GetProperties() failed: %s: %s", dbus_message_get_interface(p
->message
), dbus_message_get_error_name(r
),
690 pa_dbus_get_error_message(r
));
694 if (!dbus_message_iter_init(r
, &arg_i
)) {
695 pa_log("GetProperties reply has no arguments.");
699 if (dbus_message_iter_get_arg_type(&arg_i
) != DBUS_TYPE_ARRAY
) {
700 pa_log("GetProperties argument is not an array.");
704 dbus_message_iter_recurse(&arg_i
, &element_i
);
705 while (dbus_message_iter_get_arg_type(&element_i
) != DBUS_TYPE_INVALID
) {
707 if (dbus_message_iter_get_arg_type(&element_i
) == DBUS_TYPE_DICT_ENTRY
) {
708 DBusMessageIter dict_i
;
710 dbus_message_iter_recurse(&element_i
, &dict_i
);
712 if (dbus_message_has_interface(p
->message
, "org.bluez.Manager")) {
713 if (parse_manager_property(y
, &dict_i
) < 0)
716 } else if (dbus_message_has_interface(p
->message
, "org.bluez.Adapter")) {
717 if (parse_adapter_property(y
, &dict_i
) < 0)
720 } else if (dbus_message_has_interface(p
->message
, "org.bluez.Device")) {
721 if (parse_device_property(d
, &dict_i
) < 0)
724 } else if (parse_audio_property(d
, dbus_message_get_interface(p
->message
), &dict_i
) < 0)
729 dbus_message_iter_next(&element_i
);
733 if (d
!= NULL
&& old_any_connected
!= pa_bluetooth_device_any_audio_connected(d
))
734 run_callback(d
, false);
737 dbus_message_unref(r
);
739 PA_LLIST_REMOVE(pa_dbus_pending
, y
->pending
, p
);
740 pa_dbus_pending_free(p
);
743 static pa_dbus_pending
* send_and_add_to_pending(pa_bluetooth_discovery
*y
, DBusMessage
*m
, DBusPendingCallNotifyFunction func
,
746 DBusPendingCall
*call
;
751 pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y
->connection
), m
, &call
, -1));
753 p
= pa_dbus_pending_new(pa_dbus_connection_get(y
->connection
), m
, call
, y
, call_data
);
754 PA_LLIST_PREPEND(pa_dbus_pending
, y
->pending
, p
);
755 dbus_pending_call_set_notify(call
, func
, p
, NULL
);
760 static void register_endpoint_reply(DBusPendingCall
*pending
, void *userdata
) {
764 pa_bluetooth_discovery
*y
;
771 pa_assert_se(p
= userdata
);
772 pa_assert_se(y
= p
->context_data
);
773 pa_assert_se(endpoint
= p
->call_data
);
774 pa_assert_se(r
= dbus_pending_call_steal_reply(pending
));
776 if (dbus_message_is_error(r
, DBUS_ERROR_SERVICE_UNKNOWN
)) {
777 pa_log_debug("Bluetooth daemon is apparently not available.");
778 remove_all_devices(y
);
782 if (dbus_message_is_error(r
, PA_BLUETOOTH_ERROR_NOT_SUPPORTED
)) {
783 pa_log_info("Couldn't register endpoint %s, because BlueZ is configured to disable the endpoint type.", endpoint
);
787 if (dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
) {
788 pa_log("org.bluez.Media.RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r
),
789 pa_dbus_get_error_message(r
));
794 dbus_message_unref(r
);
796 PA_LLIST_REMOVE(pa_dbus_pending
, y
->pending
, p
);
797 pa_dbus_pending_free(p
);
802 static void register_endpoint(pa_bluetooth_discovery
*y
, const char *path
, const char *endpoint
, const char *uuid
) {
804 DBusMessageIter i
, d
;
807 pa_log_debug("Registering %s on adapter %s.", endpoint
, path
);
809 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Media", "RegisterEndpoint"));
811 dbus_message_iter_init_append(m
, &i
);
813 dbus_message_iter_append_basic(&i
, DBUS_TYPE_OBJECT_PATH
, &endpoint
);
815 dbus_message_iter_open_container(&i
, DBUS_TYPE_ARRAY
, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
816 DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING
,
819 pa_dbus_append_basic_variant_dict_entry(&d
, "UUID", DBUS_TYPE_STRING
, &uuid
);
821 pa_dbus_append_basic_variant_dict_entry(&d
, "Codec", DBUS_TYPE_BYTE
, &codec
);
823 if (pa_streq(uuid
, HFP_AG_UUID
) || pa_streq(uuid
, HFP_HS_UUID
)) {
824 uint8_t capability
= 0;
825 pa_dbus_append_basic_array_variant_dict_entry(&d
, "Capabilities", DBUS_TYPE_BYTE
, &capability
, 1);
827 a2dp_sbc_t capabilities
;
829 capabilities
.channel_mode
= SBC_CHANNEL_MODE_MONO
| SBC_CHANNEL_MODE_DUAL_CHANNEL
|
830 SBC_CHANNEL_MODE_STEREO
| SBC_CHANNEL_MODE_JOINT_STEREO
;
831 capabilities
.frequency
= SBC_SAMPLING_FREQ_16000
| SBC_SAMPLING_FREQ_32000
|
832 SBC_SAMPLING_FREQ_44100
| SBC_SAMPLING_FREQ_48000
;
833 capabilities
.allocation_method
= SBC_ALLOCATION_SNR
| SBC_ALLOCATION_LOUDNESS
;
834 capabilities
.subbands
= SBC_SUBBANDS_4
| SBC_SUBBANDS_8
;
835 capabilities
.block_length
= SBC_BLOCK_LENGTH_4
| SBC_BLOCK_LENGTH_8
|
836 SBC_BLOCK_LENGTH_12
| SBC_BLOCK_LENGTH_16
;
837 capabilities
.min_bitpool
= MIN_BITPOOL
;
838 capabilities
.max_bitpool
= MAX_BITPOOL
;
840 pa_dbus_append_basic_array_variant_dict_entry(&d
, "Capabilities", DBUS_TYPE_BYTE
, &capabilities
, sizeof(capabilities
));
843 dbus_message_iter_close_container(&i
, &d
);
845 send_and_add_to_pending(y
, m
, register_endpoint_reply
, pa_xstrdup(endpoint
));
848 static void found_adapter(pa_bluetooth_discovery
*y
, const char *path
) {
851 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Adapter", "GetProperties"));
852 send_and_add_to_pending(y
, m
, get_properties_reply
, NULL
);
854 register_endpoint(y
, path
, HFP_AG_ENDPOINT
, HFP_AG_UUID
);
855 register_endpoint(y
, path
, HFP_HS_ENDPOINT
, HFP_HS_UUID
);
856 register_endpoint(y
, path
, A2DP_SOURCE_ENDPOINT
, A2DP_SOURCE_UUID
);
857 register_endpoint(y
, path
, A2DP_SINK_ENDPOINT
, A2DP_SINK_UUID
);
860 static void list_adapters(pa_bluetooth_discovery
*y
) {
864 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "GetProperties"));
865 send_and_add_to_pending(y
, m
, get_properties_reply
, NULL
);
868 static int transport_parse_property(pa_bluetooth_transport
*t
, DBusMessageIter
*i
) {
870 DBusMessageIter variant_i
;
872 key
= check_variant_property(i
);
876 dbus_message_iter_recurse(i
, &variant_i
);
878 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
880 case DBUS_TYPE_BOOLEAN
: {
883 dbus_message_iter_get_basic(&variant_i
, &value
);
885 if (pa_streq(key
, "NREC") && t
->nrec
!= value
) {
887 pa_log_debug("Transport %s: Property 'NREC' changed to %s.", t
->path
, t
->nrec
? "True" : "False");
888 pa_hook_fire(&t
->device
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED
], t
);
898 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*m
, void *userdata
) {
900 pa_bluetooth_discovery
*y
;
905 pa_assert_se(y
= userdata
);
907 dbus_error_init(&err
);
909 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
910 dbus_message_get_interface(m
),
911 dbus_message_get_path(m
),
912 dbus_message_get_member(m
));
914 if (dbus_message_is_signal(m
, "org.bluez.Adapter", "DeviceRemoved")) {
916 pa_bluetooth_device
*d
;
918 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
919 pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err
.message
);
923 pa_log_debug("Device %s removed", path
);
925 if ((d
= pa_hashmap_remove(y
->devices
, path
))) {
926 run_callback(d
, true);
930 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
932 } else if (dbus_message_is_signal(m
, "org.bluez.Adapter", "DeviceCreated")) {
935 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
936 pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err
.message
);
940 pa_log_debug("Device %s created", path
);
942 found_device(y
, path
);
943 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
945 } else if (dbus_message_is_signal(m
, "org.bluez.Manager", "AdapterAdded")) {
948 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
949 pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err
.message
);
953 pa_log_debug("Adapter %s created", path
);
955 found_adapter(y
, path
);
956 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
958 } else if (dbus_message_is_signal(m
, "org.bluez.Audio", "PropertyChanged") ||
959 dbus_message_is_signal(m
, "org.bluez.Headset", "PropertyChanged") ||
960 dbus_message_is_signal(m
, "org.bluez.AudioSink", "PropertyChanged") ||
961 dbus_message_is_signal(m
, "org.bluez.AudioSource", "PropertyChanged") ||
962 dbus_message_is_signal(m
, "org.bluez.HandsfreeGateway", "PropertyChanged") ||
963 dbus_message_is_signal(m
, "org.bluez.Device", "PropertyChanged")) {
965 pa_bluetooth_device
*d
;
967 if ((d
= pa_hashmap_get(y
->devices
, dbus_message_get_path(m
)))) {
968 DBusMessageIter arg_i
;
969 bool old_any_connected
= pa_bluetooth_device_any_audio_connected(d
);
971 if (!dbus_message_iter_init(m
, &arg_i
)) {
972 pa_log("Failed to parse PropertyChanged for device %s", d
->path
);
976 if (dbus_message_has_interface(m
, "org.bluez.Device")) {
977 if (parse_device_property(d
, &arg_i
) < 0)
980 } else if (parse_audio_property(d
, dbus_message_get_interface(m
), &arg_i
) < 0)
983 if (old_any_connected
!= pa_bluetooth_device_any_audio_connected(d
))
984 run_callback(d
, false);
987 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
989 } else if (dbus_message_is_signal(m
, "org.freedesktop.DBus", "NameOwnerChanged")) {
990 const char *name
, *old_owner
, *new_owner
;
992 if (!dbus_message_get_args(m
, &err
,
993 DBUS_TYPE_STRING
, &name
,
994 DBUS_TYPE_STRING
, &old_owner
,
995 DBUS_TYPE_STRING
, &new_owner
,
996 DBUS_TYPE_INVALID
)) {
997 pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err
.message
);
1001 if (pa_streq(name
, "org.bluez")) {
1002 if (old_owner
&& *old_owner
) {
1003 pa_log_debug("Bluetooth daemon disappeared.");
1004 remove_all_devices(y
);
1007 if (new_owner
&& *new_owner
) {
1008 pa_log_debug("Bluetooth daemon appeared.");
1013 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1014 } else if (dbus_message_is_signal(m
, "org.bluez.MediaTransport", "PropertyChanged")) {
1015 pa_bluetooth_transport
*t
;
1016 DBusMessageIter arg_i
;
1018 if (!(t
= pa_hashmap_get(y
->transports
, dbus_message_get_path(m
))))
1021 if (!dbus_message_iter_init(m
, &arg_i
)) {
1022 pa_log("Failed to parse PropertyChanged for transport %s", t
->path
);
1026 if (transport_parse_property(t
, &arg_i
) < 0)
1029 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1033 dbus_error_free(&err
);
1035 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1038 pa_bluetooth_device
* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery
*y
, const char* address
) {
1039 pa_bluetooth_device
*d
;
1043 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1046 while ((d
= pa_hashmap_iterate(y
->devices
, &state
, NULL
)))
1047 if (pa_streq(d
->address
, address
))
1048 return d
->device_info_valid
== 1 ? d
: NULL
;
1053 pa_bluetooth_device
* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery
*y
, const char* path
) {
1054 pa_bluetooth_device
*d
;
1057 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1060 if ((d
= pa_hashmap_get(y
->devices
, path
)))
1061 if (d
->device_info_valid
== 1)
1067 bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device
*d
) {
1072 if (d
->dead
|| d
->device_info_valid
!= 1)
1075 if (d
->audio_state
== PA_BT_AUDIO_STATE_INVALID
)
1078 /* Make sure audio_state is *not* in CONNECTING state before we fire the
1079 * hook to report the new device state. This is actually very important in
1080 * order to make module-card-restore work well with headsets: if the headset
1081 * supports both HSP and A2DP, one of those profiles is connected first and
1082 * then the other, and lastly the Audio interface becomes connected.
1083 * Checking only audio_state means that this function will return false at
1084 * the time when only the first connection has been made. This is good,
1085 * because otherwise, if the first connection is for HSP and we would
1086 * already load a new device module instance, and module-card-restore tries
1087 * to restore the A2DP profile, that would fail because A2DP is not yet
1088 * connected. Waiting until the Audio interface gets connected means that
1089 * both headset profiles will be connected when the device module is
1091 if (d
->audio_state
== PA_BT_AUDIO_STATE_CONNECTING
)
1094 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++)
1095 if (d
->transports
[i
])
1101 int pa_bluetooth_transport_acquire(pa_bluetooth_transport
*t
, bool optional
, size_t *imtu
, size_t *omtu
) {
1102 const char *accesstype
= "rw";
1109 pa_assert(t
->device
);
1110 pa_assert(t
->device
->discovery
);
1113 /* FIXME: we are trying to acquire the transport only if the stream is
1114 playing, without actually initiating the stream request from our side
1115 (which is typically undesireable specially for hfgw use-cases.
1116 However this approach is racy, since the stream could have been
1117 suspended in the meantime, so we can't really guarantee that the
1118 stream will not be requested until BlueZ's API supports this
1120 if (t
->state
< PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
) {
1121 pa_log_info("Failed optional acquire of transport %s", t
->path
);
1126 dbus_error_init(&err
);
1128 pa_assert_se(m
= dbus_message_new_method_call(t
->owner
, t
->path
, "org.bluez.MediaTransport", "Acquire"));
1129 pa_assert_se(dbus_message_append_args(m
, DBUS_TYPE_STRING
, &accesstype
, DBUS_TYPE_INVALID
));
1130 r
= dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t
->device
->discovery
->connection
), m
, -1, &err
);
1132 if (dbus_error_is_set(&err
) || !r
) {
1133 dbus_error_free(&err
);
1137 if (!dbus_message_get_args(r
, &err
, DBUS_TYPE_UNIX_FD
, &ret
, DBUS_TYPE_UINT16
, &i
, DBUS_TYPE_UINT16
, &o
,
1138 DBUS_TYPE_INVALID
)) {
1139 pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err
.message
);
1141 dbus_error_free(&err
);
1152 dbus_message_unref(r
);
1156 void pa_bluetooth_transport_release(pa_bluetooth_transport
*t
) {
1157 const char *accesstype
= "rw";
1162 pa_assert(t
->device
);
1163 pa_assert(t
->device
->discovery
);
1165 dbus_error_init(&err
);
1167 pa_assert_se(m
= dbus_message_new_method_call(t
->owner
, t
->path
, "org.bluez.MediaTransport", "Release"));
1168 pa_assert_se(dbus_message_append_args(m
, DBUS_TYPE_STRING
, &accesstype
, DBUS_TYPE_INVALID
));
1169 dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t
->device
->discovery
->connection
), m
, -1, &err
);
1171 if (dbus_error_is_set(&err
)) {
1172 pa_log("Failed to release transport %s: %s", t
->path
, err
.message
);
1173 dbus_error_free(&err
);
1175 pa_log_info("Transport %s released", t
->path
);
1178 static void set_property(pa_bluetooth_discovery
*y
, const char *bus
, const char *path
, const char *interface
,
1179 const char *prop_name
, int prop_type
, void *prop_value
) {
1185 pa_assert(interface
);
1186 pa_assert(prop_name
);
1188 pa_assert_se(m
= dbus_message_new_method_call(bus
, path
, interface
, "SetProperty"));
1189 dbus_message_iter_init_append(m
, &i
);
1190 dbus_message_iter_append_basic(&i
, DBUS_TYPE_STRING
, &prop_name
);
1191 pa_dbus_append_basic_variant(&i
, prop_type
, prop_value
);
1193 dbus_message_set_no_reply(m
, true);
1194 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y
->connection
), m
, NULL
));
1195 dbus_message_unref(m
);
1198 void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport
*t
, uint16_t value
) {
1199 dbus_uint16_t gain
= PA_MIN(value
, HSP_MAX_GAIN
);
1202 pa_assert(t
->profile
== PROFILE_HSP
);
1204 set_property(t
->device
->discovery
, "org.bluez", t
->device
->path
, "org.bluez.Headset",
1205 "MicrophoneGain", DBUS_TYPE_UINT16
, &gain
);
1208 void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport
*t
, uint16_t value
) {
1209 dbus_uint16_t gain
= PA_MIN(value
, HSP_MAX_GAIN
);
1212 pa_assert(t
->profile
== PROFILE_HSP
);
1214 set_property(t
->device
->discovery
, "org.bluez", t
->device
->path
, "org.bluez.Headset",
1215 "SpeakerGain", DBUS_TYPE_UINT16
, &gain
);
1218 static int setup_dbus(pa_bluetooth_discovery
*y
) {
1221 dbus_error_init(&err
);
1223 y
->connection
= pa_dbus_bus_get(y
->core
, DBUS_BUS_SYSTEM
, &err
);
1225 if (dbus_error_is_set(&err
) || !y
->connection
) {
1226 pa_log("Failed to get D-Bus connection: %s", err
.message
);
1227 dbus_error_free(&err
);
1234 static pa_bluetooth_transport
*transport_new(pa_bluetooth_device
*d
, const char *owner
, const char *path
, enum profile p
,
1235 const uint8_t *config
, int size
) {
1236 pa_bluetooth_transport
*t
;
1238 t
= pa_xnew0(pa_bluetooth_transport
, 1);
1240 t
->owner
= pa_xstrdup(owner
);
1241 t
->path
= pa_xstrdup(path
);
1243 t
->config_size
= size
;
1246 t
->config
= pa_xnew(uint8_t, size
);
1247 memcpy(t
->config
, config
, size
);
1250 t
->state
= audio_state_to_transport_state(d
->profile_state
[p
]);
1255 static DBusMessage
*endpoint_set_configuration(DBusConnection
*conn
, DBusMessage
*m
, void *userdata
) {
1256 pa_bluetooth_discovery
*y
= userdata
;
1257 pa_bluetooth_device
*d
;
1258 pa_bluetooth_transport
*t
;
1259 const char *sender
, *path
, *dev_path
= NULL
, *uuid
= NULL
;
1260 uint8_t *config
= NULL
;
1262 pa_bool_t nrec
= false;
1264 DBusMessageIter args
, props
;
1266 bool old_any_connected
;
1268 if (!dbus_message_iter_init(m
, &args
) || !pa_streq(dbus_message_get_signature(m
), "oa{sv}")) {
1269 pa_log("Invalid signature for method SetConfiguration");
1273 dbus_message_iter_get_basic(&args
, &path
);
1275 if (pa_hashmap_get(y
->transports
, path
)) {
1276 pa_log("org.bluez.MediaEndpoint.SetConfiguration: Transport %s is already configured.", path
);
1280 pa_assert_se(dbus_message_iter_next(&args
));
1282 dbus_message_iter_recurse(&args
, &props
);
1283 if (dbus_message_iter_get_arg_type(&props
) != DBUS_TYPE_DICT_ENTRY
)
1286 /* Read transport properties */
1287 while (dbus_message_iter_get_arg_type(&props
) == DBUS_TYPE_DICT_ENTRY
) {
1289 DBusMessageIter value
, entry
;
1292 dbus_message_iter_recurse(&props
, &entry
);
1293 dbus_message_iter_get_basic(&entry
, &key
);
1295 dbus_message_iter_next(&entry
);
1296 dbus_message_iter_recurse(&entry
, &value
);
1298 var
= dbus_message_iter_get_arg_type(&value
);
1300 if (strcasecmp(key
, "UUID") == 0) {
1301 if (var
!= DBUS_TYPE_STRING
)
1304 dbus_message_iter_get_basic(&value
, &uuid
);
1305 } else if (strcasecmp(key
, "Device") == 0) {
1306 if (var
!= DBUS_TYPE_OBJECT_PATH
)
1309 dbus_message_iter_get_basic(&value
, &dev_path
);
1310 } else if (strcasecmp(key
, "NREC") == 0) {
1311 dbus_bool_t tmp_boolean
;
1312 if (var
!= DBUS_TYPE_BOOLEAN
)
1315 dbus_message_iter_get_basic(&value
, &tmp_boolean
);
1317 } else if (strcasecmp(key
, "Configuration") == 0) {
1318 DBusMessageIter array
;
1319 if (var
!= DBUS_TYPE_ARRAY
)
1322 dbus_message_iter_recurse(&value
, &array
);
1323 dbus_message_iter_get_fixed_array(&array
, &config
, &size
);
1326 dbus_message_iter_next(&props
);
1329 d
= found_device(y
, dev_path
);
1333 if (dbus_message_has_path(m
, HFP_AG_ENDPOINT
))
1335 else if (dbus_message_has_path(m
, HFP_HS_ENDPOINT
))
1337 else if (dbus_message_has_path(m
, A2DP_SOURCE_ENDPOINT
))
1340 p
= PROFILE_A2DP_SOURCE
;
1342 if (d
->transports
[p
] != NULL
) {
1343 pa_log("Cannot configure transport %s because profile %d is already used", path
, p
);
1347 old_any_connected
= pa_bluetooth_device_any_audio_connected(d
);
1349 sender
= dbus_message_get_sender(m
);
1351 t
= transport_new(d
, sender
, path
, p
, config
, size
);
1355 d
->transports
[p
] = t
;
1356 pa_assert_se(pa_hashmap_put(y
->transports
, t
->path
, t
) >= 0);
1358 pa_log_debug("Transport %s profile %d available", t
->path
, t
->profile
);
1360 pa_assert_se(r
= dbus_message_new_method_return(m
));
1362 if (old_any_connected
!= pa_bluetooth_device_any_audio_connected(d
))
1363 run_callback(d
, false);
1368 pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments");
1371 pa_assert_se(r
= dbus_message_new_error(m
, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1372 "Unable to set configuration"));
1376 static DBusMessage
*endpoint_clear_configuration(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
1377 pa_bluetooth_discovery
*y
= userdata
;
1378 pa_bluetooth_transport
*t
;
1383 dbus_error_init(&e
);
1385 if (!dbus_message_get_args(m
, &e
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
1386 pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e
.message
);
1387 dbus_error_free(&e
);
1391 if ((t
= pa_hashmap_get(y
->transports
, path
))) {
1392 bool old_any_connected
= pa_bluetooth_device_any_audio_connected(t
->device
);
1394 pa_log_debug("Clearing transport %s profile %d", t
->path
, t
->profile
);
1395 t
->device
->transports
[t
->profile
] = NULL
;
1396 pa_hashmap_remove(y
->transports
, t
->path
);
1397 t
->state
= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
;
1398 pa_hook_fire(&y
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED
], t
);
1400 if (old_any_connected
!= pa_bluetooth_device_any_audio_connected(t
->device
))
1401 run_callback(t
->device
, false);
1406 pa_assert_se(r
= dbus_message_new_method_return(m
));
1411 pa_assert_se(r
= dbus_message_new_error(m
, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1412 "Unable to clear configuration"));
1416 static uint8_t a2dp_default_bitpool(uint8_t freq
, uint8_t mode
) {
1419 case SBC_SAMPLING_FREQ_16000
:
1420 case SBC_SAMPLING_FREQ_32000
:
1423 case SBC_SAMPLING_FREQ_44100
:
1426 case SBC_CHANNEL_MODE_MONO
:
1427 case SBC_CHANNEL_MODE_DUAL_CHANNEL
:
1430 case SBC_CHANNEL_MODE_STEREO
:
1431 case SBC_CHANNEL_MODE_JOINT_STEREO
:
1435 pa_log_warn("Invalid channel mode %u", mode
);
1439 case SBC_SAMPLING_FREQ_48000
:
1442 case SBC_CHANNEL_MODE_MONO
:
1443 case SBC_CHANNEL_MODE_DUAL_CHANNEL
:
1446 case SBC_CHANNEL_MODE_STEREO
:
1447 case SBC_CHANNEL_MODE_JOINT_STEREO
:
1451 pa_log_warn("Invalid channel mode %u", mode
);
1456 pa_log_warn("Invalid sampling freq %u", freq
);
1461 static DBusMessage
*endpoint_select_configuration(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
1462 pa_bluetooth_discovery
*y
= userdata
;
1463 a2dp_sbc_t
*cap
, config
;
1464 uint8_t *pconf
= (uint8_t *) &config
;
1469 static const struct {
1473 { 16000U, SBC_SAMPLING_FREQ_16000
},
1474 { 32000U, SBC_SAMPLING_FREQ_32000
},
1475 { 44100U, SBC_SAMPLING_FREQ_44100
},
1476 { 48000U, SBC_SAMPLING_FREQ_48000
}
1479 dbus_error_init(&e
);
1481 if (!dbus_message_get_args(m
, &e
, DBUS_TYPE_ARRAY
, DBUS_TYPE_BYTE
, &cap
, &size
, DBUS_TYPE_INVALID
)) {
1482 pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e
.message
);
1483 dbus_error_free(&e
);
1487 if (dbus_message_has_path(m
, HFP_AG_ENDPOINT
) || dbus_message_has_path(m
, HFP_HS_ENDPOINT
))
1490 pa_assert(size
== sizeof(config
));
1492 memset(&config
, 0, sizeof(config
));
1494 /* Find the lowest freq that is at least as high as the requested
1496 for (i
= 0; (unsigned) i
< PA_ELEMENTSOF(freq_table
); i
++)
1497 if (freq_table
[i
].rate
>= y
->core
->default_sample_spec
.rate
&& (cap
->frequency
& freq_table
[i
].cap
)) {
1498 config
.frequency
= freq_table
[i
].cap
;
1502 if ((unsigned) i
== PA_ELEMENTSOF(freq_table
)) {
1503 for (--i
; i
>= 0; i
--) {
1504 if (cap
->frequency
& freq_table
[i
].cap
) {
1505 config
.frequency
= freq_table
[i
].cap
;
1511 pa_log("Not suitable sample rate");
1516 pa_assert((unsigned) i
< PA_ELEMENTSOF(freq_table
));
1518 if (y
->core
->default_sample_spec
.channels
<= 1) {
1519 if (cap
->channel_mode
& SBC_CHANNEL_MODE_MONO
)
1520 config
.channel_mode
= SBC_CHANNEL_MODE_MONO
;
1523 if (y
->core
->default_sample_spec
.channels
>= 2) {
1524 if (cap
->channel_mode
& SBC_CHANNEL_MODE_JOINT_STEREO
)
1525 config
.channel_mode
= SBC_CHANNEL_MODE_JOINT_STEREO
;
1526 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_STEREO
)
1527 config
.channel_mode
= SBC_CHANNEL_MODE_STEREO
;
1528 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_DUAL_CHANNEL
)
1529 config
.channel_mode
= SBC_CHANNEL_MODE_DUAL_CHANNEL
;
1530 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_MONO
) {
1531 config
.channel_mode
= SBC_CHANNEL_MODE_MONO
;
1533 pa_log("No supported channel modes");
1538 if (cap
->block_length
& SBC_BLOCK_LENGTH_16
)
1539 config
.block_length
= SBC_BLOCK_LENGTH_16
;
1540 else if (cap
->block_length
& SBC_BLOCK_LENGTH_12
)
1541 config
.block_length
= SBC_BLOCK_LENGTH_12
;
1542 else if (cap
->block_length
& SBC_BLOCK_LENGTH_8
)
1543 config
.block_length
= SBC_BLOCK_LENGTH_8
;
1544 else if (cap
->block_length
& SBC_BLOCK_LENGTH_4
)
1545 config
.block_length
= SBC_BLOCK_LENGTH_4
;
1547 pa_log_error("No supported block lengths");
1551 if (cap
->subbands
& SBC_SUBBANDS_8
)
1552 config
.subbands
= SBC_SUBBANDS_8
;
1553 else if (cap
->subbands
& SBC_SUBBANDS_4
)
1554 config
.subbands
= SBC_SUBBANDS_4
;
1556 pa_log_error("No supported subbands");
1560 if (cap
->allocation_method
& SBC_ALLOCATION_LOUDNESS
)
1561 config
.allocation_method
= SBC_ALLOCATION_LOUDNESS
;
1562 else if (cap
->allocation_method
& SBC_ALLOCATION_SNR
)
1563 config
.allocation_method
= SBC_ALLOCATION_SNR
;
1565 config
.min_bitpool
= (uint8_t) PA_MAX(MIN_BITPOOL
, cap
->min_bitpool
);
1566 config
.max_bitpool
= (uint8_t) PA_MIN(a2dp_default_bitpool(config
.frequency
, config
.channel_mode
), cap
->max_bitpool
);
1569 pa_assert_se(r
= dbus_message_new_method_return(m
));
1571 pa_assert_se(dbus_message_append_args(
1573 DBUS_TYPE_ARRAY
, DBUS_TYPE_BYTE
, &pconf
, size
,
1574 DBUS_TYPE_INVALID
));
1579 pa_assert_se(r
= dbus_message_new_error(m
, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1580 "Unable to select configuration"));
1584 static DBusHandlerResult
endpoint_handler(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
1585 struct pa_bluetooth_discovery
*y
= userdata
;
1586 DBusMessage
*r
= NULL
;
1592 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
1593 dbus_message_get_interface(m
),
1594 dbus_message_get_path(m
),
1595 dbus_message_get_member(m
));
1597 path
= dbus_message_get_path(m
);
1598 dbus_error_init(&e
);
1600 if (!pa_streq(path
, A2DP_SOURCE_ENDPOINT
) && !pa_streq(path
, A2DP_SINK_ENDPOINT
) && !pa_streq(path
, HFP_AG_ENDPOINT
) &&
1601 !pa_streq(path
, HFP_HS_ENDPOINT
))
1602 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1604 if (dbus_message_is_method_call(m
, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1605 const char *xml
= ENDPOINT_INTROSPECT_XML
;
1607 pa_assert_se(r
= dbus_message_new_method_return(m
));
1608 pa_assert_se(dbus_message_append_args(
1610 DBUS_TYPE_STRING
, &xml
,
1611 DBUS_TYPE_INVALID
));
1613 } else if (dbus_message_is_method_call(m
, "org.bluez.MediaEndpoint", "SetConfiguration")) {
1614 r
= endpoint_set_configuration(c
, m
, userdata
);
1615 } else if (dbus_message_is_method_call(m
, "org.bluez.MediaEndpoint", "SelectConfiguration")) {
1616 r
= endpoint_select_configuration(c
, m
, userdata
);
1617 } else if (dbus_message_is_method_call(m
, "org.bluez.MediaEndpoint", "ClearConfiguration"))
1618 r
= endpoint_clear_configuration(c
, m
, userdata
);
1620 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
1623 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y
->connection
), r
, NULL
));
1624 dbus_message_unref(r
);
1627 return DBUS_HANDLER_RESULT_HANDLED
;
1630 pa_bluetooth_discovery
* pa_bluetooth_discovery_get(pa_core
*c
) {
1632 pa_bluetooth_discovery
*y
;
1633 DBusConnection
*conn
;
1635 static const DBusObjectPathVTable vtable_endpoint
= {
1636 .message_function
= endpoint_handler
,
1641 dbus_error_init(&err
);
1643 if ((y
= pa_shared_get(c
, "bluetooth-discovery")))
1644 return pa_bluetooth_discovery_ref(y
);
1646 y
= pa_xnew0(pa_bluetooth_discovery
, 1);
1649 y
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
1650 y
->transports
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
1651 PA_LLIST_HEAD_INIT(pa_dbus_pending
, y
->pending
);
1653 for (i
= 0; i
< PA_BLUETOOTH_HOOK_MAX
; i
++)
1654 pa_hook_init(&y
->hooks
[i
], y
);
1656 pa_shared_set(c
, "bluetooth-discovery", y
);
1658 if (setup_dbus(y
) < 0)
1661 conn
= pa_dbus_connection_get(y
->connection
);
1663 /* dynamic detection of bluetooth audio devices */
1664 if (!dbus_connection_add_filter(conn
, filter_cb
, y
, NULL
)) {
1665 pa_log_error("Failed to add filter function");
1669 y
->filter_added
= true;
1671 if (pa_dbus_add_matches(
1673 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
1674 ",arg0='org.bluez'",
1675 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1676 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1677 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1678 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1679 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1680 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1681 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1682 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1683 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1684 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1686 pa_log("Failed to add D-Bus matches: %s", err
.message
);
1690 pa_assert_se(dbus_connection_register_object_path(conn
, HFP_AG_ENDPOINT
, &vtable_endpoint
, y
));
1691 pa_assert_se(dbus_connection_register_object_path(conn
, HFP_HS_ENDPOINT
, &vtable_endpoint
, y
));
1692 pa_assert_se(dbus_connection_register_object_path(conn
, A2DP_SOURCE_ENDPOINT
, &vtable_endpoint
, y
));
1693 pa_assert_se(dbus_connection_register_object_path(conn
, A2DP_SINK_ENDPOINT
, &vtable_endpoint
, y
));
1701 pa_bluetooth_discovery_unref(y
);
1703 dbus_error_free(&err
);
1708 pa_bluetooth_discovery
* pa_bluetooth_discovery_ref(pa_bluetooth_discovery
*y
) {
1710 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1717 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery
*y
) {
1721 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1723 if (PA_REFCNT_DEC(y
) > 0)
1726 pa_dbus_free_pending_list(&y
->pending
);
1729 remove_all_devices(y
);
1730 pa_hashmap_free(y
->devices
, NULL
, NULL
);
1733 if (y
->transports
) {
1734 pa_assert(pa_hashmap_isempty(y
->transports
));
1735 pa_hashmap_free(y
->transports
, NULL
, NULL
);
1738 if (y
->connection
) {
1739 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), HFP_AG_ENDPOINT
);
1740 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), HFP_HS_ENDPOINT
);
1741 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SOURCE_ENDPOINT
);
1742 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SINK_ENDPOINT
);
1743 pa_dbus_remove_matches(
1744 pa_dbus_connection_get(y
->connection
),
1745 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
1746 ",arg0='org.bluez'",
1747 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1748 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
1749 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1750 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1751 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1752 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1753 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1754 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1755 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1756 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1757 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1760 if (y
->filter_added
)
1761 dbus_connection_remove_filter(pa_dbus_connection_get(y
->connection
), filter_cb
, y
);
1763 pa_dbus_connection_unref(y
->connection
);
1766 for (i
= 0; i
< PA_BLUETOOTH_HOOK_MAX
; i
++)
1767 pa_hook_done(&y
->hooks
[i
]);
1770 pa_shared_remove(y
->core
, "bluetooth-discovery");
1775 pa_hook
* pa_bluetooth_discovery_hook(pa_bluetooth_discovery
*y
, pa_bluetooth_hook_t hook
) {
1777 pa_assert(PA_REFCNT_VALUE(y
) > 0);
1779 return &y
->hooks
[hook
];
1782 const char*pa_bluetooth_get_form_factor(uint32_t class) {
1786 static const char * const table
[] = {
1797 if (((class >> 8) & 31) != 4)
1800 if ((i
= (class >> 2) & 63) > PA_ELEMENTSOF(table
))
1806 pa_log_debug("Unknown Bluetooth minor device class %u", i
);
1811 char *pa_bluetooth_cleanup_name(const char *name
) {
1813 pa_bool_t space
= false;
1817 while ((*name
>= 1 && *name
<= 32) || *name
>= 127)
1820 t
= pa_xstrdup(name
);
1822 for (s
= d
= t
; *s
; s
++) {
1824 if (*s
<= 32 || *s
>= 127 || *s
== '_') {
1842 pa_bool_t
pa_bluetooth_uuid_has(pa_bluetooth_uuid
*uuids
, const char *uuid
) {
1846 if (strcasecmp(uuids
->uuid
, uuid
) == 0)
1849 uuids
= uuids
->next
;