]> code.delx.au - pulseaudio/blob - src/modules/bluetooth/bluetooth-util.c
bluetooth: Don't access a transport after it's freed.
[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 <pulse/xmalloc.h>
27
28 #include <pulsecore/core-util.h>
29 #include <pulsecore/shared.h>
30 #include <pulsecore/dbus-shared.h>
31
32 #include "bluetooth-util.h"
33 #include "a2dp-codecs.h"
34
35 #define HFP_AG_ENDPOINT "/MediaEndpoint/HFPAG"
36 #define HFP_HS_ENDPOINT "/MediaEndpoint/HFPHS"
37 #define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
38 #define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
39
40 #define ENDPOINT_INTROSPECT_XML \
41 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
42 "<node>" \
43 " <interface name=\"org.bluez.MediaEndpoint\">" \
44 " <method name=\"SetConfiguration\">" \
45 " <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \
46 " <arg name=\"configuration\" direction=\"in\" type=\"ay\"/>" \
47 " </method>" \
48 " <method name=\"SelectConfiguration\">" \
49 " <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>" \
50 " <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>" \
51 " </method>" \
52 " <method name=\"ClearConfiguration\">" \
53 " </method>" \
54 " <method name=\"Release\">" \
55 " </method>" \
56 " </interface>" \
57 " <interface name=\"org.freedesktop.DBus.Introspectable\">" \
58 " <method name=\"Introspect\">" \
59 " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
60 " </method>" \
61 " </interface>" \
62 "</node>"
63
64 struct pa_bluetooth_discovery {
65 PA_REFCNT_DECLARE;
66
67 pa_core *core;
68 pa_dbus_connection *connection;
69 PA_LLIST_HEAD(pa_dbus_pending, pending);
70 pa_hashmap *devices;
71 pa_hashmap *transports;
72 pa_hook hooks[PA_BLUETOOTH_HOOK_MAX];
73 pa_bool_t filter_added;
74 };
75
76 static void get_properties_reply(DBusPendingCall *pending, void *userdata);
77 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func, void *call_data);
78 static void found_adapter(pa_bluetooth_discovery *y, const char *path);
79 static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path);
80
81 static pa_bt_audio_state_t audio_state_from_string(const char* value) {
82 pa_assert(value);
83
84 if (pa_streq(value, "disconnected"))
85 return PA_BT_AUDIO_STATE_DISCONNECTED;
86 else if (pa_streq(value, "connecting"))
87 return PA_BT_AUDIO_STATE_CONNECTING;
88 else if (pa_streq(value, "connected"))
89 return PA_BT_AUDIO_STATE_CONNECTED;
90 else if (pa_streq(value, "playing"))
91 return PA_BT_AUDIO_STATE_PLAYING;
92
93 return PA_BT_AUDIO_STATE_INVALID;
94 }
95
96 const char *pa_bt_profile_to_string(enum profile profile) {
97 switch(profile) {
98 case PROFILE_A2DP:
99 return "a2dp";
100 case PROFILE_A2DP_SOURCE:
101 return "a2dp_source";
102 case PROFILE_HSP:
103 return "hsp";
104 case PROFILE_HFGW:
105 return "hfgw";
106 case PROFILE_OFF:
107 pa_assert_not_reached();
108 }
109
110 pa_assert_not_reached();
111 }
112
113 static int profile_from_interface(const char *interface, enum profile *p) {
114 pa_assert(interface);
115 pa_assert(p);
116
117 if (pa_streq(interface, "org.bluez.AudioSink")) {
118 *p = PROFILE_A2DP;
119 return 0;
120 } else if (pa_streq(interface, "org.bluez.AudioSource")) {
121 *p = PROFILE_A2DP_SOURCE;
122 return 0;
123 } else if (pa_streq(interface, "org.bluez.Headset")) {
124 *p = PROFILE_HSP;
125 return 0;
126 } else if (pa_streq(interface, "org.bluez.HandsfreeGateway")) {
127 *p = PROFILE_HFGW;
128 return 0;
129 }
130
131 return -1;
132 }
133
134 static pa_bluetooth_transport_state_t audio_state_to_transport_state(pa_bt_audio_state_t state) {
135 switch (state) {
136 case PA_BT_AUDIO_STATE_INVALID: /* Typically if state hasn't been received yet */
137 case PA_BT_AUDIO_STATE_DISCONNECTED:
138 case PA_BT_AUDIO_STATE_CONNECTING:
139 return PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
140 case PA_BT_AUDIO_STATE_CONNECTED:
141 return PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
142 case PA_BT_AUDIO_STATE_PLAYING:
143 return PA_BLUETOOTH_TRANSPORT_STATE_PLAYING;
144 }
145
146 pa_assert_not_reached();
147 }
148
149 static pa_bluetooth_uuid *uuid_new(const char *uuid) {
150 pa_bluetooth_uuid *u;
151
152 u = pa_xnew(pa_bluetooth_uuid, 1);
153 u->uuid = pa_xstrdup(uuid);
154 PA_LLIST_INIT(pa_bluetooth_uuid, u);
155
156 return u;
157 }
158
159 static void uuid_free(pa_bluetooth_uuid *u) {
160 pa_assert(u);
161
162 pa_xfree(u->uuid);
163 pa_xfree(u);
164 }
165
166 static pa_bluetooth_device* device_new(pa_bluetooth_discovery *discovery, const char *path) {
167 pa_bluetooth_device *d;
168 unsigned i;
169
170 pa_assert(discovery);
171 pa_assert(path);
172
173 d = pa_xnew0(pa_bluetooth_device, 1);
174
175 d->discovery = discovery;
176 d->dead = FALSE;
177
178 d->device_info_valid = 0;
179
180 d->name = NULL;
181 d->path = pa_xstrdup(path);
182 d->paired = -1;
183 d->alias = NULL;
184 PA_LLIST_HEAD_INIT(pa_bluetooth_uuid, d->uuids);
185 d->address = NULL;
186 d->class = -1;
187 d->trusted = -1;
188
189 d->audio_state = PA_BT_AUDIO_STATE_INVALID;
190
191 for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
192 d->profile_state[i] = PA_BT_AUDIO_STATE_INVALID;
193
194 return d;
195 }
196
197 static void transport_free(pa_bluetooth_transport *t) {
198 pa_assert(t);
199
200 pa_xfree(t->owner);
201 pa_xfree(t->path);
202 pa_xfree(t->config);
203 pa_xfree(t);
204 }
205
206 static void device_free(pa_bluetooth_device *d) {
207 pa_bluetooth_uuid *u;
208 pa_bluetooth_transport *t;
209 unsigned i;
210
211 pa_assert(d);
212
213 for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
214 if (!(t = d->transports[i]))
215 continue;
216
217 d->transports[i] = NULL;
218 pa_hashmap_remove(d->discovery->transports, t->path);
219 t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
220 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
221 transport_free(t);
222 }
223
224 while ((u = d->uuids)) {
225 PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u);
226 uuid_free(u);
227 }
228
229 pa_xfree(d->name);
230 pa_xfree(d->path);
231 pa_xfree(d->alias);
232 pa_xfree(d->address);
233 pa_xfree(d);
234 }
235
236 static pa_bool_t device_is_audio_ready(const pa_bluetooth_device *d) {
237 unsigned i;
238
239 pa_assert(d);
240
241 if (!d->device_info_valid || d->audio_state == PA_BT_AUDIO_STATE_INVALID)
242 return FALSE;
243
244 for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
245 if (d->profile_state[i] != PA_BT_AUDIO_STATE_INVALID)
246 return TRUE;
247
248 return FALSE;
249 }
250
251 static const char *check_variant_property(DBusMessageIter *i) {
252 const char *key;
253
254 pa_assert(i);
255
256 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
257 pa_log("Property name not a string.");
258 return NULL;
259 }
260
261 dbus_message_iter_get_basic(i, &key);
262
263 if (!dbus_message_iter_next(i)) {
264 pa_log("Property value missing");
265 return NULL;
266 }
267
268 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
269 pa_log("Property value not a variant.");
270 return NULL;
271 }
272
273 return key;
274 }
275
276 static int parse_manager_property(pa_bluetooth_discovery *y, DBusMessageIter *i) {
277 const char *key;
278 DBusMessageIter variant_i;
279
280 pa_assert(y);
281
282 key = check_variant_property(i);
283 if (key == NULL)
284 return -1;
285
286 dbus_message_iter_recurse(i, &variant_i);
287
288 switch (dbus_message_iter_get_arg_type(&variant_i)) {
289
290 case DBUS_TYPE_ARRAY: {
291
292 DBusMessageIter ai;
293 dbus_message_iter_recurse(&variant_i, &ai);
294
295 if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_OBJECT_PATH &&
296 pa_streq(key, "Adapters")) {
297
298 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
299 const char *value;
300
301 dbus_message_iter_get_basic(&ai, &value);
302
303 found_adapter(y, value);
304
305 dbus_message_iter_next(&ai);
306 }
307 }
308
309 break;
310 }
311 }
312
313 return 0;
314 }
315
316 static int parse_adapter_property(pa_bluetooth_discovery *y, DBusMessageIter *i) {
317 const char *key;
318 DBusMessageIter variant_i;
319
320 pa_assert(y);
321
322 key = check_variant_property(i);
323 if (key == NULL)
324 return -1;
325
326 dbus_message_iter_recurse(i, &variant_i);
327
328 switch (dbus_message_iter_get_arg_type(&variant_i)) {
329
330 case DBUS_TYPE_ARRAY: {
331
332 DBusMessageIter ai;
333 dbus_message_iter_recurse(&variant_i, &ai);
334
335 if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_OBJECT_PATH &&
336 pa_streq(key, "Devices")) {
337
338 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
339 const char *value;
340
341 dbus_message_iter_get_basic(&ai, &value);
342
343 found_device(y, value);
344
345 dbus_message_iter_next(&ai);
346 }
347 }
348
349 break;
350 }
351 }
352
353 return 0;
354 }
355
356 static int parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i) {
357 const char *key;
358 DBusMessageIter variant_i;
359
360 pa_assert(d);
361
362 key = check_variant_property(i);
363 if (key == NULL)
364 return -1;
365
366 dbus_message_iter_recurse(i, &variant_i);
367
368 /* pa_log_debug("Parsing property org.bluez.Device.%s", key); */
369
370 switch (dbus_message_iter_get_arg_type(&variant_i)) {
371
372 case DBUS_TYPE_STRING: {
373
374 const char *value;
375 dbus_message_iter_get_basic(&variant_i, &value);
376
377 if (pa_streq(key, "Name")) {
378 pa_xfree(d->name);
379 d->name = pa_xstrdup(value);
380 } else if (pa_streq(key, "Alias")) {
381 pa_xfree(d->alias);
382 d->alias = pa_xstrdup(value);
383 } else if (pa_streq(key, "Address")) {
384 pa_xfree(d->address);
385 d->address = pa_xstrdup(value);
386 }
387
388 /* pa_log_debug("Value %s", value); */
389
390 break;
391 }
392
393 case DBUS_TYPE_BOOLEAN: {
394
395 dbus_bool_t value;
396 dbus_message_iter_get_basic(&variant_i, &value);
397
398 if (pa_streq(key, "Paired"))
399 d->paired = !!value;
400 else if (pa_streq(key, "Trusted"))
401 d->trusted = !!value;
402
403 /* pa_log_debug("Value %s", pa_yes_no(value)); */
404
405 break;
406 }
407
408 case DBUS_TYPE_UINT32: {
409
410 uint32_t value;
411 dbus_message_iter_get_basic(&variant_i, &value);
412
413 if (pa_streq(key, "Class"))
414 d->class = (int) value;
415
416 /* pa_log_debug("Value %u", (unsigned) value); */
417
418 break;
419 }
420
421 case DBUS_TYPE_ARRAY: {
422
423 DBusMessageIter ai;
424 dbus_message_iter_recurse(&variant_i, &ai);
425
426 if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING &&
427 pa_streq(key, "UUIDs")) {
428 DBusMessage *m;
429 pa_bool_t has_audio = FALSE;
430
431 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
432 pa_bluetooth_uuid *node;
433 const char *value;
434 struct pa_bluetooth_hook_uuid_data uuiddata;
435
436 dbus_message_iter_get_basic(&ai, &value);
437
438 if (pa_bluetooth_uuid_has(d->uuids, value)) {
439 dbus_message_iter_next(&ai);
440 continue;
441 }
442
443 node = uuid_new(value);
444 PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
445
446 uuiddata.device = d;
447 uuiddata.uuid = value;
448 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED], &uuiddata);
449
450 /* Vudentz said the interfaces are here when the UUIDs are announced */
451 if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) {
452 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.HandsfreeGateway", "GetProperties"));
453 send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
454 has_audio = TRUE;
455 } else if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) {
456 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset", "GetProperties"));
457 send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
458 has_audio = TRUE;
459 } else if (strcasecmp(A2DP_SINK_UUID, value) == 0) {
460 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink", "GetProperties"));
461 send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
462 has_audio = TRUE;
463 } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) {
464 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource", "GetProperties"));
465 send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
466 has_audio = TRUE;
467 }
468
469 dbus_message_iter_next(&ai);
470 }
471
472 /* 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 */
473 if (has_audio) {
474 pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
475 send_and_add_to_pending(d->discovery, m, get_properties_reply, d);
476 }
477 }
478
479 break;
480 }
481 }
482
483 return 0;
484 }
485
486 static const char *transport_state_to_string(pa_bluetooth_transport_state_t state) {
487 switch (state) {
488 case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
489 return "disconnected";
490 case PA_BLUETOOTH_TRANSPORT_STATE_IDLE:
491 return "idle";
492 case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
493 return "playing";
494 }
495
496 pa_assert_not_reached();
497 }
498
499 static int parse_audio_property(pa_bluetooth_device *d, const char *interface, DBusMessageIter *i) {
500 pa_bluetooth_transport *transport;
501 const char *key;
502 DBusMessageIter variant_i;
503 bool is_audio_interface;
504 enum profile p = PROFILE_OFF;
505
506 pa_assert(d);
507 pa_assert(interface);
508 pa_assert(i);
509
510 if (!(is_audio_interface = pa_streq(interface, "org.bluez.Audio")))
511 if (profile_from_interface(interface, &p) < 0)
512 return 0; /* Interface not known so silently ignore property */
513
514 key = check_variant_property(i);
515 if (key == NULL)
516 return -1;
517
518 transport = p == PROFILE_OFF ? NULL : d->transports[p];
519
520 dbus_message_iter_recurse(i, &variant_i);
521
522 /* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
523
524 switch (dbus_message_iter_get_arg_type(&variant_i)) {
525
526 case DBUS_TYPE_STRING: {
527
528 const char *value;
529 dbus_message_iter_get_basic(&variant_i, &value);
530
531 if (pa_streq(key, "State")) {
532 pa_bt_audio_state_t state = audio_state_from_string(value);
533 pa_bluetooth_transport_state_t old_state;
534
535 pa_log_debug("Device %s interface %s property 'State' changed to value '%s'", d->path, interface, value);
536
537 if (state == PA_BT_AUDIO_STATE_INVALID)
538 return -1;
539
540 if (is_audio_interface) {
541 d->audio_state = state;
542 break;
543 }
544
545 pa_assert(p != PROFILE_OFF);
546
547 d->profile_state[p] = state;
548
549 if (!transport)
550 break;
551
552 old_state = transport->state;
553 transport->state = audio_state_to_transport_state(state);
554
555 if (transport->state != old_state) {
556 pa_log_debug("Transport %s (profile %s) changed state from %s to %s.", transport->path,
557 pa_bt_profile_to_string(transport->profile), transport_state_to_string(old_state),
558 transport_state_to_string(transport->state));
559
560 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], transport);
561 }
562 }
563
564 break;
565 }
566
567 case DBUS_TYPE_UINT16: {
568 uint16_t value;
569
570 dbus_message_iter_get_basic(&variant_i, &value);
571
572 if (pa_streq(key, "MicrophoneGain")) {
573 uint16_t gain;
574
575 pa_log_debug("dbus: property '%s' changed to value '%u'", key, value);
576
577 if (!transport) {
578 pa_log("Volume change does not have an associated transport");
579 return -1;
580 }
581
582 if ((gain = PA_MIN(value, HSP_MAX_GAIN)) == transport->microphone_gain)
583 break;
584
585 transport->microphone_gain = gain;
586 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED], transport);
587 } else if (pa_streq(key, "SpeakerGain")) {
588 uint16_t gain;
589
590 pa_log_debug("dbus: property '%s' changed to value '%u'", key, value);
591
592 if (!transport) {
593 pa_log("Volume change does not have an associated transport");
594 return -1;
595 }
596
597 if ((gain = PA_MIN(value, HSP_MAX_GAIN)) == transport->speaker_gain)
598 break;
599
600 transport->speaker_gain = gain;
601 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED], transport);
602 }
603
604 break;
605 }
606 }
607
608 return 0;
609 }
610
611 static void run_callback(pa_bluetooth_device *d, pa_bool_t dead) {
612 pa_assert(d);
613
614 if (!device_is_audio_ready(d))
615 return;
616
617 d->dead = dead;
618 pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], d);
619 }
620
621 static void remove_all_devices(pa_bluetooth_discovery *y) {
622 pa_bluetooth_device *d;
623
624 pa_assert(y);
625
626 while ((d = pa_hashmap_steal_first(y->devices))) {
627 run_callback(d, TRUE);
628 device_free(d);
629 }
630 }
631
632 static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path) {
633 DBusMessage *m;
634 pa_bluetooth_device *d;
635
636 pa_assert(y);
637 pa_assert(path);
638
639 d = pa_hashmap_get(y->devices, path);
640 if (d)
641 return d;
642
643 d = device_new(y, path);
644
645 pa_hashmap_put(y->devices, d->path, d);
646
647 pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties"));
648 send_and_add_to_pending(y, m, get_properties_reply, d);
649
650 /* Before we read the other properties (Audio, AudioSink, AudioSource,
651 * Headset) we wait that the UUID is read */
652 return d;
653 }
654
655 static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
656 DBusMessage *r;
657 DBusMessageIter arg_i, element_i;
658 pa_dbus_pending *p;
659 pa_bluetooth_device *d;
660 pa_bluetooth_discovery *y;
661 int valid;
662 bool old_any_connected;
663
664 pa_assert_se(p = userdata);
665 pa_assert_se(y = p->context_data);
666 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
667
668 /* pa_log_debug("Got %s.GetProperties response for %s", */
669 /* dbus_message_get_interface(p->message), */
670 /* dbus_message_get_path(p->message)); */
671
672 /* We don't use p->call_data here right-away since the device
673 * might already be invalidated at this point */
674
675 if (dbus_message_has_interface(p->message, "org.bluez.Manager") ||
676 dbus_message_has_interface(p->message, "org.bluez.Adapter"))
677 d = NULL;
678 else
679 d = pa_hashmap_get(y->devices, dbus_message_get_path(p->message));
680
681 pa_assert(p->call_data == d);
682
683 if (d != NULL)
684 old_any_connected = pa_bluetooth_device_any_audio_connected(d);
685
686 valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1;
687
688 if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties"))
689 d->device_info_valid = valid;
690
691 if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
692 pa_log_debug("Bluetooth daemon is apparently not available.");
693 remove_all_devices(y);
694 goto finish2;
695 }
696
697 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
698 pa_log("%s.GetProperties() failed: %s: %s", dbus_message_get_interface(p->message), dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
699 goto finish;
700 }
701
702 if (!dbus_message_iter_init(r, &arg_i)) {
703 pa_log("GetProperties reply has no arguments.");
704 goto finish;
705 }
706
707 if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
708 pa_log("GetProperties argument is not an array.");
709 goto finish;
710 }
711
712 dbus_message_iter_recurse(&arg_i, &element_i);
713 while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) {
714
715 if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
716 DBusMessageIter dict_i;
717
718 dbus_message_iter_recurse(&element_i, &dict_i);
719
720 if (dbus_message_has_interface(p->message, "org.bluez.Manager")) {
721 if (parse_manager_property(y, &dict_i) < 0)
722 goto finish;
723
724 } else if (dbus_message_has_interface(p->message, "org.bluez.Adapter")) {
725 if (parse_adapter_property(y, &dict_i) < 0)
726 goto finish;
727
728 } else if (dbus_message_has_interface(p->message, "org.bluez.Device")) {
729 if (parse_device_property(d, &dict_i) < 0)
730 goto finish;
731
732 } else if (parse_audio_property(d, dbus_message_get_interface(p->message), &dict_i) < 0)
733 goto finish;
734
735 }
736
737 dbus_message_iter_next(&element_i);
738 }
739
740 finish:
741 if (d != NULL && old_any_connected != pa_bluetooth_device_any_audio_connected(d))
742 run_callback(d, FALSE);
743
744 finish2:
745 dbus_message_unref(r);
746
747 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
748 pa_dbus_pending_free(p);
749 }
750
751 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func, void *call_data) {
752 pa_dbus_pending *p;
753 DBusPendingCall *call;
754
755 pa_assert(y);
756 pa_assert(m);
757
758 pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
759
760 p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, call_data);
761 PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);
762 dbus_pending_call_set_notify(call, func, p, NULL);
763
764 return p;
765 }
766
767 static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
768 DBusError e;
769 DBusMessage *r;
770 pa_dbus_pending *p;
771 pa_bluetooth_discovery *y;
772 char *endpoint;
773
774 pa_assert(pending);
775
776 dbus_error_init(&e);
777
778 pa_assert_se(p = userdata);
779 pa_assert_se(y = p->context_data);
780 pa_assert_se(endpoint = p->call_data);
781 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
782
783 if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
784 pa_log_debug("Bluetooth daemon is apparently not available.");
785 remove_all_devices(y);
786 goto finish;
787 }
788
789 if (dbus_message_is_error(r, PA_BLUETOOTH_ERROR_NOT_SUPPORTED)) {
790 pa_log_info("Couldn't register endpoint %s, because BlueZ is configured to disable the endpoint type.", endpoint);
791 goto finish;
792 }
793
794 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
795 pa_log("org.bluez.Media.RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
796 goto finish;
797 }
798
799 finish:
800 dbus_message_unref(r);
801
802 PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
803 pa_dbus_pending_free(p);
804
805 pa_xfree(endpoint);
806 }
807
808 static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
809 DBusMessage *m;
810 DBusMessageIter i, d;
811 uint8_t codec = 0;
812
813 pa_log_debug("Registering %s on adapter %s.", endpoint, path);
814
815 pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Media", "RegisterEndpoint"));
816
817 dbus_message_iter_init_append(m, &i);
818
819 dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint);
820
821 dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
822 DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
823 &d);
824
825 pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid);
826
827 pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec);
828
829 if (pa_streq(uuid, HFP_AG_UUID) || pa_streq(uuid, HFP_HS_UUID)) {
830 uint8_t capability = 0;
831 pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capability, 1);
832 } else {
833 a2dp_sbc_t capabilities;
834
835 capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL |
836 SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO;
837 capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 |
838 SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000;
839 capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS;
840 capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8;
841 capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 |
842 SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16;
843 capabilities.min_bitpool = MIN_BITPOOL;
844 capabilities.max_bitpool = MAX_BITPOOL;
845
846 pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
847 }
848
849 dbus_message_iter_close_container(&i, &d);
850
851 send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
852 }
853
854 static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
855 DBusMessage *m;
856
857 pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "GetProperties"));
858 send_and_add_to_pending(y, m, get_properties_reply, NULL);
859
860 register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID);
861 register_endpoint(y, path, HFP_HS_ENDPOINT, HFP_HS_UUID);
862 register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID);
863 register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID);
864 }
865
866 static void list_adapters(pa_bluetooth_discovery *y) {
867 DBusMessage *m;
868 pa_assert(y);
869
870 pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "GetProperties"));
871 send_and_add_to_pending(y, m, get_properties_reply, NULL);
872 }
873
874 static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *i) {
875 const char *key;
876 DBusMessageIter variant_i;
877
878 key = check_variant_property(i);
879 if (key == NULL)
880 return -1;
881
882 dbus_message_iter_recurse(i, &variant_i);
883
884 switch (dbus_message_iter_get_arg_type(&variant_i)) {
885
886 case DBUS_TYPE_BOOLEAN: {
887
888 dbus_bool_t value;
889 dbus_message_iter_get_basic(&variant_i, &value);
890
891 if (pa_streq(key, "NREC") && t->nrec != value) {
892 t->nrec = value;
893 pa_log_debug("Transport %s: Property 'NREC' changed to %s.", t->path, t->nrec ? "True" : "False");
894 pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED], t);
895 }
896
897 break;
898 }
899 }
900
901 return 0;
902 }
903
904 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
905 DBusError err;
906 pa_bluetooth_discovery *y;
907
908 pa_assert(bus);
909 pa_assert(m);
910
911 pa_assert_se(y = userdata);
912
913 dbus_error_init(&err);
914
915 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
916 dbus_message_get_interface(m),
917 dbus_message_get_path(m),
918 dbus_message_get_member(m));
919
920 if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) {
921 const char *path;
922 pa_bluetooth_device *d;
923
924 if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
925 pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err.message);
926 goto fail;
927 }
928
929 pa_log_debug("Device %s removed", path);
930
931 if ((d = pa_hashmap_remove(y->devices, path))) {
932 run_callback(d, TRUE);
933 device_free(d);
934 }
935
936 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
937
938 } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceCreated")) {
939 const char *path;
940
941 if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
942 pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err.message);
943 goto fail;
944 }
945
946 pa_log_debug("Device %s created", path);
947
948 found_device(y, path);
949 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
950
951 } else if (dbus_message_is_signal(m, "org.bluez.Manager", "AdapterAdded")) {
952 const char *path;
953
954 if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
955 pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err.message);
956 goto fail;
957 }
958
959 pa_log_debug("Adapter %s created", path);
960
961 found_adapter(y, path);
962 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
963
964 } else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") ||
965 dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
966 dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") ||
967 dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged") ||
968 dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged") ||
969 dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
970
971 pa_bluetooth_device *d;
972
973 if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
974 DBusMessageIter arg_i;
975 bool old_any_connected = pa_bluetooth_device_any_audio_connected(d);
976
977 if (!dbus_message_iter_init(m, &arg_i)) {
978 pa_log("Failed to parse PropertyChanged: %s", err.message);
979 goto fail;
980 }
981
982 if (dbus_message_has_interface(m, "org.bluez.Device")) {
983 if (parse_device_property(d, &arg_i) < 0)
984 goto fail;
985
986 } else if (parse_audio_property(d, dbus_message_get_interface(m), &arg_i) < 0)
987 goto fail;
988
989 if (old_any_connected != pa_bluetooth_device_any_audio_connected(d))
990 run_callback(d, FALSE);
991 }
992
993 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
994
995 } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
996 const char *name, *old_owner, *new_owner;
997
998 if (!dbus_message_get_args(m, &err,
999 DBUS_TYPE_STRING, &name,
1000 DBUS_TYPE_STRING, &old_owner,
1001 DBUS_TYPE_STRING, &new_owner,
1002 DBUS_TYPE_INVALID)) {
1003 pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
1004 goto fail;
1005 }
1006
1007 if (pa_streq(name, "org.bluez")) {
1008 if (old_owner && *old_owner) {
1009 pa_log_debug("Bluetooth daemon disappeared.");
1010 remove_all_devices(y);
1011 }
1012
1013 if (new_owner && *new_owner) {
1014 pa_log_debug("Bluetooth daemon appeared.");
1015 list_adapters(y);
1016 }
1017 }
1018
1019 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1020 } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) {
1021 pa_bluetooth_transport *t;
1022 DBusMessageIter arg_i;
1023
1024 if (!(t = pa_hashmap_get(y->transports, dbus_message_get_path(m))))
1025 goto fail;
1026
1027 if (!dbus_message_iter_init(m, &arg_i)) {
1028 pa_log("Failed to parse PropertyChanged: %s", err.message);
1029 goto fail;
1030 }
1031
1032 if (transport_parse_property(t, &arg_i) < 0)
1033 goto fail;
1034
1035 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1036 }
1037
1038 fail:
1039 dbus_error_free(&err);
1040
1041 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1042 }
1043
1044 pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) {
1045 pa_bluetooth_device *d;
1046 void *state = NULL;
1047
1048 pa_assert(y);
1049 pa_assert(PA_REFCNT_VALUE(y) > 0);
1050 pa_assert(address);
1051
1052 if (!pa_hook_is_firing(&y->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED]))
1053 pa_bluetooth_discovery_sync(y);
1054
1055 while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
1056 if (pa_streq(d->address, address))
1057 return device_is_audio_ready(d) ? d : NULL;
1058
1059 return NULL;
1060 }
1061
1062 pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) {
1063 pa_bluetooth_device *d;
1064
1065 pa_assert(y);
1066 pa_assert(PA_REFCNT_VALUE(y) > 0);
1067 pa_assert(path);
1068
1069 if (!pa_hook_is_firing(&y->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED]))
1070 pa_bluetooth_discovery_sync(y);
1071
1072 if ((d = pa_hashmap_get(y->devices, path)))
1073 if (device_is_audio_ready(d))
1074 return d;
1075
1076 return NULL;
1077 }
1078
1079 bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d) {
1080 unsigned i;
1081
1082 pa_assert(d);
1083
1084 if (d->dead || !device_is_audio_ready(d))
1085 return false;
1086
1087 /* Make sure audio_state is *not* in CONNECTING state before we fire the hook
1088 * to report the new device state. This is actually very important in order to
1089 * make module-card-restore work well with headsets: if the headset
1090 * supports both HSP and A2DP, one of those profiles is connected first and
1091 * then the other, and lastly the Audio interface becomes connected.
1092 * Checking only audio_state means that this function will return false at
1093 * the time when only the first connection has been made. This is good,
1094 * because otherwise, if the first connection is for HSP and we would
1095 * already load a new device module instance, and module-card-restore tries
1096 * to restore the A2DP profile, that would fail because A2DP is not yet
1097 * connected. Waiting until the Audio interface gets connected means that
1098 * both headset profiles will be connected when the device module is
1099 * loaded. */
1100 if (d->audio_state == PA_BT_AUDIO_STATE_CONNECTING)
1101 return false;
1102
1103 for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
1104 if (d->transports[i])
1105 return true;
1106
1107 return false;
1108 }
1109
1110 int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
1111 const char *accesstype = "rw";
1112 DBusMessage *m, *r;
1113 DBusError err;
1114 int ret;
1115 uint16_t i, o;
1116
1117 pa_assert(t);
1118 pa_assert(t->device);
1119 pa_assert(t->device->discovery);
1120
1121 if (optional) {
1122 /* FIXME: we are trying to acquire the transport only if the stream is
1123 playing, without actually initiating the stream request from our side
1124 (which is typically undesireable specially for hfgw use-cases.
1125 However this approach is racy, since the stream could have been
1126 suspended in the meantime, so we can't really guarantee that the
1127 stream will not be requested until BlueZ's API supports this
1128 atomically. */
1129 if (t->state < PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) {
1130 pa_log_info("Failed optional acquire of transport %s", t->path);
1131 return -1;
1132 }
1133 }
1134
1135 dbus_error_init(&err);
1136
1137 pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Acquire"));
1138 pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
1139 r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
1140
1141 if (dbus_error_is_set(&err) || !r) {
1142 dbus_error_free(&err);
1143 return -1;
1144 }
1145
1146 if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_UINT16, &i, DBUS_TYPE_UINT16, &o, DBUS_TYPE_INVALID)) {
1147 pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err.message);
1148 ret = -1;
1149 dbus_error_free(&err);
1150 goto fail;
1151 }
1152
1153 if (imtu)
1154 *imtu = i;
1155
1156 if (omtu)
1157 *omtu = o;
1158
1159 fail:
1160 dbus_message_unref(r);
1161 return ret;
1162 }
1163
1164 void pa_bluetooth_transport_release(pa_bluetooth_transport *t) {
1165 const char *accesstype = "rw";
1166 DBusMessage *m;
1167 DBusError err;
1168
1169 pa_assert(t);
1170 pa_assert(t->device);
1171 pa_assert(t->device->discovery);
1172
1173 dbus_error_init(&err);
1174
1175 pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Release"));
1176 pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
1177 dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
1178
1179 if (dbus_error_is_set(&err)) {
1180 pa_log("Failed to release transport %s: %s", t->path, err.message);
1181 dbus_error_free(&err);
1182 } else
1183 pa_log_info("Transport %s released", t->path);
1184 }
1185
1186 static void set_property(pa_bluetooth_discovery *y, const char *bus, const char *path, const char *interface,
1187 const char *prop_name, int prop_type, void *prop_value) {
1188 DBusMessage *m;
1189 DBusMessageIter i;
1190
1191 pa_assert(y);
1192 pa_assert(path);
1193 pa_assert(interface);
1194 pa_assert(prop_name);
1195
1196 pa_assert_se(m = dbus_message_new_method_call(bus, path, interface, "SetProperty"));
1197 dbus_message_iter_init_append(m, &i);
1198 dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &prop_name);
1199 pa_dbus_append_basic_variant(&i, prop_type, prop_value);
1200
1201 dbus_message_set_no_reply(m, true);
1202 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), m, NULL));
1203 dbus_message_unref(m);
1204 }
1205
1206 void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value) {
1207 dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
1208
1209 pa_assert(t);
1210 pa_assert(t->profile == PROFILE_HSP);
1211
1212 set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
1213 "MicrophoneGain", DBUS_TYPE_UINT16, &gain);
1214 }
1215
1216 void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value) {
1217 dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN);
1218
1219 pa_assert(t);
1220 pa_assert(t->profile == PROFILE_HSP);
1221
1222 set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset",
1223 "SpeakerGain", DBUS_TYPE_UINT16, &gain);
1224 }
1225
1226 static int setup_dbus(pa_bluetooth_discovery *y) {
1227 DBusError err;
1228
1229 dbus_error_init(&err);
1230
1231 y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err);
1232
1233 if (dbus_error_is_set(&err) || !y->connection) {
1234 pa_log("Failed to get D-Bus connection: %s", err.message);
1235 dbus_error_free(&err);
1236 return -1;
1237 }
1238
1239 return 0;
1240 }
1241
1242 static pa_bluetooth_transport *transport_new(pa_bluetooth_device *d, const char *owner, const char *path, enum profile p,
1243 const uint8_t *config, int size) {
1244 pa_bluetooth_transport *t;
1245
1246 t = pa_xnew0(pa_bluetooth_transport, 1);
1247 t->device = d;
1248 t->owner = pa_xstrdup(owner);
1249 t->path = pa_xstrdup(path);
1250 t->profile = p;
1251 t->config_size = size;
1252
1253 if (size > 0) {
1254 t->config = pa_xnew(uint8_t, size);
1255 memcpy(t->config, config, size);
1256 }
1257
1258 t->state = audio_state_to_transport_state(d->profile_state[p]);
1259
1260 return t;
1261 }
1262
1263 static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
1264 pa_bluetooth_discovery *y = userdata;
1265 pa_bluetooth_device *d;
1266 pa_bluetooth_transport *t;
1267 const char *sender, *path, *dev_path = NULL, *uuid = NULL;
1268 uint8_t *config = NULL;
1269 int size = 0;
1270 pa_bool_t nrec = FALSE;
1271 enum profile p;
1272 DBusMessageIter args, props;
1273 DBusMessage *r;
1274 bool old_any_connected;
1275
1276 dbus_message_iter_init(m, &args);
1277
1278 dbus_message_iter_get_basic(&args, &path);
1279
1280 if (pa_hashmap_get(y->transports, path)) {
1281 pa_log("org.bluez.MediaEndpoint.SetConfiguration: Transport %s is already configured.", path);
1282 goto fail;
1283 }
1284
1285 if (!dbus_message_iter_next(&args))
1286 goto fail;
1287
1288 dbus_message_iter_recurse(&args, &props);
1289 if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
1290 goto fail;
1291
1292 /* Read transport properties */
1293 while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) {
1294 const char *key;
1295 DBusMessageIter value, entry;
1296 int var;
1297
1298 dbus_message_iter_recurse(&props, &entry);
1299 dbus_message_iter_get_basic(&entry, &key);
1300
1301 dbus_message_iter_next(&entry);
1302 dbus_message_iter_recurse(&entry, &value);
1303
1304 var = dbus_message_iter_get_arg_type(&value);
1305 if (strcasecmp(key, "UUID") == 0) {
1306 if (var != DBUS_TYPE_STRING)
1307 goto fail;
1308 dbus_message_iter_get_basic(&value, &uuid);
1309 } else if (strcasecmp(key, "Device") == 0) {
1310 if (var != DBUS_TYPE_OBJECT_PATH)
1311 goto fail;
1312 dbus_message_iter_get_basic(&value, &dev_path);
1313 } else if (strcasecmp(key, "NREC") == 0) {
1314 dbus_bool_t tmp_boolean;
1315 if (var != DBUS_TYPE_BOOLEAN)
1316 goto fail;
1317 dbus_message_iter_get_basic(&value, &tmp_boolean);
1318 nrec = tmp_boolean;
1319 } else if (strcasecmp(key, "Configuration") == 0) {
1320 DBusMessageIter array;
1321 if (var != DBUS_TYPE_ARRAY)
1322 goto fail;
1323 dbus_message_iter_recurse(&value, &array);
1324 dbus_message_iter_get_fixed_array(&array, &config, &size);
1325 }
1326
1327 dbus_message_iter_next(&props);
1328 }
1329
1330 d = found_device(y, dev_path);
1331 if (!d)
1332 goto fail;
1333
1334 if (dbus_message_has_path(m, HFP_AG_ENDPOINT))
1335 p = PROFILE_HSP;
1336 else if (dbus_message_has_path(m, HFP_HS_ENDPOINT))
1337 p = PROFILE_HFGW;
1338 else if (dbus_message_has_path(m, A2DP_SOURCE_ENDPOINT))
1339 p = PROFILE_A2DP;
1340 else
1341 p = PROFILE_A2DP_SOURCE;
1342
1343 if (d->transports[p] != NULL) {
1344 pa_log("Cannot configure transport %s because profile %d is already used", path, p);
1345 goto fail;
1346 }
1347
1348 old_any_connected = pa_bluetooth_device_any_audio_connected(d);
1349
1350 sender = dbus_message_get_sender(m);
1351
1352 t = transport_new(d, sender, path, p, config, size);
1353 if (nrec)
1354 t->nrec = nrec;
1355
1356 d->transports[p] = t;
1357 pa_assert_se(pa_hashmap_put(y->transports, t->path, t) >= 0);
1358
1359 pa_log_debug("Transport %s profile %d available", t->path, t->profile);
1360
1361 pa_assert_se(r = dbus_message_new_method_return(m));
1362
1363 if (old_any_connected != pa_bluetooth_device_any_audio_connected(d))
1364 run_callback(d, FALSE);
1365
1366 return r;
1367
1368 fail:
1369 pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments");
1370 pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1371 "Unable to set configuration")));
1372 return r;
1373 }
1374
1375 static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
1376 pa_bluetooth_discovery *y = userdata;
1377 pa_bluetooth_transport *t;
1378 DBusMessage *r;
1379 DBusError e;
1380 const char *path;
1381
1382 dbus_error_init(&e);
1383
1384 if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
1385 pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e.message);
1386 dbus_error_free(&e);
1387 goto fail;
1388 }
1389
1390 if ((t = pa_hashmap_get(y->transports, path))) {
1391 bool old_any_connected = pa_bluetooth_device_any_audio_connected(t->device);
1392
1393 pa_log_debug("Clearing transport %s profile %d", t->path, t->profile);
1394 t->device->transports[t->profile] = NULL;
1395 pa_hashmap_remove(y->transports, t->path);
1396 t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED;
1397 pa_hook_fire(&y->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
1398
1399 if (old_any_connected != pa_bluetooth_device_any_audio_connected(t->device))
1400 run_callback(t->device, FALSE);
1401
1402 transport_free(t);
1403 }
1404
1405 pa_assert_se(r = dbus_message_new_method_return(m));
1406
1407 return r;
1408
1409 fail:
1410 pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1411 "Unable to clear configuration")));
1412 return r;
1413 }
1414
1415 static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
1416
1417 switch (freq) {
1418 case SBC_SAMPLING_FREQ_16000:
1419 case SBC_SAMPLING_FREQ_32000:
1420 return 53;
1421
1422 case SBC_SAMPLING_FREQ_44100:
1423
1424 switch (mode) {
1425 case SBC_CHANNEL_MODE_MONO:
1426 case SBC_CHANNEL_MODE_DUAL_CHANNEL:
1427 return 31;
1428
1429 case SBC_CHANNEL_MODE_STEREO:
1430 case SBC_CHANNEL_MODE_JOINT_STEREO:
1431 return 53;
1432
1433 default:
1434 pa_log_warn("Invalid channel mode %u", mode);
1435 return 53;
1436 }
1437
1438 case SBC_SAMPLING_FREQ_48000:
1439
1440 switch (mode) {
1441 case SBC_CHANNEL_MODE_MONO:
1442 case SBC_CHANNEL_MODE_DUAL_CHANNEL:
1443 return 29;
1444
1445 case SBC_CHANNEL_MODE_STEREO:
1446 case SBC_CHANNEL_MODE_JOINT_STEREO:
1447 return 51;
1448
1449 default:
1450 pa_log_warn("Invalid channel mode %u", mode);
1451 return 51;
1452 }
1453
1454 default:
1455 pa_log_warn("Invalid sampling freq %u", freq);
1456 return 53;
1457 }
1458 }
1459
1460 static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
1461 pa_bluetooth_discovery *y = userdata;
1462 a2dp_sbc_t *cap, config;
1463 uint8_t *pconf = (uint8_t *) &config;
1464 int i, size;
1465 DBusMessage *r;
1466 DBusError e;
1467
1468 static const struct {
1469 uint32_t rate;
1470 uint8_t cap;
1471 } freq_table[] = {
1472 { 16000U, SBC_SAMPLING_FREQ_16000 },
1473 { 32000U, SBC_SAMPLING_FREQ_32000 },
1474 { 44100U, SBC_SAMPLING_FREQ_44100 },
1475 { 48000U, SBC_SAMPLING_FREQ_48000 }
1476 };
1477
1478 dbus_error_init(&e);
1479
1480 if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
1481 pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message);
1482 dbus_error_free(&e);
1483 goto fail;
1484 }
1485
1486 if (dbus_message_has_path(m, HFP_AG_ENDPOINT) || dbus_message_has_path(m, HFP_HS_ENDPOINT))
1487 goto done;
1488
1489 pa_assert(size == sizeof(config));
1490
1491 memset(&config, 0, sizeof(config));
1492
1493 /* Find the lowest freq that is at least as high as the requested
1494 * sampling rate */
1495 for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
1496 if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
1497 config.frequency = freq_table[i].cap;
1498 break;
1499 }
1500
1501 if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
1502 for (--i; i >= 0; i--) {
1503 if (cap->frequency & freq_table[i].cap) {
1504 config.frequency = freq_table[i].cap;
1505 break;
1506 }
1507 }
1508
1509 if (i < 0) {
1510 pa_log("Not suitable sample rate");
1511 goto fail;
1512 }
1513 }
1514
1515 pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
1516
1517 if (y->core->default_sample_spec.channels <= 1) {
1518 if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
1519 config.channel_mode = SBC_CHANNEL_MODE_MONO;
1520 }
1521
1522 if (y->core->default_sample_spec.channels >= 2) {
1523 if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
1524 config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
1525 else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
1526 config.channel_mode = SBC_CHANNEL_MODE_STEREO;
1527 else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
1528 config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
1529 else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) {
1530 config.channel_mode = SBC_CHANNEL_MODE_MONO;
1531 } else {
1532 pa_log("No supported channel modes");
1533 goto fail;
1534 }
1535 }
1536
1537 if (cap->block_length & SBC_BLOCK_LENGTH_16)
1538 config.block_length = SBC_BLOCK_LENGTH_16;
1539 else if (cap->block_length & SBC_BLOCK_LENGTH_12)
1540 config.block_length = SBC_BLOCK_LENGTH_12;
1541 else if (cap->block_length & SBC_BLOCK_LENGTH_8)
1542 config.block_length = SBC_BLOCK_LENGTH_8;
1543 else if (cap->block_length & SBC_BLOCK_LENGTH_4)
1544 config.block_length = SBC_BLOCK_LENGTH_4;
1545 else {
1546 pa_log_error("No supported block lengths");
1547 goto fail;
1548 }
1549
1550 if (cap->subbands & SBC_SUBBANDS_8)
1551 config.subbands = SBC_SUBBANDS_8;
1552 else if (cap->subbands & SBC_SUBBANDS_4)
1553 config.subbands = SBC_SUBBANDS_4;
1554 else {
1555 pa_log_error("No supported subbands");
1556 goto fail;
1557 }
1558
1559 if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS)
1560 config.allocation_method = SBC_ALLOCATION_LOUDNESS;
1561 else if (cap->allocation_method & SBC_ALLOCATION_SNR)
1562 config.allocation_method = SBC_ALLOCATION_SNR;
1563
1564 config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool);
1565 config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool);
1566
1567 done:
1568 pa_assert_se(r = dbus_message_new_method_return(m));
1569
1570 pa_assert_se(dbus_message_append_args(
1571 r,
1572 DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size,
1573 DBUS_TYPE_INVALID));
1574
1575 return r;
1576
1577 fail:
1578 pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1579 "Unable to select configuration")));
1580 return r;
1581 }
1582
1583 static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
1584 struct pa_bluetooth_discovery *y = userdata;
1585 DBusMessage *r = NULL;
1586 DBusError e;
1587 const char *path;
1588
1589 pa_assert(y);
1590
1591 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
1592 dbus_message_get_interface(m),
1593 dbus_message_get_path(m),
1594 dbus_message_get_member(m));
1595
1596 path = dbus_message_get_path(m);
1597 dbus_error_init(&e);
1598
1599 if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT) && !pa_streq(path, HFP_HS_ENDPOINT))
1600 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1601
1602 if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1603 const char *xml = ENDPOINT_INTROSPECT_XML;
1604
1605 pa_assert_se(r = dbus_message_new_method_return(m));
1606 pa_assert_se(dbus_message_append_args(
1607 r,
1608 DBUS_TYPE_STRING, &xml,
1609 DBUS_TYPE_INVALID));
1610
1611 } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SetConfiguration")) {
1612 r = endpoint_set_configuration(c, m, userdata);
1613 } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SelectConfiguration")) {
1614 r = endpoint_select_configuration(c, m, userdata);
1615 } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "ClearConfiguration"))
1616 r = endpoint_clear_configuration(c, m, userdata);
1617 else
1618 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1619
1620 if (r) {
1621 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
1622 dbus_message_unref(r);
1623 }
1624
1625 return DBUS_HANDLER_RESULT_HANDLED;
1626 }
1627
1628 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
1629 DBusError err;
1630 pa_bluetooth_discovery *y;
1631 unsigned i;
1632 static const DBusObjectPathVTable vtable_endpoint = {
1633 .message_function = endpoint_handler,
1634 };
1635
1636 pa_assert(c);
1637
1638 dbus_error_init(&err);
1639
1640 if ((y = pa_shared_get(c, "bluetooth-discovery")))
1641 return pa_bluetooth_discovery_ref(y);
1642
1643 y = pa_xnew0(pa_bluetooth_discovery, 1);
1644 PA_REFCNT_INIT(y);
1645 y->core = c;
1646 y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1647 y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1648 PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
1649
1650 for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
1651 pa_hook_init(&y->hooks[i], y);
1652
1653 pa_shared_set(c, "bluetooth-discovery", y);
1654
1655 if (setup_dbus(y) < 0)
1656 goto fail;
1657
1658 /* dynamic detection of bluetooth audio devices */
1659 if (!dbus_connection_add_filter(pa_dbus_connection_get(y->connection), filter_cb, y, NULL)) {
1660 pa_log_error("Failed to add filter function");
1661 goto fail;
1662 }
1663 y->filter_added = TRUE;
1664
1665 if (pa_dbus_add_matches(
1666 pa_dbus_connection_get(y->connection), &err,
1667 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
1668 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1669 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1670 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1671 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1672 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1673 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1674 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1675 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1676 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1677 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1678 NULL) < 0) {
1679 pa_log("Failed to add D-Bus matches: %s", err.message);
1680 goto fail;
1681 }
1682
1683 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT, &vtable_endpoint, y));
1684 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), HFP_HS_ENDPOINT, &vtable_endpoint, y));
1685 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y));
1686 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT, &vtable_endpoint, y));
1687
1688 list_adapters(y);
1689
1690 return y;
1691
1692 fail:
1693
1694 if (y)
1695 pa_bluetooth_discovery_unref(y);
1696
1697 dbus_error_free(&err);
1698
1699 return NULL;
1700 }
1701
1702 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
1703 pa_assert(y);
1704 pa_assert(PA_REFCNT_VALUE(y) > 0);
1705
1706 PA_REFCNT_INC(y);
1707
1708 return y;
1709 }
1710
1711 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
1712 unsigned i;
1713
1714 pa_assert(y);
1715 pa_assert(PA_REFCNT_VALUE(y) > 0);
1716
1717 if (PA_REFCNT_DEC(y) > 0)
1718 return;
1719
1720 pa_dbus_free_pending_list(&y->pending);
1721
1722 if (y->devices) {
1723 remove_all_devices(y);
1724 pa_hashmap_free(y->devices, NULL, NULL);
1725 }
1726
1727 if (y->transports) {
1728 pa_assert(pa_hashmap_isempty(y->transports));
1729 pa_hashmap_free(y->transports, NULL, NULL);
1730 }
1731
1732 if (y->connection) {
1733 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT);
1734 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_HS_ENDPOINT);
1735 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
1736 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
1737 pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
1738 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
1739 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1740 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
1741 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1742 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1743 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1744 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1745 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1746 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1747 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1748 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1749 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1750 NULL);
1751
1752 if (y->filter_added)
1753 dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
1754
1755 pa_dbus_connection_unref(y->connection);
1756 }
1757
1758 for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
1759 pa_hook_done(&y->hooks[i]);
1760
1761 if (y->core)
1762 pa_shared_remove(y->core, "bluetooth-discovery");
1763
1764 pa_xfree(y);
1765 }
1766
1767 void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *y) {
1768 pa_assert(y);
1769 pa_assert(PA_REFCNT_VALUE(y) > 0);
1770
1771 pa_dbus_sync_pending_list(&y->pending);
1772 }
1773
1774 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) {
1775 pa_assert(y);
1776 pa_assert(PA_REFCNT_VALUE(y) > 0);
1777
1778 return &y->hooks[hook];
1779 }
1780
1781 const char*pa_bluetooth_get_form_factor(uint32_t class) {
1782 unsigned i;
1783 const char *r;
1784
1785 static const char * const table[] = {
1786 [1] = "headset",
1787 [2] = "hands-free",
1788 [4] = "microphone",
1789 [5] = "speaker",
1790 [6] = "headphone",
1791 [7] = "portable",
1792 [8] = "car",
1793 [10] = "hifi"
1794 };
1795
1796 if (((class >> 8) & 31) != 4)
1797 return NULL;
1798
1799 if ((i = (class >> 2) & 63) > PA_ELEMENTSOF(table))
1800 r = NULL;
1801 else
1802 r = table[i];
1803
1804 if (!r)
1805 pa_log_debug("Unknown Bluetooth minor device class %u", i);
1806
1807 return r;
1808 }
1809
1810 char *pa_bluetooth_cleanup_name(const char *name) {
1811 char *t, *s, *d;
1812 pa_bool_t space = FALSE;
1813
1814 pa_assert(name);
1815
1816 while ((*name >= 1 && *name <= 32) || *name >= 127)
1817 name++;
1818
1819 t = pa_xstrdup(name);
1820
1821 for (s = d = t; *s; s++) {
1822
1823 if (*s <= 32 || *s >= 127 || *s == '_') {
1824 space = TRUE;
1825 continue;
1826 }
1827
1828 if (space) {
1829 *(d++) = ' ';
1830 space = FALSE;
1831 }
1832
1833 *(d++) = *s;
1834 }
1835
1836 *d = 0;
1837
1838 return t;
1839 }
1840
1841 pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
1842 pa_assert(uuid);
1843
1844 while (uuids) {
1845 if (strcasecmp(uuids->uuid, uuid) == 0)
1846 return TRUE;
1847
1848 uuids = uuids->next;
1849 }
1850
1851 return FALSE;
1852 }