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 <pulsecore/core-util.h>
27 #include <pulsecore/shared.h>
28 #include <pulsecore/dbus-shared.h>
30 #include "bluetooth-util.h"
32 struct pa_bluetooth_discovery {
36 pa_dbus_connection *connection;
37 PA_LLIST_HEAD(pa_dbus_pending, pending);
40 pa_bool_t filter_added;
43 static void get_properties_reply(DBusPendingCall *pending, void *userdata);
44 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func);
46 static pa_bt_audio_state_t pa_bt_audio_state_from_string(const char* value) {
49 if (pa_streq(value, "disconnected"))
50 return PA_BT_AUDIO_STATE_DISCONNECTED;
51 else if (pa_streq(value, "connecting"))
52 return PA_BT_AUDIO_STATE_CONNECTING;
53 else if (pa_streq(value, "connected"))
54 return PA_BT_AUDIO_STATE_CONNECTED;
55 else if (pa_streq(value, "playing"))
56 return PA_BT_AUDIO_STATE_PLAYING;
58 return PA_BT_AUDIO_STATE_INVALID;
61 static pa_bluetooth_uuid *uuid_new(const char *uuid) {
64 u = pa_xnew(pa_bluetooth_uuid, 1);
65 u->uuid = pa_xstrdup(uuid);
66 PA_LLIST_INIT(pa_bluetooth_uuid, u);
71 static void uuid_free(pa_bluetooth_uuid *u) {
78 static pa_bluetooth_device* device_new(const char *path) {
79 pa_bluetooth_device *d;
81 d = pa_xnew(pa_bluetooth_device, 1);
85 d->device_info_valid = 0;
88 d->path = pa_xstrdup(path);
91 d->device_connected = -1;
92 PA_LLIST_HEAD_INIT(pa_bluetooth_uuid, d->uuids);
97 d->audio_state = PA_BT_AUDIO_STATE_INVALID;
98 d->audio_sink_state = PA_BT_AUDIO_STATE_INVALID;
99 d->audio_source_state = PA_BT_AUDIO_STATE_INVALID;
100 d->headset_state = PA_BT_AUDIO_STATE_INVALID;
101 d->hfgw_state = PA_BT_AUDIO_STATE_INVALID;
106 static void device_free(pa_bluetooth_device *d) {
107 pa_bluetooth_uuid *u;
111 while ((u = d->uuids)) {
112 PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u);
119 pa_xfree(d->address);
123 static pa_bool_t device_is_audio(pa_bluetooth_device *d) {
127 d->device_info_valid && (d->hfgw_state != PA_BT_AUDIO_STATE_INVALID ||
128 (d->audio_state != PA_BT_AUDIO_STATE_INVALID &&
129 (d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID ||
130 d->audio_source_state != PA_BT_AUDIO_STATE_INVALID ||
131 d->headset_state != PA_BT_AUDIO_STATE_INVALID)));
134 static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) {
136 DBusMessageIter variant_i;
142 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
143 pa_log("Property name not a string.");
147 dbus_message_iter_get_basic(i, &key);
149 if (!dbus_message_iter_next(i)) {
150 pa_log("Property value missing");
154 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
155 pa_log("Property value not a variant.");
159 dbus_message_iter_recurse(i, &variant_i);
161 /* pa_log_debug("Parsing property org.bluez.Device.%s", key); */
163 switch (dbus_message_iter_get_arg_type(&variant_i)) {
165 case DBUS_TYPE_STRING: {
168 dbus_message_iter_get_basic(&variant_i, &value);
170 if (pa_streq(key, "Name")) {
172 d->name = pa_xstrdup(value);
173 } else if (pa_streq(key, "Alias")) {
175 d->alias = pa_xstrdup(value);
176 } else if (pa_streq(key, "Address")) {
177 pa_xfree(d->address);
178 d->address = pa_xstrdup(value);
181 /* pa_log_debug("Value %s", value); */
186 case DBUS_TYPE_BOOLEAN: {
189 dbus_message_iter_get_basic(&variant_i, &value);
191 if (pa_streq(key, "Paired"))
193 else if (pa_streq(key, "Connected"))
194 d->device_connected = !!value;
195 else if (pa_streq(key, "Trusted"))
196 d->trusted = !!value;
198 /* pa_log_debug("Value %s", pa_yes_no(value)); */
203 case DBUS_TYPE_UINT32: {
206 dbus_message_iter_get_basic(&variant_i, &value);
208 if (pa_streq(key, "Class"))
209 d->class = (int) value;
211 /* pa_log_debug("Value %u", (unsigned) value); */
216 case DBUS_TYPE_ARRAY: {
219 dbus_message_iter_recurse(&variant_i, &ai);
221 if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING &&
222 pa_streq(key, "UUIDs")) {
224 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
225 pa_bluetooth_uuid *node;
229 dbus_message_iter_get_basic(&ai, &value);
230 node = uuid_new(value);
231 PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
233 /* Vudentz said the interfaces are here when the UUIDs are announced */
234 if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) {
235 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.HandsfreeGateway", "GetProperties"));
236 send_and_add_to_pending(y, d, m, get_properties_reply);
237 } else if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) {
238 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset", "GetProperties"));
239 send_and_add_to_pending(y, d, m, get_properties_reply);
240 } else if (strcasecmp(A2DP_SINK_UUID, value) == 0) {
241 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink", "GetProperties"));
242 send_and_add_to_pending(y, d, m, get_properties_reply);
243 } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) {
244 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource", "GetProperties"));
245 send_and_add_to_pending(y, d, m, get_properties_reply);
248 /* 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 */
249 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
250 send_and_add_to_pending(y, d, m, get_properties_reply);
252 if (!dbus_message_iter_next(&ai))
264 static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessageIter *i) {
266 DBusMessageIter variant_i;
272 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
273 pa_log("Property name not a string.");
277 dbus_message_iter_get_basic(i, &key);
279 if (!dbus_message_iter_next(i)) {
280 pa_log("Property value missing");
284 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
285 pa_log("Property value not a variant.");
289 dbus_message_iter_recurse(i, &variant_i);
291 /* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
293 switch (dbus_message_iter_get_arg_type(&variant_i)) {
295 case DBUS_TYPE_STRING: {
298 dbus_message_iter_get_basic(&variant_i, &value);
300 if (pa_streq(key, "State")) {
301 *state = pa_bt_audio_state_from_string(value);
302 pa_log_debug("dbus: property 'State' changed to value '%s'", value);
312 static void run_callback(pa_bluetooth_discovery *y, pa_bluetooth_device *d, pa_bool_t dead) {
316 if (!device_is_audio(d))
320 pa_hook_fire(&y->hook, d);
323 static void remove_all_devices(pa_bluetooth_discovery *y) {
324 pa_bluetooth_device *d;
328 while ((d = pa_hashmap_steal_first(y->devices))) {
329 run_callback(y, d, TRUE);
334 static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
336 DBusMessageIter arg_i, element_i;
338 pa_bluetooth_device *d;
339 pa_bluetooth_discovery *y;
342 pa_assert_se(p = userdata);
343 pa_assert_se(y = p->context_data);
344 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
346 /* pa_log_debug("Got %s.GetProperties response for %s", */
347 /* dbus_message_get_interface(p->message), */
348 /* dbus_message_get_path(p->message)); */
350 /* We don't use p->call_data here right-away since the device
351 * might already be invalidated at this point */
353 if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(p->message))))
356 pa_assert(p->call_data == d);
358 valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1;
360 if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties"))
361 d->device_info_valid = valid;
363 if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
364 pa_log_debug("Bluetooth daemon is apparently not available.");
365 remove_all_devices(y);
369 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
371 if (!dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD))
372 pa_log("Error from GetProperties reply: %s", dbus_message_get_error_name(r));
377 if (!dbus_message_iter_init(r, &arg_i)) {
378 pa_log("GetProperties reply has no arguments.");
382 if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
383 pa_log("GetProperties argument is not an array.");
387 dbus_message_iter_recurse(&arg_i, &element_i);
388 while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) {
390 if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
391 DBusMessageIter dict_i;
393 dbus_message_iter_recurse(&element_i, &dict_i);
395 if (dbus_message_has_interface(p->message, "org.bluez.Device")) {
396 if (parse_device_property(y, d, &dict_i) < 0)
399 } else if (dbus_message_has_interface(p->message, "org.bluez.Audio")) {
400 if (parse_audio_property(y, &d->audio_state, &dict_i) < 0)
403 } else if (dbus_message_has_interface(p->message, "org.bluez.Headset")) {
404 if (parse_audio_property(y, &d->headset_state, &dict_i) < 0)
407 } else if (dbus_message_has_interface(p->message, "org.bluez.AudioSink")) {
408 if (parse_audio_property(y, &d->audio_sink_state, &dict_i) < 0)
411 } else if (dbus_message_has_interface(p->message, "org.bluez.AudioSource")) {
412 if (parse_audio_property(y, &d->audio_source_state, &dict_i) < 0)
415 } else if (dbus_message_has_interface(p->message, "org.bluez.HandsfreeGateway")) {
416 if (parse_audio_property(y, &d->hfgw_state, &arg_i) < 0)
421 if (!dbus_message_iter_next(&element_i))
426 run_callback(y, d, FALSE);
429 dbus_message_unref(r);
431 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
432 pa_dbus_pending_free(p);
435 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func) {
437 DBusPendingCall *call;
442 pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
444 p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, d);
445 PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);
446 dbus_pending_call_set_notify(call, func, p, NULL);
451 static void found_device(pa_bluetooth_discovery *y, const char* path) {
453 pa_bluetooth_device *d;
458 if (pa_hashmap_get(y->devices, path))
461 d = device_new(path);
463 pa_hashmap_put(y->devices, d->path, d);
465 pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties"));
466 send_and_add_to_pending(y, d, m, get_properties_reply);
468 /* Before we read the other properties (Audio, AudioSink, AudioSource,
469 * Headset) we wait that the UUID is read */
472 static void list_devices_reply(DBusPendingCall *pending, void *userdata) {
478 pa_bluetooth_discovery *y;
484 pa_assert_se(p = userdata);
485 pa_assert_se(y = p->context_data);
486 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
488 if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
489 pa_log_debug("Bluetooth daemon is apparently not available.");
490 remove_all_devices(y);
494 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
495 pa_log("Error from ListDevices reply: %s", dbus_message_get_error_name(r));
499 if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) {
500 pa_log("org.bluez.Adapter.ListDevices returned an error: '%s'\n", e.message);
505 for (i = 0; i < num; ++i)
506 found_device(y, paths[i]);
511 dbus_free_string_array (paths);
513 dbus_message_unref(r);
515 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
516 pa_dbus_pending_free(p);
519 static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
522 pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "ListDevices"));
523 send_and_add_to_pending(y, NULL, m, list_devices_reply);
526 static void list_adapters_reply(DBusPendingCall *pending, void *userdata) {
532 pa_bluetooth_discovery *y;
538 pa_assert_se(p = userdata);
539 pa_assert_se(y = p->context_data);
540 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
542 if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
543 pa_log_debug("Bluetooth daemon is apparently not available.");
544 remove_all_devices(y);
548 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
549 pa_log("Error from ListAdapters reply: %s", dbus_message_get_error_name(r));
553 if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) {
554 pa_log("org.bluez.Manager.ListAdapters returned an error: %s", e.message);
559 for (i = 0; i < num; ++i)
560 found_adapter(y, paths[i]);
565 dbus_free_string_array (paths);
567 dbus_message_unref(r);
569 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
570 pa_dbus_pending_free(p);
573 static void list_adapters(pa_bluetooth_discovery *y) {
577 pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "ListAdapters"));
578 send_and_add_to_pending(y, NULL, m, list_adapters_reply);
581 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
583 pa_bluetooth_discovery *y;
588 pa_assert_se(y = userdata);
590 dbus_error_init(&err);
592 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
593 dbus_message_get_interface(m),
594 dbus_message_get_path(m),
595 dbus_message_get_member(m));
597 if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) {
599 pa_bluetooth_device *d;
601 if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
602 pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err.message);
606 pa_log_debug("Device %s removed", path);
608 if ((d = pa_hashmap_remove(y->devices, path))) {
609 run_callback(y, d, TRUE);
613 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
615 } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceCreated")) {
618 if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
619 pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err.message);
623 pa_log_debug("Device %s created", path);
625 found_device(y, path);
626 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
628 } else if (dbus_message_is_signal(m, "org.bluez.Manager", "AdapterAdded")) {
631 if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
632 pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err.message);
636 pa_log_debug("Adapter %s created", path);
638 found_adapter(y, path);
639 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
641 } else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") ||
642 dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
643 dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") ||
644 dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged") ||
645 dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged") ||
646 dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
648 pa_bluetooth_device *d;
650 if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
651 DBusMessageIter arg_i;
653 if (!dbus_message_iter_init(m, &arg_i)) {
654 pa_log("Failed to parse PropertyChanged: %s", err.message);
658 if (dbus_message_has_interface(m, "org.bluez.Device")) {
659 if (parse_device_property(y, d, &arg_i) < 0)
662 } else if (dbus_message_has_interface(m, "org.bluez.Audio")) {
663 if (parse_audio_property(y, &d->audio_state, &arg_i) < 0)
666 } else if (dbus_message_has_interface(m, "org.bluez.Headset")) {
667 if (parse_audio_property(y, &d->headset_state, &arg_i) < 0)
670 } else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) {
671 if (parse_audio_property(y, &d->audio_sink_state, &arg_i) < 0)
674 } else if (dbus_message_has_interface(m, "org.bluez.AudioSource")) {
675 if (parse_audio_property(y, &d->audio_source_state, &arg_i) < 0)
678 } else if (dbus_message_has_interface(m, "org.bluez.HandsfreeGateway")) {
679 if (parse_audio_property(y, &d->hfgw_state, &arg_i) < 0)
683 run_callback(y, d, FALSE);
686 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
688 } else if (dbus_message_is_signal(m, "org.bluez.Device", "DisconnectRequested")) {
689 pa_bluetooth_device *d;
691 if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
692 /* Device will disconnect in 2 sec */
693 d->audio_state = PA_BT_AUDIO_STATE_DISCONNECTED;
694 d->audio_sink_state = PA_BT_AUDIO_STATE_DISCONNECTED;
695 d->audio_source_state = PA_BT_AUDIO_STATE_DISCONNECTED;
696 d->headset_state = PA_BT_AUDIO_STATE_DISCONNECTED;
697 d->hfgw_state = PA_BT_AUDIO_STATE_DISCONNECTED;
699 run_callback(y, d, FALSE);
702 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
704 } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
705 const char *name, *old_owner, *new_owner;
707 if (!dbus_message_get_args(m, &err,
708 DBUS_TYPE_STRING, &name,
709 DBUS_TYPE_STRING, &old_owner,
710 DBUS_TYPE_STRING, &new_owner,
711 DBUS_TYPE_INVALID)) {
712 pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
716 if (pa_streq(name, "org.bluez")) {
717 if (old_owner && *old_owner) {
718 pa_log_debug("Bluetooth daemon disappeared.");
719 remove_all_devices(y);
722 if (new_owner && *new_owner) {
723 pa_log_debug("Bluetooth daemon appeared.");
728 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
732 dbus_error_free(&err);
734 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
737 const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) {
738 pa_bluetooth_device *d;
742 pa_assert(PA_REFCNT_VALUE(y) > 0);
745 if (!pa_hook_is_firing(&y->hook))
746 pa_bluetooth_discovery_sync(y);
748 while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
749 if (pa_streq(d->address, address))
750 return device_is_audio(d) ? d : NULL;
755 const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) {
756 pa_bluetooth_device *d;
759 pa_assert(PA_REFCNT_VALUE(y) > 0);
762 if (!pa_hook_is_firing(&y->hook))
763 pa_bluetooth_discovery_sync(y);
765 if ((d = pa_hashmap_get(y->devices, path)))
766 if (device_is_audio(d))
772 static int setup_dbus(pa_bluetooth_discovery *y) {
775 dbus_error_init(&err);
777 y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err);
779 if (dbus_error_is_set(&err) || !y->connection) {
780 pa_log("Failed to get D-Bus connection: %s", err.message);
781 dbus_error_free(&err);
788 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
790 pa_bluetooth_discovery *y;
794 dbus_error_init(&err);
796 if ((y = pa_shared_get(c, "bluetooth-discovery")))
797 return pa_bluetooth_discovery_ref(y);
799 y = pa_xnew0(pa_bluetooth_discovery, 1);
802 y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
803 PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
804 pa_hook_init(&y->hook, y);
805 pa_shared_set(c, "bluetooth-discovery", y);
807 if (setup_dbus(y) < 0)
810 /* dynamic detection of bluetooth audio devices */
811 if (!dbus_connection_add_filter(pa_dbus_connection_get(y->connection), filter_cb, y, NULL)) {
812 pa_log_error("Failed to add filter function");
815 y->filter_added = TRUE;
817 if (pa_dbus_add_matches(
818 pa_dbus_connection_get(y->connection), &err,
819 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
820 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
821 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
822 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
823 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
824 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'",
825 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
826 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
827 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
828 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
829 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
831 pa_log("Failed to add D-Bus matches: %s", err.message);
842 pa_bluetooth_discovery_unref(y);
844 dbus_error_free(&err);
849 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
851 pa_assert(PA_REFCNT_VALUE(y) > 0);
858 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
860 pa_assert(PA_REFCNT_VALUE(y) > 0);
862 if (PA_REFCNT_DEC(y) > 0)
865 pa_dbus_free_pending_list(&y->pending);
868 remove_all_devices(y);
869 pa_hashmap_free(y->devices, NULL, NULL);
873 pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
874 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
875 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
876 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
877 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
878 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
879 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
880 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'",
881 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
882 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
883 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
884 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
885 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
889 dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
891 pa_dbus_connection_unref(y->connection);
894 pa_hook_done(&y->hook);
897 pa_shared_remove(y->core, "bluetooth-discovery");
902 void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *y) {
904 pa_assert(PA_REFCNT_VALUE(y) > 0);
906 pa_dbus_sync_pending_list(&y->pending);
909 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y) {
911 pa_assert(PA_REFCNT_VALUE(y) > 0);
916 const char*pa_bluetooth_get_form_factor(uint32_t class) {
920 static const char * const table[] = {
931 if (((class >> 8) & 31) != 4)
934 if ((i = (class >> 2) & 63) > PA_ELEMENTSOF(table))
940 pa_log_debug("Unknown Bluetooth minor device class %u", i);
945 char *pa_bluetooth_cleanup_name(const char *name) {
947 pa_bool_t space = FALSE;
951 while ((*name >= 1 && *name <= 32) || *name >= 127)
954 t = pa_xstrdup(name);
956 for (s = d = t; *s; s++) {
958 if (*s <= 32 || *s >= 127 || *s == '_') {
976 pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
980 if (strcasecmp(uuids->uuid, uuid) == 0)