X-Git-Url: https://code.delx.au/pulseaudio/blobdiff_plain/d186d0f1056dafefa935d243e2f6b0fb4d7bdfcf..b705e38a795cba0b6ef8c611ed78fa884185025d:/src/modules/bluetooth/bluez5-util.c diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index ba55366e..c7fb6ae5 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -38,9 +38,14 @@ #include "bluez5-util.h" #define BLUEZ_SERVICE "org.bluez" +#define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1" +#define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1" +#define BLUEZ_MEDIA_INTERFACE BLUEZ_SERVICE ".Media1" #define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1" #define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1" +#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported" + #define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource" #define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink" @@ -76,6 +81,7 @@ struct pa_bluetooth_discovery { pa_dbus_connection *connection; bool filter_added; bool matches_added; + bool objects_listed; pa_hook hooks[PA_BLUETOOTH_HOOK_MAX]; pa_hashmap *adapters; pa_hashmap *devices; @@ -101,6 +107,31 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusM return p; } +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; +} + 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; @@ -257,6 +288,73 @@ bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) { return false; } +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; + } + + transport_state_changed(t, state); + } + + 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; +} + static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char *path) { pa_bluetooth_device *d; @@ -266,6 +364,7 @@ static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char d = pa_xnew0(pa_bluetooth_device, 1); d->discovery = y; d->path = pa_xstrdup(path); + d->uuids = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree); pa_hashmap_put(y->devices, d->path, d); @@ -296,8 +395,8 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_d pa_assert(local); while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) - if (pa_streq(d->address, remote) && pa_streq(d->adapter->address, local)) - return d->device_info_valid == 1 ? d : NULL; + if (d->device_info_valid == 1 && pa_streq(d->address, remote) && pa_streq(d->adapter->address, local)) + return d; return NULL; } @@ -317,6 +416,9 @@ static void device_free(pa_bluetooth_device *d) { pa_bluetooth_transport_free(t); } + if (d->uuids) + pa_hashmap_free(d->uuids); + d->discovery = NULL; d->adapter = NULL; pa_xfree(d->path); @@ -336,16 +438,20 @@ static void device_remove(pa_bluetooth_discovery *y, const char *path) { } } -static void device_remove_all(pa_bluetooth_discovery *y) { - pa_bluetooth_device *d; +static void set_device_info_valid(pa_bluetooth_device *device, int valid) { + bool old_any_connected; - pa_assert(y); + pa_assert(device); + pa_assert(valid == -1 || valid == 0 || valid == 1); - while ((d = pa_hashmap_steal_first(y->devices))) { - d->device_info_valid = -1; - pa_hook_fire(&y->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], d); - device_free(d); - } + if (valid == device->device_info_valid) + return; + + old_any_connected = pa_bluetooth_device_any_transport_connected(device); + device->device_info_valid = valid; + + if (pa_bluetooth_device_any_transport_connected(device) != old_any_connected) + pa_hook_fire(&device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], device); } static pa_bluetooth_adapter* adapter_create(pa_bluetooth_discovery *y, const char *path) { @@ -363,20 +469,423 @@ static pa_bluetooth_adapter* adapter_create(pa_bluetooth_discovery *y, const cha return a; } -static void adapter_remove_all(pa_bluetooth_discovery *y) { +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) + if (d->adapter == a) { + set_device_info_valid(d, -1); + d->adapter = NULL; + } + + 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; - pa_assert(y); + 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); + } +} - /* When this function is called all devices have already been freed */ +static void parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) { + const char *key; + DBusMessageIter variant_i; - while ((a = pa_hashmap_steal_first(y->adapters))) { - pa_xfree(a->path); - pa_xfree(a->address); - pa_xfree(a); + 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")) { + if (is_property_change) { + 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")) { + + if (is_property_change) { + pa_log_warn("Device property 'Adapter' expected to be constant but changed for %s, ignoring", d->path); + return; + } + + if (d->adapter_path) { + pa_log_warn("Device %s: Received a duplicate 'Adapter' property, ignoring", d->path); + return; + } + + d->adapter_path = pa_xstrdup(value); + + d->adapter = pa_hashmap_get(d->discovery->adapters, value); + if (!d->adapter) + pa_log_info("Device %s: 'Adapter' property references an unknown adapter %s.", d->path, value); + + 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")) { + /* bluetoothd never removes UUIDs from a device object so there + * is no need to handle it here. */ + 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; + } } } +static int parse_device_properties(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) { + 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_device_property(d, &dict_i, is_property_change); + dbus_message_iter_next(&element_i); + } + + if (!d->address || !d->adapter_path || !d->alias) { + pa_log_error("Non-optional information missing for device %s", d->path); + set_device_info_valid(d, -1); + return -1; + } + + if (!is_property_change && d->adapter) + set_device_info_valid(d, 1); + + /* If d->adapter is NULL, device_info_valid will be left as 0, and updated + * after all interfaces have been parsed. */ + + return 0; +} + +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")) { + const char *value; + + 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); + } + + dbus_message_iter_next(&element_i); + } +} + +static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) { + 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); +} + +static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) { + DBusMessage *m; + DBusMessageIter i, d; + uint8_t codec = 0; + + pa_log_debug("Registering %s on adapter %s", endpoint, path); + + pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, path, BLUEZ_MEDIA_INTERFACE, "RegisterEndpoint")); + + dbus_message_iter_init_append(m, &i); + dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint); + 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); + pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec); + + if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE) || pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK)) { + a2dp_sbc_t capabilities; + + capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | + SBC_CHANNEL_MODE_JOINT_STEREO; + capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | + SBC_SAMPLING_FREQ_48000; + capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS; + capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8; + capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16; + capabilities.min_bitpool = MIN_BITPOOL; + capabilities.max_bitpool = MAX_BITPOOL; + + pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities)); + } + + dbus_message_iter_close_container(&i, &d); + + send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint)); +} + +static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) { + DBusMessageIter element_i; + const char *path; + void *state; + pa_bluetooth_device *d; + + 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))) { + pa_log_error("Found duplicated D-Bus path for device %s", path); + return; + } else + a = adapter_create(y, path); + + pa_log_debug("Adapter %s found", path); + + parse_adapter_properties(a, &iface_i, false); + if (!a->address) + return; + + register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SOURCE); + register_endpoint(y, path, A2DP_SINK_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SINK); + + } else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) { + + if ((d = pa_hashmap_get(y->devices, path))) { + if (d->device_info_valid != 0) { + 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); + + parse_device_properties(d, &iface_i, false); + + } else + pa_log_debug("Unknown interface %s found, skipping", interface); + + dbus_message_iter_next(&element_i); + } + + PA_HASHMAP_FOREACH(d, y->devices, state) { + if (d->device_info_valid != 0) + continue; + + if (!d->adapter && d->adapter_path) { + d->adapter = pa_hashmap_get(d->discovery->adapters, d->adapter_path); + if (!d->adapter || !d->adapter->address) { + pa_log_error("Device %s is child of nonexistent or corrupted adapter %s", d->path, d->adapter_path); + set_device_info_valid(d, -1); + } else + set_device_info_valid(d, 1); + } + } + + return; +} + +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); + + parse_interfaces_and_properties(y, &dict_i); + + dbus_message_iter_next(&element_i); + } + + y->objects_listed = true; + +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); +} + 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); @@ -409,16 +918,123 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us if (pa_streq(name, BLUEZ_SERVICE)) { if (old_owner && *old_owner) { pa_log_debug("Bluetooth daemon disappeared"); - device_remove_all(y); - adapter_remove_all(y); + pa_hashmap_remove_all(y->devices); + pa_hashmap_remove_all(y->adapters); + y->objects_listed = false; } if (new_owner && *new_owner) { pa_log_debug("Bluetooth daemon appeared"); - /* TODO: get managed objects */ + get_managed_objects(y); } } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } 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; + } + + parse_interfaces_and_properties(y, &arg_i); + + 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; + + } 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); + + } 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; + } + + if (d->device_info_valid != 1) { + pa_log_warn("Properties changed in a device which information is unknown or invalid"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + parse_device_properties(d, &arg_i, true); + } 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); + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } @@ -902,14 +1518,13 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) { DBusConnection *conn; unsigned i; - if ((y = pa_shared_get(c, "bluetooth-discovery"))) - return pa_bluetooth_discovery_ref(y); - y = pa_xnew0(pa_bluetooth_discovery, 1); PA_REFCNT_INIT(y); y->core = c; - y->adapters = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + y->adapters = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, + (pa_free_cb_t) adapter_free); + y->devices = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, + (pa_free_cb_t) device_free); y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending); @@ -937,6 +1552,15 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) { if (pa_dbus_add_matches(conn, &err, "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'" ",arg0='" BLUEZ_SERVICE "'", + "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'", + "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager'," + "member='InterfacesRemoved'", + "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'" + ",arg0='" BLUEZ_ADAPTER_INTERFACE "'", + "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'" + ",arg0='" BLUEZ_DEVICE_INTERFACE "'", + "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'" + ",arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'", NULL) < 0) { pa_log_error("Failed to add D-Bus matches: %s", err.message); goto fail; @@ -946,6 +1570,8 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) { endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SINK); endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE); + get_managed_objects(y); + return y; fail: @@ -973,15 +1599,11 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) { pa_dbus_free_pending_list(&y->pending); - if (y->devices) { - device_remove_all(y); + if (y->devices) pa_hashmap_free(y->devices); - } - if (y->adapters) { - adapter_remove_all(y); + if (y->adapters) pa_hashmap_free(y->adapters); - } if (y->transports) { pa_assert(pa_hashmap_isempty(y->transports)); @@ -994,6 +1616,16 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) { 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 "'", + "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager'," + "member='InterfacesAdded'", + "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.ObjectManager'," + "member='InterfacesRemoved'", + "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties'," + "member='PropertiesChanged',arg0='" BLUEZ_ADAPTER_INTERFACE "'", + "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties'," + "member='PropertiesChanged',arg0='" BLUEZ_DEVICE_INTERFACE "'", + "type='signal',sender='" BLUEZ_SERVICE "',interface='org.freedesktop.DBus.Properties'," + "member='PropertiesChanged',arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'", NULL); if (y->filter_added)