]> code.delx.au - pulseaudio/blob - src/modules/bluetooth/bluetooth-util.c
bluetooth: do not hand out access to devices that are not fully configured yet
[pulseaudio] / src / modules / bluetooth / bluetooth-util.c
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2008-2009 Joao Paulo Rechi Vita
5
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.
10
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.
15
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
19 USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <pulsecore/core-util.h>
27 #include <pulsecore/shared.h>
28 #include <pulsecore/dbus-shared.h>
29
30 #include "bluetooth-util.h"
31
32 struct pa_bluetooth_discovery {
33 PA_REFCNT_DECLARE;
34
35 pa_core *core;
36 pa_dbus_connection *connection;
37 PA_LLIST_HEAD(pa_dbus_pending, pending);
38 pa_hashmap *devices;
39 pa_hook hook;
40 };
41
42 static void get_properties_reply(DBusPendingCall *pending, void *userdata);
43 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func);
44
45 static pa_bt_audio_state_t pa_bt_audio_state_from_string(const char* value) {
46 pa_assert(value);
47
48 if (pa_streq(value, "disconnected"))
49 return PA_BT_AUDIO_STATE_DISCONNECTED;
50 else if (pa_streq(value, "connecting"))
51 return PA_BT_AUDIO_STATE_CONNECTING;
52 else if (pa_streq(value, "connected"))
53 return PA_BT_AUDIO_STATE_CONNECTED;
54 else if (pa_streq(value, "playing"))
55 return PA_BT_AUDIO_STATE_PLAYING;
56
57 return PA_BT_AUDIO_STATE_INVALID;
58 }
59
60 static pa_bluetooth_uuid *uuid_new(const char *uuid) {
61 pa_bluetooth_uuid *u;
62
63 u = pa_xnew(pa_bluetooth_uuid, 1);
64 u->uuid = pa_xstrdup(uuid);
65 PA_LLIST_INIT(pa_bluetooth_uuid, u);
66
67 return u;
68 }
69
70 static void uuid_free(pa_bluetooth_uuid *u) {
71 pa_assert(u);
72
73 pa_xfree(u->uuid);
74 pa_xfree(u);
75 }
76
77 static pa_bluetooth_device* device_new(const char *path) {
78 pa_bluetooth_device *d;
79
80 d = pa_xnew(pa_bluetooth_device, 1);
81
82 d->dead = FALSE;
83
84 d->device_info_valid = 0;
85
86 d->name = NULL;
87 d->path = pa_xstrdup(path);
88 d->paired = -1;
89 d->alias = NULL;
90 d->device_connected = -1;
91 PA_LLIST_HEAD_INIT(pa_bluetooth_uuid, d->uuids);
92 d->address = NULL;
93 d->class = -1;
94 d->trusted = -1;
95
96 d->audio_state = PA_BT_AUDIO_STATE_INVALID;
97 d->audio_sink_state = PA_BT_AUDIO_STATE_INVALID;
98 d->audio_source_state = PA_BT_AUDIO_STATE_INVALID;
99 d->headset_state = PA_BT_AUDIO_STATE_INVALID;
100
101 return d;
102 }
103
104 static void device_free(pa_bluetooth_device *d) {
105 pa_bluetooth_uuid *u;
106
107 pa_assert(d);
108
109 while ((u = d->uuids)) {
110 PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u);
111 uuid_free(u);
112 }
113
114 pa_xfree(d->name);
115 pa_xfree(d->path);
116 pa_xfree(d->alias);
117 pa_xfree(d->address);
118 pa_xfree(d);
119 }
120
121 static pa_bool_t device_is_audio(pa_bluetooth_device *d) {
122 pa_assert(d);
123
124 return
125 d->device_info_valid &&
126 (d->audio_state != PA_BT_AUDIO_STATE_INVALID &&
127 (d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID ||
128 d->audio_source_state != PA_BT_AUDIO_STATE_INVALID ||
129 d->headset_state != PA_BT_AUDIO_STATE_INVALID));
130 }
131
132 static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) {
133 const char *key;
134 DBusMessageIter variant_i;
135
136 pa_assert(y);
137 pa_assert(d);
138 pa_assert(i);
139
140 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
141 pa_log("Property name not a string.");
142 return -1;
143 }
144
145 dbus_message_iter_get_basic(i, &key);
146
147 if (!dbus_message_iter_next(i)) {
148 pa_log("Property value missing");
149 return -1;
150 }
151
152 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
153 pa_log("Property value not a variant.");
154 return -1;
155 }
156
157 dbus_message_iter_recurse(i, &variant_i);
158
159 /* pa_log_debug("Parsing property org.bluez.Device.%s", key); */
160
161 switch (dbus_message_iter_get_arg_type(&variant_i)) {
162
163 case DBUS_TYPE_STRING: {
164
165 const char *value;
166 dbus_message_iter_get_basic(&variant_i, &value);
167
168 if (pa_streq(key, "Name")) {
169 pa_xfree(d->name);
170 d->name = pa_xstrdup(value);
171 } else if (pa_streq(key, "Alias")) {
172 pa_xfree(d->alias);
173 d->alias = pa_xstrdup(value);
174 } else if (pa_streq(key, "Address")) {
175 pa_xfree(d->address);
176 d->address = pa_xstrdup(value);
177 }
178
179 /* pa_log_debug("Value %s", value); */
180
181 break;
182 }
183
184 case DBUS_TYPE_BOOLEAN: {
185
186 dbus_bool_t value;
187 dbus_message_iter_get_basic(&variant_i, &value);
188
189 if (pa_streq(key, "Paired"))
190 d->paired = !!value;
191 else if (pa_streq(key, "Connected"))
192 d->device_connected = !!value;
193 else if (pa_streq(key, "Trusted"))
194 d->trusted = !!value;
195
196 /* pa_log_debug("Value %s", pa_yes_no(value)); */
197
198 break;
199 }
200
201 case DBUS_TYPE_UINT32: {
202
203 uint32_t value;
204 dbus_message_iter_get_basic(&variant_i, &value);
205
206 if (pa_streq(key, "Class"))
207 d->class = (int) value;
208
209 /* pa_log_debug("Value %u", (unsigned) value); */
210
211 break;
212 }
213
214 case DBUS_TYPE_ARRAY: {
215
216 DBusMessageIter ai;
217 dbus_message_iter_recurse(&variant_i, &ai);
218
219 if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING &&
220 pa_streq(key, "UUIDs")) {
221
222 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
223 pa_bluetooth_uuid *node;
224 const char *value;
225 DBusMessage *m;
226
227 dbus_message_iter_get_basic(&ai, &value);
228 node = uuid_new(value);
229 PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
230
231 /* Vudentz said the interfaces are here when the UUIDs are announced */
232 if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) {
233 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset", "GetProperties"));
234 send_and_add_to_pending(y, d, m, get_properties_reply);
235 } else if (strcasecmp(A2DP_SINK_UUID, value) == 0) {
236 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink", "GetProperties"));
237 send_and_add_to_pending(y, d, m, get_properties_reply);
238 } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) {
239 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource", "GetProperties"));
240 send_and_add_to_pending(y, d, m, get_properties_reply);
241 }
242
243 /* 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 */
244 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
245 send_and_add_to_pending(y, d, m, get_properties_reply);
246
247 if (!dbus_message_iter_next(&ai))
248 break;
249 }
250 }
251
252 break;
253 }
254 }
255
256 return 0;
257 }
258
259 static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessageIter *i) {
260 const char *key;
261 DBusMessageIter variant_i;
262
263 pa_assert(u);
264 pa_assert(state);
265 pa_assert(i);
266
267 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
268 pa_log("Property name not a string.");
269 return -1;
270 }
271
272 dbus_message_iter_get_basic(i, &key);
273
274 if (!dbus_message_iter_next(i)) {
275 pa_log("Property value missing");
276 return -1;
277 }
278
279 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
280 pa_log("Property value not a variant.");
281 return -1;
282 }
283
284 dbus_message_iter_recurse(i, &variant_i);
285
286 /* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
287
288 switch (dbus_message_iter_get_arg_type(&variant_i)) {
289
290 case DBUS_TYPE_STRING: {
291
292 const char *value;
293 dbus_message_iter_get_basic(&variant_i, &value);
294
295 if (pa_streq(key, "State"))
296 *state = pa_bt_audio_state_from_string(value);
297 /* pa_log_debug("Value %s", value); */
298
299 break;
300 }
301 }
302
303 return 0;
304 }
305
306 static void run_callback(pa_bluetooth_discovery *y, pa_bluetooth_device *d, pa_bool_t dead) {
307 pa_assert(y);
308 pa_assert(d);
309
310 if (!device_is_audio(d))
311 return;
312
313 d->dead = dead;
314 pa_hook_fire(&y->hook, d);
315 }
316
317 static void remove_all_devices(pa_bluetooth_discovery *y) {
318 pa_bluetooth_device *d;
319
320 pa_assert(y);
321
322 while ((d = pa_hashmap_steal_first(y->devices))) {
323 run_callback(y, d, TRUE);
324 device_free(d);
325 }
326 }
327
328 static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
329 DBusMessage *r;
330 DBusMessageIter arg_i, element_i;
331 pa_dbus_pending *p;
332 pa_bluetooth_device *d;
333 pa_bluetooth_discovery *y;
334 int valid;
335
336 pa_assert_se(p = userdata);
337 pa_assert_se(y = p->context_data);
338 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
339
340 /* pa_log_debug("Got %s.GetProperties response for %s", */
341 /* dbus_message_get_interface(p->message), */
342 /* dbus_message_get_path(p->message)); */
343
344 d = p->call_data;
345
346 valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1;
347
348 if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties"))
349 d->device_info_valid = valid;
350
351 if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
352 pa_log_debug("Bluetooth daemon is apparently not available.");
353 remove_all_devices(y);
354 goto finish2;
355 }
356
357 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
358
359 if (!dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD))
360 pa_log("Error from GetProperties reply: %s", dbus_message_get_error_name(r));
361
362 goto finish;
363 }
364
365 if (!dbus_message_iter_init(r, &arg_i)) {
366 pa_log("GetProperties reply has no arguments.");
367 goto finish;
368 }
369
370 if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
371 pa_log("GetProperties argument is not an array.");
372 goto finish;
373 }
374
375 dbus_message_iter_recurse(&arg_i, &element_i);
376 while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) {
377
378 if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
379 DBusMessageIter dict_i;
380
381 dbus_message_iter_recurse(&element_i, &dict_i);
382
383 if (dbus_message_has_interface(p->message, "org.bluez.Device")) {
384 if (parse_device_property(y, d, &dict_i) < 0)
385 goto finish;
386
387 } else if (dbus_message_has_interface(p->message, "org.bluez.Audio")) {
388 if (parse_audio_property(y, &d->audio_state, &dict_i) < 0)
389 goto finish;
390
391 } else if (dbus_message_has_interface(p->message, "org.bluez.Headset")) {
392 if (parse_audio_property(y, &d->headset_state, &dict_i) < 0)
393 goto finish;
394
395 } else if (dbus_message_has_interface(p->message, "org.bluez.AudioSink")) {
396 if (parse_audio_property(y, &d->audio_sink_state, &dict_i) < 0)
397 goto finish;
398 } else if (dbus_message_has_interface(p->message, "org.bluez.AudioSource")) {
399 if (parse_audio_property(y, &d->audio_source_state, &dict_i) < 0)
400 goto finish;
401 }
402 }
403
404 if (!dbus_message_iter_next(&element_i))
405 break;
406 }
407
408 finish:
409 run_callback(y, d, FALSE);
410
411 finish2:
412 dbus_message_unref(r);
413
414 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
415 pa_dbus_pending_free(p);
416 }
417
418 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func) {
419 pa_dbus_pending *p;
420 DBusPendingCall *call;
421
422 pa_assert(y);
423 pa_assert(m);
424
425 pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
426
427 p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, d);
428 PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);
429 dbus_pending_call_set_notify(call, func, p, NULL);
430
431 return p;
432 }
433
434 static void found_device(pa_bluetooth_discovery *y, const char* path) {
435 DBusMessage *m;
436 pa_bluetooth_device *d;
437
438 pa_assert(y);
439 pa_assert(path);
440
441 if (pa_hashmap_get(y->devices, path))
442 return;
443
444 d = device_new(path);
445
446 pa_hashmap_put(y->devices, d->path, d);
447
448 pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties"));
449 send_and_add_to_pending(y, d, m, get_properties_reply);
450
451 /* Before we read the other properties (Audio, AudioSink, AudioSource,
452 * Headset) we wait that the UUID is read */
453 }
454
455 static void list_devices_reply(DBusPendingCall *pending, void *userdata) {
456 DBusError e;
457 DBusMessage *r;
458 char **paths = NULL;
459 int num = -1;
460 pa_dbus_pending *p;
461 pa_bluetooth_discovery *y;
462
463 pa_assert(pending);
464
465 dbus_error_init(&e);
466
467 pa_assert_se(p = userdata);
468 pa_assert_se(y = p->context_data);
469 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
470
471 if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
472 pa_log_debug("Bluetooth daemon is apparently not available.");
473 remove_all_devices(y);
474 goto finish;
475 }
476
477 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
478 pa_log("Error from ListDevices reply: %s", dbus_message_get_error_name(r));
479 goto finish;
480 }
481
482 if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) {
483 pa_log("org.bluez.Adapter.ListDevices returned an error: '%s'\n", e.message);
484 dbus_error_free(&e);
485 } else {
486 int i;
487
488 for (i = 0; i < num; ++i)
489 found_device(y, paths[i]);
490 }
491
492 finish:
493 if (paths)
494 dbus_free_string_array (paths);
495
496 dbus_message_unref(r);
497
498 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
499 pa_dbus_pending_free(p);
500 }
501
502 static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
503 DBusMessage *m;
504
505 pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "ListDevices"));
506 send_and_add_to_pending(y, NULL, m, list_devices_reply);
507 }
508
509 static void list_adapters_reply(DBusPendingCall *pending, void *userdata) {
510 DBusError e;
511 DBusMessage *r;
512 char **paths = NULL;
513 int num = -1;
514 pa_dbus_pending *p;
515 pa_bluetooth_discovery *y;
516
517 pa_assert(pending);
518
519 dbus_error_init(&e);
520
521 pa_assert_se(p = userdata);
522 pa_assert_se(y = p->context_data);
523 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
524
525 if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
526 pa_log_debug("Bluetooth daemon is apparently not available.");
527 remove_all_devices(y);
528 goto finish;
529 }
530
531 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
532 pa_log("Error from ListAdapters reply: %s", dbus_message_get_error_name(r));
533 goto finish;
534 }
535
536 if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) {
537 pa_log("org.bluez.Manager.ListAdapters returned an error: %s", e.message);
538 dbus_error_free(&e);
539 } else {
540 int i;
541
542 for (i = 0; i < num; ++i)
543 found_adapter(y, paths[i]);
544 }
545
546 finish:
547 if (paths)
548 dbus_free_string_array (paths);
549
550 dbus_message_unref(r);
551
552 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
553 pa_dbus_pending_free(p);
554 }
555
556 static void list_adapters(pa_bluetooth_discovery *y) {
557 DBusMessage *m;
558 pa_assert(y);
559
560 pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "ListAdapters"));
561 send_and_add_to_pending(y, NULL, m, list_adapters_reply);
562 }
563
564 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
565 DBusError err;
566 pa_bluetooth_discovery *y;
567
568 pa_assert(bus);
569 pa_assert(m);
570
571 pa_assert_se(y = userdata);
572
573 dbus_error_init(&err);
574
575 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
576 dbus_message_get_interface(m),
577 dbus_message_get_path(m),
578 dbus_message_get_member(m));
579
580 if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) {
581 const char *path;
582 pa_bluetooth_device *d;
583
584 if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
585 pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err.message);
586 goto fail;
587 }
588
589 pa_log_debug("Device %s removed", path);
590
591 if ((d = pa_hashmap_remove(y->devices, path))) {
592 run_callback(y, d, TRUE);
593 device_free(d);
594 }
595
596 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
597
598 } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceCreated")) {
599 const char *path;
600
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.DeviceCreated: %s", err.message);
603 goto fail;
604 }
605
606 pa_log_debug("Device %s created", path);
607
608 found_device(y, path);
609 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
610
611 } else if (dbus_message_is_signal(m, "org.bluez.Manager", "AdapterAdded")) {
612 const char *path;
613
614 if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
615 pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err.message);
616 goto fail;
617 }
618
619 pa_log_debug("Adapter %s created", path);
620
621 found_adapter(y, path);
622 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
623
624 } else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") ||
625 dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
626 dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") ||
627 dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged") ||
628 dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
629
630 pa_bluetooth_device *d;
631
632 if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
633 DBusMessageIter arg_i;
634
635 if (!dbus_message_iter_init(m, &arg_i)) {
636 pa_log("Failed to parse PropertyChanged: %s", err.message);
637 goto fail;
638 }
639
640 if (dbus_message_has_interface(m, "org.bluez.Device")) {
641 if (parse_device_property(y, d, &arg_i) < 0)
642 goto fail;
643
644 } else if (dbus_message_has_interface(m, "org.bluez.Audio")) {
645 if (parse_audio_property(y, &d->audio_state, &arg_i) < 0)
646 goto fail;
647
648 } else if (dbus_message_has_interface(m, "org.bluez.Headset")) {
649 if (parse_audio_property(y, &d->headset_state, &arg_i) < 0)
650 goto fail;
651
652 } else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) {
653 if (parse_audio_property(y, &d->audio_sink_state, &arg_i) < 0)
654 goto fail;
655 } else if (dbus_message_has_interface(m, "org.bluez.AudioSource")) {
656 if (parse_audio_property(y, &d->audio_source_state, &arg_i) < 0)
657 goto fail;
658 }
659
660 run_callback(y, d, FALSE);
661 }
662
663 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
664
665 } else if (dbus_message_is_signal(m, "org.bluez.Device", "DisconnectRequested")) {
666 pa_bluetooth_device *d;
667
668 if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
669 /* Device will disconnect in 2 sec */
670 d->audio_state = PA_BT_AUDIO_STATE_DISCONNECTED;
671 d->audio_sink_state = PA_BT_AUDIO_STATE_DISCONNECTED;
672 d->audio_source_state = PA_BT_AUDIO_STATE_DISCONNECTED;
673 d->headset_state = PA_BT_AUDIO_STATE_DISCONNECTED;
674
675 run_callback(y, d, FALSE);
676 }
677
678 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
679
680 } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
681 const char *name, *old_owner, *new_owner;
682
683 if (!dbus_message_get_args(m, &err,
684 DBUS_TYPE_STRING, &name,
685 DBUS_TYPE_STRING, &old_owner,
686 DBUS_TYPE_STRING, &new_owner,
687 DBUS_TYPE_INVALID)) {
688 pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
689 goto fail;
690 }
691
692 if (pa_streq(name, "org.bluez")) {
693 if (old_owner && *old_owner) {
694 pa_log_debug("Bluetooth daemon disappeared.");
695 remove_all_devices(y);
696 }
697
698 if (new_owner && *new_owner) {
699 pa_log_debug("Bluetooth daemon appeared.");
700 list_adapters(y);
701 }
702 }
703
704 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
705 }
706
707 fail:
708 dbus_error_free(&err);
709
710 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
711 }
712
713 const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) {
714 pa_bluetooth_device *d;
715 void *state = NULL;
716
717 pa_assert(y);
718 pa_assert(PA_REFCNT_VALUE(y) > 0);
719 pa_assert(address);
720
721 if (!pa_hook_is_firing(&y->hook))
722 pa_bluetooth_discovery_sync(y);
723
724 while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
725 if (pa_streq(d->address, address))
726 return device_is_audio(d) ? d : NULL;
727
728 return NULL;
729 }
730
731 const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) {
732 pa_bluetooth_device *d;
733
734 pa_assert(y);
735 pa_assert(PA_REFCNT_VALUE(y) > 0);
736 pa_assert(path);
737
738 if (!pa_hook_is_firing(&y->hook))
739 pa_bluetooth_discovery_sync(y);
740
741 if ((d = pa_hashmap_get(y->devices, path)))
742 if (device_is_audio(d))
743 return d;
744
745 return NULL;
746 }
747
748 static int setup_dbus(pa_bluetooth_discovery *y) {
749 DBusError err;
750
751 dbus_error_init(&err);
752
753 y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err);
754
755 if (dbus_error_is_set(&err) || !y->connection) {
756 pa_log("Failed to get D-Bus connection: %s", err.message);
757 dbus_error_free(&err);
758 return -1;
759 }
760
761 return 0;
762 }
763
764 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
765 DBusError err;
766 pa_bluetooth_discovery *y;
767
768 pa_assert(c);
769
770 dbus_error_init(&err);
771
772 if ((y = pa_shared_get(c, "bluetooth-discovery")))
773 return pa_bluetooth_discovery_ref(y);
774
775 y = pa_xnew0(pa_bluetooth_discovery, 1);
776 PA_REFCNT_INIT(y);
777 y->core = c;
778 y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
779 PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
780 pa_hook_init(&y->hook, y);
781 pa_shared_set(c, "bluetooth-discovery", y);
782
783 if (setup_dbus(y) < 0)
784 goto fail;
785
786 /* dynamic detection of bluetooth audio devices */
787 if (!dbus_connection_add_filter(pa_dbus_connection_get(y->connection), filter_cb, y, NULL)) {
788 pa_log_error("Failed to add filter function");
789 goto fail;
790 }
791
792 if (pa_dbus_add_matches(
793 pa_dbus_connection_get(y->connection), &err,
794 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
795 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
796 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
797 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
798 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
799 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'",
800 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
801 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
802 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
803 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", NULL) < 0) {
804 pa_log("Failed to add D-Bus matches: %s", err.message);
805 goto fail;
806 }
807
808 list_adapters(y);
809
810 return y;
811
812 fail:
813
814 if (y)
815 pa_bluetooth_discovery_unref(y);
816
817 dbus_error_free(&err);
818
819 return NULL;
820 }
821
822 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
823 pa_assert(y);
824 pa_assert(PA_REFCNT_VALUE(y) > 0);
825
826 PA_REFCNT_INC(y);
827
828 return y;
829 }
830
831 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
832 pa_assert(y);
833 pa_assert(PA_REFCNT_VALUE(y) > 0);
834
835 if (PA_REFCNT_DEC(y) > 0)
836 return;
837
838 pa_dbus_free_pending_list(&y->pending);
839
840 if (y->devices) {
841 remove_all_devices(y);
842 pa_hashmap_free(y->devices, NULL, NULL);
843 }
844
845 if (y->connection) {
846 pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
847 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
848 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
849 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
850 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
851 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
852 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
853 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'",
854 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
855 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
856 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
857 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", NULL);
858
859 dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
860
861 pa_dbus_connection_unref(y->connection);
862 }
863
864 pa_hook_done(&y->hook);
865
866 if (y->core)
867 pa_shared_remove(y->core, "bluetooth-discovery");
868
869 pa_xfree(y);
870 }
871
872 void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *y) {
873 pa_assert(y);
874 pa_assert(PA_REFCNT_VALUE(y) > 0);
875
876 pa_dbus_sync_pending_list(&y->pending);
877 }
878
879 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y) {
880 pa_assert(y);
881 pa_assert(PA_REFCNT_VALUE(y) > 0);
882
883 return &y->hook;
884 }
885
886 const char*pa_bluetooth_get_form_factor(uint32_t class) {
887 unsigned i;
888 const char *r;
889
890 static const char * const table[] = {
891 [1] = "headset",
892 [2] = "hands-free",
893 [4] = "microphone",
894 [5] = "speaker",
895 [6] = "headphone",
896 [7] = "portable",
897 [8] = "car",
898 [10] = "hifi"
899 };
900
901 if (((class >> 8) & 31) != 4)
902 return NULL;
903
904 if ((i = (class >> 2) & 63) > PA_ELEMENTSOF(table))
905 r = NULL;
906 else
907 r = table[i];
908
909 if (!r)
910 pa_log_debug("Unknown Bluetooth minor device class %u", i);
911
912 return r;
913 }
914
915 char *pa_bluetooth_cleanup_name(const char *name) {
916 char *t, *s, *d;
917 pa_bool_t space = FALSE;
918
919 pa_assert(name);
920
921 while ((*name >= 1 && *name <= 32) || *name >= 127)
922 name++;
923
924 t = pa_xstrdup(name);
925
926 for (s = d = t; *s; s++) {
927
928 if (*s <= 32 || *s >= 127 || *s == '_') {
929 space = TRUE;
930 continue;
931 }
932
933 if (space) {
934 *(d++) = ' ';
935 space = FALSE;
936 }
937
938 *(d++) = *s;
939 }
940
941 *d = 0;
942
943 return t;
944 }
945
946 pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
947 pa_assert(uuid);
948
949 while (uuids) {
950 if (strcasecmp(uuids->uuid, uuid) == 0)
951 return TRUE;
952
953 uuids = uuids->next;
954 }
955
956 return FALSE;
957 }