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