2 This file is part of PulseAudio.
4 Copyright 2008 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 <pulsecore/core-util.h>
27 #include <pulsecore/shared.h>
28 #include <modules/dbus-util.h>
30 #include "bluetooth-util.h"
32 struct pa_bluetooth_discovery
{
36 pa_dbus_connection
*connection
;
37 PA_LLIST_HEAD(pa_dbus_pending
, pending
);
42 static pa_bluetooth_uuid
*uuid_new(const char *uuid
) {
45 u
= pa_xnew(pa_bluetooth_uuid
, 1);
46 u
->uuid
= pa_xstrdup(uuid
);
47 PA_LLIST_INIT(pa_bluetooth_uuid
, u
);
52 static void uuid_free(pa_bluetooth_uuid
*u
) {
59 static pa_bluetooth_device
* device_new(const char *path
) {
60 pa_bluetooth_device
*d
;
62 d
= pa_xnew(pa_bluetooth_device
, 1);
66 d
->device_info_valid
= d
->audio_sink_info_valid
= d
->headset_info_valid
= 0;
69 d
->path
= pa_xstrdup(path
);
72 d
->device_connected
= -1;
73 PA_LLIST_HEAD_INIT(pa_bluetooth_uuid
, d
->uuids
);
78 d
->audio_sink_connected
= -1;
80 d
->headset_connected
= -1;
85 static void device_free(pa_bluetooth_device
*d
) {
90 while ((u
= d
->uuids
)) {
91 PA_LLIST_REMOVE(pa_bluetooth_uuid
, d
->uuids
, u
);
102 static pa_bool_t
device_is_loaded(pa_bluetooth_device
*d
) {
106 d
->device_info_valid
&&
107 d
->audio_sink_info_valid
&&
108 d
->headset_info_valid
;
111 static pa_bool_t
device_is_audio(pa_bluetooth_device
*d
) {
114 pa_assert(d
->device_info_valid
);
115 pa_assert(d
->audio_sink_info_valid
);
116 pa_assert(d
->headset_info_valid
);
119 d
->device_info_valid
> 0 &&
120 (d
->audio_sink_info_valid
> 0 || d
->headset_info_valid
> 0);
123 static int parse_device_property(pa_bluetooth_discovery
*y
, pa_bluetooth_device
*d
, DBusMessageIter
*i
) {
125 DBusMessageIter variant_i
;
131 if (dbus_message_iter_get_arg_type(i
) != DBUS_TYPE_STRING
) {
132 pa_log("Property name not a string.");
136 dbus_message_iter_get_basic(i
, &key
);
138 if (!dbus_message_iter_next(i
)) {
139 pa_log("Property value missing");
143 if (dbus_message_iter_get_arg_type(i
) != DBUS_TYPE_VARIANT
) {
144 pa_log("Property value not a variant.");
148 dbus_message_iter_recurse(i
, &variant_i
);
150 /* pa_log_debug("Parsing property org.bluez.Device.%s", key); */
152 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
154 case DBUS_TYPE_STRING
: {
157 dbus_message_iter_get_basic(&variant_i
, &value
);
159 if (pa_streq(key
, "Name")) {
161 d
->name
= pa_xstrdup(value
);
162 } else if (pa_streq(key
, "Alias")) {
164 d
->alias
= pa_xstrdup(value
);
165 } else if (pa_streq(key
, "Address")) {
166 pa_xfree(d
->address
);
167 d
->address
= pa_xstrdup(value
);
170 /* pa_log_debug("Value %s", value); */
175 case DBUS_TYPE_BOOLEAN
: {
178 dbus_message_iter_get_basic(&variant_i
, &value
);
180 if (pa_streq(key
, "Paired"))
182 else if (pa_streq(key
, "Connected"))
183 d
->device_connected
= !!value
;
184 else if (pa_streq(key
, "Trusted"))
185 d
->trusted
= !!value
;
187 /* pa_log_debug("Value %s", pa_yes_no(value)); */
192 case DBUS_TYPE_UINT32
: {
195 dbus_message_iter_get_basic(&variant_i
, &value
);
197 if (pa_streq(key
, "Class"))
198 d
->class = (int) value
;
200 /* pa_log_debug("Value %u", (unsigned) value); */
205 case DBUS_TYPE_ARRAY
: {
208 dbus_message_iter_recurse(&variant_i
, &ai
);
210 if (dbus_message_iter_get_arg_type(&ai
) == DBUS_TYPE_STRING
&&
211 pa_streq(key
, "UUIDs")) {
213 while (dbus_message_iter_get_arg_type(&ai
) != DBUS_TYPE_INVALID
) {
214 pa_bluetooth_uuid
*node
;
217 dbus_message_iter_get_basic(&ai
, &value
);
218 node
= uuid_new(value
);
219 PA_LLIST_PREPEND(pa_bluetooth_uuid
, d
->uuids
, node
);
221 if (!dbus_message_iter_next(&ai
))
233 static int parse_audio_property(pa_bluetooth_discovery
*u
, int *connected
, DBusMessageIter
*i
) {
235 DBusMessageIter variant_i
;
238 pa_assert(connected
);
241 if (dbus_message_iter_get_arg_type(i
) != DBUS_TYPE_STRING
) {
242 pa_log("Property name not a string.");
246 dbus_message_iter_get_basic(i
, &key
);
248 if (!dbus_message_iter_next(i
)) {
249 pa_log("Property value missing");
253 if (dbus_message_iter_get_arg_type(i
) != DBUS_TYPE_VARIANT
) {
254 pa_log("Property value not a variant.");
258 dbus_message_iter_recurse(i
, &variant_i
);
260 /* pa_log_debug("Parsing property org.bluez.{AudioSink|Headset}.%s", key); */
262 switch (dbus_message_iter_get_arg_type(&variant_i
)) {
264 case DBUS_TYPE_BOOLEAN
: {
267 dbus_message_iter_get_basic(&variant_i
, &value
);
269 if (pa_streq(key
, "Connected"))
270 *connected
= !!value
;
272 /* pa_log_debug("Value %s", pa_yes_no(value)); */
281 static void run_callback(pa_bluetooth_discovery
*y
, pa_bluetooth_device
*d
, pa_bool_t dead
) {
285 if (!device_is_loaded(d
))
288 if (!device_is_audio(d
))
292 pa_hook_fire(&y
->hook
, d
);
295 static void get_properties_reply(DBusPendingCall
*pending
, void *userdata
) {
297 DBusMessageIter arg_i
, element_i
;
299 pa_bluetooth_device
*d
;
300 pa_bluetooth_discovery
*y
;
303 pa_assert_se(p
= userdata
);
304 pa_assert_se(y
= p
->context_data
);
305 pa_assert_se(r
= dbus_pending_call_steal_reply(pending
));
307 /* pa_log_debug("Got %s.GetProperties response for %s", */
308 /* dbus_message_get_interface(p->message), */
309 /* dbus_message_get_path(p->message)); */
313 valid
= dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
? -1 : 1;
315 if (dbus_message_is_method_call(p
->message
, "org.bluez.Device", "GetProperties"))
316 d
->device_info_valid
= valid
;
317 else if (dbus_message_is_method_call(p
->message
, "org.bluez.Headset", "GetProperties"))
318 d
->headset_info_valid
= valid
;
319 else if (dbus_message_is_method_call(p
->message
, "org.bluez.AudioSink", "GetProperties"))
320 d
->audio_sink_info_valid
= valid
;
322 if (dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
) {
324 if (!dbus_message_is_error(r
, DBUS_ERROR_UNKNOWN_METHOD
))
325 pa_log("Error from GetProperties reply: %s", dbus_message_get_error_name(r
));
330 if (!dbus_message_iter_init(r
, &arg_i
)) {
331 pa_log("GetProperties reply has no arguments.");
335 if (dbus_message_iter_get_arg_type(&arg_i
) != DBUS_TYPE_ARRAY
) {
336 pa_log("GetProperties argument is not an array.");
340 dbus_message_iter_recurse(&arg_i
, &element_i
);
341 while (dbus_message_iter_get_arg_type(&element_i
) != DBUS_TYPE_INVALID
) {
343 if (dbus_message_iter_get_arg_type(&element_i
) == DBUS_TYPE_DICT_ENTRY
) {
344 DBusMessageIter dict_i
;
346 dbus_message_iter_recurse(&element_i
, &dict_i
);
348 if (dbus_message_has_interface(p
->message
, "org.bluez.Device")) {
349 if (parse_device_property(y
, d
, &dict_i
) < 0)
352 } else if (dbus_message_has_interface(p
->message
, "org.bluez.Headset")) {
353 if (parse_audio_property(y
, &d
->headset_connected
, &dict_i
) < 0)
356 } else if (dbus_message_has_interface(p
->message
, "org.bluez.AudioSink")) {
357 if (parse_audio_property(y
, &d
->audio_sink_connected
, &dict_i
) < 0)
362 if (!dbus_message_iter_next(&element_i
))
367 run_callback(y
, d
, FALSE
);
369 dbus_message_unref(r
);
371 PA_LLIST_REMOVE(pa_dbus_pending
, y
->pending
, p
);
372 pa_dbus_pending_free(p
);
375 static pa_dbus_pending
* send_and_add_to_pending(pa_bluetooth_discovery
*y
, pa_bluetooth_device
*d
, DBusMessage
*m
, DBusPendingCallNotifyFunction func
) {
377 DBusPendingCall
*call
;
382 pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y
->connection
), m
, &call
, -1));
384 p
= pa_dbus_pending_new(pa_dbus_connection_get(y
->connection
), m
, call
, y
, d
);
385 PA_LLIST_PREPEND(pa_dbus_pending
, y
->pending
, p
);
386 dbus_pending_call_set_notify(call
, func
, p
, NULL
);
391 static void found_device(pa_bluetooth_discovery
*y
, const char* path
) {
393 pa_bluetooth_device
*d
;
398 d
= device_new(path
);
400 pa_hashmap_put(y
->devices
, d
->path
, d
);
402 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Device", "GetProperties"));
403 send_and_add_to_pending(y
, d
, m
, get_properties_reply
);
405 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Headset", "GetProperties"));
406 send_and_add_to_pending(y
, d
, m
, get_properties_reply
);
408 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.AudioSink", "GetProperties"));
409 send_and_add_to_pending(y
, d
, m
, get_properties_reply
);
412 static void list_devices_reply(DBusPendingCall
*pending
, void *userdata
) {
418 pa_bluetooth_discovery
*y
;
424 pa_assert_se(p
= userdata
);
425 pa_assert_se(y
= p
->context_data
);
426 pa_assert_se(r
= dbus_pending_call_steal_reply(pending
));
428 if (dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
) {
429 pa_log("Error from ListDevices reply: %s", dbus_message_get_error_name(r
));
433 if (!dbus_message_get_args(r
, &e
, DBUS_TYPE_ARRAY
, DBUS_TYPE_OBJECT_PATH
, &paths
, &num
, DBUS_TYPE_INVALID
)) {
434 pa_log("org.bluez.Adapter.ListDevices returned an error: '%s'\n", e
.message
);
439 for (i
= 0; i
< num
; ++i
)
440 found_device(y
, paths
[i
]);
445 dbus_free_string_array (paths
);
447 dbus_message_unref(r
);
449 PA_LLIST_REMOVE(pa_dbus_pending
, y
->pending
, p
);
450 pa_dbus_pending_free(p
);
453 static void found_adapter(pa_bluetooth_discovery
*y
, const char *path
) {
456 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Adapter", "ListDevices"));
457 send_and_add_to_pending(y
, NULL
, m
, list_devices_reply
);
460 static void list_adapters_reply(DBusPendingCall
*pending
, void *userdata
) {
466 pa_bluetooth_discovery
*y
;
472 pa_assert_se(p
= userdata
);
473 pa_assert_se(y
= p
->context_data
);
474 pa_assert_se(r
= dbus_pending_call_steal_reply(pending
));
476 if (dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
) {
477 pa_log("Error from ListAdapters reply: %s", dbus_message_get_error_name(r
));
481 if (!dbus_message_get_args(r
, &e
, DBUS_TYPE_ARRAY
, DBUS_TYPE_OBJECT_PATH
, &paths
, &num
, DBUS_TYPE_INVALID
)) {
482 pa_log("org.bluez.Manager.ListAdapters returned an error: %s", e
.message
);
487 for (i
= 0; i
< num
; ++i
)
488 found_adapter(y
, paths
[i
]);
493 dbus_free_string_array (paths
);
495 dbus_message_unref(r
);
497 PA_LLIST_REMOVE(pa_dbus_pending
, y
->pending
, p
);
498 pa_dbus_pending_free(p
);
501 static void list_adapters(pa_bluetooth_discovery
*y
) {
505 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "ListAdapters"));
506 send_and_add_to_pending(y
, NULL
, m
, list_adapters_reply
);
509 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*m
, void *userdata
) {
511 pa_bluetooth_discovery
*y
;
516 pa_assert_se(y
= userdata
);
518 dbus_error_init(&err
);
520 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
521 dbus_message_get_interface(m
),
522 dbus_message_get_path(m
),
523 dbus_message_get_member(m
));
525 if (dbus_message_is_signal(m
, "org.bluez.Adapter", "DeviceRemoved")) {
527 pa_bluetooth_device
*d
;
529 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
530 pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err
.message
);
534 pa_log_debug("Device %s removed", path
);
536 if ((d
= pa_hashmap_remove(y
->devices
, path
))) {
537 run_callback(y
, d
, TRUE
);
541 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
543 } else if (dbus_message_is_signal(m
, "org.bluez.Adapter", "DeviceCreated")) {
546 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
547 pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err
.message
);
551 pa_log_debug("Device %s created", path
);
553 found_device(y
, path
);
554 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
556 } else if (dbus_message_is_signal(m
, "org.bluez.Manager", "AdapterAdded")) {
559 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
560 pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err
.message
);
564 pa_log_debug("Adapter %s created", path
);
566 found_adapter(y
, path
);
567 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
569 } else if (dbus_message_is_signal(m
, "org.bluez.Headset", "PropertyChanged") ||
570 dbus_message_is_signal(m
, "org.bluez.AudioSink", "PropertyChanged") ||
571 dbus_message_is_signal(m
, "org.bluez.Device", "PropertyChanged")) {
573 pa_bluetooth_device
*d
;
575 if ((d
= pa_hashmap_get(y
->devices
, dbus_message_get_path(m
)))) {
576 DBusMessageIter arg_i
;
578 if (!dbus_message_iter_init(m
, &arg_i
)) {
579 pa_log("Failed to parse PropertyChanged: %s", err
.message
);
583 if (dbus_message_has_interface(m
, "org.bluez.Device")) {
584 if (parse_device_property(y
, d
, &arg_i
) < 0)
587 } else if (dbus_message_has_interface(m
, "org.bluez.Headset")) {
588 if (parse_audio_property(y
, &d
->headset_connected
, &arg_i
) < 0)
591 } else if (dbus_message_has_interface(m
, "org.bluez.AudioSink")) {
592 if (parse_audio_property(y
, &d
->audio_sink_connected
, &arg_i
) < 0)
596 run_callback(y
, d
, FALSE
);
599 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
603 dbus_error_free(&err
);
605 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
608 const pa_bluetooth_device
* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery
*y
, const char* address
) {
609 pa_bluetooth_device
*d
;
613 pa_assert(PA_REFCNT_VALUE(y
) > 0);
616 if (!pa_hook_is_firing(&y
->hook
))
617 pa_bluetooth_discovery_sync(y
);
619 while ((d
= pa_hashmap_iterate(y
->devices
, &state
, NULL
)))
620 if (pa_streq(d
->address
, address
))
626 const pa_bluetooth_device
* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery
*y
, const char* path
) {
628 pa_assert(PA_REFCNT_VALUE(y
) > 0);
631 if (!pa_hook_is_firing(&y
->hook
))
632 pa_bluetooth_discovery_sync(y
);
634 return pa_hashmap_get(y
->devices
, path
);
637 static int setup_dbus(pa_bluetooth_discovery
*y
) {
640 dbus_error_init(&err
);
642 y
->connection
= pa_dbus_bus_get(y
->core
, DBUS_BUS_SYSTEM
, &err
);
644 if (dbus_error_is_set(&err
) || !y
->connection
) {
645 pa_log("Failed to get D-Bus connection: %s", err
.message
);
646 dbus_error_free(&err
);
653 pa_bluetooth_discovery
* pa_bluetooth_discovery_get(pa_core
*c
) {
655 pa_bluetooth_discovery
*y
;
659 dbus_error_init(&err
);
661 if ((y
= pa_shared_get(c
, "bluetooth-discovery")))
662 return pa_bluetooth_discovery_ref(y
);
664 y
= pa_xnew0(pa_bluetooth_discovery
, 1);
667 y
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
668 PA_LLIST_HEAD_INIT(pa_dbus_pending
, y
->pending
);
669 pa_hook_init(&y
->hook
, y
);
670 pa_shared_set(c
, "bluetooth-discovery", y
);
672 if (setup_dbus(y
) < 0)
675 /* dynamic detection of bluetooth audio devices */
676 if (!dbus_connection_add_filter(pa_dbus_connection_get(y
->connection
), filter_cb
, y
, NULL
)) {
677 pa_log_error("Failed to add filter function");
681 if (pa_dbus_add_matches(
682 pa_dbus_connection_get(y
->connection
), &err
,
683 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
684 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
685 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
686 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
687 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
688 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL
) < 0) {
689 pa_log("Failed to add D-Bus matches: %s", err
.message
);
700 pa_bluetooth_discovery_unref(y
);
702 dbus_error_free(&err
);
707 pa_bluetooth_discovery
* pa_bluetooth_discovery_ref(pa_bluetooth_discovery
*y
) {
709 pa_assert(PA_REFCNT_VALUE(y
) > 0);
716 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery
*y
) {
717 pa_bluetooth_device
*d
;
720 pa_assert(PA_REFCNT_VALUE(y
) > 0);
722 if (PA_REFCNT_DEC(y
) > 0)
725 pa_dbus_free_pending_list(&y
->pending
);
728 while ((d
= pa_hashmap_steal_first(y
->devices
))) {
729 run_callback(y
, d
, TRUE
);
733 pa_hashmap_free(y
->devices
, NULL
, NULL
);
737 pa_dbus_remove_matches(pa_dbus_connection_get(y
->connection
),
738 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
739 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
740 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
741 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
742 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
743 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
744 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL
);
746 dbus_connection_remove_filter(pa_dbus_connection_get(y
->connection
), filter_cb
, y
);
748 pa_dbus_connection_unref(y
->connection
);
751 pa_hook_done(&y
->hook
);
754 pa_shared_remove(y
->core
, "bluetooth-discovery");
757 void pa_bluetooth_discovery_sync(pa_bluetooth_discovery
*y
) {
759 pa_assert(PA_REFCNT_VALUE(y
) > 0);
761 pa_dbus_sync_pending_list(&y
->pending
);
764 pa_hook
* pa_bluetooth_discovery_hook(pa_bluetooth_discovery
*y
) {
766 pa_assert(PA_REFCNT_VALUE(y
) > 0);
771 const char*pa_bluetooth_get_form_factor(uint32_t class) {
775 static const char * const table
[] = {
786 if (((class >> 8) & 31) != 4)
789 if ((i
= (class >> 2) & 63) > PA_ELEMENTSOF(table
))
795 pa_log_debug("Unknown Bluetooth minor device class %u", i
);
800 char *pa_bluetooth_cleanup_name(const char *name
) {
802 pa_bool_t space
= FALSE
;
806 while ((*name
>= 1 && *name
<= 32) || *name
>= 127)
809 t
= pa_xstrdup(name
);
811 for (s
= d
= t
; *s
; s
++) {
813 if (*s
<= 32 || *s
>= 127 || *s
== '_') {