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