c3b08ea312bc0e245fa6e1223151448c30c769b8
[profile/ivi/pulseaudio-panda.git] / 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 <pulsecore/core-util.h>
27 #include <pulsecore/shared.h>
28 #include <pulsecore/dbus-shared.h>
29
30 #include "bluetooth-util.h"
31
32 struct pa_bluetooth_discovery {
33     PA_REFCNT_DECLARE;
34
35     pa_core *core;
36     pa_dbus_connection *connection;
37     PA_LLIST_HEAD(pa_dbus_pending, pending);
38     pa_hashmap *devices;
39     pa_hook hook;
40     pa_bool_t filter_added;
41 };
42
43 static void get_properties_reply(DBusPendingCall *pending, void *userdata);
44 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func);
45
46 static pa_bt_audio_state_t pa_bt_audio_state_from_string(const char* value) {
47     pa_assert(value);
48
49     if (pa_streq(value, "disconnected"))
50         return PA_BT_AUDIO_STATE_DISCONNECTED;
51     else if (pa_streq(value, "connecting"))
52         return PA_BT_AUDIO_STATE_CONNECTING;
53     else if (pa_streq(value, "connected"))
54         return PA_BT_AUDIO_STATE_CONNECTED;
55     else if (pa_streq(value, "playing"))
56         return PA_BT_AUDIO_STATE_PLAYING;
57
58     return PA_BT_AUDIO_STATE_INVALID;
59 }
60
61 static pa_bluetooth_uuid *uuid_new(const char *uuid) {
62     pa_bluetooth_uuid *u;
63
64     u = pa_xnew(pa_bluetooth_uuid, 1);
65     u->uuid = pa_xstrdup(uuid);
66     PA_LLIST_INIT(pa_bluetooth_uuid, u);
67
68     return u;
69 }
70
71 static void uuid_free(pa_bluetooth_uuid *u) {
72     pa_assert(u);
73
74     pa_xfree(u->uuid);
75     pa_xfree(u);
76 }
77
78 static pa_bluetooth_device* device_new(const char *path) {
79     pa_bluetooth_device *d;
80
81     d = pa_xnew(pa_bluetooth_device, 1);
82
83     d->dead = FALSE;
84
85     d->device_info_valid = 0;
86
87     d->name = NULL;
88     d->path = pa_xstrdup(path);
89     d->paired = -1;
90     d->alias = NULL;
91     d->device_connected = -1;
92     PA_LLIST_HEAD_INIT(pa_bluetooth_uuid, d->uuids);
93     d->address = NULL;
94     d->class = -1;
95     d->trusted = -1;
96
97     d->audio_state = PA_BT_AUDIO_STATE_INVALID;
98     d->audio_sink_state = PA_BT_AUDIO_STATE_INVALID;
99     d->audio_source_state = PA_BT_AUDIO_STATE_INVALID;
100     d->headset_state = PA_BT_AUDIO_STATE_INVALID;
101     d->hfgw_state = PA_BT_AUDIO_STATE_INVALID;
102
103     return d;
104 }
105
106 static void device_free(pa_bluetooth_device *d) {
107     pa_bluetooth_uuid *u;
108
109     pa_assert(d);
110
111     while ((u = d->uuids)) {
112         PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u);
113         uuid_free(u);
114     }
115
116     pa_xfree(d->name);
117     pa_xfree(d->path);
118     pa_xfree(d->alias);
119     pa_xfree(d->address);
120     pa_xfree(d);
121 }
122
123 static pa_bool_t device_is_audio(pa_bluetooth_device *d) {
124     pa_assert(d);
125
126     return
127         d->device_info_valid && (d->hfgw_state != PA_BT_AUDIO_STATE_INVALID ||
128         (d->audio_state != PA_BT_AUDIO_STATE_INVALID &&
129          (d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID ||
130           d->audio_source_state != PA_BT_AUDIO_STATE_INVALID ||
131           d->headset_state != PA_BT_AUDIO_STATE_INVALID)));
132 }
133
134 static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) {
135     const char *key;
136     DBusMessageIter variant_i;
137
138     pa_assert(y);
139     pa_assert(d);
140     pa_assert(i);
141
142     if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
143         pa_log("Property name not a string.");
144         return -1;
145     }
146
147     dbus_message_iter_get_basic(i, &key);
148
149     if (!dbus_message_iter_next(i))  {
150         pa_log("Property value missing");
151         return -1;
152     }
153
154     if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
155         pa_log("Property value not a variant.");
156         return -1;
157     }
158
159     dbus_message_iter_recurse(i, &variant_i);
160
161 /*     pa_log_debug("Parsing property org.bluez.Device.%s", key); */
162
163     switch (dbus_message_iter_get_arg_type(&variant_i)) {
164
165         case DBUS_TYPE_STRING: {
166
167             const char *value;
168             dbus_message_iter_get_basic(&variant_i, &value);
169
170             if (pa_streq(key, "Name")) {
171                 pa_xfree(d->name);
172                 d->name = pa_xstrdup(value);
173             } else if (pa_streq(key, "Alias")) {
174                 pa_xfree(d->alias);
175                 d->alias = pa_xstrdup(value);
176             } else if (pa_streq(key, "Address")) {
177                 pa_xfree(d->address);
178                 d->address = pa_xstrdup(value);
179             }
180
181 /*             pa_log_debug("Value %s", value); */
182
183             break;
184         }
185
186         case DBUS_TYPE_BOOLEAN: {
187
188             dbus_bool_t value;
189             dbus_message_iter_get_basic(&variant_i, &value);
190
191             if (pa_streq(key, "Paired"))
192                 d->paired = !!value;
193             else if (pa_streq(key, "Connected"))
194                 d->device_connected = !!value;
195             else if (pa_streq(key, "Trusted"))
196                 d->trusted = !!value;
197
198 /*             pa_log_debug("Value %s", pa_yes_no(value)); */
199
200             break;
201         }
202
203         case DBUS_TYPE_UINT32: {
204
205             uint32_t value;
206             dbus_message_iter_get_basic(&variant_i, &value);
207
208             if (pa_streq(key, "Class"))
209                 d->class = (int) value;
210
211 /*             pa_log_debug("Value %u", (unsigned) value); */
212
213             break;
214         }
215
216         case DBUS_TYPE_ARRAY: {
217
218             DBusMessageIter ai;
219             dbus_message_iter_recurse(&variant_i, &ai);
220
221             if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING &&
222                 pa_streq(key, "UUIDs")) {
223
224                 while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
225                     pa_bluetooth_uuid *node;
226                     const char *value;
227                     DBusMessage *m;
228
229                     dbus_message_iter_get_basic(&ai, &value);
230                     node = uuid_new(value);
231                     PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
232
233                     /* Vudentz said the interfaces are here when the UUIDs are announced */
234                     if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) {
235                         pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.HandsfreeGateway", "GetProperties"));
236                         send_and_add_to_pending(y, d, m, get_properties_reply);
237                     } else if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) {
238                         pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset", "GetProperties"));
239                         send_and_add_to_pending(y, d, m, get_properties_reply);
240                     } else if (strcasecmp(A2DP_SINK_UUID, value) == 0) {
241                         pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink", "GetProperties"));
242                         send_and_add_to_pending(y, d, m, get_properties_reply);
243                     } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) {
244                         pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource", "GetProperties"));
245                         send_and_add_to_pending(y, d, m, get_properties_reply);
246                     }
247
248                     /* 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 */
249                     pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
250                     send_and_add_to_pending(y, d, m, get_properties_reply);
251
252                     if (!dbus_message_iter_next(&ai))
253                         break;
254                 }
255             }
256
257             break;
258         }
259     }
260
261     return 0;
262 }
263
264 static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessageIter *i) {
265     const char *key;
266     DBusMessageIter variant_i;
267
268     pa_assert(u);
269     pa_assert(state);
270     pa_assert(i);
271
272     if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
273         pa_log("Property name not a string.");
274         return -1;
275     }
276
277     dbus_message_iter_get_basic(i, &key);
278
279     if (!dbus_message_iter_next(i))  {
280         pa_log("Property value missing");
281         return -1;
282     }
283
284     if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
285         pa_log("Property value not a variant.");
286         return -1;
287     }
288
289     dbus_message_iter_recurse(i, &variant_i);
290
291 /*     pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
292
293     switch (dbus_message_iter_get_arg_type(&variant_i)) {
294
295         case DBUS_TYPE_STRING: {
296
297             const char *value;
298             dbus_message_iter_get_basic(&variant_i, &value);
299
300             if (pa_streq(key, "State")) {
301                 *state = pa_bt_audio_state_from_string(value);
302                 pa_log_debug("dbus: property 'State' changed to value '%s'", value);
303             }
304
305             break;
306         }
307     }
308
309     return 0;
310 }
311
312 static void run_callback(pa_bluetooth_discovery *y, pa_bluetooth_device *d, pa_bool_t dead) {
313     pa_assert(y);
314     pa_assert(d);
315
316     if (!device_is_audio(d))
317         return;
318
319     d->dead = dead;
320     pa_hook_fire(&y->hook, d);
321 }
322
323 static void remove_all_devices(pa_bluetooth_discovery *y) {
324     pa_bluetooth_device *d;
325
326     pa_assert(y);
327
328     while ((d = pa_hashmap_steal_first(y->devices))) {
329         run_callback(y, d, TRUE);
330         device_free(d);
331     }
332 }
333
334 static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
335     DBusMessage *r;
336     DBusMessageIter arg_i, element_i;
337     pa_dbus_pending *p;
338     pa_bluetooth_device *d;
339     pa_bluetooth_discovery *y;
340     int valid;
341
342     pa_assert_se(p = userdata);
343     pa_assert_se(y = p->context_data);
344     pa_assert_se(r = dbus_pending_call_steal_reply(pending));
345
346 /*     pa_log_debug("Got %s.GetProperties response for %s", */
347 /*                  dbus_message_get_interface(p->message), */
348 /*                  dbus_message_get_path(p->message)); */
349
350     /* We don't use p->call_data here right-away since the device
351      * might already be invalidated at this point */
352
353     if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(p->message))))
354         return;
355
356     pa_assert(p->call_data == d);
357
358     valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1;
359
360     if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties"))
361         d->device_info_valid = valid;
362
363     if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
364         pa_log_debug("Bluetooth daemon is apparently not available.");
365         remove_all_devices(y);
366         goto finish2;
367     }
368
369     if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
370
371         if (!dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD))
372             pa_log("Error from GetProperties reply: %s", dbus_message_get_error_name(r));
373
374         goto finish;
375     }
376
377     if (!dbus_message_iter_init(r, &arg_i)) {
378         pa_log("GetProperties reply has no arguments.");
379         goto finish;
380     }
381
382     if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
383         pa_log("GetProperties argument is not an array.");
384         goto finish;
385     }
386
387     dbus_message_iter_recurse(&arg_i, &element_i);
388     while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) {
389
390         if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
391             DBusMessageIter dict_i;
392
393             dbus_message_iter_recurse(&element_i, &dict_i);
394
395             if (dbus_message_has_interface(p->message, "org.bluez.Device")) {
396                 if (parse_device_property(y, d, &dict_i) < 0)
397                     goto finish;
398
399             } else if (dbus_message_has_interface(p->message, "org.bluez.Audio")) {
400                 if (parse_audio_property(y, &d->audio_state, &dict_i) < 0)
401                     goto finish;
402
403             } else if (dbus_message_has_interface(p->message, "org.bluez.Headset")) {
404                 if (parse_audio_property(y, &d->headset_state, &dict_i) < 0)
405                     goto finish;
406
407             }  else if (dbus_message_has_interface(p->message, "org.bluez.AudioSink")) {
408                 if (parse_audio_property(y, &d->audio_sink_state, &dict_i) < 0)
409                     goto finish;
410
411             }  else if (dbus_message_has_interface(p->message, "org.bluez.AudioSource")) {
412                 if (parse_audio_property(y, &d->audio_source_state, &dict_i) < 0)
413                     goto finish;
414
415             }  else if (dbus_message_has_interface(p->message, "org.bluez.HandsfreeGateway")) {
416                 if (parse_audio_property(y, &d->hfgw_state, &arg_i) < 0)
417                     goto finish;
418             }
419         }
420
421         if (!dbus_message_iter_next(&element_i))
422             break;
423     }
424
425 finish:
426     run_callback(y, d, FALSE);
427
428 finish2:
429     dbus_message_unref(r);
430
431     PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
432     pa_dbus_pending_free(p);
433 }
434
435 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func) {
436     pa_dbus_pending *p;
437     DBusPendingCall *call;
438
439     pa_assert(y);
440     pa_assert(m);
441
442     pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
443
444     p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, d);
445     PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);
446     dbus_pending_call_set_notify(call, func, p, NULL);
447
448     return p;
449 }
450
451 static void found_device(pa_bluetooth_discovery *y, const char* path) {
452     DBusMessage *m;
453     pa_bluetooth_device *d;
454
455     pa_assert(y);
456     pa_assert(path);
457
458     if (pa_hashmap_get(y->devices, path))
459         return;
460
461     d = device_new(path);
462
463     pa_hashmap_put(y->devices, d->path, d);
464
465     pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties"));
466     send_and_add_to_pending(y, d, m, get_properties_reply);
467
468     /* Before we read the other properties (Audio, AudioSink, AudioSource,
469      * Headset) we wait that the UUID is read */
470 }
471
472 static void list_devices_reply(DBusPendingCall *pending, void *userdata) {
473     DBusError e;
474     DBusMessage *r;
475     char **paths = NULL;
476     int num = -1;
477     pa_dbus_pending *p;
478     pa_bluetooth_discovery *y;
479
480     pa_assert(pending);
481
482     dbus_error_init(&e);
483
484     pa_assert_se(p = userdata);
485     pa_assert_se(y = p->context_data);
486     pa_assert_se(r = dbus_pending_call_steal_reply(pending));
487
488     if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
489         pa_log_debug("Bluetooth daemon is apparently not available.");
490         remove_all_devices(y);
491         goto finish;
492     }
493
494     if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
495         pa_log("Error from ListDevices reply: %s", dbus_message_get_error_name(r));
496         goto finish;
497     }
498
499     if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) {
500         pa_log("org.bluez.Adapter.ListDevices returned an error: '%s'\n", e.message);
501         dbus_error_free(&e);
502     } else {
503         int i;
504
505         for (i = 0; i < num; ++i)
506             found_device(y, paths[i]);
507     }
508
509 finish:
510     if (paths)
511         dbus_free_string_array (paths);
512
513     dbus_message_unref(r);
514
515     PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
516     pa_dbus_pending_free(p);
517 }
518
519 static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
520     DBusMessage *m;
521
522     pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "ListDevices"));
523     send_and_add_to_pending(y, NULL, m, list_devices_reply);
524 }
525
526 static void list_adapters_reply(DBusPendingCall *pending, void *userdata) {
527     DBusError e;
528     DBusMessage *r;
529     char **paths = NULL;
530     int num = -1;
531     pa_dbus_pending *p;
532     pa_bluetooth_discovery *y;
533
534     pa_assert(pending);
535
536     dbus_error_init(&e);
537
538     pa_assert_se(p = userdata);
539     pa_assert_se(y = p->context_data);
540     pa_assert_se(r = dbus_pending_call_steal_reply(pending));
541
542     if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
543         pa_log_debug("Bluetooth daemon is apparently not available.");
544         remove_all_devices(y);
545         goto finish;
546     }
547
548     if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
549         pa_log("Error from ListAdapters reply: %s", dbus_message_get_error_name(r));
550         goto finish;
551     }
552
553     if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) {
554         pa_log("org.bluez.Manager.ListAdapters returned an error: %s", e.message);
555         dbus_error_free(&e);
556     } else {
557         int i;
558
559         for (i = 0; i < num; ++i)
560             found_adapter(y, paths[i]);
561     }
562
563 finish:
564     if (paths)
565         dbus_free_string_array (paths);
566
567     dbus_message_unref(r);
568
569     PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
570     pa_dbus_pending_free(p);
571 }
572
573 static void list_adapters(pa_bluetooth_discovery *y) {
574     DBusMessage *m;
575     pa_assert(y);
576
577     pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "ListAdapters"));
578     send_and_add_to_pending(y, NULL, m, list_adapters_reply);
579 }
580
581 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
582     DBusError err;
583     pa_bluetooth_discovery *y;
584
585     pa_assert(bus);
586     pa_assert(m);
587
588     pa_assert_se(y = userdata);
589
590     dbus_error_init(&err);
591
592     pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
593             dbus_message_get_interface(m),
594             dbus_message_get_path(m),
595             dbus_message_get_member(m));
596
597     if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) {
598         const char *path;
599         pa_bluetooth_device *d;
600
601         if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
602             pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err.message);
603             goto fail;
604         }
605
606         pa_log_debug("Device %s removed", path);
607
608         if ((d = pa_hashmap_remove(y->devices, path))) {
609             run_callback(y, d, TRUE);
610             device_free(d);
611         }
612
613         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
614
615     } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceCreated")) {
616         const char *path;
617
618         if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
619             pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err.message);
620             goto fail;
621         }
622
623         pa_log_debug("Device %s created", path);
624
625         found_device(y, path);
626         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
627
628     } else if (dbus_message_is_signal(m, "org.bluez.Manager", "AdapterAdded")) {
629         const char *path;
630
631         if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
632             pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err.message);
633             goto fail;
634         }
635
636         pa_log_debug("Adapter %s created", path);
637
638         found_adapter(y, path);
639         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
640
641     } else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") ||
642                dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
643                dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") ||
644                dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged") ||
645                dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged") ||
646                dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
647
648         pa_bluetooth_device *d;
649
650         if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
651             DBusMessageIter arg_i;
652
653             if (!dbus_message_iter_init(m, &arg_i)) {
654                 pa_log("Failed to parse PropertyChanged: %s", err.message);
655                 goto fail;
656             }
657
658             if (dbus_message_has_interface(m, "org.bluez.Device")) {
659                 if (parse_device_property(y, d, &arg_i) < 0)
660                     goto fail;
661
662             } else if (dbus_message_has_interface(m, "org.bluez.Audio")) {
663                 if (parse_audio_property(y, &d->audio_state, &arg_i) < 0)
664                     goto fail;
665
666             } else if (dbus_message_has_interface(m, "org.bluez.Headset")) {
667                 if (parse_audio_property(y, &d->headset_state, &arg_i) < 0)
668                     goto fail;
669
670             }  else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) {
671                 if (parse_audio_property(y, &d->audio_sink_state, &arg_i) < 0)
672                     goto fail;
673
674             }  else if (dbus_message_has_interface(m, "org.bluez.AudioSource")) {
675                 if (parse_audio_property(y, &d->audio_source_state, &arg_i) < 0)
676                     goto fail;
677
678             }  else if (dbus_message_has_interface(m, "org.bluez.HandsfreeGateway")) {
679                 if (parse_audio_property(y, &d->hfgw_state, &arg_i) < 0)
680                     goto fail;
681             }
682
683             run_callback(y, d, FALSE);
684         }
685
686         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
687
688     } else if (dbus_message_is_signal(m, "org.bluez.Device", "DisconnectRequested")) {
689         pa_bluetooth_device *d;
690
691         if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
692             /* Device will disconnect in 2 sec */
693             d->audio_state = PA_BT_AUDIO_STATE_DISCONNECTED;
694             d->audio_sink_state = PA_BT_AUDIO_STATE_DISCONNECTED;
695             d->audio_source_state = PA_BT_AUDIO_STATE_DISCONNECTED;
696             d->headset_state = PA_BT_AUDIO_STATE_DISCONNECTED;
697             d->hfgw_state = PA_BT_AUDIO_STATE_DISCONNECTED;
698
699             run_callback(y, d, FALSE);
700         }
701
702         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
703
704     } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
705         const char *name, *old_owner, *new_owner;
706
707         if (!dbus_message_get_args(m, &err,
708                                    DBUS_TYPE_STRING, &name,
709                                    DBUS_TYPE_STRING, &old_owner,
710                                    DBUS_TYPE_STRING, &new_owner,
711                                    DBUS_TYPE_INVALID)) {
712             pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
713             goto fail;
714         }
715
716         if (pa_streq(name, "org.bluez")) {
717             if (old_owner && *old_owner) {
718                 pa_log_debug("Bluetooth daemon disappeared.");
719                 remove_all_devices(y);
720             }
721
722             if (new_owner && *new_owner) {
723                 pa_log_debug("Bluetooth daemon appeared.");
724                 list_adapters(y);
725             }
726         }
727
728         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
729     }
730
731 fail:
732     dbus_error_free(&err);
733
734     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
735 }
736
737 const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) {
738     pa_bluetooth_device *d;
739     void *state = NULL;
740
741     pa_assert(y);
742     pa_assert(PA_REFCNT_VALUE(y) > 0);
743     pa_assert(address);
744
745     if (!pa_hook_is_firing(&y->hook))
746         pa_bluetooth_discovery_sync(y);
747
748     while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
749         if (pa_streq(d->address, address))
750             return device_is_audio(d) ? d : NULL;
751
752     return NULL;
753 }
754
755 const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) {
756     pa_bluetooth_device *d;
757
758     pa_assert(y);
759     pa_assert(PA_REFCNT_VALUE(y) > 0);
760     pa_assert(path);
761
762     if (!pa_hook_is_firing(&y->hook))
763         pa_bluetooth_discovery_sync(y);
764
765     if ((d = pa_hashmap_get(y->devices, path)))
766         if (device_is_audio(d))
767             return d;
768
769     return NULL;
770 }
771
772 static int setup_dbus(pa_bluetooth_discovery *y) {
773     DBusError err;
774
775     dbus_error_init(&err);
776
777     y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err);
778
779     if (dbus_error_is_set(&err) || !y->connection) {
780         pa_log("Failed to get D-Bus connection: %s", err.message);
781         dbus_error_free(&err);
782         return -1;
783     }
784
785     return 0;
786 }
787
788 pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
789     DBusError err;
790     pa_bluetooth_discovery *y;
791
792     pa_assert(c);
793
794     dbus_error_init(&err);
795
796     if ((y = pa_shared_get(c, "bluetooth-discovery")))
797         return pa_bluetooth_discovery_ref(y);
798
799     y = pa_xnew0(pa_bluetooth_discovery, 1);
800     PA_REFCNT_INIT(y);
801     y->core = c;
802     y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
803     PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
804     pa_hook_init(&y->hook, y);
805     pa_shared_set(c, "bluetooth-discovery", y);
806
807     if (setup_dbus(y) < 0)
808         goto fail;
809
810     /* dynamic detection of bluetooth audio devices */
811     if (!dbus_connection_add_filter(pa_dbus_connection_get(y->connection), filter_cb, y, NULL)) {
812         pa_log_error("Failed to add filter function");
813         goto fail;
814     }
815     y->filter_added = TRUE;
816
817     if (pa_dbus_add_matches(
818                 pa_dbus_connection_get(y->connection), &err,
819                 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
820                 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
821                 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
822                 "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
823                 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
824                 "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'",
825                 "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
826                 "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
827                 "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
828                 "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
829                 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
830                 NULL) < 0) {
831         pa_log("Failed to add D-Bus matches: %s", err.message);
832         goto fail;
833     }
834
835     list_adapters(y);
836
837     return y;
838
839 fail:
840
841     if (y)
842         pa_bluetooth_discovery_unref(y);
843
844     dbus_error_free(&err);
845
846     return NULL;
847 }
848
849 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
850     pa_assert(y);
851     pa_assert(PA_REFCNT_VALUE(y) > 0);
852
853     PA_REFCNT_INC(y);
854
855     return y;
856 }
857
858 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
859     pa_assert(y);
860     pa_assert(PA_REFCNT_VALUE(y) > 0);
861
862     if (PA_REFCNT_DEC(y) > 0)
863         return;
864
865     pa_dbus_free_pending_list(&y->pending);
866
867     if (y->devices) {
868         remove_all_devices(y);
869         pa_hashmap_free(y->devices, NULL, NULL);
870     }
871
872     if (y->connection) {
873         pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
874                                "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
875                                "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
876                                "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
877                                "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
878                                "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
879                                "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
880                                "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'",
881                                "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
882                                "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
883                                "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
884                                "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
885                                "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
886                                NULL);
887
888         if (y->filter_added)
889             dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
890
891         pa_dbus_connection_unref(y->connection);
892     }
893
894     pa_hook_done(&y->hook);
895
896     if (y->core)
897         pa_shared_remove(y->core, "bluetooth-discovery");
898
899     pa_xfree(y);
900 }
901
902 void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *y) {
903     pa_assert(y);
904     pa_assert(PA_REFCNT_VALUE(y) > 0);
905
906     pa_dbus_sync_pending_list(&y->pending);
907 }
908
909 pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y) {
910     pa_assert(y);
911     pa_assert(PA_REFCNT_VALUE(y) > 0);
912
913     return &y->hook;
914 }
915
916 const char*pa_bluetooth_get_form_factor(uint32_t class) {
917     unsigned i;
918     const char *r;
919
920     static const char * const table[] = {
921         [1] = "headset",
922         [2] = "hands-free",
923         [4] = "microphone",
924         [5] = "speaker",
925         [6] = "headphone",
926         [7] = "portable",
927         [8] = "car",
928         [10] = "hifi"
929     };
930
931     if (((class >> 8) & 31) != 4)
932         return NULL;
933
934     if ((i = (class >> 2) & 63) > PA_ELEMENTSOF(table))
935         r =  NULL;
936     else
937         r = table[i];
938
939     if (!r)
940         pa_log_debug("Unknown Bluetooth minor device class %u", i);
941
942     return r;
943 }
944
945 char *pa_bluetooth_cleanup_name(const char *name) {
946     char *t, *s, *d;
947     pa_bool_t space = FALSE;
948
949     pa_assert(name);
950
951     while ((*name >= 1 && *name <= 32) || *name >= 127)
952         name++;
953
954     t = pa_xstrdup(name);
955
956     for (s = d = t; *s; s++) {
957
958         if (*s <= 32 || *s >= 127 || *s == '_') {
959             space = TRUE;
960             continue;
961         }
962
963         if (space) {
964             *(d++) = ' ';
965             space = FALSE;
966         }
967
968         *(d++) = *s;
969     }
970
971     *d = 0;
972
973     return t;
974 }
975
976 pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
977     pa_assert(uuid);
978
979     while (uuids) {
980         if (strcasecmp(uuids->uuid, uuid) == 0)
981             return TRUE;
982
983         uuids = uuids->next;
984     }
985
986     return FALSE;
987 }