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