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