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
);
406 static void list_devices_reply(DBusPendingCall
*pending
, void *userdata
) {
412 pa_bluetooth_discovery
*y
;
418 pa_assert_se(p
= userdata
);
419 pa_assert_se(y
= p
->context_data
);
420 pa_assert_se(r
= dbus_pending_call_steal_reply(pending
));
422 if (dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
) {
423 pa_log("Error from ListDevices reply: %s", dbus_message_get_error_name(r
));
427 if (!dbus_message_get_args(r
, &e
, DBUS_TYPE_ARRAY
, DBUS_TYPE_OBJECT_PATH
, &paths
, &num
, DBUS_TYPE_INVALID
)) {
428 pa_log("org.bluez.Adapter.ListDevices returned an error: '%s'\n", e
.message
);
433 for (i
= 0; i
< num
; ++i
)
434 found_device(y
, paths
[i
]);
439 dbus_free_string_array (paths
);
441 dbus_message_unref(r
);
443 PA_LLIST_REMOVE(pa_dbus_pending
, y
->pending
, p
);
444 pa_dbus_pending_free(p
);
447 static void found_adapter(pa_bluetooth_discovery
*y
, const char *path
) {
450 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", path
, "org.bluez.Adapter", "ListDevices"));
451 send_and_add_to_pending(y
, NULL
, m
, list_devices_reply
);
454 static void list_adapters_reply(DBusPendingCall
*pending
, void *userdata
) {
460 pa_bluetooth_discovery
*y
;
466 pa_assert_se(p
= userdata
);
467 pa_assert_se(y
= p
->context_data
);
468 pa_assert_se(r
= dbus_pending_call_steal_reply(pending
));
470 if (dbus_message_get_type(r
) == DBUS_MESSAGE_TYPE_ERROR
) {
471 pa_log("Error from ListAdapters reply: %s", dbus_message_get_error_name(r
));
475 if (!dbus_message_get_args(r
, &e
, DBUS_TYPE_ARRAY
, DBUS_TYPE_OBJECT_PATH
, &paths
, &num
, DBUS_TYPE_INVALID
)) {
476 pa_log("org.bluez.Manager.ListAdapters returned an error: %s", e
.message
);
481 for (i
= 0; i
< num
; ++i
)
482 found_adapter(y
, paths
[i
]);
487 dbus_free_string_array (paths
);
489 dbus_message_unref(r
);
491 PA_LLIST_REMOVE(pa_dbus_pending
, y
->pending
, p
);
492 pa_dbus_pending_free(p
);
495 static void list_adapters(pa_bluetooth_discovery
*y
) {
499 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "ListAdapters"));
500 send_and_add_to_pending(y
, NULL
, m
, list_adapters_reply
);
503 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*m
, void *userdata
) {
505 pa_bluetooth_discovery
*y
;
510 pa_assert_se(y
= userdata
);
512 dbus_error_init(&err
);
514 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
515 dbus_message_get_interface(m
),
516 dbus_message_get_path(m
),
517 dbus_message_get_member(m
));
519 if (dbus_message_is_signal(m
, "org.bluez.Adapter", "DeviceRemoved")) {
521 pa_bluetooth_device
*d
;
523 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
524 pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err
.message
);
528 pa_log_debug("Device %s removed", path
);
530 if ((d
= pa_hashmap_remove(y
->devices
, path
))) {
531 run_callback(y
, d
, TRUE
);
535 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
537 } else if (dbus_message_is_signal(m
, "org.bluez.Adapter", "DeviceCreated")) {
540 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
541 pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err
.message
);
545 pa_log_debug("Device %s created", path
);
547 found_device(y
, path
);
548 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
550 } else if (dbus_message_is_signal(m
, "org.bluez.Manager", "AdapterAdded")) {
553 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_OBJECT_PATH
, &path
, DBUS_TYPE_INVALID
)) {
554 pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err
.message
);
558 pa_log_debug("Adapter %s created", path
);
560 found_adapter(y
, path
);
561 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
563 } else if (dbus_message_is_signal(m
, "org.bluez.Headset", "PropertyChanged") ||
564 dbus_message_is_signal(m
, "org.bluez.AudioSink", "PropertyChanged") ||
565 dbus_message_is_signal(m
, "org.bluez.Device", "PropertyChanged")) {
567 pa_bluetooth_device
*d
;
569 if ((d
= pa_hashmap_get(y
->devices
, dbus_message_get_path(m
)))) {
570 DBusMessageIter arg_i
;
572 if (!dbus_message_iter_init(m
, &arg_i
)) {
573 pa_log("Failed to parse PropertyChanged: %s", err
.message
);
577 if (dbus_message_has_interface(m
, "org.bluez.Device")) {
578 if (parse_device_property(y
, d
, &arg_i
) < 0)
581 } else if (dbus_message_has_interface(m
, "org.bluez.Headset")) {
582 if (parse_audio_property(y
, &d
->headset_connected
, &arg_i
) < 0)
585 } else if (dbus_message_has_interface(m
, "org.bluez.AudioSink")) {
586 if (parse_audio_property(y
, &d
->audio_sink_connected
, &arg_i
) < 0)
590 run_callback(y
, d
, FALSE
);
593 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
597 dbus_error_free(&err
);
599 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
602 const pa_bluetooth_device
* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery
*y
, const char* address
) {
603 pa_bluetooth_device
*d
;
607 pa_assert(PA_REFCNT_VALUE(y
) > 0);
610 if (!pa_hook_is_firing(&y
->hook
))
611 pa_bluetooth_discovery_sync(y
);
613 while ((d
= pa_hashmap_iterate(y
->devices
, &state
, NULL
)))
614 if (pa_streq(d
->address
, address
))
620 const pa_bluetooth_device
* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery
*y
, const char* path
) {
622 pa_assert(PA_REFCNT_VALUE(y
) > 0);
625 if (!pa_hook_is_firing(&y
->hook
))
626 pa_bluetooth_discovery_sync(y
);
628 return pa_hashmap_get(y
->devices
, path
);
631 static int setup_dbus(pa_bluetooth_discovery
*y
) {
634 dbus_error_init(&err
);
636 y
->connection
= pa_dbus_bus_get(y
->core
, DBUS_BUS_SYSTEM
, &err
);
638 if (dbus_error_is_set(&err
) || !y
->connection
) {
639 pa_log("Failed to get D-Bus connection: %s", err
.message
);
640 dbus_error_free(&err
);
647 pa_bluetooth_discovery
* pa_bluetooth_discovery_get(pa_core
*c
) {
649 pa_bluetooth_discovery
*y
;
653 dbus_error_init(&err
);
655 if ((y
= pa_shared_get(c
, "bluetooth-discovery")))
656 return pa_bluetooth_discovery_ref(y
);
658 y
= pa_xnew0(pa_bluetooth_discovery
, 1);
661 y
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
662 PA_LLIST_HEAD_INIT(pa_dbus_pending
, y
->pending
);
663 pa_hook_init(&y
->hook
, y
);
664 pa_shared_set(c
, "bluetooth-discovery", y
);
666 if (setup_dbus(y
) < 0)
669 /* dynamic detection of bluetooth audio devices */
670 if (!dbus_connection_add_filter(pa_dbus_connection_get(y
->connection
), filter_cb
, y
, NULL
)) {
671 pa_log_error("Failed to add filter function");
675 if (pa_dbus_add_matches(
676 pa_dbus_connection_get(y
->connection
), &err
,
677 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
678 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
679 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
680 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
681 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
682 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL
) < 0) {
683 pa_log("Failed to add D-Bus matches: %s", err
.message
);
694 pa_bluetooth_discovery_unref(y
);
696 dbus_error_free(&err
);
701 pa_bluetooth_discovery
* pa_bluetooth_discovery_ref(pa_bluetooth_discovery
*y
) {
703 pa_assert(PA_REFCNT_VALUE(y
) > 0);
710 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery
*y
) {
711 pa_bluetooth_device
*d
;
714 pa_assert(PA_REFCNT_VALUE(y
) > 0);
716 if (PA_REFCNT_DEC(y
) > 0)
719 pa_dbus_free_pending_list(&y
->pending
);
722 while ((d
= pa_hashmap_steal_first(y
->devices
))) {
723 run_callback(y
, d
, TRUE
);
727 pa_hashmap_free(y
->devices
, NULL
, NULL
);
731 pa_dbus_remove_matches(pa_dbus_connection_get(y
->connection
),
732 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
733 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
734 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
735 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
736 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
737 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
738 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL
);
740 dbus_connection_remove_filter(pa_dbus_connection_get(y
->connection
), filter_cb
, y
);
742 pa_dbus_connection_unref(y
->connection
);
745 pa_hook_done(&y
->hook
);
748 pa_shared_remove(y
->core
, "bluetooth-discovery");
751 void pa_bluetooth_discovery_sync(pa_bluetooth_discovery
*y
) {
753 pa_assert(PA_REFCNT_VALUE(y
) > 0);
755 pa_dbus_sync_pending_list(&y
->pending
);
758 pa_hook
* pa_bluetooth_discovery_hook(pa_bluetooth_discovery
*y
) {
760 pa_assert(PA_REFCNT_VALUE(y
) > 0);
765 const char*pa_bluetooth_get_form_factor(uint32_t class) {
769 static const char * const table
[] = {
780 if (((class >> 8) & 31) != 4)
783 if ((i
= (class >> 2) & 63) > PA_ELEMENTSOF(table
))
789 pa_log_debug("Unknown Bluetooth minor device class %u", i
);
794 char *pa_bluetooth_cleanup_name(const char *name
) {
796 pa_bool_t space
= FALSE
;
800 while ((*name
>= 1 && *name
<= 32) || *name
>= 127)
803 t
= pa_xstrdup(name
);
805 for (s
= d
= t
; *s
; s
++) {
807 if (*s
<= 32 || *s
>= 127 || *s
== '_') {