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