]> code.delx.au - pulseaudio/blob - src/modules/bluetooth/bluetooth-util.c
bluetooth: remove racy GetProperties to check profile
[pulseaudio] / src / modules / bluetooth / bluetooth-util.c
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2008 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 <modules/dbus-util.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 pa_bluetooth_uuid *uuid_new(const char *uuid) {
43 pa_bluetooth_uuid *u;
44
45 u = pa_xnew(pa_bluetooth_uuid, 1);
46 u->uuid = pa_xstrdup(uuid);
47 PA_LLIST_INIT(pa_bluetooth_uuid, u);
48
49 return u;
50 }
51
52 static void uuid_free(pa_bluetooth_uuid *u) {
53 pa_assert(u);
54
55 pa_xfree(u->uuid);
56 pa_xfree(u);
57 }
58
59 static pa_bluetooth_device* device_new(const char *path) {
60 pa_bluetooth_device *d;
61
62 d = pa_xnew(pa_bluetooth_device, 1);
63
64 d->dead = FALSE;
65
66 d->device_info_valid = d->audio_sink_info_valid = d->headset_info_valid = 0;
67
68 d->name = NULL;
69 d->path = pa_xstrdup(path);
70 d->paired = -1;
71 d->alias = NULL;
72 d->device_connected = -1;
73 PA_LLIST_HEAD_INIT(pa_bluetooth_uuid, d->uuids);
74 d->address = NULL;
75 d->class = -1;
76 d->trusted = -1;
77
78 d->audio_sink_connected = -1;
79
80 d->headset_connected = -1;
81
82 return d;
83 }
84
85 static void device_free(pa_bluetooth_device *d) {
86 pa_bluetooth_uuid *u;
87
88 pa_assert(d);
89
90 while ((u = d->uuids)) {
91 PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u);
92 uuid_free(u);
93 }
94
95 pa_xfree(d->name);
96 pa_xfree(d->path);
97 pa_xfree(d->alias);
98 pa_xfree(d->address);
99 pa_xfree(d);
100 }
101
102 static pa_bool_t device_is_loaded(pa_bluetooth_device *d) {
103 pa_assert(d);
104
105 return
106 d->device_info_valid &&
107 d->audio_sink_info_valid &&
108 d->headset_info_valid;
109 }
110
111 static pa_bool_t device_is_audio(pa_bluetooth_device *d) {
112 pa_assert(d);
113
114 pa_assert(d->device_info_valid);
115 pa_assert(d->audio_sink_info_valid);
116 pa_assert(d->headset_info_valid);
117
118 return
119 d->device_info_valid > 0 &&
120 (d->audio_sink_info_valid > 0 || d->headset_info_valid > 0);
121 }
122
123 static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) {
124 const char *key;
125 DBusMessageIter variant_i;
126
127 pa_assert(y);
128 pa_assert(d);
129 pa_assert(i);
130
131 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
132 pa_log("Property name not a string.");
133 return -1;
134 }
135
136 dbus_message_iter_get_basic(i, &key);
137
138 if (!dbus_message_iter_next(i)) {
139 pa_log("Property value missing");
140 return -1;
141 }
142
143 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
144 pa_log("Property value not a variant.");
145 return -1;
146 }
147
148 dbus_message_iter_recurse(i, &variant_i);
149
150 /* pa_log_debug("Parsing property org.bluez.Device.%s", key); */
151
152 switch (dbus_message_iter_get_arg_type(&variant_i)) {
153
154 case DBUS_TYPE_STRING: {
155
156 const char *value;
157 dbus_message_iter_get_basic(&variant_i, &value);
158
159 if (pa_streq(key, "Name")) {
160 pa_xfree(d->name);
161 d->name = pa_xstrdup(value);
162 } else if (pa_streq(key, "Alias")) {
163 pa_xfree(d->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);
168 }
169
170 /* pa_log_debug("Value %s", value); */
171
172 break;
173 }
174
175 case DBUS_TYPE_BOOLEAN: {
176
177 dbus_bool_t value;
178 dbus_message_iter_get_basic(&variant_i, &value);
179
180 if (pa_streq(key, "Paired"))
181 d->paired = !!value;
182 else if (pa_streq(key, "Connected"))
183 d->device_connected = !!value;
184 else if (pa_streq(key, "Trusted"))
185 d->trusted = !!value;
186
187 /* pa_log_debug("Value %s", pa_yes_no(value)); */
188
189 break;
190 }
191
192 case DBUS_TYPE_UINT32: {
193
194 uint32_t value;
195 dbus_message_iter_get_basic(&variant_i, &value);
196
197 if (pa_streq(key, "Class"))
198 d->class = (int) value;
199
200 /* pa_log_debug("Value %u", (unsigned) value); */
201
202 break;
203 }
204
205 case DBUS_TYPE_ARRAY: {
206
207 DBusMessageIter ai;
208 dbus_message_iter_recurse(&variant_i, &ai);
209
210 if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING &&
211 pa_streq(key, "UUIDs")) {
212
213 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
214 pa_bluetooth_uuid *node;
215 const char *value;
216
217 dbus_message_iter_get_basic(&ai, &value);
218 node = uuid_new(value);
219 PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
220
221 if (!dbus_message_iter_next(&ai))
222 break;
223 }
224 }
225
226 break;
227 }
228 }
229
230 return 0;
231 }
232
233 static int parse_audio_property(pa_bluetooth_discovery *u, int *connected, DBusMessageIter *i) {
234 const char *key;
235 DBusMessageIter variant_i;
236
237 pa_assert(u);
238 pa_assert(connected);
239 pa_assert(i);
240
241 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
242 pa_log("Property name not a string.");
243 return -1;
244 }
245
246 dbus_message_iter_get_basic(i, &key);
247
248 if (!dbus_message_iter_next(i)) {
249 pa_log("Property value missing");
250 return -1;
251 }
252
253 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
254 pa_log("Property value not a variant.");
255 return -1;
256 }
257
258 dbus_message_iter_recurse(i, &variant_i);
259
260 /* pa_log_debug("Parsing property org.bluez.{AudioSink|Headset}.%s", key); */
261
262 switch (dbus_message_iter_get_arg_type(&variant_i)) {
263
264 case DBUS_TYPE_BOOLEAN: {
265
266 dbus_bool_t value;
267 dbus_message_iter_get_basic(&variant_i, &value);
268
269 if (pa_streq(key, "Connected"))
270 *connected = !!value;
271
272 /* pa_log_debug("Value %s", pa_yes_no(value)); */
273
274 break;
275 }
276 }
277
278 return 0;
279 }
280
281 static void run_callback(pa_bluetooth_discovery *y, pa_bluetooth_device *d, pa_bool_t dead) {
282 pa_assert(y);
283 pa_assert(d);
284
285 if (!device_is_loaded(d))
286 return;
287
288 if (!device_is_audio(d))
289 return;
290
291 d->dead = dead;
292 pa_hook_fire(&y->hook, d);
293 }
294
295 static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
296 DBusMessage *r;
297 DBusMessageIter arg_i, element_i;
298 pa_dbus_pending *p;
299 pa_bluetooth_device *d;
300 pa_bluetooth_discovery *y;
301 int valid;
302
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));
306
307 /* pa_log_debug("Got %s.GetProperties response for %s", */
308 /* dbus_message_get_interface(p->message), */
309 /* dbus_message_get_path(p->message)); */
310
311 d = p->call_data;
312
313 valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1;
314
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;
321
322 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
323
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));
326
327 goto finish;
328 }
329
330 if (!dbus_message_iter_init(r, &arg_i)) {
331 pa_log("GetProperties reply has no arguments.");
332 goto finish;
333 }
334
335 if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
336 pa_log("GetProperties argument is not an array.");
337 goto finish;
338 }
339
340 dbus_message_iter_recurse(&arg_i, &element_i);
341 while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) {
342
343 if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
344 DBusMessageIter dict_i;
345
346 dbus_message_iter_recurse(&element_i, &dict_i);
347
348 if (dbus_message_has_interface(p->message, "org.bluez.Device")) {
349 if (parse_device_property(y, d, &dict_i) < 0)
350 goto finish;
351
352 } else if (dbus_message_has_interface(p->message, "org.bluez.Headset")) {
353 if (parse_audio_property(y, &d->headset_connected, &dict_i) < 0)
354 goto finish;
355
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)
358 goto finish;
359 }
360 }
361
362 if (!dbus_message_iter_next(&element_i))
363 break;
364 }
365
366 finish:
367 run_callback(y, d, FALSE);
368
369 dbus_message_unref(r);
370
371 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
372 pa_dbus_pending_free(p);
373 }
374
375 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func) {
376 pa_dbus_pending *p;
377 DBusPendingCall *call;
378
379 pa_assert(y);
380 pa_assert(m);
381
382 pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
383
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);
387
388 return p;
389 }
390
391 static void found_device(pa_bluetooth_discovery *y, const char* path) {
392 DBusMessage *m;
393 pa_bluetooth_device *d;
394
395 pa_assert(y);
396 pa_assert(path);
397
398 d = device_new(path);
399
400 pa_hashmap_put(y->devices, d->path, d);
401
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);
404 }
405
406 static void list_devices_reply(DBusPendingCall *pending, void *userdata) {
407 DBusError e;
408 DBusMessage *r;
409 char **paths = NULL;
410 int num = -1;
411 pa_dbus_pending *p;
412 pa_bluetooth_discovery *y;
413
414 pa_assert(pending);
415
416 dbus_error_init(&e);
417
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));
421
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));
424 goto end;
425 }
426
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);
429 dbus_error_free(&e);
430 } else {
431 int i;
432
433 for (i = 0; i < num; ++i)
434 found_device(y, paths[i]);
435 }
436
437 end:
438 if (paths)
439 dbus_free_string_array (paths);
440
441 dbus_message_unref(r);
442
443 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
444 pa_dbus_pending_free(p);
445 }
446
447 static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
448 DBusMessage *m;
449
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);
452 }
453
454 static void list_adapters_reply(DBusPendingCall *pending, void *userdata) {
455 DBusError e;
456 DBusMessage *r;
457 char **paths = NULL;
458 int num = -1;
459 pa_dbus_pending *p;
460 pa_bluetooth_discovery *y;
461
462 pa_assert(pending);
463
464 dbus_error_init(&e);
465
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));
469
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));
472 goto end;
473 }
474
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);
477 dbus_error_free(&e);
478 } else {
479 int i;
480
481 for (i = 0; i < num; ++i)
482 found_adapter(y, paths[i]);
483 }
484
485 end:
486 if (paths)
487 dbus_free_string_array (paths);
488
489 dbus_message_unref(r);
490
491 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
492 pa_dbus_pending_free(p);
493 }
494
495 static void list_adapters(pa_bluetooth_discovery *y) {
496 DBusMessage *m;
497 pa_assert(y);
498
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);
501 }
502
503 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
504 DBusError err;
505 pa_bluetooth_discovery *y;
506
507 pa_assert(bus);
508 pa_assert(m);
509
510 pa_assert_se(y = userdata);
511
512 dbus_error_init(&err);
513
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));
518
519 if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) {
520 const char *path;
521 pa_bluetooth_device *d;
522
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);
525 goto fail;
526 }
527
528 pa_log_debug("Device %s removed", path);
529
530 if ((d = pa_hashmap_remove(y->devices, path))) {
531 run_callback(y, d, TRUE);
532 device_free(d);
533 }
534
535 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
536
537 } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceCreated")) {
538 const char *path;
539
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);
542 goto fail;
543 }
544
545 pa_log_debug("Device %s created", path);
546
547 found_device(y, path);
548 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
549
550 } else if (dbus_message_is_signal(m, "org.bluez.Manager", "AdapterAdded")) {
551 const char *path;
552
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);
555 goto fail;
556 }
557
558 pa_log_debug("Adapter %s created", path);
559
560 found_adapter(y, path);
561 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
562
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")) {
566
567 pa_bluetooth_device *d;
568
569 if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
570 DBusMessageIter arg_i;
571
572 if (!dbus_message_iter_init(m, &arg_i)) {
573 pa_log("Failed to parse PropertyChanged: %s", err.message);
574 goto fail;
575 }
576
577 if (dbus_message_has_interface(m, "org.bluez.Device")) {
578 if (parse_device_property(y, d, &arg_i) < 0)
579 goto fail;
580
581 } else if (dbus_message_has_interface(m, "org.bluez.Headset")) {
582 if (parse_audio_property(y, &d->headset_connected, &arg_i) < 0)
583 goto fail;
584
585 } else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) {
586 if (parse_audio_property(y, &d->audio_sink_connected, &arg_i) < 0)
587 goto fail;
588 }
589
590 run_callback(y, d, FALSE);
591 }
592
593 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
594 }
595
596 fail:
597 dbus_error_free(&err);
598
599 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
600 }
601
602 const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) {
603 pa_bluetooth_device *d;
604 void *state = NULL;
605
606 pa_assert(y);
607 pa_assert(PA_REFCNT_VALUE(y) > 0);
608 pa_assert(address);
609
610 if (!pa_hook_is_firing(&y->hook))
611 pa_bluetooth_discovery_sync(y);
612
613 while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
614 if (pa_streq(d->address, address))
615 return d;
616
617 return NULL;
618 }
619
620 const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) {
621 pa_assert(y);
622 pa_assert(PA_REFCNT_VALUE(y) > 0);
623 pa_assert(path);
624
625 if (!pa_hook_is_firing(&y->hook))
626 pa_bluetooth_discovery_sync(y);
627
628 return pa_hashmap_get(y->devices, path);
629 }
630
631 static int setup_dbus(pa_bluetooth_discovery *y) {
632 DBusError err;
633
634 dbus_error_init(&err);
635
636 y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err);
637
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);
641 return -1;
642 }
643
644 return 0;
645 }
646
647 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
648 DBusError err;
649 pa_bluetooth_discovery *y;
650
651 pa_assert(c);
652
653 dbus_error_init(&err);
654
655 if ((y = pa_shared_get(c, "bluetooth-discovery")))
656 return pa_bluetooth_discovery_ref(y);
657
658 y = pa_xnew0(pa_bluetooth_discovery, 1);
659 PA_REFCNT_INIT(y);
660 y->core = c;
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);
665
666 if (setup_dbus(y) < 0)
667 goto fail;
668
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");
672 goto fail;
673 }
674
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);
684 goto fail;
685 }
686
687 list_adapters(y);
688
689 return y;
690
691 fail:
692
693 if (y)
694 pa_bluetooth_discovery_unref(y);
695
696 dbus_error_free(&err);
697
698 return NULL;
699 }
700
701 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
702 pa_assert(y);
703 pa_assert(PA_REFCNT_VALUE(y) > 0);
704
705 PA_REFCNT_INC(y);
706
707 return y;
708 }
709
710 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
711 pa_bluetooth_device *d;
712
713 pa_assert(y);
714 pa_assert(PA_REFCNT_VALUE(y) > 0);
715
716 if (PA_REFCNT_DEC(y) > 0)
717 return;
718
719 pa_dbus_free_pending_list(&y->pending);
720
721 if (y->devices) {
722 while ((d = pa_hashmap_steal_first(y->devices))) {
723 run_callback(y, d, TRUE);
724 device_free(d);
725 }
726
727 pa_hashmap_free(y->devices, NULL, NULL);
728 }
729
730 if (y->connection) {
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);
739
740 dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
741
742 pa_dbus_connection_unref(y->connection);
743 }
744
745 pa_hook_done(&y->hook);
746
747 if (y->core)
748 pa_shared_remove(y->core, "bluetooth-discovery");
749 }
750
751 void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *y) {
752 pa_assert(y);
753 pa_assert(PA_REFCNT_VALUE(y) > 0);
754
755 pa_dbus_sync_pending_list(&y->pending);
756 }
757
758 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y) {
759 pa_assert(y);
760 pa_assert(PA_REFCNT_VALUE(y) > 0);
761
762 return &y->hook;
763 }
764
765 const char*pa_bluetooth_get_form_factor(uint32_t class) {
766 unsigned i;
767 const char *r;
768
769 static const char * const table[] = {
770 [1] = "headset",
771 [2] = "hands-free",
772 [4] = "microphone",
773 [5] = "speaker",
774 [6] = "headphone",
775 [7] = "portable",
776 [8] = "car",
777 [10] = "hifi"
778 };
779
780 if (((class >> 8) & 31) != 4)
781 return NULL;
782
783 if ((i = (class >> 2) & 63) > PA_ELEMENTSOF(table))
784 r = NULL;
785 else
786 r = table[i];
787
788 if (!r)
789 pa_log_debug("Unknown Bluetooth minor device class %u", i);
790
791 return r;
792 }
793
794 char *pa_bluetooth_cleanup_name(const char *name) {
795 char *t, *s, *d;
796 pa_bool_t space = FALSE;
797
798 pa_assert(name);
799
800 while ((*name >= 1 && *name <= 32) || *name >= 127)
801 name++;
802
803 t = pa_xstrdup(name);
804
805 for (s = d = t; *s; s++) {
806
807 if (*s <= 32 || *s >= 127 || *s == '_') {
808 space = TRUE;
809 continue;
810 }
811
812 if (space) {
813 *(d++) = ' ';
814 space = FALSE;
815 }
816
817 *(d++) = *s;
818 }
819
820 *d = 0;
821
822 return t;
823 }