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