]> code.delx.au - pulseaudio/blob - src/modules/bluetooth/bluetooth-util.c
392f4a5a4d39338def9ed34166a41545ab01fca8
[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 = t->device ? pa_bluetooth_device_any_audio_connected(t->device) : false;
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 transport_free(t);
1399
1400 if (t->device && old_any_connected != pa_bluetooth_device_any_audio_connected(t->device))
1401 run_callback(t->device, FALSE);
1402 }
1403
1404 pa_assert_se(r = dbus_message_new_method_return(m));
1405
1406 return r;
1407
1408 fail:
1409 pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1410 "Unable to clear configuration")));
1411 return r;
1412 }
1413
1414 static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
1415
1416 switch (freq) {
1417 case SBC_SAMPLING_FREQ_16000:
1418 case SBC_SAMPLING_FREQ_32000:
1419 return 53;
1420
1421 case SBC_SAMPLING_FREQ_44100:
1422
1423 switch (mode) {
1424 case SBC_CHANNEL_MODE_MONO:
1425 case SBC_CHANNEL_MODE_DUAL_CHANNEL:
1426 return 31;
1427
1428 case SBC_CHANNEL_MODE_STEREO:
1429 case SBC_CHANNEL_MODE_JOINT_STEREO:
1430 return 53;
1431
1432 default:
1433 pa_log_warn("Invalid channel mode %u", mode);
1434 return 53;
1435 }
1436
1437 case SBC_SAMPLING_FREQ_48000:
1438
1439 switch (mode) {
1440 case SBC_CHANNEL_MODE_MONO:
1441 case SBC_CHANNEL_MODE_DUAL_CHANNEL:
1442 return 29;
1443
1444 case SBC_CHANNEL_MODE_STEREO:
1445 case SBC_CHANNEL_MODE_JOINT_STEREO:
1446 return 51;
1447
1448 default:
1449 pa_log_warn("Invalid channel mode %u", mode);
1450 return 51;
1451 }
1452
1453 default:
1454 pa_log_warn("Invalid sampling freq %u", freq);
1455 return 53;
1456 }
1457 }
1458
1459 static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) {
1460 pa_bluetooth_discovery *y = userdata;
1461 a2dp_sbc_t *cap, config;
1462 uint8_t *pconf = (uint8_t *) &config;
1463 int i, size;
1464 DBusMessage *r;
1465 DBusError e;
1466
1467 static const struct {
1468 uint32_t rate;
1469 uint8_t cap;
1470 } freq_table[] = {
1471 { 16000U, SBC_SAMPLING_FREQ_16000 },
1472 { 32000U, SBC_SAMPLING_FREQ_32000 },
1473 { 44100U, SBC_SAMPLING_FREQ_44100 },
1474 { 48000U, SBC_SAMPLING_FREQ_48000 }
1475 };
1476
1477 dbus_error_init(&e);
1478
1479 if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
1480 pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message);
1481 dbus_error_free(&e);
1482 goto fail;
1483 }
1484
1485 if (dbus_message_has_path(m, HFP_AG_ENDPOINT) || dbus_message_has_path(m, HFP_HS_ENDPOINT))
1486 goto done;
1487
1488 pa_assert(size == sizeof(config));
1489
1490 memset(&config, 0, sizeof(config));
1491
1492 /* Find the lowest freq that is at least as high as the requested
1493 * sampling rate */
1494 for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
1495 if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
1496 config.frequency = freq_table[i].cap;
1497 break;
1498 }
1499
1500 if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
1501 for (--i; i >= 0; i--) {
1502 if (cap->frequency & freq_table[i].cap) {
1503 config.frequency = freq_table[i].cap;
1504 break;
1505 }
1506 }
1507
1508 if (i < 0) {
1509 pa_log("Not suitable sample rate");
1510 goto fail;
1511 }
1512 }
1513
1514 pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
1515
1516 if (y->core->default_sample_spec.channels <= 1) {
1517 if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
1518 config.channel_mode = SBC_CHANNEL_MODE_MONO;
1519 }
1520
1521 if (y->core->default_sample_spec.channels >= 2) {
1522 if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
1523 config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
1524 else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
1525 config.channel_mode = SBC_CHANNEL_MODE_STEREO;
1526 else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
1527 config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
1528 else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) {
1529 config.channel_mode = SBC_CHANNEL_MODE_MONO;
1530 } else {
1531 pa_log("No supported channel modes");
1532 goto fail;
1533 }
1534 }
1535
1536 if (cap->block_length & SBC_BLOCK_LENGTH_16)
1537 config.block_length = SBC_BLOCK_LENGTH_16;
1538 else if (cap->block_length & SBC_BLOCK_LENGTH_12)
1539 config.block_length = SBC_BLOCK_LENGTH_12;
1540 else if (cap->block_length & SBC_BLOCK_LENGTH_8)
1541 config.block_length = SBC_BLOCK_LENGTH_8;
1542 else if (cap->block_length & SBC_BLOCK_LENGTH_4)
1543 config.block_length = SBC_BLOCK_LENGTH_4;
1544 else {
1545 pa_log_error("No supported block lengths");
1546 goto fail;
1547 }
1548
1549 if (cap->subbands & SBC_SUBBANDS_8)
1550 config.subbands = SBC_SUBBANDS_8;
1551 else if (cap->subbands & SBC_SUBBANDS_4)
1552 config.subbands = SBC_SUBBANDS_4;
1553 else {
1554 pa_log_error("No supported subbands");
1555 goto fail;
1556 }
1557
1558 if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS)
1559 config.allocation_method = SBC_ALLOCATION_LOUDNESS;
1560 else if (cap->allocation_method & SBC_ALLOCATION_SNR)
1561 config.allocation_method = SBC_ALLOCATION_SNR;
1562
1563 config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool);
1564 config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool);
1565
1566 done:
1567 pa_assert_se(r = dbus_message_new_method_return(m));
1568
1569 pa_assert_se(dbus_message_append_args(
1570 r,
1571 DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size,
1572 DBUS_TYPE_INVALID));
1573
1574 return r;
1575
1576 fail:
1577 pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
1578 "Unable to select configuration")));
1579 return r;
1580 }
1581
1582 static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
1583 struct pa_bluetooth_discovery *y = userdata;
1584 DBusMessage *r = NULL;
1585 DBusError e;
1586 const char *path;
1587
1588 pa_assert(y);
1589
1590 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
1591 dbus_message_get_interface(m),
1592 dbus_message_get_path(m),
1593 dbus_message_get_member(m));
1594
1595 path = dbus_message_get_path(m);
1596 dbus_error_init(&e);
1597
1598 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))
1599 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1600
1601 if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1602 const char *xml = ENDPOINT_INTROSPECT_XML;
1603
1604 pa_assert_se(r = dbus_message_new_method_return(m));
1605 pa_assert_se(dbus_message_append_args(
1606 r,
1607 DBUS_TYPE_STRING, &xml,
1608 DBUS_TYPE_INVALID));
1609
1610 } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SetConfiguration")) {
1611 r = endpoint_set_configuration(c, m, userdata);
1612 } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SelectConfiguration")) {
1613 r = endpoint_select_configuration(c, m, userdata);
1614 } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "ClearConfiguration"))
1615 r = endpoint_clear_configuration(c, m, userdata);
1616 else
1617 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1618
1619 if (r) {
1620 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
1621 dbus_message_unref(r);
1622 }
1623
1624 return DBUS_HANDLER_RESULT_HANDLED;
1625 }
1626
1627 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
1628 DBusError err;
1629 pa_bluetooth_discovery *y;
1630 unsigned i;
1631 static const DBusObjectPathVTable vtable_endpoint = {
1632 .message_function = endpoint_handler,
1633 };
1634
1635 pa_assert(c);
1636
1637 dbus_error_init(&err);
1638
1639 if ((y = pa_shared_get(c, "bluetooth-discovery")))
1640 return pa_bluetooth_discovery_ref(y);
1641
1642 y = pa_xnew0(pa_bluetooth_discovery, 1);
1643 PA_REFCNT_INIT(y);
1644 y->core = c;
1645 y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1646 y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
1647 PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
1648
1649 for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
1650 pa_hook_init(&y->hooks[i], y);
1651
1652 pa_shared_set(c, "bluetooth-discovery", y);
1653
1654 if (setup_dbus(y) < 0)
1655 goto fail;
1656
1657 /* dynamic detection of bluetooth audio devices */
1658 if (!dbus_connection_add_filter(pa_dbus_connection_get(y->connection), filter_cb, y, NULL)) {
1659 pa_log_error("Failed to add filter function");
1660 goto fail;
1661 }
1662 y->filter_added = TRUE;
1663
1664 if (pa_dbus_add_matches(
1665 pa_dbus_connection_get(y->connection), &err,
1666 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
1667 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1668 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1669 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1670 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1671 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1672 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1673 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1674 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1675 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1676 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1677 NULL) < 0) {
1678 pa_log("Failed to add D-Bus matches: %s", err.message);
1679 goto fail;
1680 }
1681
1682 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT, &vtable_endpoint, y));
1683 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), HFP_HS_ENDPOINT, &vtable_endpoint, y));
1684 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y));
1685 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT, &vtable_endpoint, y));
1686
1687 list_adapters(y);
1688
1689 return y;
1690
1691 fail:
1692
1693 if (y)
1694 pa_bluetooth_discovery_unref(y);
1695
1696 dbus_error_free(&err);
1697
1698 return NULL;
1699 }
1700
1701 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
1702 pa_assert(y);
1703 pa_assert(PA_REFCNT_VALUE(y) > 0);
1704
1705 PA_REFCNT_INC(y);
1706
1707 return y;
1708 }
1709
1710 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
1711 unsigned i;
1712
1713 pa_assert(y);
1714 pa_assert(PA_REFCNT_VALUE(y) > 0);
1715
1716 if (PA_REFCNT_DEC(y) > 0)
1717 return;
1718
1719 pa_dbus_free_pending_list(&y->pending);
1720
1721 if (y->devices) {
1722 remove_all_devices(y);
1723 pa_hashmap_free(y->devices, NULL, NULL);
1724 }
1725
1726 if (y->transports) {
1727 pa_assert(pa_hashmap_isempty(y->transports));
1728 pa_hashmap_free(y->transports, NULL, NULL);
1729 }
1730
1731 if (y->connection) {
1732 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT);
1733 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_HS_ENDPOINT);
1734 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
1735 dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
1736 pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
1737 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
1738 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
1739 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
1740 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
1741 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
1742 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
1743 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
1744 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
1745 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
1746 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
1747 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
1748 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
1749 NULL);
1750
1751 if (y->filter_added)
1752 dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
1753
1754 pa_dbus_connection_unref(y->connection);
1755 }
1756
1757 for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
1758 pa_hook_done(&y->hooks[i]);
1759
1760 if (y->core)
1761 pa_shared_remove(y->core, "bluetooth-discovery");
1762
1763 pa_xfree(y);
1764 }
1765
1766 void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *y) {
1767 pa_assert(y);
1768 pa_assert(PA_REFCNT_VALUE(y) > 0);
1769
1770 pa_dbus_sync_pending_list(&y->pending);
1771 }
1772
1773 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) {
1774 pa_assert(y);
1775 pa_assert(PA_REFCNT_VALUE(y) > 0);
1776
1777 return &y->hooks[hook];
1778 }
1779
1780 const char*pa_bluetooth_get_form_factor(uint32_t class) {
1781 unsigned i;
1782 const char *r;
1783
1784 static const char * const table[] = {
1785 [1] = "headset",
1786 [2] = "hands-free",
1787 [4] = "microphone",
1788 [5] = "speaker",
1789 [6] = "headphone",
1790 [7] = "portable",
1791 [8] = "car",
1792 [10] = "hifi"
1793 };
1794
1795 if (((class >> 8) & 31) != 4)
1796 return NULL;
1797
1798 if ((i = (class >> 2) & 63) > PA_ELEMENTSOF(table))
1799 r = NULL;
1800 else
1801 r = table[i];
1802
1803 if (!r)
1804 pa_log_debug("Unknown Bluetooth minor device class %u", i);
1805
1806 return r;
1807 }
1808
1809 char *pa_bluetooth_cleanup_name(const char *name) {
1810 char *t, *s, *d;
1811 pa_bool_t space = FALSE;
1812
1813 pa_assert(name);
1814
1815 while ((*name >= 1 && *name <= 32) || *name >= 127)
1816 name++;
1817
1818 t = pa_xstrdup(name);
1819
1820 for (s = d = t; *s; s++) {
1821
1822 if (*s <= 32 || *s >= 127 || *s == '_') {
1823 space = TRUE;
1824 continue;
1825 }
1826
1827 if (space) {
1828 *(d++) = ' ';
1829 space = FALSE;
1830 }
1831
1832 *(d++) = *s;
1833 }
1834
1835 *d = 0;
1836
1837 return t;
1838 }
1839
1840 pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
1841 pa_assert(uuid);
1842
1843 while (uuids) {
1844 if (strcasecmp(uuids->uuid, uuid) == 0)
1845 return TRUE;
1846
1847 uuids = uuids->next;
1848 }
1849
1850 return FALSE;
1851 }