" </interface>" \
"</node>"
+#define MEDIA_ENDPOINT_1_INTROSPECT_XML \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>" \
+ " <interface name=\"org.bluez.MediaEndpoint1\">" \
+ " <method name=\"SetConfiguration\">" \
+ " <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \
+ " <arg name=\"configuration\" 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\">" \
+ " </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>"
+
+typedef enum pa_bluez_version {
+ BLUEZ_VERSION_UNKNOWN,
+ BLUEZ_VERSION_4,
+ BLUEZ_VERSION_5,
+} pa_bluez_version_t;
+
struct pa_bluetooth_discovery {
PA_REFCNT_DECLARE;
pa_core *core;
pa_dbus_connection *connection;
PA_LLIST_HEAD(pa_dbus_pending, pending);
+ pa_bluez_version_t version;
bool adapters_listed;
pa_hashmap *devices;
pa_hashmap *transports;
return PA_BT_AUDIO_STATE_INVALID;
}
+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")) /* We don't need such a separation */
+ *state = PA_BLUETOOTH_TRANSPORT_STATE_PLAYING;
+ else
+ return -1;
+
+ return 0;
+}
+
const char *pa_bt_profile_to_string(enum profile profile) {
switch(profile) {
case PROFILE_A2DP:
dbus_message_iter_recurse(i, &variant_i);
-/* pa_log_debug("Parsing property org.bluez.Device.%s", key); */
-
switch (dbus_message_iter_get_arg_type(&variant_i)) {
case DBUS_TYPE_STRING: {
uuiddata.uuid = value;
pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED], &uuiddata);
+ if (d->discovery->version >= BLUEZ_VERSION_5) {
+ dbus_message_iter_next(&ai);
+ continue;
+ }
+
/* Vudentz said the interfaces are here when the UUIDs are announced */
if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) {
pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.HandsfreeGateway",
}
static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) {
- DBusError e;
DBusMessage *r;
pa_dbus_pending *p;
pa_bluetooth_discovery *y;
char *endpoint;
pa_assert(pending);
-
- dbus_error_init(&e);
-
pa_assert_se(p = userdata);
pa_assert_se(y = p->context_data);
pa_assert_se(endpoint = p->call_data);
}
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
- pa_log("org.bluez.Media.RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r),
- pa_dbus_get_error_message(r));
+ pa_log("RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
goto finish;
}
DBusMessage *m;
DBusMessageIter i, d;
uint8_t codec = 0;
+ const char *interface = y->version == BLUEZ_VERSION_4 ? "org.bluez.Media" : "org.bluez.Media1";
pa_log_debug("Registering %s on adapter %s.", endpoint, path);
- pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Media", "RegisterEndpoint"));
+ pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, interface, "RegisterEndpoint"));
dbus_message_iter_init_append(m, &i);
send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint));
}
+static void register_adapter_endpoints(pa_bluetooth_discovery *y, const char *path) {
+ register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID);
+ register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID);
+
+ /* For BlueZ 5, only A2DP is registered in the Media API */
+ if (y->version >= BLUEZ_VERSION_5)
+ return;
+
+ register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID);
+ register_endpoint(y, path, HFP_HS_ENDPOINT, HFP_HS_UUID);
+}
+
static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
DBusMessage *m;
pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "GetProperties"));
send_and_add_to_pending(y, m, get_properties_reply, NULL);
- register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID);
- register_endpoint(y, path, HFP_HS_ENDPOINT, HFP_HS_UUID);
- register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID);
- register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID);
+ register_adapter_endpoints(y, path);
}
static void list_adapters(pa_bluetooth_discovery *y) {
send_and_add_to_pending(y, m, get_properties_reply, NULL);
}
+static int parse_device_properties(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) {
+ DBusMessageIter element_i;
+ int ret = 0;
+
+ 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);
+
+ if (parse_device_property(d, &dict_i, is_property_change) < 0)
+ ret = -1;
+
+ dbus_message_iter_next(&element_i);
+ }
+
+ if (!d->address || !d->alias || d->paired < 0 || d->trusted < 0) {
+ pa_log_error("Non-optional information missing for device %s", d->path);
+ d->device_info_valid = -1;
+ return -1;
+ }
+
+ d->device_info_valid = 1;
+ return ret;
+}
+
+static int parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
+ DBusMessageIter element_i;
+ const char *path;
+
+ 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, "org.bluez.Adapter1")) {
+ pa_log_debug("Adapter %s found", path);
+ register_adapter_endpoints(y, path);
+ } else if (pa_streq(interface, "org.bluez.Device1")) {
+ pa_bluetooth_device *d;
+
+ if (pa_hashmap_get(y->devices, path)) {
+ pa_log("Found duplicated D-Bus path for device %s", path);
+ return -1;
+ }
+
+ pa_log_debug("Device %s found", path);
+
+ d = device_new(y, path);
+ pa_hashmap_put(y->devices, d->path, d);
+
+ /* FIXME: BlueZ 5 doesn't support the old Audio interface, and thus
+ it's not possible to know if any audio profile is about to be
+ connected. This can introduce regressions with modules such as
+ module-card-restore */
+ d->audio_state = PA_BT_AUDIO_STATE_DISCONNECTED;
+
+ if (parse_device_properties(d, &iface_i, false) < 0)
+ return -1;
+ }
+
+ dbus_message_iter_next(&element_i);
+ }
+
+ return 0;
+}
+
+static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata) {
+ DBusMessage *r;
+ pa_dbus_pending *p;
+ pa_bluetooth_discovery *y;
+ 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_info("D-Bus ObjectManager not detected so falling back to BlueZ version 4 API.");
+ y->version = BLUEZ_VERSION_4;
+ list_adapters(y);
+ goto finish;
+ }
+
+ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+ pa_log("GetManagedObjects() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+ goto finish;
+ }
+
+ pa_log_info("D-Bus ObjectManager detected so assuming BlueZ version 5.");
+ y->version = BLUEZ_VERSION_5;
+
+ if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{oa{sa{sv}}}")) {
+ pa_log("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);
+
+ /* Ignore errors here and proceed with next object */
+ parse_interfaces_and_properties(y, &dict_i);
+
+ dbus_message_iter_next(&element_i);
+ }
+
+finish:
+ dbus_message_unref(r);
+
+ PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
+ pa_dbus_pending_free(p);
+}
+
+static void init_bluez(pa_bluetooth_discovery *y) {
+ DBusMessage *m;
+ pa_assert(y);
+
+ pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.freedesktop.DBus.ObjectManager",
+ "GetManagedObjects"));
+ send_and_add_to_pending(y, m, get_managed_objects_reply, NULL);
+}
+
static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *i) {
const char *key;
DBusMessageIter variant_i;
break;
}
+
+ case DBUS_TYPE_STRING: {
+
+ const char *value;
+ dbus_message_iter_get_basic(&variant_i, &value);
+
+ if (pa_streq(key, "State")) { /* Added in BlueZ 5.0 */
+ bool old_any_connected = pa_bluetooth_device_any_audio_connected(t->device);
+
+ if (transport_state_from_string(value, &t->state) < 0) {
+ pa_log("Transport %s has an invalid state: '%s'", t->path, value);
+ return -1;
+ }
+
+ pa_log_debug("dbus: transport %s set to state '%s'", t->path, value);
+ pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t);
+
+ if (old_any_connected != pa_bluetooth_device_any_audio_connected(t->device))
+ run_callback(t->device, old_any_connected);
+ }
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+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);
+
+ transport_parse_property(t, &dict_i);
+
+ dbus_message_iter_next(&element_i);
}
return 0;
pa_log_debug("Bluetooth daemon disappeared.");
remove_all_devices(y);
y->adapters_listed = false;
+ y->version = BLUEZ_VERSION_UNKNOWN;
}
if (new_owner && *new_owner) {
pa_log_debug("Bluetooth daemon appeared.");
- list_adapters(y);
+ init_bluez(y);
}
}
if (transport_parse_property(t, &arg_i) < 0)
goto fail;
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")) {
+ DBusMessageIter arg_i;
+
+ if (y->version != BLUEZ_VERSION_5)
+ 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("Invalid signature found in InterfacesAdded");
+ goto fail;
+ }
+
+ if (parse_interfaces_and_properties(y, &arg_i) < 0)
+ goto fail;
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")) {
+ const char *path;
+ DBusMessageIter arg_i;
+ DBusMessageIter element_i;
+
+ if (y->version != BLUEZ_VERSION_5)
+ 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("Invalid signature found in InterfacesRemoved");
+ goto fail;
+ }
+
+ dbus_message_iter_get_basic(&arg_i, &path);
+
+ 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 *interface;
+
+ dbus_message_iter_get_basic(&element_i, &interface);
+
+ if (pa_streq(interface, "org.bluez.Device1")) {
+ 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);
+ run_callback(d, true);
+ device_free(d);
+ }
+ }
+
+ dbus_message_iter_next(&element_i);
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.Properties", "PropertiesChanged")) {
+ DBusMessageIter arg_i;
+ const char *interface;
+
+ if (y->version != BLUEZ_VERSION_5)
+ 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("Invalid signature found in PropertiesChanged");
+ goto fail;
+ }
+
+ dbus_message_iter_get_basic(&arg_i, &interface);
+
+ 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(interface, "org.bluez.Device1")) {
+ pa_bluetooth_device *d;
+
+ if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
+ pa_log_warn("Property change in unknown device %s", dbus_message_get_path(m));
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ parse_device_properties(d, &arg_i, true);
+ } else if (pa_streq(interface, "org.bluez.MediaTransport1")) {
+ pa_bluetooth_transport *t;
+
+ 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);
+ }
+
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
}
int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
- const char *accesstype = "rw";
DBusMessage *m, *r;
DBusError err;
int ret;
uint16_t i, o;
+ const char *method;
pa_assert(t);
pa_assert(t->device);
pa_assert(t->device->discovery);
- if (optional) {
- /* FIXME: we are trying to acquire the transport only if the stream is
- playing, without actually initiating the stream request from our side
- (which is typically undesireable specially for hfgw use-cases.
- However this approach is racy, since the stream could have been
- suspended in the meantime, so we can't really guarantee that the
- stream will not be requested until BlueZ's API supports this
- atomically. */
- if (t->state < PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) {
- pa_log_info("Failed optional acquire of transport %s", t->path);
- return -1;
+ dbus_error_init(&err);
+
+ if (t->device->discovery->version == BLUEZ_VERSION_4) {
+ const char *accesstype = "rw";
+
+ if (optional) {
+ /* We are trying to acquire the transport only if the stream is
+ playing, without actually initiating the stream request from our side
+ (which is typically undesireable specially for hfgw use-cases.
+ However this approach is racy, since the stream could have been
+ suspended in the meantime, so we can't really guarantee that the
+ stream will not be requested with the API in BlueZ 4.x */
+ if (t->state < PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) {
+ pa_log_info("Failed optional acquire of unavailable transport %s", t->path);
+ return -1;
+ }
}
- }
- dbus_error_init(&err);
+ method = "Acquire";
+ pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", method));
+ pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
+ } else {
+ pa_assert(t->device->discovery->version == BLUEZ_VERSION_5);
+
+ method = optional ? "TryAcquire" : "Acquire";
+ pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport1", method));
+ }
- pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Acquire"));
- pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
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("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("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err.message);
+ pa_log("Failed to parse the media transport Acquire() reply: %s", err.message);
ret = -1;
dbus_error_free(&err);
goto fail;
}
void pa_bluetooth_transport_release(pa_bluetooth_transport *t) {
- const char *accesstype = "rw";
DBusMessage *m;
DBusError err;
dbus_error_init(&err);
- pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Release"));
- pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
+ if (t->device->discovery->version == BLUEZ_VERSION_4) {
+ const char *accesstype = "rw";
+
+ pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Release"));
+ pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID));
+ } else {
+ pa_assert(t->device->discovery->version == BLUEZ_VERSION_5);
+
+ 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, "org.bluez.MediaTransport1", "Release"));
+ }
+
dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
if (dbus_error_is_set(&err)) {
memcpy(t->config, config, size);
}
- t->state = audio_state_to_transport_state(d->profile_state[p]);
+ if (d->discovery->version == BLUEZ_VERSION_4)
+ t->state = audio_state_to_transport_state(d->profile_state[p]);
+ else
+ t->state = PA_BLUETOOTH_TRANSPORT_STATE_IDLE;
return t;
}
dbus_message_iter_get_basic(&args, &path);
if (pa_hashmap_get(y->transports, path)) {
- pa_log("org.bluez.MediaEndpoint.SetConfiguration: Transport %s is already configured.", path);
+ pa_log("Endpoint SetConfiguration: Transport %s is already configured.", path);
goto fail2;
}
return NULL;
fail:
- pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments");
+ pa_log("Endpoint SetConfiguration: invalid arguments");
fail2:
- pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
- "Unable to set configuration"));
+ pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to set configuration"));
return r;
}
dbus_error_init(&e);
if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
- pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e.message);
+ pa_log("Endpoint ClearConfiguration: %s", e.message);
dbus_error_free(&e);
goto fail;
}
return r;
fail:
- pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
- "Unable to clear configuration"));
+ pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to clear configuration"));
return r;
}
dbus_error_init(&e);
if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) {
- pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message);
+ pa_log("Endpoint SelectConfiguration: %s", e.message);
dbus_error_free(&e);
goto fail;
}
return r;
fail:
- pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments",
- "Unable to select configuration"));
+ pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to select configuration"));
return r;
}
struct pa_bluetooth_discovery *y = userdata;
DBusMessage *r = NULL;
DBusError e;
- const char *path;
+ const char *path, *interface, *member;
pa_assert(y);
- pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
- dbus_message_get_interface(m),
- dbus_message_get_path(m),
- dbus_message_get_member(m));
-
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);
+
dbus_error_init(&e);
if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT) &&
!pa_streq(path, HFP_HS_ENDPOINT))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ interface = y->version == BLUEZ_VERSION_4 ? "org.bluez.MediaEndpoint" : "org.bluez.MediaEndpoint1";
+
if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
- const char *xml = ENDPOINT_INTROSPECT_XML;
+ const char *xml = y->version == BLUEZ_VERSION_4 ? ENDPOINT_INTROSPECT_XML : MEDIA_ENDPOINT_1_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));
+ pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
- } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SetConfiguration")) {
+ } else if (dbus_message_is_method_call(m, interface, "SetConfiguration"))
r = endpoint_set_configuration(c, m, userdata);
- } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SelectConfiguration")) {
+ else if (dbus_message_is_method_call(m, interface, "SelectConfiguration"))
r = endpoint_select_configuration(c, m, userdata);
- } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "ClearConfiguration"))
+ else if (dbus_message_is_method_call(m, interface, "ClearConfiguration"))
r = endpoint_clear_configuration(c, m, userdata);
else
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
"type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
+ "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'",
+ "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
+ ",arg0='org.bluez.Device1'",
+ "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
+ ",arg0='org.bluez.MediaTransport1'",
NULL) < 0) {
pa_log("Failed to add D-Bus matches: %s", err.message);
goto fail;
pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y));
pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SINK_ENDPOINT, &vtable_endpoint, y));
- list_adapters(y);
+ init_bluez(y);
return y;
if (y->devices) {
remove_all_devices(y);
- pa_hashmap_free(y->devices, NULL);
+ pa_hashmap_free(y->devices);
}
if (y->transports) {
pa_assert(pa_hashmap_isempty(y->transports));
- pa_hashmap_free(y->transports, NULL);
+ pa_hashmap_free(y->transports);
}
if (y->connection) {
"type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'",
+ "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'",
+ "type='signal',sender='org.bluez',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
+ ",arg0='org.bluez.Device1'",
+ "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
+ ",arg0='org.bluez.MediaTransport1'",
NULL);
if (y->filter_added)
}
pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class) {
- unsigned i;
+ unsigned major, minor;
pa_bt_form_factor_t r;
static const pa_bt_form_factor_t table[] = {
[10] = PA_BT_FORM_FACTOR_HIFI
};
- if (((class >> 8) & 31) != 4)
- return PA_BT_FORM_FACTOR_UNKNOWN;
+ /*
+ * See Bluetooth Assigned Numbers:
+ * https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
+ */
+ major = (class >> 8) & 0x1F;
+ minor = (class >> 2) & 0x3F;
+
+ switch (major) {
+ case 2:
+ return PA_BT_FORM_FACTOR_PHONE;
+ case 4:
+ break;
+ default:
+ pa_log_debug("Unknown Bluetooth major device class %u", major);
+ return PA_BT_FORM_FACTOR_UNKNOWN;
+ }
- if ((i = (class >> 2) & 63) >= PA_ELEMENTSOF(table))
- r = PA_BT_FORM_FACTOR_UNKNOWN;
- else
- r = table[i];
+ r = minor < PA_ELEMENTSOF(table) ? table[minor] : PA_BT_FORM_FACTOR_UNKNOWN;
if (!r)
- pa_log_debug("Unknown Bluetooth minor device class %u", i);
+ pa_log_debug("Unknown Bluetooth minor device class %u", minor);
return r;
}
return "car";
case PA_BT_FORM_FACTOR_HIFI:
return "hifi";
+ case PA_BT_FORM_FACTOR_PHONE:
+ return "phone";
}
pa_assert_not_reached();