2 This file is part of PulseAudio.
4 Copyright 2008-2013 João Paulo Rechi Vita
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.
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.
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
26 #include <pulse/xmalloc.h>
28 #include <pulsecore/core.h>
29 #include <pulsecore/core-util.h>
30 #include <pulsecore/dbus-shared.h>
31 #include <pulsecore/log.h>
32 #include <pulsecore/macro.h>
33 #include <pulsecore/refcnt.h>
34 #include <pulsecore/shared.h>
36 #include "bluez5-util.h"
38 #define BLUEZ_SERVICE "org.bluez"
39 #define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
41 struct pa_bluetooth_discovery
{
45 pa_dbus_connection
*connection
;
48 pa_hook hooks
[PA_BLUETOOTH_HOOK_MAX
];
51 pa_hashmap
*transports
;
54 pa_bluetooth_transport
*pa_bluetooth_transport_new(pa_bluetooth_device
*d
, const char *owner
, const char *path
,
55 pa_bluetooth_profile_t p
, const uint8_t *config
, size_t size
) {
56 pa_bluetooth_transport
*t
;
58 t
= pa_xnew0(pa_bluetooth_transport
, 1);
60 t
->owner
= pa_xstrdup(owner
);
61 t
->path
= pa_xstrdup(path
);
63 t
->config_size
= size
;
66 t
->config
= pa_xnew(uint8_t, size
);
67 memcpy(t
->config
, config
, size
);
70 pa_assert_se(pa_hashmap_put(d
->discovery
->transports
, t
->path
, t
) >= 0);
75 static const char *transport_state_to_string(pa_bluetooth_transport_state_t state
) {
77 case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
:
78 return "disconnected";
79 case PA_BLUETOOTH_TRANSPORT_STATE_IDLE
:
81 case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
:
88 static void transport_state_changed(pa_bluetooth_transport
*t
, pa_bluetooth_transport_state_t state
) {
89 bool old_any_connected
;
93 if (t
->state
== state
)
96 old_any_connected
= pa_bluetooth_device_any_transport_connected(t
->device
);
98 pa_log_debug("Transport %s state changed from %s to %s",
99 t
->path
, transport_state_to_string(t
->state
), transport_state_to_string(state
));
102 if (state
== PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
)
103 t
->device
->transports
[t
->profile
] = NULL
;
105 pa_hook_fire(&t
->device
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED
], t
);
107 if (old_any_connected
!= pa_bluetooth_device_any_transport_connected(t
->device
))
108 pa_hook_fire(&t
->device
->discovery
->hooks
[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED
], t
->device
);
111 void pa_bluetooth_transport_put(pa_bluetooth_transport
*t
) {
112 transport_state_changed(t
, PA_BLUETOOTH_TRANSPORT_STATE_IDLE
);
115 void pa_bluetooth_transport_free(pa_bluetooth_transport
*t
) {
118 pa_hashmap_remove(t
->device
->discovery
->transports
, t
->path
);
125 static int bluez5_transport_acquire_cb(pa_bluetooth_transport
*t
, bool optional
, size_t *imtu
, size_t *omtu
) {
130 const char *method
= optional
? "TryAcquire" : "Acquire";
133 pa_assert(t
->device
);
134 pa_assert(t
->device
->discovery
);
136 pa_assert_se(m
= dbus_message_new_method_call(t
->owner
, t
->path
, BLUEZ_MEDIA_TRANSPORT_INTERFACE
, method
));
138 dbus_error_init(&err
);
140 r
= dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t
->device
->discovery
->connection
), m
, -1, &err
);
142 if (optional
&& pa_streq(err
.name
, "org.bluez.Error.NotAvailable"))
143 pa_log_info("Failed optional acquire of unavailable transport %s", t
->path
);
145 pa_log_error("Transport %s() failed for transport %s (%s)", method
, t
->path
, err
.message
);
147 dbus_error_free(&err
);
151 if (!dbus_message_get_args(r
, &err
, DBUS_TYPE_UNIX_FD
, &ret
, DBUS_TYPE_UINT16
, &i
, DBUS_TYPE_UINT16
, &o
,
152 DBUS_TYPE_INVALID
)) {
153 pa_log_error("Failed to parse %s() reply: %s", method
, err
.message
);
154 dbus_error_free(&err
);
166 dbus_message_unref(r
);
170 static void bluez5_transport_release_cb(pa_bluetooth_transport
*t
) {
175 pa_assert(t
->device
);
176 pa_assert(t
->device
->discovery
);
178 dbus_error_init(&err
);
180 if (t
->state
<= PA_BLUETOOTH_TRANSPORT_STATE_IDLE
) {
181 pa_log_info("Transport %s auto-released by BlueZ or already released", t
->path
);
185 pa_assert_se(m
= dbus_message_new_method_call(t
->owner
, t
->path
, BLUEZ_MEDIA_TRANSPORT_INTERFACE
, "Release"));
186 dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t
->device
->discovery
->connection
), m
, -1, &err
);
188 if (dbus_error_is_set(&err
)) {
189 pa_log_error("Failed to release transport %s: %s", t
->path
, err
.message
);
190 dbus_error_free(&err
);
192 pa_log_info("Transport %s released", t
->path
);
195 bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device
*d
) {
200 if (d
->device_info_valid
!= 1)
203 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++)
204 if (d
->transports
[i
] && d
->transports
[i
]->state
!= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
)
210 static pa_bluetooth_device
* device_create(pa_bluetooth_discovery
*y
, const char *path
) {
211 pa_bluetooth_device
*d
;
216 d
= pa_xnew0(pa_bluetooth_device
, 1);
218 d
->path
= pa_xstrdup(path
);
220 pa_hashmap_put(y
->devices
, d
->path
, d
);
225 pa_bluetooth_device
* pa_bluetooth_discovery_get_device_by_path(pa_bluetooth_discovery
*y
, const char *path
) {
226 pa_bluetooth_device
*d
;
229 pa_assert(PA_REFCNT_VALUE(y
) > 0);
232 if ((d
= pa_hashmap_get(y
->devices
, path
)))
233 if (d
->device_info_valid
== 1)
239 pa_bluetooth_device
* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_discovery
*y
, const char *remote
, const char *local
) {
240 pa_bluetooth_device
*d
;
244 pa_assert(PA_REFCNT_VALUE(y
) > 0);
248 while ((d
= pa_hashmap_iterate(y
->devices
, &state
, NULL
)))
249 if (pa_streq(d
->address
, remote
) && pa_streq(d
->adapter
->address
, local
))
250 return d
->device_info_valid
== 1 ? d
: NULL
;
255 static void device_free(pa_bluetooth_device
*d
) {
260 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++) {
261 pa_bluetooth_transport
*t
;
263 if (!(t
= d
->transports
[i
]))
266 transport_state_changed(t
, PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
);
267 pa_bluetooth_transport_free(t
);
274 pa_xfree(d
->address
);
278 static void device_remove(pa_bluetooth_discovery
*y
, const char *path
) {
279 pa_bluetooth_device
*d
;
281 if (!(d
= pa_hashmap_remove(y
->devices
, path
)))
282 pa_log_warn("Unknown device removed %s", path
);
284 pa_log_debug("Device %s removed", path
);
289 static void device_remove_all(pa_bluetooth_discovery
*y
) {
290 pa_bluetooth_device
*d
;
294 while ((d
= pa_hashmap_steal_first(y
->devices
))) {
295 d
->device_info_valid
= -1;
296 pa_hook_fire(&y
->hooks
[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED
], d
);
301 static pa_bluetooth_adapter
* adapter_create(pa_bluetooth_discovery
*y
, const char *path
) {
302 pa_bluetooth_adapter
*a
;
307 a
= pa_xnew0(pa_bluetooth_adapter
, 1);
309 a
->path
= pa_xstrdup(path
);
311 pa_hashmap_put(y
->adapters
, a
->path
, a
);
316 static void adapter_remove_all(pa_bluetooth_discovery
*y
) {
317 pa_bluetooth_adapter
*a
;
321 /* When this function is called all devices have already been freed */
323 while ((a
= pa_hashmap_steal_first(y
->adapters
))) {
325 pa_xfree(a
->address
);
330 pa_hook
* pa_bluetooth_discovery_hook(pa_bluetooth_discovery
*y
, pa_bluetooth_hook_t hook
) {
332 pa_assert(PA_REFCNT_VALUE(y
) > 0);
334 return &y
->hooks
[hook
];
337 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*m
, void *userdata
) {
338 pa_bluetooth_discovery
*y
;
343 pa_assert_se(y
= userdata
);
345 dbus_error_init(&err
);
347 if (dbus_message_is_signal(m
, "org.freedesktop.DBus", "NameOwnerChanged")) {
348 const char *name
, *old_owner
, *new_owner
;
350 if (!dbus_message_get_args(m
, &err
,
351 DBUS_TYPE_STRING
, &name
,
352 DBUS_TYPE_STRING
, &old_owner
,
353 DBUS_TYPE_STRING
, &new_owner
,
354 DBUS_TYPE_INVALID
)) {
355 pa_log_error("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err
.message
);
359 if (pa_streq(name
, BLUEZ_SERVICE
)) {
360 if (old_owner
&& *old_owner
) {
361 pa_log_debug("Bluetooth daemon disappeared");
362 device_remove_all(y
);
363 adapter_remove_all(y
);
366 if (new_owner
&& *new_owner
) {
367 pa_log_debug("Bluetooth daemon appeared");
368 /* TODO: get managed objects */
372 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
376 dbus_error_free(&err
);
378 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
381 pa_bluetooth_discovery
* pa_bluetooth_discovery_get(pa_core
*c
) {
382 pa_bluetooth_discovery
*y
;
384 DBusConnection
*conn
;
387 if ((y
= pa_shared_get(c
, "bluetooth-discovery")))
388 return pa_bluetooth_discovery_ref(y
);
390 y
= pa_xnew0(pa_bluetooth_discovery
, 1);
393 y
->adapters
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
394 y
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
395 y
->transports
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
397 for (i
= 0; i
< PA_BLUETOOTH_HOOK_MAX
; i
++)
398 pa_hook_init(&y
->hooks
[i
], y
);
400 pa_shared_set(c
, "bluetooth-discovery", y
);
402 dbus_error_init(&err
);
404 if (!(y
->connection
= pa_dbus_bus_get(y
->core
, DBUS_BUS_SYSTEM
, &err
))) {
405 pa_log_error("Failed to get D-Bus connection: %s", err
.message
);
409 conn
= pa_dbus_connection_get(y
->connection
);
411 /* dynamic detection of bluetooth audio devices */
412 if (!dbus_connection_add_filter(conn
, filter_cb
, y
, NULL
)) {
413 pa_log_error("Failed to add filter function");
416 y
->filter_added
= true;
418 if (pa_dbus_add_matches(conn
, &err
,
419 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
420 ",arg0='" BLUEZ_SERVICE
"'",
422 pa_log_error("Failed to add D-Bus matches: %s", err
.message
);
425 y
->matches_added
= true;
430 pa_bluetooth_discovery_unref(y
);
431 dbus_error_free(&err
);
436 pa_bluetooth_discovery
* pa_bluetooth_discovery_ref(pa_bluetooth_discovery
*y
) {
438 pa_assert(PA_REFCNT_VALUE(y
) > 0);
445 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery
*y
) {
447 pa_assert(PA_REFCNT_VALUE(y
) > 0);
449 if (PA_REFCNT_DEC(y
) > 0)
453 device_remove_all(y
);
454 pa_hashmap_free(y
->devices
);
458 adapter_remove_all(y
);
459 pa_hashmap_free(y
->adapters
);
463 pa_assert(pa_hashmap_isempty(y
->transports
));
464 pa_hashmap_free(y
->transports
);
469 if (y
->matches_added
)
470 pa_dbus_remove_matches(pa_dbus_connection_get(y
->connection
),
471 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',"
472 "arg0='" BLUEZ_SERVICE
"'",
476 dbus_connection_remove_filter(pa_dbus_connection_get(y
->connection
), filter_cb
, y
);
478 pa_dbus_connection_unref(y
->connection
);
481 pa_shared_remove(y
->core
, "bluetooth-discovery");