2013-08-13 01:54:00 -03:00
|
|
|
/***
|
|
|
|
|
This file is part of PulseAudio.
|
|
|
|
|
|
|
|
|
|
Copyright 2008-2013 João Paulo Rechi Vita
|
2019-04-06 11:15:58 +02:00
|
|
|
Copyrigth 2018-2019 Pali Rohár <pali.rohar@gmail.com>
|
2013-08-13 01:54:00 -03:00
|
|
|
|
|
|
|
|
PulseAudio is free software; you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU Lesser General Public License as
|
|
|
|
|
published by the Free Software Foundation; either version 2.1 of the
|
|
|
|
|
License, or (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
PulseAudio is distributed in the hope that it will be useful, but
|
|
|
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
2014-11-26 14:14:51 +01:00
|
|
|
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
|
2013-08-13 01:54:00 -03:00
|
|
|
***/
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2016-08-07 18:45:48 +03:00
|
|
|
#include <pulse/rtclock.h>
|
|
|
|
|
#include <pulse/timeval.h>
|
2013-08-13 01:54:00 -03:00
|
|
|
#include <pulse/xmalloc.h>
|
|
|
|
|
|
|
|
|
|
#include <pulsecore/core.h>
|
2013-09-18 16:16:55 -05:00
|
|
|
#include <pulsecore/core-util.h>
|
|
|
|
|
#include <pulsecore/dbus-shared.h>
|
|
|
|
|
#include <pulsecore/log.h>
|
2013-08-13 01:54:00 -03:00
|
|
|
#include <pulsecore/macro.h>
|
|
|
|
|
#include <pulsecore/refcnt.h>
|
|
|
|
|
#include <pulsecore/shared.h>
|
|
|
|
|
|
2020-12-15 16:51:11 +05:30
|
|
|
#include "a2dp-codec-api.h"
|
2019-04-06 11:15:58 +02:00
|
|
|
#include "a2dp-codec-util.h"
|
2013-09-24 19:45:24 -03:00
|
|
|
#include "a2dp-codecs.h"
|
|
|
|
|
|
2013-08-13 01:54:00 -03:00
|
|
|
#include "bluez5-util.h"
|
|
|
|
|
|
2016-08-07 18:45:48 +03:00
|
|
|
#define WAIT_FOR_PROFILES_TIMEOUT_USEC (3 * PA_USEC_PER_SEC)
|
|
|
|
|
|
2013-09-18 16:16:55 -05:00
|
|
|
#define BLUEZ_SERVICE "org.bluez"
|
2013-09-24 19:45:30 -03:00
|
|
|
#define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1"
|
|
|
|
|
#define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1"
|
2013-09-24 19:45:33 -03:00
|
|
|
#define BLUEZ_MEDIA_INTERFACE BLUEZ_SERVICE ".Media1"
|
2013-09-24 19:45:23 -03:00
|
|
|
#define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1"
|
2013-09-24 19:45:22 -03:00
|
|
|
#define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
|
2013-09-18 16:16:55 -05:00
|
|
|
|
2013-09-24 19:45:33 -03:00
|
|
|
#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
|
|
|
|
|
|
2020-12-15 16:51:11 +05:30
|
|
|
#define A2DP_OBJECT_MANAGER_PATH "/MediaEndpoint"
|
|
|
|
|
#define A2DP_SOURCE_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSource"
|
|
|
|
|
#define A2DP_SINK_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSink"
|
|
|
|
|
|
|
|
|
|
#define OBJECT_MANAGER_INTROSPECT_XML \
|
|
|
|
|
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
|
|
|
|
|
"<node>\n" \
|
|
|
|
|
" <interface name=\"org.freedesktop.DBus.ObjectManager\">\n" \
|
|
|
|
|
" <method name=\"GetManagedObjects\">\n" \
|
|
|
|
|
" <arg name=\"objects\" direction=\"out\" type=\"a{oa{sa{sv}}}\"/>\n" \
|
|
|
|
|
" </method>\n" \
|
|
|
|
|
" <signal name=\"InterfacesAdded\">\n" \
|
|
|
|
|
" <arg name=\"object\" type=\"o\"/>\n" \
|
|
|
|
|
" <arg name=\"interfaces\" type=\"a{sa{sv}}\"/>\n" \
|
|
|
|
|
" </signal>\n" \
|
|
|
|
|
" <signal name=\"InterfacesRemoved\">\n" \
|
|
|
|
|
" <arg name=\"object\" type=\"o\"/>\n" \
|
|
|
|
|
" <arg name=\"interfaces\" type=\"as\"/>\n" \
|
|
|
|
|
" </signal>\n" \
|
|
|
|
|
" </interface>\n" \
|
|
|
|
|
" <interface name=\"org.freedesktop.DBus.Introspectable\">\n" \
|
|
|
|
|
" <method name=\"Introspect\">\n" \
|
|
|
|
|
" <arg name=\"data\" direction=\"out\" type=\"s\"/>\n" \
|
|
|
|
|
" </method>\n" \
|
|
|
|
|
" </interface>\n" \
|
|
|
|
|
" <node name=\"A2DPSink\"/>\n" \
|
|
|
|
|
" <node name=\"A2DPSource\"/>\n" \
|
|
|
|
|
"</node>\n"
|
2013-09-24 19:45:23 -03:00
|
|
|
|
|
|
|
|
#define ENDPOINT_INTROSPECT_XML \
|
|
|
|
|
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
|
|
|
|
|
"<node>" \
|
|
|
|
|
" <interface name=\"" BLUEZ_MEDIA_ENDPOINT_INTERFACE "\">" \
|
|
|
|
|
" <method name=\"SetConfiguration\">" \
|
|
|
|
|
" <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \
|
|
|
|
|
" <arg name=\"properties\" direction=\"in\" type=\"ay\"/>" \
|
|
|
|
|
" </method>" \
|
|
|
|
|
" <method name=\"SelectConfiguration\">" \
|
|
|
|
|
" <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>" \
|
|
|
|
|
" <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>" \
|
|
|
|
|
" </method>" \
|
|
|
|
|
" <method name=\"ClearConfiguration\">" \
|
|
|
|
|
" <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \
|
|
|
|
|
" </method>" \
|
|
|
|
|
" <method name=\"Release\">" \
|
|
|
|
|
" </method>" \
|
|
|
|
|
" </interface>" \
|
|
|
|
|
" <interface name=\"org.freedesktop.DBus.Introspectable\">" \
|
|
|
|
|
" <method name=\"Introspect\">" \
|
|
|
|
|
" <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
|
|
|
|
|
" </method>" \
|
|
|
|
|
" </interface>" \
|
|
|
|
|
"</node>"
|
|
|
|
|
|
2013-08-13 01:54:00 -03:00
|
|
|
struct pa_bluetooth_discovery {
|
|
|
|
|
PA_REFCNT_DECLARE;
|
|
|
|
|
|
|
|
|
|
pa_core *core;
|
2013-09-18 16:16:55 -05:00
|
|
|
pa_dbus_connection *connection;
|
|
|
|
|
bool filter_added;
|
|
|
|
|
bool matches_added;
|
2013-09-24 19:45:28 -03:00
|
|
|
bool objects_listed;
|
2013-09-18 16:16:56 -05:00
|
|
|
pa_hook hooks[PA_BLUETOOTH_HOOK_MAX];
|
2013-09-18 16:16:57 -05:00
|
|
|
pa_hashmap *adapters;
|
2013-09-18 16:16:58 -05:00
|
|
|
pa_hashmap *devices;
|
2013-09-24 19:45:22 -03:00
|
|
|
pa_hashmap *transports;
|
2013-09-24 19:45:27 -03:00
|
|
|
|
2014-11-10 16:13:13 +01:00
|
|
|
int headset_backend;
|
2014-11-03 11:01:00 +01:00
|
|
|
pa_bluetooth_backend *ofono_backend, *native_backend;
|
2013-09-24 19:45:27 -03:00
|
|
|
PA_LLIST_HEAD(pa_dbus_pending, pending);
|
2013-08-13 01:54:00 -03:00
|
|
|
};
|
|
|
|
|
|
2013-09-24 19:45:27 -03:00
|
|
|
static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m,
|
|
|
|
|
DBusPendingCallNotifyFunction func, void *call_data) {
|
|
|
|
|
pa_dbus_pending *p;
|
|
|
|
|
DBusPendingCall *call;
|
|
|
|
|
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
pa_assert(m);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
|
|
|
|
|
|
|
|
|
|
p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, call_data);
|
|
|
|
|
PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);
|
|
|
|
|
dbus_pending_call_set_notify(call, func, p, NULL);
|
|
|
|
|
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:32 -03:00
|
|
|
static const char *check_variant_property(DBusMessageIter *i) {
|
|
|
|
|
const char *key;
|
|
|
|
|
|
|
|
|
|
pa_assert(i);
|
|
|
|
|
|
|
|
|
|
if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
|
|
|
|
|
pa_log_error("Property name not a string.");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(i, &key);
|
|
|
|
|
|
|
|
|
|
if (!dbus_message_iter_next(i)) {
|
|
|
|
|
pa_log_error("Property value missing");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
|
|
|
|
|
pa_log_error("Property value not a variant.");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return key;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:22 -03:00
|
|
|
pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const char *owner, const char *path,
|
|
|
|
|
pa_bluetooth_profile_t p, const uint8_t *config, size_t size) {
|
|
|
|
|
pa_bluetooth_transport *t;
|
|
|
|
|
|
|
|
|
|
t = pa_xnew0(pa_bluetooth_transport, 1);
|
|
|
|
|
t->device = d;
|
|
|
|
|
t->owner = pa_xstrdup(owner);
|
|
|
|
|
t->path = pa_xstrdup(path);
|
|
|
|
|
t->profile = p;
|
|
|
|
|
t->config_size = size;
|
|
|
|
|
|
|
|
|
|
if (size > 0) {
|
|
|
|
|
t->config = pa_xnew(uint8_t, size);
|
|
|
|
|
memcpy(t->config, config, size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return t;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const char *transport_state_to_string(pa_bluetooth_transport_state_t state) {
|
|
|
|
|
switch(state) {
|
|
|
|
|
case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
|
|
|
|
|
return "disconnected";
|
|
|
|
|
case PA_BLUETOOTH_TRANSPORT_STATE_IDLE:
|
|
|
|
|
return "idle";
|
|
|
|
|
case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING:
|
|
|
|
|
return "playing";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "invalid";
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-07 18:45:48 +03:00
|
|
|
static bool device_supports_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) {
|
|
|
|
|
switch (profile) {
|
|
|
|
|
case PA_BLUETOOTH_PROFILE_A2DP_SINK:
|
|
|
|
|
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SINK);
|
|
|
|
|
case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
|
|
|
|
|
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE);
|
|
|
|
|
case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
|
|
|
|
|
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS)
|
2017-09-02 15:44:58 +03:00
|
|
|
|| !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS_ALT)
|
2016-08-07 18:45:48 +03:00
|
|
|
|| !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_HF);
|
|
|
|
|
case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
|
|
|
|
|
return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_AG)
|
|
|
|
|
|| !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_AG);
|
|
|
|
|
case PA_BLUETOOTH_PROFILE_OFF:
|
|
|
|
|
pa_assert_not_reached();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_assert_not_reached();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool device_is_profile_connected(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) {
|
|
|
|
|
if (device->transports[profile] && device->transports[profile]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
|
|
|
|
|
return true;
|
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static unsigned device_count_disconnected_profiles(pa_bluetooth_device *device) {
|
|
|
|
|
pa_bluetooth_profile_t profile;
|
|
|
|
|
unsigned count = 0;
|
|
|
|
|
|
|
|
|
|
for (profile = 0; profile < PA_BLUETOOTH_PROFILE_COUNT; profile++) {
|
|
|
|
|
if (!device_supports_profile(device, profile))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (!device_is_profile_connected(device, profile))
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void device_stop_waiting_for_profiles(pa_bluetooth_device *device) {
|
|
|
|
|
if (!device->wait_for_profiles_timer)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
device->discovery->core->mainloop->time_free(device->wait_for_profiles_timer);
|
|
|
|
|
device->wait_for_profiles_timer = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void wait_for_profiles_cb(pa_mainloop_api *api, pa_time_event* event, const struct timeval *tv, void *userdata) {
|
|
|
|
|
pa_bluetooth_device *device = userdata;
|
|
|
|
|
pa_strbuf *buf;
|
|
|
|
|
pa_bluetooth_profile_t profile;
|
|
|
|
|
bool first = true;
|
|
|
|
|
char *profiles_str;
|
|
|
|
|
|
|
|
|
|
device_stop_waiting_for_profiles(device);
|
|
|
|
|
|
|
|
|
|
buf = pa_strbuf_new();
|
|
|
|
|
|
|
|
|
|
for (profile = 0; profile < PA_BLUETOOTH_PROFILE_COUNT; profile++) {
|
|
|
|
|
if (device_is_profile_connected(device, profile))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (!device_supports_profile(device, profile))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (first)
|
|
|
|
|
first = false;
|
|
|
|
|
else
|
|
|
|
|
pa_strbuf_puts(buf, ", ");
|
|
|
|
|
|
|
|
|
|
pa_strbuf_puts(buf, pa_bluetooth_profile_to_string(profile));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
profiles_str = pa_strbuf_to_string_free(buf);
|
|
|
|
|
pa_log_debug("Timeout expired, and device %s still has disconnected profiles: %s",
|
|
|
|
|
device->path, profiles_str);
|
|
|
|
|
pa_xfree(profiles_str);
|
|
|
|
|
pa_hook_fire(&device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], device);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void device_start_waiting_for_profiles(pa_bluetooth_device *device) {
|
|
|
|
|
pa_assert(!device->wait_for_profiles_timer);
|
|
|
|
|
device->wait_for_profiles_timer = pa_core_rttime_new(device->discovery->core,
|
|
|
|
|
pa_rtclock_now() + WAIT_FOR_PROFILES_TIMEOUT_USEC,
|
|
|
|
|
wait_for_profiles_cb, device);
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-08 12:14:42 +03:00
|
|
|
void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_transport_state_t state) {
|
2013-09-24 19:45:22 -03:00
|
|
|
bool old_any_connected;
|
bluez5-util: fix profile waiting logic
There were two bugs in the old logic. The first one:
If a device has two profiles, the old code would start the wait timer
when the first profile connects, but when the second profile connects,
the timer would not get stopped and the CONNECTION_CHANGED hook would
not get fired, because the code for that was inside an if block that
only gets executed when the first profile connects. As a result,
module-bluez5-device loading would always be delayed until the wait
timeout expires.
The second bug:
A crash was observed in device_start_waiting_for_profiles(). That
function is called whenever the connected profile count changes from 0
to 1. The function also has an assertion that checks that the timer is
not running when the function is called. That assertion crashed in the
following scenario with a headset that supports HSP and A2DP:
1. First HSP gets connected. The timer is started.
2. Then HSP gets disconnected for some reason. The timer is still
running.
3. Then A2DP gets connected. device_start_waiting_for_profiles() is
called, because the connected profile count changed from 0 to 1 again.
The timer is already running, so the assertion fails.
First I thought I'd remove the assertion from
device_start_waiting_for_profiles() and just restart the timer on the
second call, but then I figured that when the device returns to the
"everything disconnected" state in step 2, it would be better to stop
the timer. The purpose of the timer is to delay the notification of the
device becoming connected, but if the device becomes disconnected during
the waiting period, the notification doesn't make sense any more, and
therefore the timer doesn't make sense either.
BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=100237
2017-03-16 23:48:43 +02:00
|
|
|
unsigned n_disconnected_profiles;
|
|
|
|
|
bool new_device_appeared;
|
|
|
|
|
bool device_disconnected;
|
2013-09-24 19:45:22 -03:00
|
|
|
|
|
|
|
|
pa_assert(t);
|
|
|
|
|
|
|
|
|
|
if (t->state == state)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
old_any_connected = pa_bluetooth_device_any_transport_connected(t->device);
|
|
|
|
|
|
2016-08-07 18:45:54 +03:00
|
|
|
pa_log_debug("Transport %s state: %s -> %s",
|
2013-09-24 19:45:22 -03:00
|
|
|
t->path, transport_state_to_string(t->state), transport_state_to_string(state));
|
|
|
|
|
|
|
|
|
|
t->state = state;
|
|
|
|
|
|
|
|
|
|
pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
|
|
|
|
|
|
bluez5-util: fix profile waiting logic
There were two bugs in the old logic. The first one:
If a device has two profiles, the old code would start the wait timer
when the first profile connects, but when the second profile connects,
the timer would not get stopped and the CONNECTION_CHANGED hook would
not get fired, because the code for that was inside an if block that
only gets executed when the first profile connects. As a result,
module-bluez5-device loading would always be delayed until the wait
timeout expires.
The second bug:
A crash was observed in device_start_waiting_for_profiles(). That
function is called whenever the connected profile count changes from 0
to 1. The function also has an assertion that checks that the timer is
not running when the function is called. That assertion crashed in the
following scenario with a headset that supports HSP and A2DP:
1. First HSP gets connected. The timer is started.
2. Then HSP gets disconnected for some reason. The timer is still
running.
3. Then A2DP gets connected. device_start_waiting_for_profiles() is
called, because the connected profile count changed from 0 to 1 again.
The timer is already running, so the assertion fails.
First I thought I'd remove the assertion from
device_start_waiting_for_profiles() and just restart the timer on the
second call, but then I figured that when the device returns to the
"everything disconnected" state in step 2, it would be better to stop
the timer. The purpose of the timer is to delay the notification of the
device becoming connected, but if the device becomes disconnected during
the waiting period, the notification doesn't make sense any more, and
therefore the timer doesn't make sense either.
BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=100237
2017-03-16 23:48:43 +02:00
|
|
|
/* If there are profiles that are expected to get connected soon (based
|
|
|
|
|
* on the UUID list), we wait for a bit before announcing the new
|
|
|
|
|
* device, so that all profiles have time to get connected before the
|
|
|
|
|
* card object is created. If we didn't wait, the card would always
|
|
|
|
|
* have only one profile marked as available in the initial state,
|
|
|
|
|
* which would prevent module-card-restore from restoring the initial
|
|
|
|
|
* profile properly. */
|
2016-08-07 18:45:48 +03:00
|
|
|
|
bluez5-util: fix profile waiting logic
There were two bugs in the old logic. The first one:
If a device has two profiles, the old code would start the wait timer
when the first profile connects, but when the second profile connects,
the timer would not get stopped and the CONNECTION_CHANGED hook would
not get fired, because the code for that was inside an if block that
only gets executed when the first profile connects. As a result,
module-bluez5-device loading would always be delayed until the wait
timeout expires.
The second bug:
A crash was observed in device_start_waiting_for_profiles(). That
function is called whenever the connected profile count changes from 0
to 1. The function also has an assertion that checks that the timer is
not running when the function is called. That assertion crashed in the
following scenario with a headset that supports HSP and A2DP:
1. First HSP gets connected. The timer is started.
2. Then HSP gets disconnected for some reason. The timer is still
running.
3. Then A2DP gets connected. device_start_waiting_for_profiles() is
called, because the connected profile count changed from 0 to 1 again.
The timer is already running, so the assertion fails.
First I thought I'd remove the assertion from
device_start_waiting_for_profiles() and just restart the timer on the
second call, but then I figured that when the device returns to the
"everything disconnected" state in step 2, it would be better to stop
the timer. The purpose of the timer is to delay the notification of the
device becoming connected, but if the device becomes disconnected during
the waiting period, the notification doesn't make sense any more, and
therefore the timer doesn't make sense either.
BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=100237
2017-03-16 23:48:43 +02:00
|
|
|
n_disconnected_profiles = device_count_disconnected_profiles(t->device);
|
2016-08-07 18:45:48 +03:00
|
|
|
|
bluez5-util: fix profile waiting logic
There were two bugs in the old logic. The first one:
If a device has two profiles, the old code would start the wait timer
when the first profile connects, but when the second profile connects,
the timer would not get stopped and the CONNECTION_CHANGED hook would
not get fired, because the code for that was inside an if block that
only gets executed when the first profile connects. As a result,
module-bluez5-device loading would always be delayed until the wait
timeout expires.
The second bug:
A crash was observed in device_start_waiting_for_profiles(). That
function is called whenever the connected profile count changes from 0
to 1. The function also has an assertion that checks that the timer is
not running when the function is called. That assertion crashed in the
following scenario with a headset that supports HSP and A2DP:
1. First HSP gets connected. The timer is started.
2. Then HSP gets disconnected for some reason. The timer is still
running.
3. Then A2DP gets connected. device_start_waiting_for_profiles() is
called, because the connected profile count changed from 0 to 1 again.
The timer is already running, so the assertion fails.
First I thought I'd remove the assertion from
device_start_waiting_for_profiles() and just restart the timer on the
second call, but then I figured that when the device returns to the
"everything disconnected" state in step 2, it would be better to stop
the timer. The purpose of the timer is to delay the notification of the
device becoming connected, but if the device becomes disconnected during
the waiting period, the notification doesn't make sense any more, and
therefore the timer doesn't make sense either.
BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=100237
2017-03-16 23:48:43 +02:00
|
|
|
new_device_appeared = !old_any_connected && pa_bluetooth_device_any_transport_connected(t->device);
|
|
|
|
|
device_disconnected = old_any_connected && !pa_bluetooth_device_any_transport_connected(t->device);
|
2016-08-07 18:45:48 +03:00
|
|
|
|
bluez5-util: fix profile waiting logic
There were two bugs in the old logic. The first one:
If a device has two profiles, the old code would start the wait timer
when the first profile connects, but when the second profile connects,
the timer would not get stopped and the CONNECTION_CHANGED hook would
not get fired, because the code for that was inside an if block that
only gets executed when the first profile connects. As a result,
module-bluez5-device loading would always be delayed until the wait
timeout expires.
The second bug:
A crash was observed in device_start_waiting_for_profiles(). That
function is called whenever the connected profile count changes from 0
to 1. The function also has an assertion that checks that the timer is
not running when the function is called. That assertion crashed in the
following scenario with a headset that supports HSP and A2DP:
1. First HSP gets connected. The timer is started.
2. Then HSP gets disconnected for some reason. The timer is still
running.
3. Then A2DP gets connected. device_start_waiting_for_profiles() is
called, because the connected profile count changed from 0 to 1 again.
The timer is already running, so the assertion fails.
First I thought I'd remove the assertion from
device_start_waiting_for_profiles() and just restart the timer on the
second call, but then I figured that when the device returns to the
"everything disconnected" state in step 2, it would be better to stop
the timer. The purpose of the timer is to delay the notification of the
device becoming connected, but if the device becomes disconnected during
the waiting period, the notification doesn't make sense any more, and
therefore the timer doesn't make sense either.
BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=100237
2017-03-16 23:48:43 +02:00
|
|
|
if (new_device_appeared) {
|
|
|
|
|
if (n_disconnected_profiles > 0)
|
2016-08-07 18:45:48 +03:00
|
|
|
device_start_waiting_for_profiles(t->device);
|
|
|
|
|
else
|
|
|
|
|
pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], t->device);
|
bluez5-util: fix profile waiting logic
There were two bugs in the old logic. The first one:
If a device has two profiles, the old code would start the wait timer
when the first profile connects, but when the second profile connects,
the timer would not get stopped and the CONNECTION_CHANGED hook would
not get fired, because the code for that was inside an if block that
only gets executed when the first profile connects. As a result,
module-bluez5-device loading would always be delayed until the wait
timeout expires.
The second bug:
A crash was observed in device_start_waiting_for_profiles(). That
function is called whenever the connected profile count changes from 0
to 1. The function also has an assertion that checks that the timer is
not running when the function is called. That assertion crashed in the
following scenario with a headset that supports HSP and A2DP:
1. First HSP gets connected. The timer is started.
2. Then HSP gets disconnected for some reason. The timer is still
running.
3. Then A2DP gets connected. device_start_waiting_for_profiles() is
called, because the connected profile count changed from 0 to 1 again.
The timer is already running, so the assertion fails.
First I thought I'd remove the assertion from
device_start_waiting_for_profiles() and just restart the timer on the
second call, but then I figured that when the device returns to the
"everything disconnected" state in step 2, it would be better to stop
the timer. The purpose of the timer is to delay the notification of the
device becoming connected, but if the device becomes disconnected during
the waiting period, the notification doesn't make sense any more, and
therefore the timer doesn't make sense either.
BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=100237
2017-03-16 23:48:43 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (device_disconnected) {
|
|
|
|
|
if (t->device->wait_for_profiles_timer) {
|
|
|
|
|
/* If the timer is still running when the device disconnects, we
|
|
|
|
|
* never sent the notification of the device getting connected, so
|
|
|
|
|
* we don't need to send a notification about the disconnection
|
|
|
|
|
* either. Let's just stop the timer. */
|
|
|
|
|
device_stop_waiting_for_profiles(t->device);
|
|
|
|
|
} else
|
|
|
|
|
pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], t->device);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (n_disconnected_profiles == 0 && t->device->wait_for_profiles_timer) {
|
|
|
|
|
/* All profiles are now connected, so we can stop the wait timer and
|
|
|
|
|
* send a notification of the new device. */
|
|
|
|
|
device_stop_waiting_for_profiles(t->device);
|
|
|
|
|
pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], t->device);
|
2016-08-07 18:45:48 +03:00
|
|
|
}
|
2013-09-24 19:45:22 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pa_bluetooth_transport_put(pa_bluetooth_transport *t) {
|
2014-10-07 14:50:21 +03:00
|
|
|
pa_assert(t);
|
|
|
|
|
|
|
|
|
|
t->device->transports[t->profile] = t;
|
|
|
|
|
pa_assert_se(pa_hashmap_put(t->device->discovery->transports, t->path, t) >= 0);
|
2014-09-08 12:14:42 +03:00
|
|
|
pa_bluetooth_transport_set_state(t, PA_BLUETOOTH_TRANSPORT_STATE_IDLE);
|
2013-09-24 19:45:22 -03:00
|
|
|
}
|
|
|
|
|
|
2014-09-08 12:14:43 +03:00
|
|
|
void pa_bluetooth_transport_unlink(pa_bluetooth_transport *t) {
|
2014-10-07 14:50:21 +03:00
|
|
|
pa_assert(t);
|
|
|
|
|
|
2014-09-08 12:14:43 +03:00
|
|
|
pa_bluetooth_transport_set_state(t, PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED);
|
2014-10-07 14:50:21 +03:00
|
|
|
pa_hashmap_remove(t->device->discovery->transports, t->path);
|
|
|
|
|
t->device->transports[t->profile] = NULL;
|
2014-09-08 12:14:43 +03:00
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:22 -03:00
|
|
|
void pa_bluetooth_transport_free(pa_bluetooth_transport *t) {
|
|
|
|
|
pa_assert(t);
|
|
|
|
|
|
2014-10-24 09:56:50 +02:00
|
|
|
if (t->destroy)
|
|
|
|
|
t->destroy(t);
|
2014-10-07 14:50:21 +03:00
|
|
|
pa_bluetooth_transport_unlink(t);
|
|
|
|
|
|
2013-09-24 19:45:22 -03:00
|
|
|
pa_xfree(t->owner);
|
|
|
|
|
pa_xfree(t->path);
|
|
|
|
|
pa_xfree(t->config);
|
|
|
|
|
pa_xfree(t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int bluez5_transport_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
|
|
|
|
|
DBusMessage *m, *r;
|
|
|
|
|
DBusError err;
|
|
|
|
|
int ret;
|
|
|
|
|
uint16_t i, o;
|
|
|
|
|
const char *method = optional ? "TryAcquire" : "Acquire";
|
|
|
|
|
|
|
|
|
|
pa_assert(t);
|
|
|
|
|
pa_assert(t->device);
|
|
|
|
|
pa_assert(t->device->discovery);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, BLUEZ_MEDIA_TRANSPORT_INTERFACE, method));
|
|
|
|
|
|
|
|
|
|
dbus_error_init(&err);
|
|
|
|
|
|
|
|
|
|
r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
|
2017-07-03 17:35:08 +03:00
|
|
|
dbus_message_unref(m);
|
|
|
|
|
m = NULL;
|
2013-09-24 19:45:22 -03:00
|
|
|
if (!r) {
|
|
|
|
|
if (optional && pa_streq(err.name, "org.bluez.Error.NotAvailable"))
|
|
|
|
|
pa_log_info("Failed optional acquire of unavailable transport %s", t->path);
|
|
|
|
|
else
|
|
|
|
|
pa_log_error("Transport %s() failed for transport %s (%s)", method, t->path, err.message);
|
|
|
|
|
|
|
|
|
|
dbus_error_free(&err);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_UINT16, &i, DBUS_TYPE_UINT16, &o,
|
|
|
|
|
DBUS_TYPE_INVALID)) {
|
|
|
|
|
pa_log_error("Failed to parse %s() reply: %s", method, err.message);
|
|
|
|
|
dbus_error_free(&err);
|
|
|
|
|
ret = -1;
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (imtu)
|
|
|
|
|
*imtu = i;
|
|
|
|
|
|
|
|
|
|
if (omtu)
|
|
|
|
|
*omtu = o;
|
|
|
|
|
|
|
|
|
|
finish:
|
|
|
|
|
dbus_message_unref(r);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void bluez5_transport_release_cb(pa_bluetooth_transport *t) {
|
2017-07-03 17:35:08 +03:00
|
|
|
DBusMessage *m, *r;
|
2013-09-24 19:45:22 -03:00
|
|
|
DBusError err;
|
|
|
|
|
|
|
|
|
|
pa_assert(t);
|
|
|
|
|
pa_assert(t->device);
|
|
|
|
|
pa_assert(t->device->discovery);
|
|
|
|
|
|
|
|
|
|
dbus_error_init(&err);
|
|
|
|
|
|
|
|
|
|
if (t->state <= PA_BLUETOOTH_TRANSPORT_STATE_IDLE) {
|
|
|
|
|
pa_log_info("Transport %s auto-released by BlueZ or already released", t->path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, BLUEZ_MEDIA_TRANSPORT_INTERFACE, "Release"));
|
2017-07-03 17:35:08 +03:00
|
|
|
r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
|
|
|
|
|
dbus_message_unref(m);
|
|
|
|
|
m = NULL;
|
|
|
|
|
if (r) {
|
|
|
|
|
dbus_message_unref(r);
|
|
|
|
|
r = NULL;
|
|
|
|
|
}
|
2013-09-24 19:45:22 -03:00
|
|
|
|
|
|
|
|
if (dbus_error_is_set(&err)) {
|
|
|
|
|
pa_log_error("Failed to release transport %s: %s", t->path, err.message);
|
|
|
|
|
dbus_error_free(&err);
|
|
|
|
|
} else
|
|
|
|
|
pa_log_info("Transport %s released", t->path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
|
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
|
|
pa_assert(d);
|
|
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
if (!d->valid)
|
2013-09-24 19:45:22 -03:00
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++)
|
|
|
|
|
if (d->transports[i] && d->transports[i]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:38 -03:00
|
|
|
static int transport_state_from_string(const char* value, pa_bluetooth_transport_state_t *state) {
|
|
|
|
|
pa_assert(value);
|
|
|
|
|
pa_assert(state);
|
|
|
|
|
|
|
|
|
|
if (pa_streq(value, "idle"))
|
|
|
|
|
*state = PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
|
|
|
|
|
else if (pa_streq(value, "pending") || pa_streq(value, "active"))
|
|
|
|
|
*state = PA_BLUETOOTH_TRANSPORT_STATE_PLAYING;
|
|
|
|
|
else
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter *i) {
|
|
|
|
|
const char *key;
|
|
|
|
|
DBusMessageIter variant_i;
|
|
|
|
|
|
|
|
|
|
key = check_variant_property(i);
|
|
|
|
|
if (key == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(i, &variant_i);
|
|
|
|
|
|
|
|
|
|
switch (dbus_message_iter_get_arg_type(&variant_i)) {
|
|
|
|
|
|
|
|
|
|
case DBUS_TYPE_STRING: {
|
|
|
|
|
|
|
|
|
|
const char *value;
|
|
|
|
|
dbus_message_iter_get_basic(&variant_i, &value);
|
|
|
|
|
|
|
|
|
|
if (pa_streq(key, "State")) {
|
|
|
|
|
pa_bluetooth_transport_state_t state;
|
|
|
|
|
|
|
|
|
|
if (transport_state_from_string(value, &state) < 0) {
|
|
|
|
|
pa_log_error("Invalid state received: %s", value);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-08 12:14:42 +03:00
|
|
|
pa_bluetooth_transport_set_state(t, state);
|
2013-09-24 19:45:38 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int parse_transport_properties(pa_bluetooth_transport *t, DBusMessageIter *i) {
|
|
|
|
|
DBusMessageIter element_i;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(i, &element_i);
|
|
|
|
|
|
|
|
|
|
while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
|
|
|
|
|
DBusMessageIter dict_i;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&element_i, &dict_i);
|
|
|
|
|
|
|
|
|
|
parse_transport_property(t, &dict_i);
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_next(&element_i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-18 16:16:58 -05:00
|
|
|
static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char *path) {
|
|
|
|
|
pa_bluetooth_device *d;
|
|
|
|
|
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
pa_assert(path);
|
|
|
|
|
|
|
|
|
|
d = pa_xnew0(pa_bluetooth_device, 1);
|
|
|
|
|
d->discovery = y;
|
|
|
|
|
d->path = pa_xstrdup(path);
|
2013-09-24 19:45:35 -03:00
|
|
|
d->uuids = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
|
2013-09-18 16:16:58 -05:00
|
|
|
|
|
|
|
|
pa_hashmap_put(y->devices, d->path, d);
|
|
|
|
|
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_path(pa_bluetooth_discovery *y, const char *path) {
|
|
|
|
|
pa_bluetooth_device *d;
|
|
|
|
|
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
pa_assert(PA_REFCNT_VALUE(y) > 0);
|
|
|
|
|
pa_assert(path);
|
|
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
if ((d = pa_hashmap_get(y->devices, path)) && d->valid)
|
|
|
|
|
return d;
|
2013-09-18 16:16:58 -05:00
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_discovery *y, const char *remote, const char *local) {
|
|
|
|
|
pa_bluetooth_device *d;
|
|
|
|
|
void *state = NULL;
|
|
|
|
|
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
pa_assert(PA_REFCNT_VALUE(y) > 0);
|
|
|
|
|
pa_assert(remote);
|
|
|
|
|
pa_assert(local);
|
|
|
|
|
|
|
|
|
|
while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
|
2014-05-24 12:56:35 +03:00
|
|
|
if (d->valid && pa_streq(d->address, remote) && pa_streq(d->adapter->address, local))
|
2013-11-20 18:21:36 -03:00
|
|
|
return d;
|
2013-09-18 16:16:58 -05:00
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void device_free(pa_bluetooth_device *d) {
|
2013-09-24 19:45:22 -03:00
|
|
|
unsigned i;
|
|
|
|
|
|
2013-09-18 16:16:58 -05:00
|
|
|
pa_assert(d);
|
|
|
|
|
|
2016-08-07 18:45:48 +03:00
|
|
|
device_stop_waiting_for_profiles(d);
|
|
|
|
|
|
2019-06-19 11:09:11 +02:00
|
|
|
pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_UNLINK], d);
|
|
|
|
|
|
2013-09-24 19:45:22 -03:00
|
|
|
for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) {
|
|
|
|
|
pa_bluetooth_transport *t;
|
|
|
|
|
|
|
|
|
|
if (!(t = d->transports[i]))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
pa_bluetooth_transport_free(t);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:35 -03:00
|
|
|
if (d->uuids)
|
|
|
|
|
pa_hashmap_free(d->uuids);
|
|
|
|
|
|
2013-09-18 16:16:58 -05:00
|
|
|
pa_xfree(d->path);
|
|
|
|
|
pa_xfree(d->alias);
|
|
|
|
|
pa_xfree(d->address);
|
2013-12-24 22:35:40 +01:00
|
|
|
pa_xfree(d->adapter_path);
|
2013-09-18 16:16:58 -05:00
|
|
|
pa_xfree(d);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void device_remove(pa_bluetooth_discovery *y, const char *path) {
|
|
|
|
|
pa_bluetooth_device *d;
|
|
|
|
|
|
|
|
|
|
if (!(d = pa_hashmap_remove(y->devices, path)))
|
|
|
|
|
pa_log_warn("Unknown device removed %s", path);
|
|
|
|
|
else {
|
|
|
|
|
pa_log_debug("Device %s removed", path);
|
|
|
|
|
device_free(d);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
static void device_set_valid(pa_bluetooth_device *device, bool valid) {
|
2013-11-15 16:29:39 +02:00
|
|
|
bool old_any_connected;
|
|
|
|
|
|
2013-11-15 16:29:37 +02:00
|
|
|
pa_assert(device);
|
|
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
if (valid == device->valid)
|
2013-11-15 16:29:37 +02:00
|
|
|
return;
|
|
|
|
|
|
2013-11-15 16:29:39 +02:00
|
|
|
old_any_connected = pa_bluetooth_device_any_transport_connected(device);
|
2014-05-24 12:56:35 +03:00
|
|
|
device->valid = valid;
|
2013-11-15 16:29:39 +02:00
|
|
|
|
|
|
|
|
if (pa_bluetooth_device_any_transport_connected(device) != old_any_connected)
|
|
|
|
|
pa_hook_fire(&device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], device);
|
2013-11-15 16:29:37 +02:00
|
|
|
}
|
|
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
static void device_update_valid(pa_bluetooth_device *d) {
|
|
|
|
|
pa_assert(d);
|
|
|
|
|
|
|
|
|
|
if (!d->properties_received) {
|
|
|
|
|
pa_assert(!d->valid);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check if mandatory properties are set. */
|
|
|
|
|
if (!d->address || !d->adapter_path || !d->alias) {
|
|
|
|
|
device_set_valid(d, false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!d->adapter || !d->adapter->valid) {
|
|
|
|
|
device_set_valid(d, false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
device_set_valid(d, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void device_set_adapter(pa_bluetooth_device *device, pa_bluetooth_adapter *adapter) {
|
|
|
|
|
pa_assert(device);
|
|
|
|
|
|
|
|
|
|
if (adapter == device->adapter)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
device->adapter = adapter;
|
|
|
|
|
|
|
|
|
|
device_update_valid(device);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-18 16:16:57 -05:00
|
|
|
static pa_bluetooth_adapter* adapter_create(pa_bluetooth_discovery *y, const char *path) {
|
|
|
|
|
pa_bluetooth_adapter *a;
|
|
|
|
|
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
pa_assert(path);
|
|
|
|
|
|
|
|
|
|
a = pa_xnew0(pa_bluetooth_adapter, 1);
|
|
|
|
|
a->discovery = y;
|
|
|
|
|
a->path = pa_xstrdup(path);
|
|
|
|
|
|
|
|
|
|
pa_hashmap_put(y->adapters, a->path, a);
|
|
|
|
|
|
|
|
|
|
return a;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:29 -03:00
|
|
|
static void adapter_free(pa_bluetooth_adapter *a) {
|
|
|
|
|
pa_bluetooth_device *d;
|
|
|
|
|
void *state;
|
|
|
|
|
|
|
|
|
|
pa_assert(a);
|
|
|
|
|
pa_assert(a->discovery);
|
|
|
|
|
|
|
|
|
|
PA_HASHMAP_FOREACH(d, a->discovery->devices, state)
|
2014-05-24 12:56:35 +03:00
|
|
|
if (d->adapter == a)
|
|
|
|
|
device_set_adapter(d, NULL);
|
2013-09-24 19:45:29 -03:00
|
|
|
|
|
|
|
|
pa_xfree(a->path);
|
|
|
|
|
pa_xfree(a->address);
|
|
|
|
|
pa_xfree(a);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void adapter_remove(pa_bluetooth_discovery *y, const char *path) {
|
|
|
|
|
pa_bluetooth_adapter *a;
|
|
|
|
|
|
|
|
|
|
if (!(a = pa_hashmap_remove(y->adapters, path)))
|
|
|
|
|
pa_log_warn("Unknown adapter removed %s", path);
|
|
|
|
|
else {
|
|
|
|
|
pa_log_debug("Adapter %s removed", path);
|
|
|
|
|
adapter_free(a);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
static void parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i) {
|
2013-09-24 19:45:35 -03:00
|
|
|
const char *key;
|
|
|
|
|
DBusMessageIter variant_i;
|
|
|
|
|
|
|
|
|
|
pa_assert(d);
|
|
|
|
|
|
|
|
|
|
key = check_variant_property(i);
|
|
|
|
|
if (key == NULL) {
|
|
|
|
|
pa_log_error("Received invalid property for device %s", d->path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(i, &variant_i);
|
|
|
|
|
|
|
|
|
|
switch (dbus_message_iter_get_arg_type(&variant_i)) {
|
|
|
|
|
|
|
|
|
|
case DBUS_TYPE_STRING: {
|
|
|
|
|
const char *value;
|
|
|
|
|
dbus_message_iter_get_basic(&variant_i, &value);
|
|
|
|
|
|
|
|
|
|
if (pa_streq(key, "Alias")) {
|
|
|
|
|
pa_xfree(d->alias);
|
|
|
|
|
d->alias = pa_xstrdup(value);
|
|
|
|
|
pa_log_debug("%s: %s", key, value);
|
|
|
|
|
} else if (pa_streq(key, "Address")) {
|
2014-05-24 12:56:35 +03:00
|
|
|
if (d->properties_received) {
|
2013-09-24 19:45:35 -03:00
|
|
|
pa_log_warn("Device property 'Address' expected to be constant but changed for %s, ignoring", d->path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (d->address) {
|
|
|
|
|
pa_log_warn("Device %s: Received a duplicate 'Address' property, ignoring", d->path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d->address = pa_xstrdup(value);
|
|
|
|
|
pa_log_debug("%s: %s", key, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case DBUS_TYPE_OBJECT_PATH: {
|
|
|
|
|
const char *value;
|
|
|
|
|
dbus_message_iter_get_basic(&variant_i, &value);
|
|
|
|
|
|
|
|
|
|
if (pa_streq(key, "Adapter")) {
|
|
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
if (d->properties_received) {
|
2013-09-24 19:45:35 -03:00
|
|
|
pa_log_warn("Device property 'Adapter' expected to be constant but changed for %s, ignoring", d->path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:36 -03:00
|
|
|
if (d->adapter_path) {
|
2013-09-24 19:45:35 -03:00
|
|
|
pa_log_warn("Device %s: Received a duplicate 'Adapter' property, ignoring", d->path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:36 -03:00
|
|
|
d->adapter_path = pa_xstrdup(value);
|
2013-09-24 19:45:35 -03:00
|
|
|
pa_log_debug("%s: %s", key, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case DBUS_TYPE_UINT32: {
|
|
|
|
|
uint32_t value;
|
|
|
|
|
dbus_message_iter_get_basic(&variant_i, &value);
|
|
|
|
|
|
|
|
|
|
if (pa_streq(key, "Class")) {
|
|
|
|
|
d->class_of_device = value;
|
|
|
|
|
pa_log_debug("%s: %d", key, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case DBUS_TYPE_ARRAY: {
|
|
|
|
|
DBusMessageIter ai;
|
|
|
|
|
dbus_message_iter_recurse(&variant_i, &ai);
|
|
|
|
|
|
|
|
|
|
if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING && pa_streq(key, "UUIDs")) {
|
2016-08-07 18:45:52 +03:00
|
|
|
/* bluetoothd never removes UUIDs from a device object so we
|
|
|
|
|
* don't need to check for disappeared UUIDs here. */
|
2013-09-24 19:45:35 -03:00
|
|
|
while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
|
|
|
|
|
const char *value;
|
|
|
|
|
char *uuid;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(&ai, &value);
|
|
|
|
|
|
|
|
|
|
if (pa_hashmap_get(d->uuids, value)) {
|
|
|
|
|
dbus_message_iter_next(&ai);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uuid = pa_xstrdup(value);
|
|
|
|
|
pa_hashmap_put(d->uuids, uuid, uuid);
|
|
|
|
|
|
|
|
|
|
pa_log_debug("%s: %s", key, value);
|
|
|
|
|
dbus_message_iter_next(&ai);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
static void parse_device_properties(pa_bluetooth_device *d, DBusMessageIter *i) {
|
2013-09-24 19:45:35 -03:00
|
|
|
DBusMessageIter element_i;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(i, &element_i);
|
|
|
|
|
|
|
|
|
|
while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
|
|
|
|
|
DBusMessageIter dict_i;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&element_i, &dict_i);
|
2014-05-24 12:56:35 +03:00
|
|
|
parse_device_property(d, &dict_i);
|
2013-09-24 19:45:35 -03:00
|
|
|
dbus_message_iter_next(&element_i);
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
if (!d->properties_received) {
|
|
|
|
|
d->properties_received = true;
|
2016-08-07 18:45:51 +03:00
|
|
|
device_update_valid(d);
|
2013-11-15 16:29:38 +02:00
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
if (!d->address || !d->adapter_path || !d->alias)
|
|
|
|
|
pa_log_error("Non-optional information missing for device %s", d->path);
|
|
|
|
|
}
|
2013-09-24 19:45:35 -03:00
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:32 -03:00
|
|
|
static void parse_adapter_properties(pa_bluetooth_adapter *a, DBusMessageIter *i, bool is_property_change) {
|
|
|
|
|
DBusMessageIter element_i;
|
|
|
|
|
|
|
|
|
|
pa_assert(a);
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(i, &element_i);
|
|
|
|
|
|
|
|
|
|
while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
|
|
|
|
|
DBusMessageIter dict_i, variant_i;
|
|
|
|
|
const char *key;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&element_i, &dict_i);
|
|
|
|
|
|
|
|
|
|
key = check_variant_property(&dict_i);
|
|
|
|
|
if (key == NULL) {
|
|
|
|
|
pa_log_error("Received invalid property for adapter %s", a->path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&dict_i, &variant_i);
|
|
|
|
|
|
|
|
|
|
if (dbus_message_iter_get_arg_type(&variant_i) == DBUS_TYPE_STRING && pa_streq(key, "Address")) {
|
2013-09-29 18:49:23 +03:00
|
|
|
const char *value;
|
2013-09-24 19:45:32 -03:00
|
|
|
|
|
|
|
|
if (is_property_change) {
|
|
|
|
|
pa_log_warn("Adapter property 'Address' expected to be constant but changed for %s, ignoring", a->path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (a->address) {
|
|
|
|
|
pa_log_warn("Adapter %s received a duplicate 'Address' property, ignoring", a->path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(&variant_i, &value);
|
|
|
|
|
a->address = pa_xstrdup(value);
|
2014-05-24 12:56:34 +03:00
|
|
|
a->valid = true;
|
2013-09-24 19:45:32 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_next(&element_i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-15 16:51:11 +05:30
|
|
|
static void register_legacy_sbc_endpoint_reply(DBusPendingCall *pending, void *userdata) {
|
2013-09-24 19:45:33 -03:00
|
|
|
DBusMessage *r;
|
|
|
|
|
pa_dbus_pending *p;
|
|
|
|
|
pa_bluetooth_discovery *y;
|
|
|
|
|
char *endpoint;
|
|
|
|
|
|
|
|
|
|
pa_assert(pending);
|
|
|
|
|
pa_assert_se(p = userdata);
|
|
|
|
|
pa_assert_se(y = p->context_data);
|
|
|
|
|
pa_assert_se(endpoint = p->call_data);
|
|
|
|
|
pa_assert_se(r = dbus_pending_call_steal_reply(pending));
|
|
|
|
|
|
|
|
|
|
if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) {
|
|
|
|
|
pa_log_info("Couldn't register endpoint %s because it is disabled in BlueZ", endpoint);
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
|
|
|
|
|
pa_log_error(BLUEZ_MEDIA_INTERFACE ".RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r),
|
|
|
|
|
pa_dbus_get_error_message(r));
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
finish:
|
|
|
|
|
dbus_message_unref(r);
|
|
|
|
|
|
|
|
|
|
PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
|
|
|
|
|
pa_dbus_pending_free(p);
|
|
|
|
|
|
|
|
|
|
pa_xfree(endpoint);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-15 16:51:11 +05:30
|
|
|
static void register_legacy_sbc_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_codec *a2dp_codec, const char *path, const char *endpoint, const char *uuid) {
|
2013-09-24 19:45:33 -03:00
|
|
|
DBusMessage *m;
|
|
|
|
|
DBusMessageIter i, d;
|
2019-04-06 11:15:58 +02:00
|
|
|
uint8_t capabilities[MAX_A2DP_CAPS_SIZE];
|
|
|
|
|
size_t capabilities_size;
|
|
|
|
|
uint8_t codec_id;
|
2013-09-24 19:45:33 -03:00
|
|
|
|
|
|
|
|
pa_log_debug("Registering %s on adapter %s", endpoint, path);
|
|
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
codec_id = a2dp_codec->id.codec_id;
|
|
|
|
|
capabilities_size = a2dp_codec->fill_capabilities(capabilities);
|
|
|
|
|
pa_assert(capabilities_size != 0);
|
|
|
|
|
|
2013-09-24 19:45:33 -03:00
|
|
|
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, path, BLUEZ_MEDIA_INTERFACE, "RegisterEndpoint"));
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_init_append(m, &i);
|
2016-10-29 21:06:53 +03:00
|
|
|
pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint));
|
2013-09-24 19:45:33 -03:00
|
|
|
dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
|
|
|
|
|
DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &d);
|
|
|
|
|
pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid);
|
2019-04-06 11:15:58 +02:00
|
|
|
pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec_id);
|
|
|
|
|
pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, capabilities_size);
|
2013-09-24 19:45:33 -03:00
|
|
|
|
|
|
|
|
dbus_message_iter_close_container(&i, &d);
|
|
|
|
|
|
2020-12-15 16:51:11 +05:30
|
|
|
send_and_add_to_pending(y, m, register_legacy_sbc_endpoint_reply, pa_xstrdup(endpoint));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void register_application_reply(DBusPendingCall *pending, void *userdata) {
|
|
|
|
|
DBusMessage *r;
|
|
|
|
|
pa_dbus_pending *p;
|
|
|
|
|
pa_bluetooth_adapter *a;
|
|
|
|
|
pa_bluetooth_discovery *y;
|
|
|
|
|
char *path;
|
|
|
|
|
bool fallback = true;
|
|
|
|
|
|
|
|
|
|
pa_assert(pending);
|
|
|
|
|
pa_assert_se(p = userdata);
|
|
|
|
|
pa_assert_se(y = p->context_data);
|
|
|
|
|
pa_assert_se(path = p->call_data);
|
|
|
|
|
pa_assert_se(r = dbus_pending_call_steal_reply(pending));
|
|
|
|
|
|
|
|
|
|
if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) {
|
|
|
|
|
pa_log_info("Couldn't register media application for adapter %s because it is disabled in BlueZ", path);
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
|
|
|
|
|
pa_log_warn(BLUEZ_MEDIA_INTERFACE ".RegisterApplication() failed: %s: %s",
|
|
|
|
|
dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
|
|
|
|
|
pa_log_warn("Couldn't register media application for adapter %s", path);
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a = pa_hashmap_get(y->adapters, path);
|
|
|
|
|
if (!a) {
|
|
|
|
|
pa_log_error("Couldn't register media application for adapter %s because it does not exist anymore", path);
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fallback = false;
|
|
|
|
|
a->application_registered = true;
|
|
|
|
|
pa_log_debug("Media application for adapter %s was successfully registered", path);
|
|
|
|
|
|
|
|
|
|
finish:
|
|
|
|
|
dbus_message_unref(r);
|
|
|
|
|
|
|
|
|
|
PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
|
|
|
|
|
pa_dbus_pending_free(p);
|
|
|
|
|
|
|
|
|
|
if (fallback) {
|
|
|
|
|
/* If bluez does not support RegisterApplication, fallback to old legacy API with just one SBC codec */
|
|
|
|
|
const pa_a2dp_codec *a2dp_codec_sbc;
|
|
|
|
|
a2dp_codec_sbc = pa_bluetooth_get_a2dp_codec("sbc");
|
|
|
|
|
pa_assert(a2dp_codec_sbc);
|
|
|
|
|
register_legacy_sbc_endpoint(y, a2dp_codec_sbc, path, A2DP_SINK_ENDPOINT "/sbc",
|
|
|
|
|
PA_BLUETOOTH_UUID_A2DP_SINK);
|
|
|
|
|
register_legacy_sbc_endpoint(y, a2dp_codec_sbc, path, A2DP_SOURCE_ENDPOINT "/sbc",
|
|
|
|
|
PA_BLUETOOTH_UUID_A2DP_SOURCE);
|
|
|
|
|
pa_log_warn("Only SBC codec is available for A2DP profiles");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_xfree(path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void register_application(pa_bluetooth_adapter *a) {
|
|
|
|
|
DBusMessage *m;
|
|
|
|
|
DBusMessageIter i, d;
|
|
|
|
|
const char *object_manager_path = A2DP_OBJECT_MANAGER_PATH;
|
|
|
|
|
|
|
|
|
|
if (a->application_registered) {
|
|
|
|
|
pa_log_info("Media application is already registered for adapter %s", a->path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_log_debug("Registering media application for adapter %s", a->path);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, a->path,
|
|
|
|
|
BLUEZ_MEDIA_INTERFACE, "RegisterApplication"));
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_init_append(m, &i);
|
|
|
|
|
pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &object_manager_path));
|
|
|
|
|
dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY,
|
|
|
|
|
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
|
|
|
|
|
DBUS_TYPE_STRING_AS_STRING
|
|
|
|
|
DBUS_TYPE_VARIANT_AS_STRING
|
|
|
|
|
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
|
|
|
|
|
&d);
|
|
|
|
|
dbus_message_iter_close_container(&i, &d);
|
|
|
|
|
|
|
|
|
|
send_and_add_to_pending(a->discovery, m, register_application_reply, pa_xstrdup(a->path));
|
2013-09-24 19:45:33 -03:00
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:31 -03:00
|
|
|
static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
|
|
|
|
|
DBusMessageIter element_i;
|
|
|
|
|
const char *path;
|
2013-09-24 19:45:36 -03:00
|
|
|
void *state;
|
2013-09-24 19:45:35 -03:00
|
|
|
pa_bluetooth_device *d;
|
2013-09-24 19:45:31 -03:00
|
|
|
|
|
|
|
|
pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_OBJECT_PATH);
|
|
|
|
|
dbus_message_iter_get_basic(dict_i, &path);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_next(dict_i));
|
|
|
|
|
pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_ARRAY);
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(dict_i, &element_i);
|
|
|
|
|
|
|
|
|
|
while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
|
|
|
|
|
DBusMessageIter iface_i;
|
|
|
|
|
const char *interface;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&element_i, &iface_i);
|
|
|
|
|
|
|
|
|
|
pa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_STRING);
|
|
|
|
|
dbus_message_iter_get_basic(&iface_i, &interface);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_next(&iface_i));
|
|
|
|
|
pa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY);
|
|
|
|
|
|
|
|
|
|
if (pa_streq(interface, BLUEZ_ADAPTER_INTERFACE)) {
|
|
|
|
|
pa_bluetooth_adapter *a;
|
|
|
|
|
|
|
|
|
|
if ((a = pa_hashmap_get(y->adapters, path))) {
|
2014-05-03 14:21:31 +03:00
|
|
|
pa_log_error("Found duplicated D-Bus path for adapter %s", path);
|
2013-09-24 19:45:31 -03:00
|
|
|
return;
|
|
|
|
|
} else
|
|
|
|
|
a = adapter_create(y, path);
|
|
|
|
|
|
|
|
|
|
pa_log_debug("Adapter %s found", path);
|
|
|
|
|
|
2013-09-24 19:45:32 -03:00
|
|
|
parse_adapter_properties(a, &iface_i, false);
|
2014-05-24 12:56:35 +03:00
|
|
|
|
2014-05-24 12:56:34 +03:00
|
|
|
if (!a->valid)
|
2013-09-24 19:45:32 -03:00
|
|
|
return;
|
|
|
|
|
|
2020-12-15 16:51:11 +05:30
|
|
|
register_application(a);
|
2013-09-24 19:45:31 -03:00
|
|
|
} else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
|
|
|
|
|
|
|
|
|
|
if ((d = pa_hashmap_get(y->devices, path))) {
|
2014-05-24 12:56:35 +03:00
|
|
|
if (d->properties_received) {
|
2013-09-24 19:45:31 -03:00
|
|
|
pa_log_error("Found duplicated D-Bus path for device %s", path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else
|
|
|
|
|
d = device_create(y, path);
|
|
|
|
|
|
|
|
|
|
pa_log_debug("Device %s found", d->path);
|
|
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
parse_device_properties(d, &iface_i);
|
2013-09-24 19:45:31 -03:00
|
|
|
} else
|
|
|
|
|
pa_log_debug("Unknown interface %s found, skipping", interface);
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_next(&element_i);
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-15 16:29:38 +02:00
|
|
|
PA_HASHMAP_FOREACH(d, y->devices, state) {
|
2014-05-24 12:56:35 +03:00
|
|
|
if (d->properties_received && !d->tried_to_link_with_adapter) {
|
|
|
|
|
if (d->adapter_path) {
|
|
|
|
|
device_set_adapter(d, pa_hashmap_get(d->discovery->adapters, d->adapter_path));
|
2013-11-15 16:29:38 +02:00
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
if (!d->adapter)
|
|
|
|
|
pa_log("Device %s points to a nonexistent adapter %s.", d->path, d->adapter_path);
|
|
|
|
|
else if (!d->adapter->valid)
|
|
|
|
|
pa_log("Device %s points to an invalid adapter %s.", d->path, d->adapter_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d->tried_to_link_with_adapter = true;
|
2013-09-24 19:45:36 -03:00
|
|
|
}
|
2013-11-15 16:29:38 +02:00
|
|
|
}
|
2013-09-24 19:45:36 -03:00
|
|
|
|
2013-09-24 19:45:31 -03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-03 12:13:20 +01:00
|
|
|
void pa_bluetooth_discovery_set_ofono_running(pa_bluetooth_discovery *y, bool is_running) {
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
|
|
|
|
|
pa_log_debug("oFono is running: %s", pa_yes_no(is_running));
|
2014-11-10 16:13:13 +01:00
|
|
|
if (y->headset_backend != HEADSET_BACKEND_AUTO)
|
|
|
|
|
return;
|
|
|
|
|
|
2017-03-14 07:32:31 +01:00
|
|
|
/* If ofono starts running, all devices that might be connected to the HS role
|
|
|
|
|
* need to be disconnected, so that the devices can be handled by ofono */
|
|
|
|
|
if (is_running) {
|
|
|
|
|
void *state;
|
|
|
|
|
pa_bluetooth_device *d;
|
|
|
|
|
|
|
|
|
|
PA_HASHMAP_FOREACH(d, y->devices, state) {
|
|
|
|
|
if (device_supports_profile(d, PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)) {
|
|
|
|
|
DBusMessage *m;
|
|
|
|
|
|
|
|
|
|
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, d->path, "org.bluez.Device1", "Disconnect"));
|
|
|
|
|
dbus_message_set_no_reply(m, true);
|
2017-09-06 09:56:10 +02:00
|
|
|
pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), m, NULL));
|
2017-03-14 07:32:31 +01:00
|
|
|
dbus_message_unref(m);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-11 19:02:16 +01:00
|
|
|
pa_bluetooth_native_backend_enable_hs_role(y->native_backend, !is_running);
|
2014-11-03 12:13:20 +01:00
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:28 -03:00
|
|
|
static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata) {
|
|
|
|
|
pa_dbus_pending *p;
|
|
|
|
|
pa_bluetooth_discovery *y;
|
|
|
|
|
DBusMessage *r;
|
|
|
|
|
DBusMessageIter arg_i, element_i;
|
|
|
|
|
|
|
|
|
|
pa_assert_se(p = userdata);
|
|
|
|
|
pa_assert_se(y = p->context_data);
|
|
|
|
|
pa_assert_se(r = dbus_pending_call_steal_reply(pending));
|
|
|
|
|
|
|
|
|
|
if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
|
|
|
|
|
pa_log_warn("BlueZ D-Bus ObjectManager not available");
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
|
|
|
|
|
pa_log_error("GetManagedObjects() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{oa{sa{sv}}}")) {
|
|
|
|
|
pa_log_error("Invalid reply signature for GetManagedObjects()");
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&arg_i, &element_i);
|
|
|
|
|
while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
|
|
|
|
|
DBusMessageIter dict_i;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&element_i, &dict_i);
|
|
|
|
|
|
2013-09-24 19:45:31 -03:00
|
|
|
parse_interfaces_and_properties(y, &dict_i);
|
2013-09-24 19:45:28 -03:00
|
|
|
|
|
|
|
|
dbus_message_iter_next(&element_i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
y->objects_listed = true;
|
|
|
|
|
|
2017-03-11 19:02:16 +01:00
|
|
|
if (!y->native_backend && y->headset_backend != HEADSET_BACKEND_OFONO)
|
|
|
|
|
y->native_backend = pa_bluetooth_native_backend_new(y->core, y, (y->headset_backend == HEADSET_BACKEND_NATIVE));
|
2014-11-10 16:13:13 +01:00
|
|
|
if (!y->ofono_backend && y->headset_backend != HEADSET_BACKEND_NATIVE)
|
2014-11-03 11:01:00 +01:00
|
|
|
y->ofono_backend = pa_bluetooth_ofono_backend_new(y->core, y);
|
2014-09-08 13:35:33 +03:00
|
|
|
|
2013-09-24 19:45:28 -03:00
|
|
|
finish:
|
|
|
|
|
dbus_message_unref(r);
|
|
|
|
|
|
|
|
|
|
PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
|
|
|
|
|
pa_dbus_pending_free(p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void get_managed_objects(pa_bluetooth_discovery *y) {
|
|
|
|
|
DBusMessage *m;
|
|
|
|
|
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, "/", "org.freedesktop.DBus.ObjectManager",
|
|
|
|
|
"GetManagedObjects"));
|
|
|
|
|
send_and_add_to_pending(y, m, get_managed_objects_reply, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-18 16:16:56 -05:00
|
|
|
pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) {
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
pa_assert(PA_REFCNT_VALUE(y) > 0);
|
|
|
|
|
|
|
|
|
|
return &y->hooks[hook];
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-18 16:16:55 -05:00
|
|
|
static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
|
|
|
|
|
pa_bluetooth_discovery *y;
|
|
|
|
|
DBusError err;
|
|
|
|
|
|
|
|
|
|
pa_assert(bus);
|
|
|
|
|
pa_assert(m);
|
|
|
|
|
pa_assert_se(y = userdata);
|
|
|
|
|
|
|
|
|
|
dbus_error_init(&err);
|
|
|
|
|
|
|
|
|
|
if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
|
|
|
|
|
const char *name, *old_owner, *new_owner;
|
|
|
|
|
|
|
|
|
|
if (!dbus_message_get_args(m, &err,
|
|
|
|
|
DBUS_TYPE_STRING, &name,
|
|
|
|
|
DBUS_TYPE_STRING, &old_owner,
|
|
|
|
|
DBUS_TYPE_STRING, &new_owner,
|
|
|
|
|
DBUS_TYPE_INVALID)) {
|
|
|
|
|
pa_log_error("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pa_streq(name, BLUEZ_SERVICE)) {
|
|
|
|
|
if (old_owner && *old_owner) {
|
|
|
|
|
pa_log_debug("Bluetooth daemon disappeared");
|
2013-11-15 16:29:40 +02:00
|
|
|
pa_hashmap_remove_all(y->devices);
|
2013-09-29 18:49:22 +03:00
|
|
|
pa_hashmap_remove_all(y->adapters);
|
2013-09-24 19:45:28 -03:00
|
|
|
y->objects_listed = false;
|
2014-11-03 11:01:00 +01:00
|
|
|
if (y->ofono_backend) {
|
|
|
|
|
pa_bluetooth_ofono_backend_free(y->ofono_backend);
|
|
|
|
|
y->ofono_backend = NULL;
|
|
|
|
|
}
|
|
|
|
|
if (y->native_backend) {
|
|
|
|
|
pa_bluetooth_native_backend_free(y->native_backend);
|
|
|
|
|
y->native_backend = NULL;
|
2014-09-08 13:35:33 +03:00
|
|
|
}
|
2013-09-18 16:16:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (new_owner && *new_owner) {
|
|
|
|
|
pa_log_debug("Bluetooth daemon appeared");
|
2013-09-24 19:45:28 -03:00
|
|
|
get_managed_objects(y);
|
2013-09-18 16:16:55 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
2013-09-24 19:45:30 -03:00
|
|
|
} else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")) {
|
|
|
|
|
DBusMessageIter arg_i;
|
|
|
|
|
|
|
|
|
|
if (!y->objects_listed)
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
|
|
|
|
|
|
|
|
|
|
if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oa{sa{sv}}")) {
|
|
|
|
|
pa_log_error("Invalid signature found in InterfacesAdded");
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:31 -03:00
|
|
|
parse_interfaces_and_properties(y, &arg_i);
|
2013-09-24 19:45:30 -03:00
|
|
|
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
|
} else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")) {
|
|
|
|
|
const char *p;
|
|
|
|
|
DBusMessageIter arg_i;
|
|
|
|
|
DBusMessageIter element_i;
|
|
|
|
|
|
|
|
|
|
if (!y->objects_listed)
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
|
|
|
|
|
|
|
|
|
|
if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oas")) {
|
|
|
|
|
pa_log_error("Invalid signature found in InterfacesRemoved");
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(&arg_i, &p);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_next(&arg_i));
|
|
|
|
|
pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&arg_i, &element_i);
|
|
|
|
|
|
|
|
|
|
while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) {
|
|
|
|
|
const char *iface;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(&element_i, &iface);
|
|
|
|
|
|
|
|
|
|
if (pa_streq(iface, BLUEZ_DEVICE_INTERFACE))
|
|
|
|
|
device_remove(y, p);
|
|
|
|
|
else if (pa_streq(iface, BLUEZ_ADAPTER_INTERFACE))
|
|
|
|
|
adapter_remove(y, p);
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_next(&element_i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
|
|
2013-09-24 19:45:34 -03:00
|
|
|
} else if (dbus_message_is_signal(m, "org.freedesktop.DBus.Properties", "PropertiesChanged")) {
|
|
|
|
|
DBusMessageIter arg_i;
|
|
|
|
|
const char *iface;
|
|
|
|
|
|
|
|
|
|
if (!y->objects_listed)
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* No reply received yet from GetManagedObjects */
|
|
|
|
|
|
|
|
|
|
if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "sa{sv}as")) {
|
|
|
|
|
pa_log_error("Invalid signature found in PropertiesChanged");
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(&arg_i, &iface);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_next(&arg_i));
|
|
|
|
|
pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
|
|
|
|
|
|
|
|
|
|
if (pa_streq(iface, BLUEZ_ADAPTER_INTERFACE)) {
|
|
|
|
|
pa_bluetooth_adapter *a;
|
|
|
|
|
|
|
|
|
|
pa_log_debug("Properties changed in adapter %s", dbus_message_get_path(m));
|
|
|
|
|
|
|
|
|
|
if (!(a = pa_hashmap_get(y->adapters, dbus_message_get_path(m)))) {
|
|
|
|
|
pa_log_warn("Properties changed in unknown adapter");
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parse_adapter_properties(a, &arg_i, true);
|
2013-09-24 19:45:37 -03:00
|
|
|
|
|
|
|
|
} else if (pa_streq(iface, BLUEZ_DEVICE_INTERFACE)) {
|
|
|
|
|
pa_bluetooth_device *d;
|
|
|
|
|
|
|
|
|
|
pa_log_debug("Properties changed in device %s", dbus_message_get_path(m));
|
|
|
|
|
|
|
|
|
|
if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
|
|
|
|
|
pa_log_warn("Properties changed in unknown device");
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
if (!d->properties_received)
|
2013-09-24 19:45:37 -03:00
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
|
|
2014-05-24 12:56:35 +03:00
|
|
|
parse_device_properties(d, &arg_i);
|
2013-09-24 19:45:38 -03:00
|
|
|
} else if (pa_streq(iface, BLUEZ_MEDIA_TRANSPORT_INTERFACE)) {
|
|
|
|
|
pa_bluetooth_transport *t;
|
|
|
|
|
|
|
|
|
|
pa_log_debug("Properties changed in transport %s", dbus_message_get_path(m));
|
|
|
|
|
|
|
|
|
|
if (!(t = pa_hashmap_get(y->transports, dbus_message_get_path(m))))
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
|
|
|
|
|
|
parse_transport_properties(t, &arg_i);
|
2013-09-24 19:45:34 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
2013-09-18 16:16:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
dbus_error_free(&err);
|
|
|
|
|
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:24 -03:00
|
|
|
const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
|
|
|
|
|
switch(profile) {
|
|
|
|
|
case PA_BLUETOOTH_PROFILE_A2DP_SINK:
|
|
|
|
|
return "a2dp_sink";
|
|
|
|
|
case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
|
|
|
|
|
return "a2dp_source";
|
2014-08-22 11:07:11 +03:00
|
|
|
case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
|
|
|
|
|
return "headset_head_unit";
|
|
|
|
|
case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
|
|
|
|
|
return "headset_audio_gateway";
|
2013-09-24 19:45:24 -03:00
|
|
|
case PA_BLUETOOTH_PROFILE_OFF:
|
|
|
|
|
return "off";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
static const pa_a2dp_codec *a2dp_endpoint_to_a2dp_codec(const char *endpoint) {
|
|
|
|
|
const char *codec_name;
|
|
|
|
|
|
|
|
|
|
if (pa_startswith(endpoint, A2DP_SINK_ENDPOINT "/"))
|
|
|
|
|
codec_name = endpoint + strlen(A2DP_SINK_ENDPOINT "/");
|
|
|
|
|
else if (pa_startswith(endpoint, A2DP_SOURCE_ENDPOINT "/"))
|
|
|
|
|
codec_name = endpoint + strlen(A2DP_SOURCE_ENDPOINT "/");
|
|
|
|
|
else
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
return pa_bluetooth_get_a2dp_codec(codec_name);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-24 19:45:23 -03:00
|
|
|
static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
|
2013-09-24 19:45:24 -03:00
|
|
|
pa_bluetooth_discovery *y = userdata;
|
|
|
|
|
pa_bluetooth_device *d;
|
|
|
|
|
pa_bluetooth_transport *t;
|
2019-04-06 11:15:58 +02:00
|
|
|
const pa_a2dp_codec *a2dp_codec = NULL;
|
2013-09-24 19:45:24 -03:00
|
|
|
const char *sender, *path, *endpoint_path, *dev_path = NULL, *uuid = NULL;
|
|
|
|
|
const uint8_t *config = NULL;
|
|
|
|
|
int size = 0;
|
|
|
|
|
pa_bluetooth_profile_t p = PA_BLUETOOTH_PROFILE_OFF;
|
|
|
|
|
DBusMessageIter args, props;
|
2013-09-24 19:45:23 -03:00
|
|
|
DBusMessage *r;
|
|
|
|
|
|
2013-09-24 19:45:24 -03:00
|
|
|
if (!dbus_message_iter_init(m, &args) || !pa_streq(dbus_message_get_signature(m), "oa{sv}")) {
|
|
|
|
|
pa_log_error("Invalid signature for method SetConfiguration()");
|
|
|
|
|
goto fail2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(&args, &path);
|
|
|
|
|
|
|
|
|
|
if (pa_hashmap_get(y->transports, path)) {
|
|
|
|
|
pa_log_error("Endpoint SetConfiguration(): Transport %s is already configured.", path);
|
|
|
|
|
goto fail2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_next(&args));
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&args, &props);
|
|
|
|
|
if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
|
|
|
|
|
goto fail;
|
|
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
endpoint_path = dbus_message_get_path(m);
|
|
|
|
|
|
2013-09-24 19:45:24 -03:00
|
|
|
/* Read transport properties */
|
|
|
|
|
while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) {
|
|
|
|
|
const char *key;
|
|
|
|
|
DBusMessageIter value, entry;
|
|
|
|
|
int var;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&props, &entry);
|
|
|
|
|
dbus_message_iter_get_basic(&entry, &key);
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_next(&entry);
|
|
|
|
|
dbus_message_iter_recurse(&entry, &value);
|
|
|
|
|
|
|
|
|
|
var = dbus_message_iter_get_arg_type(&value);
|
|
|
|
|
|
|
|
|
|
if (pa_streq(key, "UUID")) {
|
|
|
|
|
if (var != DBUS_TYPE_STRING) {
|
|
|
|
|
pa_log_error("Property %s of wrong type %c", key, (char)var);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(&value, &uuid);
|
|
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
if (pa_startswith(endpoint_path, A2DP_SINK_ENDPOINT "/"))
|
|
|
|
|
p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
|
|
|
|
|
else if (pa_startswith(endpoint_path, A2DP_SOURCE_ENDPOINT "/"))
|
|
|
|
|
p = PA_BLUETOOTH_PROFILE_A2DP_SINK;
|
2013-09-24 19:45:24 -03:00
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
if ((pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE) && p != PA_BLUETOOTH_PROFILE_A2DP_SINK) ||
|
|
|
|
|
(pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK) && p != PA_BLUETOOTH_PROFILE_A2DP_SOURCE)) {
|
2013-09-24 19:45:24 -03:00
|
|
|
pa_log_error("UUID %s of transport %s incompatible with endpoint %s", uuid, path, endpoint_path);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
} else if (pa_streq(key, "Device")) {
|
|
|
|
|
if (var != DBUS_TYPE_OBJECT_PATH) {
|
|
|
|
|
pa_log_error("Property %s of wrong type %c", key, (char)var);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(&value, &dev_path);
|
|
|
|
|
} else if (pa_streq(key, "Configuration")) {
|
|
|
|
|
DBusMessageIter array;
|
|
|
|
|
|
|
|
|
|
if (var != DBUS_TYPE_ARRAY) {
|
|
|
|
|
pa_log_error("Property %s of wrong type %c", key, (char)var);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&value, &array);
|
|
|
|
|
var = dbus_message_iter_get_arg_type(&array);
|
|
|
|
|
if (var != DBUS_TYPE_BYTE) {
|
|
|
|
|
pa_log_error("%s is an array of wrong type %c", key, (char)var);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_get_fixed_array(&array, &config, &size);
|
|
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
a2dp_codec = a2dp_endpoint_to_a2dp_codec(endpoint_path);
|
|
|
|
|
pa_assert(a2dp_codec);
|
2013-09-24 19:45:24 -03:00
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
if (!a2dp_codec->is_configuration_valid(config, size))
|
2013-09-24 19:45:24 -03:00
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_next(&props);
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
if (!a2dp_codec)
|
|
|
|
|
goto fail2;
|
|
|
|
|
|
2013-09-24 19:45:24 -03:00
|
|
|
if ((d = pa_hashmap_get(y->devices, dev_path))) {
|
2014-05-24 12:56:35 +03:00
|
|
|
if (!d->valid) {
|
2013-09-24 19:45:24 -03:00
|
|
|
pa_log_error("Information about device %s is invalid", dev_path);
|
|
|
|
|
goto fail2;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2014-03-07 21:26:28 +06:00
|
|
|
/* InterfacesAdded signal is probably on its way, device_info_valid is kept as 0. */
|
2013-09-24 19:45:24 -03:00
|
|
|
pa_log_warn("SetConfiguration() received for unknown device %s", dev_path);
|
|
|
|
|
d = device_create(y, dev_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (d->transports[p] != NULL) {
|
|
|
|
|
pa_log_error("Cannot configure transport %s because profile %s is already used", path, pa_bluetooth_profile_to_string(p));
|
|
|
|
|
goto fail2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sender = dbus_message_get_sender(m);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(r = dbus_message_new_method_return(m));
|
|
|
|
|
pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
|
|
|
|
|
dbus_message_unref(r);
|
|
|
|
|
|
2014-09-08 12:14:42 +03:00
|
|
|
t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
|
2019-04-06 11:15:58 +02:00
|
|
|
t->a2dp_codec = a2dp_codec;
|
2013-09-24 19:45:24 -03:00
|
|
|
t->acquire = bluez5_transport_acquire_cb;
|
|
|
|
|
t->release = bluez5_transport_release_cb;
|
|
|
|
|
pa_bluetooth_transport_put(t);
|
|
|
|
|
|
|
|
|
|
pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
|
2020-12-15 16:51:11 +05:30
|
|
|
pa_log_info("Selected codec: %s", a2dp_codec->name);
|
2013-09-24 19:45:24 -03:00
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
pa_log_error("Endpoint SetConfiguration(): invalid arguments");
|
2013-09-24 19:45:23 -03:00
|
|
|
|
2013-09-24 19:45:24 -03:00
|
|
|
fail2:
|
|
|
|
|
pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to set configuration"));
|
2013-09-24 19:45:23 -03:00
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
|
2013-09-24 19:45:25 -03:00
|
|
|
pa_bluetooth_discovery *y = userdata;
|
2019-04-06 11:15:58 +02:00
|
|
|
const char *endpoint_path;
|
|
|
|
|
uint8_t *cap;
|
|
|
|
|
int size;
|
|
|
|
|
const pa_a2dp_codec *a2dp_codec;
|
|
|
|
|
uint8_t config[MAX_A2DP_CAPS_SIZE];
|
|
|
|
|
uint8_t *config_ptr = config;
|
|
|
|
|
size_t config_size;
|
2013-09-24 19:45:23 -03:00
|
|
|
DBusMessage *r;
|
2013-09-24 19:45:25 -03:00
|
|
|
DBusError err;
|
2013-09-24 19:45:23 -03:00
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
endpoint_path = dbus_message_get_path(m);
|
2013-09-24 19:45:25 -03:00
|
|
|
|
|
|
|
|
dbus_error_init(&err);
|
|
|
|
|
|
|
|
|
|
if (!dbus_message_get_args(m, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
|
|
|
|
|
pa_log_error("Endpoint SelectConfiguration(): %s", err.message);
|
|
|
|
|
dbus_error_free(&err);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
a2dp_codec = a2dp_endpoint_to_a2dp_codec(endpoint_path);
|
|
|
|
|
pa_assert(a2dp_codec);
|
2013-09-24 19:45:25 -03:00
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
config_size = a2dp_codec->fill_preferred_configuration(&y->core->default_sample_spec, cap, size, config);
|
|
|
|
|
if (config_size == 0)
|
2013-09-24 19:45:25 -03:00
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
|
pa_assert_se(r = dbus_message_new_method_return(m));
|
2019-04-06 11:15:58 +02:00
|
|
|
pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &config_ptr, config_size, DBUS_TYPE_INVALID));
|
2013-09-24 19:45:25 -03:00
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to select configuration"));
|
2013-09-24 19:45:23 -03:00
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static DBusMessage *endpoint_clear_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
|
2013-09-24 19:45:26 -03:00
|
|
|
pa_bluetooth_discovery *y = userdata;
|
|
|
|
|
pa_bluetooth_transport *t;
|
2013-09-24 19:45:23 -03:00
|
|
|
DBusMessage *r;
|
2013-09-24 19:45:26 -03:00
|
|
|
DBusError err;
|
|
|
|
|
const char *path;
|
2013-09-24 19:45:23 -03:00
|
|
|
|
2013-09-24 19:45:26 -03:00
|
|
|
dbus_error_init(&err);
|
|
|
|
|
|
|
|
|
|
if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
|
|
|
|
|
pa_log_error("Endpoint ClearConfiguration(): %s", err.message);
|
|
|
|
|
dbus_error_free(&err);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
2013-09-24 19:45:23 -03:00
|
|
|
|
2013-09-24 19:45:26 -03:00
|
|
|
if ((t = pa_hashmap_get(y->transports, path))) {
|
|
|
|
|
pa_log_debug("Clearing transport %s profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
|
|
|
|
|
pa_bluetooth_transport_free(t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_assert_se(r = dbus_message_new_method_return(m));
|
|
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to clear configuration"));
|
2013-09-24 19:45:23 -03:00
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static DBusMessage *endpoint_release(DBusConnection *conn, DBusMessage *m, void *userdata) {
|
2018-01-18 22:27:54 +02:00
|
|
|
DBusMessage *r = NULL;
|
2013-09-24 19:45:23 -03:00
|
|
|
|
2018-01-18 22:27:54 +02:00
|
|
|
/* From doc/media-api.txt in bluez:
|
|
|
|
|
*
|
|
|
|
|
* This method gets called when the service daemon
|
|
|
|
|
* unregisters the endpoint. An endpoint can use it to do
|
|
|
|
|
* cleanup tasks. There is no need to unregister the
|
|
|
|
|
* endpoint, because when this method gets called it has
|
|
|
|
|
* already been unregistered.
|
|
|
|
|
*
|
|
|
|
|
* We don't have any cleanup to do. */
|
|
|
|
|
|
|
|
|
|
/* Reply only if requested. Generally bluetoothd doesn't request a reply
|
|
|
|
|
* to the Release() call. Sending replies when not requested on the system
|
|
|
|
|
* bus tends to cause errors in syslog from dbus-daemon, because it
|
|
|
|
|
* doesn't let unexpected replies through, so it's important to have this
|
|
|
|
|
* check here. */
|
|
|
|
|
if (!dbus_message_get_no_reply(m))
|
|
|
|
|
pa_assert_se(r = dbus_message_new_method_return(m));
|
2013-09-24 19:45:23 -03:00
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
|
|
|
|
|
struct pa_bluetooth_discovery *y = userdata;
|
|
|
|
|
DBusMessage *r = NULL;
|
|
|
|
|
const char *path, *interface, *member;
|
|
|
|
|
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
|
|
|
|
|
path = dbus_message_get_path(m);
|
|
|
|
|
interface = dbus_message_get_interface(m);
|
|
|
|
|
member = dbus_message_get_member(m);
|
|
|
|
|
|
|
|
|
|
pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
|
|
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
if (!a2dp_endpoint_to_a2dp_codec(path))
|
2013-09-24 19:45:23 -03:00
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
|
|
|
|
|
|
if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
|
|
|
|
|
const char *xml = ENDPOINT_INTROSPECT_XML;
|
|
|
|
|
|
|
|
|
|
pa_assert_se(r = dbus_message_new_method_return(m));
|
|
|
|
|
pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
|
|
|
|
|
|
|
|
|
|
} else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"))
|
|
|
|
|
r = endpoint_set_configuration(c, m, userdata);
|
|
|
|
|
else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SelectConfiguration"))
|
|
|
|
|
r = endpoint_select_configuration(c, m, userdata);
|
|
|
|
|
else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "ClearConfiguration"))
|
|
|
|
|
r = endpoint_clear_configuration(c, m, userdata);
|
|
|
|
|
else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "Release"))
|
|
|
|
|
r = endpoint_release(c, m, userdata);
|
|
|
|
|
else
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
|
|
|
|
|
|
if (r) {
|
|
|
|
|
pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
|
|
|
|
|
dbus_message_unref(r);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
static void endpoint_init(pa_bluetooth_discovery *y, const char *endpoint) {
|
2013-09-24 19:45:23 -03:00
|
|
|
static const DBusObjectPathVTable vtable_endpoint = {
|
|
|
|
|
.message_function = endpoint_handler,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pa_assert(y);
|
2019-04-06 11:15:58 +02:00
|
|
|
pa_assert(endpoint);
|
2013-09-24 19:45:23 -03:00
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), endpoint,
|
|
|
|
|
&vtable_endpoint, y));
|
2013-09-24 19:45:23 -03:00
|
|
|
}
|
|
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
static void endpoint_done(pa_bluetooth_discovery *y, const char *endpoint) {
|
2013-09-24 19:45:23 -03:00
|
|
|
pa_assert(y);
|
2019-04-06 11:15:58 +02:00
|
|
|
pa_assert(endpoint);
|
2013-09-24 19:45:23 -03:00
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), endpoint);
|
2013-09-24 19:45:23 -03:00
|
|
|
}
|
|
|
|
|
|
2020-12-15 16:51:11 +05:30
|
|
|
static void append_a2dp_object(DBusMessageIter *iter, const char *endpoint, const char *uuid, uint8_t codec_id, uint8_t *capabilities, uint8_t capabilities_size) {
|
|
|
|
|
const char *interface_name = BLUEZ_MEDIA_ENDPOINT_INTERFACE;
|
|
|
|
|
DBusMessageIter object, array, entry, dict;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &object);
|
|
|
|
|
pa_assert_se(dbus_message_iter_append_basic(&object, DBUS_TYPE_OBJECT_PATH, &endpoint));
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_open_container(&object, DBUS_TYPE_ARRAY,
|
|
|
|
|
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
|
|
|
|
|
DBUS_TYPE_STRING_AS_STRING
|
|
|
|
|
DBUS_TYPE_ARRAY_AS_STRING
|
|
|
|
|
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
|
|
|
|
|
DBUS_TYPE_STRING_AS_STRING
|
|
|
|
|
DBUS_TYPE_VARIANT_AS_STRING
|
|
|
|
|
DBUS_DICT_ENTRY_END_CHAR_AS_STRING
|
|
|
|
|
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
|
|
|
|
|
&array);
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &entry);
|
|
|
|
|
pa_assert_se(dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface_name));
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_open_container(&entry, DBUS_TYPE_ARRAY,
|
|
|
|
|
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
|
|
|
|
|
DBUS_TYPE_STRING_AS_STRING
|
|
|
|
|
DBUS_TYPE_VARIANT_AS_STRING
|
|
|
|
|
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
|
|
|
|
|
&dict);
|
|
|
|
|
|
|
|
|
|
pa_dbus_append_basic_variant_dict_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid);
|
|
|
|
|
pa_dbus_append_basic_variant_dict_entry(&dict, "Codec", DBUS_TYPE_BYTE, &codec_id);
|
|
|
|
|
pa_dbus_append_basic_array_variant_dict_entry(&dict, "Capabilities", DBUS_TYPE_BYTE,
|
|
|
|
|
capabilities, capabilities_size);
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_close_container(&entry, &dict);
|
|
|
|
|
dbus_message_iter_close_container(&array, &entry);
|
|
|
|
|
dbus_message_iter_close_container(&object, &array);
|
|
|
|
|
dbus_message_iter_close_container(iter, &object);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
|
|
|
|
|
struct pa_bluetooth_discovery *y = userdata;
|
|
|
|
|
DBusMessage *r;
|
|
|
|
|
const char *path, *interface, *member;
|
|
|
|
|
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
|
|
|
|
|
path = dbus_message_get_path(m);
|
|
|
|
|
interface = dbus_message_get_interface(m);
|
|
|
|
|
member = dbus_message_get_member(m);
|
|
|
|
|
|
|
|
|
|
pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
|
|
|
|
|
|
|
|
|
|
if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
|
|
|
|
|
const char *xml = OBJECT_MANAGER_INTROSPECT_XML;
|
|
|
|
|
|
|
|
|
|
pa_assert_se(r = dbus_message_new_method_return(m));
|
|
|
|
|
pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
|
|
|
|
|
} else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects")) {
|
|
|
|
|
DBusMessageIter iter, array;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
pa_assert_se(r = dbus_message_new_method_return(m));
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_init_append(r, &iter);
|
|
|
|
|
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
|
|
|
|
|
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
|
|
|
|
|
DBUS_TYPE_OBJECT_PATH_AS_STRING
|
|
|
|
|
DBUS_TYPE_ARRAY_AS_STRING
|
|
|
|
|
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
|
|
|
|
|
DBUS_TYPE_STRING_AS_STRING
|
|
|
|
|
DBUS_TYPE_ARRAY_AS_STRING
|
|
|
|
|
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
|
|
|
|
|
DBUS_TYPE_STRING_AS_STRING
|
|
|
|
|
DBUS_TYPE_VARIANT_AS_STRING
|
|
|
|
|
DBUS_DICT_ENTRY_END_CHAR_AS_STRING
|
|
|
|
|
DBUS_DICT_ENTRY_END_CHAR_AS_STRING
|
|
|
|
|
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
|
|
|
|
|
&array);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < pa_bluetooth_a2dp_codec_count(); i++) {
|
|
|
|
|
const pa_a2dp_codec *a2dp_codec;
|
|
|
|
|
uint8_t capabilities[MAX_A2DP_CAPS_SIZE];
|
|
|
|
|
uint8_t capabilities_size;
|
|
|
|
|
uint8_t codec_id;
|
|
|
|
|
char *endpoint;
|
|
|
|
|
|
|
|
|
|
a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
|
|
|
|
|
codec_id = a2dp_codec->id.codec_id;
|
|
|
|
|
capabilities_size = a2dp_codec->fill_capabilities(capabilities);
|
|
|
|
|
pa_assert(capabilities_size != 0);
|
|
|
|
|
|
|
|
|
|
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
|
|
|
|
|
append_a2dp_object(&array, endpoint, PA_BLUETOOTH_UUID_A2DP_SINK, codec_id,
|
|
|
|
|
capabilities, capabilities_size);
|
|
|
|
|
pa_xfree(endpoint);
|
|
|
|
|
|
|
|
|
|
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
|
|
|
|
|
append_a2dp_object(&array, endpoint, PA_BLUETOOTH_UUID_A2DP_SOURCE, codec_id,
|
|
|
|
|
capabilities, capabilities_size);
|
|
|
|
|
pa_xfree(endpoint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_close_container(&iter, &array);
|
|
|
|
|
} else
|
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL));
|
|
|
|
|
dbus_message_unref(r);
|
|
|
|
|
|
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void object_manager_init(pa_bluetooth_discovery *y) {
|
|
|
|
|
static const DBusObjectPathVTable vtable = {
|
|
|
|
|
.message_function = object_manager_handler,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection),
|
|
|
|
|
A2DP_OBJECT_MANAGER_PATH, &vtable, y));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void object_manager_done(pa_bluetooth_discovery *y) {
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection),
|
|
|
|
|
A2DP_OBJECT_MANAGER_PATH);
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-10 16:13:13 +01:00
|
|
|
pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend) {
|
2013-08-13 01:54:00 -03:00
|
|
|
pa_bluetooth_discovery *y;
|
2013-09-18 16:16:55 -05:00
|
|
|
DBusError err;
|
|
|
|
|
DBusConnection *conn;
|
2019-04-06 11:15:58 +02:00
|
|
|
unsigned i, count;
|
|
|
|
|
const pa_a2dp_codec *a2dp_codec;
|
|
|
|
|
char *endpoint;
|
2013-08-13 01:54:00 -03:00
|
|
|
|
|
|
|
|
y = pa_xnew0(pa_bluetooth_discovery, 1);
|
|
|
|
|
PA_REFCNT_INIT(y);
|
|
|
|
|
y->core = c;
|
2014-11-10 16:13:13 +01:00
|
|
|
y->headset_backend = headset_backend;
|
2013-09-29 18:49:22 +03:00
|
|
|
y->adapters = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
|
|
|
|
|
(pa_free_cb_t) adapter_free);
|
2013-11-15 16:29:40 +02:00
|
|
|
y->devices = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
|
|
|
|
|
(pa_free_cb_t) device_free);
|
2013-09-24 19:45:22 -03:00
|
|
|
y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
|
2013-09-24 19:45:27 -03:00
|
|
|
PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
|
2013-08-13 01:54:00 -03:00
|
|
|
|
2013-09-18 16:16:56 -05:00
|
|
|
for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++)
|
|
|
|
|
pa_hook_init(&y->hooks[i], y);
|
|
|
|
|
|
2013-08-13 01:54:00 -03:00
|
|
|
pa_shared_set(c, "bluetooth-discovery", y);
|
|
|
|
|
|
2013-09-18 16:16:55 -05:00
|
|
|
dbus_error_init(&err);
|
|
|
|
|
|
|
|
|
|
if (!(y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err))) {
|
|
|
|
|
pa_log_error("Failed to get D-Bus connection: %s", err.message);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
conn = pa_dbus_connection_get(y->connection);
|
|
|
|
|
|
|
|
|
|
/* dynamic detection of bluetooth audio devices */
|
|
|
|
|
if (!dbus_connection_add_filter(conn, filter_cb, y, NULL)) {
|
|
|
|
|
pa_log_error("Failed to add filter function");
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
y->filter_added = true;
|
|
|
|
|
|
|
|
|
|
if (pa_dbus_add_matches(conn, &err,
|
|
|
|
|
"type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
|
|
|
|
|
",arg0='" BLUEZ_SERVICE "'",
|
2013-09-24 19:45:30 -03:00
|
|
|
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
|
|
|
|
|
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',"
|
|
|
|
|
"member='InterfacesRemoved'",
|
2013-09-24 19:45:34 -03:00
|
|
|
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
|
|
|
|
|
",arg0='" BLUEZ_ADAPTER_INTERFACE "'",
|
2013-09-24 19:45:37 -03:00
|
|
|
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
|
|
|
|
|
",arg0='" BLUEZ_DEVICE_INTERFACE "'",
|
2013-09-24 19:45:38 -03:00
|
|
|
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
|
2020-12-15 16:51:11 +05:30
|
|
|
",arg0='" BLUEZ_MEDIA_ENDPOINT_INTERFACE "'",
|
|
|
|
|
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
|
2013-09-24 19:45:38 -03:00
|
|
|
",arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'",
|
2013-09-18 16:16:55 -05:00
|
|
|
NULL) < 0) {
|
|
|
|
|
pa_log_error("Failed to add D-Bus matches: %s", err.message);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
y->matches_added = true;
|
|
|
|
|
|
2020-12-15 16:51:11 +05:30
|
|
|
object_manager_init(y);
|
|
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
count = pa_bluetooth_a2dp_codec_count();
|
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
|
a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
|
|
|
|
|
|
|
|
|
|
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
|
|
|
|
|
endpoint_init(y, endpoint);
|
|
|
|
|
pa_xfree(endpoint);
|
|
|
|
|
|
|
|
|
|
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
|
|
|
|
|
endpoint_init(y, endpoint);
|
|
|
|
|
pa_xfree(endpoint);
|
|
|
|
|
}
|
2013-09-24 19:45:23 -03:00
|
|
|
|
2013-09-24 19:45:28 -03:00
|
|
|
get_managed_objects(y);
|
|
|
|
|
|
2013-08-13 01:54:00 -03:00
|
|
|
return y;
|
2013-09-18 16:16:55 -05:00
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
pa_bluetooth_discovery_unref(y);
|
|
|
|
|
dbus_error_free(&err);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
2013-08-13 01:54:00 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
|
|
|
|
|
pa_assert(y);
|
|
|
|
|
pa_assert(PA_REFCNT_VALUE(y) > 0);
|
|
|
|
|
|
|
|
|
|
PA_REFCNT_INC(y);
|
|
|
|
|
|
|
|
|
|
return y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
|
2019-04-06 11:15:58 +02:00
|
|
|
unsigned i, count;
|
|
|
|
|
const pa_a2dp_codec *a2dp_codec;
|
|
|
|
|
char *endpoint;
|
|
|
|
|
|
2013-08-13 01:54:00 -03:00
|
|
|
pa_assert(y);
|
|
|
|
|
pa_assert(PA_REFCNT_VALUE(y) > 0);
|
|
|
|
|
|
|
|
|
|
if (PA_REFCNT_DEC(y) > 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2013-09-24 19:45:27 -03:00
|
|
|
pa_dbus_free_pending_list(&y->pending);
|
|
|
|
|
|
2017-04-21 21:49:58 +02:00
|
|
|
if (y->ofono_backend)
|
|
|
|
|
pa_bluetooth_ofono_backend_free(y->ofono_backend);
|
|
|
|
|
if (y->native_backend)
|
|
|
|
|
pa_bluetooth_native_backend_free(y->native_backend);
|
|
|
|
|
|
2013-09-29 18:49:22 +03:00
|
|
|
if (y->adapters)
|
2013-09-18 16:16:57 -05:00
|
|
|
pa_hashmap_free(y->adapters);
|
|
|
|
|
|
2014-11-28 13:43:07 +01:00
|
|
|
if (y->devices)
|
|
|
|
|
pa_hashmap_free(y->devices);
|
|
|
|
|
|
2013-09-24 19:45:22 -03:00
|
|
|
if (y->transports) {
|
|
|
|
|
pa_assert(pa_hashmap_isempty(y->transports));
|
|
|
|
|
pa_hashmap_free(y->transports);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-18 16:16:55 -05:00
|
|
|
if (y->connection) {
|
|
|
|
|
|
|
|
|
|
if (y->matches_added)
|
|
|
|
|
pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
|
|
|
|
|
"type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',"
|
|
|
|
|
"arg0='" BLUEZ_SERVICE "'",
|
2013-09-24 19:45:30 -03:00
|
|
|
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',"
|
|
|
|
|
"member='InterfacesAdded'",
|
|
|
|
|
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',"
|
|
|
|
|
"member='InterfacesRemoved'",
|
2013-09-24 19:45:34 -03:00
|
|
|
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
|
|
|
|
|
"member='PropertiesChanged',arg0='" BLUEZ_ADAPTER_INTERFACE "'",
|
2013-09-24 19:45:37 -03:00
|
|
|
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
|
|
|
|
|
"member='PropertiesChanged',arg0='" BLUEZ_DEVICE_INTERFACE "'",
|
2013-09-24 19:45:38 -03:00
|
|
|
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
|
2020-12-15 16:51:11 +05:30
|
|
|
"member='PropertiesChanged',arg0='" BLUEZ_MEDIA_ENDPOINT_INTERFACE "'",
|
|
|
|
|
"type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',"
|
2013-09-24 19:45:38 -03:00
|
|
|
"member='PropertiesChanged',arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'",
|
2013-09-18 16:16:55 -05:00
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
if (y->filter_added)
|
|
|
|
|
dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
|
|
|
|
|
|
2020-12-15 16:51:11 +05:30
|
|
|
object_manager_done(y);
|
|
|
|
|
|
2019-04-06 11:15:58 +02:00
|
|
|
count = pa_bluetooth_a2dp_codec_count();
|
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
|
a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
|
|
|
|
|
|
|
|
|
|
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
|
|
|
|
|
endpoint_done(y, endpoint);
|
|
|
|
|
pa_xfree(endpoint);
|
|
|
|
|
|
|
|
|
|
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
|
|
|
|
|
endpoint_done(y, endpoint);
|
|
|
|
|
pa_xfree(endpoint);
|
|
|
|
|
}
|
2013-09-24 19:45:23 -03:00
|
|
|
|
2013-09-18 16:16:55 -05:00
|
|
|
pa_dbus_connection_unref(y->connection);
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-13 01:54:00 -03:00
|
|
|
pa_shared_remove(y->core, "bluetooth-discovery");
|
|
|
|
|
pa_xfree(y);
|
|
|
|
|
}
|