[pulseaudio-commits] [Git][pulseaudio/pulseaudio][master] 9 commits: bt/bluez5-device: Update link to assigned Baseband numbers

PulseAudio Marge Bot (@pulseaudio-merge-bot) gitlab at gitlab.freedesktop.org
Fri Jul 30 12:51:17 UTC 2021



PulseAudio Marge Bot pushed to branch master at PulseAudio / pulseaudio


Commits:
66e26723 by Marijn Suijten at 2021-07-28T09:11:43+02:00
bt/bluez5-device: Update link to assigned Baseband numbers

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/482>

- - - - -
7a84a246 by Sebastian Reichel at 2021-07-28T09:11:43+02:00
bluetooth: backend-native: add battery level reporting

Devices for Apple's iOS uses a few extra HFP AT commands to
inform the iPhone about the headphone's battery status.
Apple documented the AT commands in the following document:

https://developer.apple.com/hardwaredrivers/BluetoothDesignGuidelines.pdf

The patch has been tested with a Bose QC35, which results
in the following communication:

D: [pulseaudio] backend-native.c: RFCOMM << AT+VGS=14
D: [pulseaudio] backend-native.c: RFCOMM >> OK
D: [pulseaudio] backend-native.c: RFCOMM << AT+XAPL=009E-400C-0129,3
D: [pulseaudio] backend-native.c: RFCOMM >> +XAPL=iPhone,2
D: [pulseaudio] backend-native.c: RFCOMM >> OK
D: [pulseaudio] backend-native.c: RFCOMM << AT+XEVENT=Bose SoundLink,158
D: [pulseaudio] backend-native.c: RFCOMM >> OK
D: [pulseaudio] backend-native.c: RFCOMM << AT+IPHONEACCEV=2,1,4,2,0
N: [pulseaudio] backend-native.c: Battery Level: 50%
N: [pulseaudio] backend-native.c: Dock Status: undocked
D: [pulseaudio] backend-native.c: RFCOMM >> OK

[Marijn: Adapt for recent HSP/HFP code changes]

Co-authored-by: Marijn Suijten <marijns95 at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/482>

- - - - -
4cbac238 by Marijn Suijten at 2021-07-28T09:11:43+02:00
bluetooth/native: Signal support for dock status in XAPL reply

The previous commit parses both battery level and dock status (if only
for printing to logs). Make sure bit `2` is set in the `+XAPL=` reply to
signify support for reading this, too.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/482>

- - - - -
e6157c8b by Marijn Suijten at 2021-07-28T09:11:43+02:00
bt/native: Answer AT command with ERROR if unhandled

The peer will wait some time and eventually time out the connection if
no reply is sent back. When sending `ERROR` the peer can decide to break
the RFCOMM connection immediately or continue when a command is not
critical.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/482>

- - - - -
d2c97190 by Marijn Suijten at 2021-07-28T09:11:43+02:00
bt/native: Parse specified number of arguments in IPHONEACCEV

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/482>

- - - - -
c667befe by Marijn Suijten at 2021-07-28T09:11:43+02:00
bluetooth: Provide (HSP/HFP-received) battery level as device property

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/482>

- - - - -
f7955eeb by Marijn Suijten at 2021-07-28T09:11:43+02:00
bluetooth: Register as BlueZ experimental BatteryProvider1

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/482>

- - - - -
713e3f06 by Marijn Suijten at 2021-07-28T09:11:43+02:00
bluetooth: Deregister battery provider when profile disconnects

Whenever a device disconnects the device is not removed from BlueZ, only
the profiles that had an active connection are disconnected. Since we
were providing this battery level based on AT commands received through
HSP/HFP these services should be responsible for deregistering it again.

Deregister the interface to signal BlueZ (And UPower in return) that the
battery level won't be accurate/updated anymore.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/482>

- - - - -
a246bb77 by Marijn Suijten at 2021-07-28T09:11:43+02:00
bluetooth/native: Accept and report battery HF indicator value

HF indicator 2 (see [assigned-numbers], Hands-Free Profile) is able to
report battery percentage at 1% intervals (in range [0, 100]), contrary
to the `+XAPL` `+IPHONEACCEV` extension which only supports 10%
increments.  This does not guarantee increased granularity however, as
peers may still be limited to imprecise battery measurements internally
or round to coarser percentages.
Supporting both additionally broadens the range of devices for which PA
can report its battery level.

[assigned-numbers]: https://www.bluetooth.com/specifications/assigned-numbers/

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/482>

- - - - -


4 changed files:

- src/modules/bluetooth/backend-native.c
- src/modules/bluetooth/bluez5-util.c
- src/modules/bluetooth/bluez5-util.h
- src/modules/bluetooth/module-bluez5-device.c


Changes:

=====================================
src/modules/bluetooth/backend-native.c
=====================================
@@ -61,6 +61,7 @@ struct hfp_config {
     int state;
     bool support_codec_negotiation;
     bool support_msbc;
+    bool supports_indicators;
     int selected_codec;
 };
 
@@ -76,6 +77,7 @@ enum hfp_hf_features {
     HFP_HF_ESTATUS = 5,
     HFP_HF_ECALL = 6,
     HFP_HF_CODECS = 7,
+    HFP_HF_INDICATORS = 8,
 };
 
 enum hfp_ag_features {
@@ -89,12 +91,13 @@ enum hfp_ag_features {
     HFP_AG_ECALL = 7,
     HFP_AG_EERR = 8,
     HFP_AG_CODECS = 9,
+    HFP_AG_INDICATORS = 10,
 };
 
 /* gateway features we support, which is as little as we can get away with */
 static uint32_t hfp_features =
     /* HFP 1.6 requires this */
-    (1 << HFP_AG_ESTATUS ) | (1 << HFP_AG_CODECS);
+    (1 << HFP_AG_ESTATUS ) | (1 << HFP_AG_CODECS) | (1 << HFP_AG_INDICATORS);
 
 #define HSP_AG_PROFILE "/Profile/HSPAGProfile"
 #define HFP_AG_PROFILE "/Profile/HFPAGProfile"
@@ -559,7 +562,7 @@ static pa_volume_t set_source_volume(pa_bluetooth_transport *t, pa_volume_t volu
 static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf)
 {
     struct hfp_config *c = t->config;
-    int val;
+    int indicator, val;
     char str[5];
     const char *r;
     size_t len;
@@ -574,6 +577,7 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
         c->capabilities = val;
         pa_log_info("HFP capabilities returns 0x%x", val);
         rfcomm_write_response(fd, "+BRSF: %d", hfp_features);
+        c->supports_indicators = !!(1 << HFP_HF_INDICATORS);
         c->state = 1;
 
         return true;
@@ -605,7 +609,7 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
         /* we declare minimal no indicators */
         rfcomm_write_response(fd, "+CIND: "
                      /* many indicators can be supported, only call and
-                      * callheld are mandatory, so that's all we repy */
+                      * callheld are mandatory, so that's all we reply */
                      "(\"service\",(0-1)),"
                      "(\"call\",(0-1)),"
                      "(\"callsetup\",(0-3)),"
@@ -655,6 +659,35 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
             transport_put(t);
         }
 
+        return true;
+    } else if (c->supports_indicators && pa_startswith(buf, "AT+BIND=?")) {
+        // Support battery indication
+        rfcomm_write_response(fd, "+BIND: (2)");
+        return true;
+    } else if (c->supports_indicators && pa_startswith(buf, "AT+BIND?")) {
+        // Battery indication is enabled
+        rfcomm_write_response(fd, "+BIND: 2,1");
+        return true;
+    } else if (c->supports_indicators && pa_startswith(buf, "AT+BIND=")) {
+        // If this comma-separated list contains `2`, the HF is
+        // able to report values for the battery indicator.
+        return true;
+    } else if (c->supports_indicators && sscanf(buf, "AT+BIEV=%u,%u", &indicator, &val)) {
+        switch (indicator) {
+            case 2:
+                pa_log_notice("Battery Level: %d%%", val);
+                if (val < 0 || val > 100) {
+                    pa_log_error("Battery HF indicator %d out of [0, 100] range", val);
+                    rfcomm_write_response(fd, "ERROR");
+                    return false;
+                }
+                pa_bluetooth_device_report_battery_level(t->device, val, "HFP 1.7 HF indicator");
+                break;
+            default:
+                pa_log_error("Unknown HF indicator %u", indicator);
+                rfcomm_write_response(fd, "ERROR");
+                return false;
+        }
         return true;
     } if (c->state == 4) {
         /* the ack for the codec setting may take a while. we need
@@ -686,6 +719,11 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
 
     if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
         pa_log_info("Lost RFCOMM connection.");
+        // TODO: Keep track of which profile is the current battery provider,
+        // only deregister if it is us currently providing these levels.
+        // (Also helpful to fill the 'Source' property)
+        // We might also move this to Profile1::RequestDisconnection
+        pa_bluetooth_device_deregister_battery(t->device);
         goto fail;
     }
 
@@ -693,7 +731,9 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
         char buf[512];
         ssize_t len;
         int gain, dummy;
-        bool  do_reply = false;
+        bool do_reply = false;
+        int vendor, product, version, features;
+        int num;
 
         len = pa_read(fd, buf, 511, NULL);
         if (len < 0) {
@@ -734,9 +774,55 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
             do_reply = true;
         } else if (sscanf(buf, "AT+CKPD=%d", &dummy) == 1) {
             do_reply = true;
+        } else if (sscanf(buf, "AT+XAPL=%04x-%04x-%04x,%d", &vendor, &product, &version, &features) == 4) {
+            if (features & 0x2)
+                /* claim, that we support battery status reports */
+                rfcomm_write_response(fd, "+XAPL=iPhone,6");
+            do_reply = true;
+        } else if (sscanf(buf, "AT+IPHONEACCEV=%d", &num) == 1) {
+            char *substr = buf, *keystr;
+            int key, val, i;
+
+            do_reply = true;
+
+            for (i = 0; i < num; ++i) {
+                keystr = strchr(substr, ',');
+                if (!keystr) {
+                    pa_log_warn("%s misses key for argument #%d", buf, i);
+                    do_reply = false;
+                    break;
+                }
+                keystr++;
+                substr = strchr(keystr, ',');
+                if (!substr) {
+                    pa_log_warn("%s misses value for argument #%d", buf, i);
+                    do_reply = false;
+                    break;
+                }
+                substr++;
+
+                key = atoi(keystr);
+                val = atoi(substr);
+
+                switch (key) {
+                    case 1:
+                        pa_log_notice("Battery Level: %d0%%", val + 1);
+                        pa_bluetooth_device_report_battery_level(t->device, (val + 1) * 10, "Apple accessory indication");
+                        break;
+                    case 2:
+                        pa_log_notice("Dock Status: %s", val ? "docked" : "undocked");
+                        break;
+                    default:
+                        pa_log_debug("Unexpected IPHONEACCEV key %#x", key);
+                        break;
+                }
+            }
+            if (!do_reply)
+                rfcomm_write_response(fd, "ERROR");
         } else if (t->config) { /* t->config is only non-null for hfp profile */
             do_reply = hfp_rfcomm_handle(fd, t, buf);
         } else {
+            rfcomm_write_response(fd, "ERROR");
             do_reply = false;
         }
 


=====================================
src/modules/bluetooth/bluez5-util.c
=====================================
@@ -50,6 +50,7 @@
 #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 PULSEAUDIO_BASE_PATH "/org/pulseaudio"
 
 #define OBJECT_MANAGER_INTROSPECT_XML                                          \
     DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                                  \
@@ -868,6 +869,100 @@ bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
     return false;
 }
 
+/* Returns a path containing /org/pulseaudio + /bluez/hciXX */
+static char *adapter_battery_provider_path(pa_bluetooth_adapter *d) {
+    const char *devname = d->path + sizeof("/org") - 1;
+    return pa_sprintf_malloc(PULSEAUDIO_BASE_PATH "%s", devname);
+}
+
+/* Returns a path containing /org/pulseaudio + /bluez/hciXX/dev_XX_XX_XX_XX_XX_XX */
+static char *device_battery_provider_path(pa_bluetooth_device *d) {
+    const char *devname = d->path + sizeof("/org") - 1;
+    return pa_sprintf_malloc(PULSEAUDIO_BASE_PATH "%s", devname);
+}
+
+static void append_battery_provider(pa_bluetooth_device *d, DBusMessageIter *object);
+static void append_battery_provider_properties(pa_bluetooth_device *d, DBusMessageIter *object, bool only_percentage);
+
+void pa_bluetooth_device_report_battery_level(pa_bluetooth_device *d, uint8_t level, const char *reporting_source) {
+    bool had_battery_provider = d->has_battery_level;
+    d->has_battery_level = true;
+    d->battery_level = level;
+    pa_assert_se(d->battery_source = reporting_source);
+
+    pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_BATTERY_LEVEL_CHANGED], d);
+
+    if (!had_battery_provider) {
+        DBusMessage *m;
+        DBusMessageIter iter;
+        char *provider_path;
+
+        if (!d->adapter->battery_provider_registered) {
+            pa_log_debug("No battery provider registered on adapter of %s", d->path);
+            return;
+        }
+
+        provider_path = adapter_battery_provider_path(d->adapter);
+
+        pa_log_debug("Registering new battery for %s with level %d", d->path, level);
+
+        pa_assert_se(m = dbus_message_new_signal(provider_path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesAdded"));
+        dbus_message_iter_init_append(m, &iter);
+        append_battery_provider(d, &iter);
+        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(d->discovery->connection), m, NULL));
+
+        pa_xfree(provider_path);
+    } else {
+        DBusMessage *m;
+        DBusMessageIter iter;
+        char *battery_path = device_battery_provider_path(d);
+
+        pa_log_debug("Notifying battery Percentage for %s changed %d", battery_path, level);
+
+        pa_assert_se(m = dbus_message_new_signal(battery_path, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged"));
+        dbus_message_iter_init_append(m, &iter);
+        append_battery_provider_properties(d, &iter, true);
+        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(d->discovery->connection), m, NULL));
+        pa_xfree(battery_path);
+    }
+}
+
+/* Notify BlueZ that we're no longer providing battery info for this device */
+void pa_bluetooth_device_deregister_battery(pa_bluetooth_device *d) {
+    static const char *interface_name = BLUEZ_BATTERY_PROVIDER_INTERFACE;
+    DBusMessage *m;
+    DBusMessageIter iter, array;
+    char *battery_path, *provider_path;
+
+    if (!d->has_battery_level)
+        return;
+
+    d->has_battery_level = false;
+    pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_BATTERY_LEVEL_CHANGED], d);
+
+    if (!d->adapter->battery_provider_registered)
+        return;
+
+    battery_path = device_battery_provider_path(d);
+    provider_path = adapter_battery_provider_path(d->adapter);
+
+    pa_log_debug("Deregistering battery provider %s", battery_path);
+
+    pa_assert_se(m = dbus_message_new_signal(provider_path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesRemoved"));
+    dbus_message_iter_init_append(m, &iter);
+    pa_assert_se(dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &battery_path));
+    pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &array));
+    pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &interface_name));
+    pa_assert_se(dbus_message_iter_close_container(&iter, &array));
+
+    pa_assert_se(dbus_connection_send(pa_dbus_connection_get(d->discovery->connection), m, NULL));
+    d->has_battery_level = false;
+
+    pa_xfree(battery_path);
+    pa_xfree(provider_path);
+}
+
+
 static int transport_state_from_string(const char* value, pa_bluetooth_transport_state_t *state) {
     pa_assert(value);
     pa_assert(state);
@@ -1164,6 +1259,179 @@ static void device_set_adapter(pa_bluetooth_device *device, pa_bluetooth_adapter
     device_update_valid(device);
 }
 
+static void append_battery_provider_properties(pa_bluetooth_device *d, DBusMessageIter *entry, bool only_percentage) {
+    static const char *interface_name = BLUEZ_BATTERY_PROVIDER_INTERFACE;
+    DBusMessageIter dict;
+
+    pa_assert_se(dbus_message_iter_append_basic(entry, DBUS_TYPE_STRING, &interface_name));
+
+    pa_assert_se(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, "Percentage", DBUS_TYPE_BYTE, &d->battery_level);
+
+    if (!only_percentage) {
+        pa_assert(d->battery_source);
+        pa_dbus_append_basic_variant_dict_entry(&dict, "Device", DBUS_TYPE_OBJECT_PATH, &d->path);
+        pa_dbus_append_basic_variant_dict_entry(&dict, "Source", DBUS_TYPE_STRING, &d->battery_source);
+    }
+
+    pa_assert_se(dbus_message_iter_close_container(entry, &dict));
+}
+
+static void append_battery_provider(pa_bluetooth_device *d, DBusMessageIter *object) {
+    char *battery_path = device_battery_provider_path(d);
+    DBusMessageIter array, entry;
+
+    pa_assert_se(dbus_message_iter_append_basic(object, DBUS_TYPE_OBJECT_PATH, &battery_path));
+
+    pa_assert_se(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));
+
+    pa_assert_se(dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &entry));
+    append_battery_provider_properties(d, &entry, false);
+    pa_assert_se(dbus_message_iter_close_container(&array, &entry));
+    pa_assert_se(dbus_message_iter_close_container(object, &array));
+
+    pa_xfree(battery_path);
+}
+
+static DBusHandlerResult battery_provider_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
+    pa_bluetooth_adapter *a = userdata;
+    DBusMessage *r = NULL;
+    const char *path, *interface, *member;
+
+    pa_assert(a);
+
+    path = dbus_message_get_path(m);
+    interface = dbus_message_get_interface(m);
+    member = dbus_message_get_member(m);
+
+    pa_log_debug("%s %s %s", path, interface, member);
+
+    if (dbus_message_is_method_call(m, DBUS_INTERFACE_OBJECT_MANAGER, "GetManagedObjects")) {
+        DBusMessageIter iter, array, object;
+        pa_bluetooth_device *d;
+        void *state;
+
+        pa_assert_se(r = dbus_message_new_method_return(m));
+
+        dbus_message_iter_init_append(r, &iter);
+        pa_assert_se(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));
+
+        PA_HASHMAP_FOREACH(d, a->discovery->devices, state) {
+
+            if (d->has_battery_level) {
+                pa_log_debug("%s: battery level  = %d", d->path, d->battery_level);
+                pa_assert_se(dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &object));
+                append_battery_provider(d, &object);
+                pa_assert_se(dbus_message_iter_close_container(&array, &object));
+            }
+        }
+
+        pa_assert_se(dbus_message_iter_close_container(&iter, &array));
+    } else
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    pa_assert_se(dbus_connection_send(c, r, NULL));
+    dbus_message_unref(r);
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static void adapter_register_battery_provider(pa_bluetooth_adapter *a) {
+    DBusMessage *m, *r;
+    DBusError error;
+
+    static const DBusObjectPathVTable vtable_profile = {
+        .message_function = battery_provider_handler,
+    };
+
+    char *provider_path = adapter_battery_provider_path(a);
+
+    pa_log_debug("Registering battery provider for %s at %s", a->path, provider_path);
+
+    pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(a->discovery->connection), provider_path, &vtable_profile, a));
+
+    pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, a->path, BLUEZ_BATTERY_PROVIDER_MANAGER_INTERFACE, "RegisterBatteryProvider"));
+    pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_OBJECT_PATH, &provider_path, DBUS_TYPE_INVALID));
+
+    dbus_error_init(&error);
+    if (!(r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(a->discovery->connection), m, -1, &error))) {
+        if (dbus_error_has_name(&error, DBUS_ERROR_UNKNOWN_METHOD))
+            pa_log_notice("Could not find " BLUEZ_BATTERY_PROVIDER_MANAGER_INTERFACE
+                          ".RegisterBatteryProvider(), is bluetoothd started with experimental features enabled (-E flag)?");
+        else
+            pa_log_warn(BLUEZ_BATTERY_PROVIDER_MANAGER_INTERFACE ".RegisterBatteryProvider() Failed: %s:%s", error.name, error.message);
+        dbus_error_free(&error);
+        dbus_connection_unregister_object_path(pa_dbus_connection_get(a->discovery->connection), provider_path);
+    } else {
+        dbus_message_unref(r);
+        a->battery_provider_registered = true;
+    }
+
+    dbus_message_unref(m);
+    pa_xfree(provider_path);
+}
+
+static void adapter_deregister_battery_provider(pa_bluetooth_adapter *a) {
+    DBusMessage *m, *r;
+    DBusError error;
+    char *provider_path;
+
+    if (!a->battery_provider_registered) {
+        pa_log_debug("No battery provider registered for %s", a->path);
+        return;
+    }
+
+    provider_path = adapter_battery_provider_path(a);
+
+    pa_log_debug("Deregistering battery provider at %s", provider_path);
+
+    pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, a->path, BLUEZ_BATTERY_PROVIDER_MANAGER_INTERFACE, "UnregisterBatteryProvider"));
+    pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_OBJECT_PATH, &provider_path, DBUS_TYPE_INVALID));
+
+    dbus_error_init(&error);
+    if (!(r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(a->discovery->connection), m, -1, &error))) {
+        pa_log_error(BLUEZ_BATTERY_PROVIDER_MANAGER_INTERFACE ".UnregisterBatteryProvider() Failed: %s:%s", error.name, error.message);
+        dbus_error_free(&error);
+    } else {
+        dbus_message_unref(r);
+        a->battery_provider_registered = false;
+    }
+
+    dbus_message_unref(m);
+
+    dbus_connection_unregister_object_path(pa_dbus_connection_get(a->discovery->connection), provider_path);
+
+    pa_xfree(provider_path);
+}
+
 static pa_bluetooth_adapter* adapter_create(pa_bluetooth_discovery *y, const char *path) {
     pa_bluetooth_adapter *a;
 
@@ -1186,6 +1454,8 @@ static void adapter_free(pa_bluetooth_adapter *a) {
     pa_assert(a);
     pa_assert(a->discovery);
 
+    adapter_deregister_battery_provider(a);
+
     PA_HASHMAP_FOREACH(d, a->discovery->devices, state)
         if (d->adapter == a)
             device_set_adapter(d, NULL);
@@ -1720,6 +1990,7 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
                 return;
 
             register_application(a);
+            adapter_register_battery_provider(a);
         } else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
 
             if ((d = pa_hashmap_get(y->devices, path))) {


=====================================
src/modules/bluetooth/bluez5-util.h
=====================================
@@ -27,6 +27,8 @@
 
 #define BLUEZ_SERVICE "org.bluez"
 #define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1"
+#define BLUEZ_BATTERY_PROVIDER_INTERFACE BLUEZ_SERVICE ".BatteryProvider1"
+#define BLUEZ_BATTERY_PROVIDER_MANAGER_INTERFACE BLUEZ_SERVICE ".BatteryProviderManager1"
 #define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1"
 #define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1"
 #define BLUEZ_MEDIA_INTERFACE BLUEZ_SERVICE ".Media1"
@@ -64,6 +66,7 @@ typedef struct pa_bluetooth_backend pa_bluetooth_backend;
 typedef enum pa_bluetooth_hook {
     PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED,        /* Call data: pa_bluetooth_device */
     PA_BLUETOOTH_HOOK_DEVICE_UNLINK,                    /* Call data: pa_bluetooth_device */
+    PA_BLUETOOTH_HOOK_DEVICE_BATTERY_LEVEL_CHANGED,     /* Call data: pa_bluetooth_device */
     PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,          /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_SOURCE_VOLUME_CHANGED,  /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_SINK_VOLUME_CHANGED,    /* Call data: pa_bluetooth_transport */
@@ -150,6 +153,10 @@ struct pa_bluetooth_device {
     pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT];
 
     pa_time_event *wait_for_profiles_timer;
+
+    bool has_battery_level;
+    uint8_t battery_level;
+    const char *battery_source;
 };
 
 struct pa_bluetooth_adapter {
@@ -159,6 +166,7 @@ struct pa_bluetooth_adapter {
 
     bool valid;
     bool application_registered;
+    bool battery_provider_registered;
 };
 
 #ifdef HAVE_BLUEZ_5_OFONO_HEADSET
@@ -197,6 +205,8 @@ void pa_bluetooth_transport_load_a2dp_sink_volume(pa_bluetooth_transport *t);
 
 bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d);
 bool pa_bluetooth_device_switch_codec(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, pa_hashmap *capabilities_hashmap, const pa_a2dp_endpoint_conf *endpoint_conf, void (*codec_switch_cb)(bool, pa_bluetooth_profile_t profile, void *), void *userdata);
+void pa_bluetooth_device_report_battery_level(pa_bluetooth_device *d, uint8_t level, const char *reporting_source);
+void pa_bluetooth_device_deregister_battery(pa_bluetooth_device *d);
 
 pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_path(pa_bluetooth_discovery *y, const char *path);
 pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_discovery *y, const char *remote, const char *local);


=====================================
src/modules/bluetooth/module-bluez5-device.c
=====================================
@@ -103,6 +103,7 @@ struct userdata {
     pa_core *core;
 
     pa_hook_slot *device_connection_changed_slot;
+    pa_hook_slot *device_battery_level_changed_slot;
     pa_hook_slot *transport_state_changed_slot;
     pa_hook_slot *transport_sink_volume_changed_slot;
     pa_hook_slot *transport_source_volume_changed_slot;
@@ -186,8 +187,8 @@ static pa_bluetooth_form_factor_t form_factor_from_class(uint32_t class_of_devic
     };
 
     /*
-     * See Bluetooth Assigned Numbers:
-     * https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
+     * See Bluetooth Assigned Numbers for Baseband
+     * https://www.bluetooth.com/specifications/assigned-numbers/baseband/
      */
     major = (class_of_device >> 8) & 0x1F;
     minor = (class_of_device >> 2) & 0x3F;
@@ -2157,6 +2158,12 @@ static int add_card(struct userdata *u) {
     data.name = pa_sprintf_malloc("bluez_card.%s", d->address);
     data.namereg_fail = false;
 
+    if (d->has_battery_level) {
+        // See device_battery_level_changed_cb
+        uint8_t level = d->battery_level;
+        pa_proplist_setf(data.proplist, "bluetooth.battery", "%d%%", level);
+    }
+
     create_card_ports(u, data.ports);
 
     PA_HASHMAP_FOREACH(uuid, d->uuids, state) {
@@ -2295,6 +2302,25 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
     return PA_HOOK_OK;
 }
 
+static pa_hook_result_t device_battery_level_changed_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
+    uint8_t level;
+
+    pa_assert(d);
+    pa_assert(u);
+
+    if (d != u->device)
+        return PA_HOOK_OK;
+
+    if (d->has_battery_level) {
+        level = d->battery_level;
+        pa_proplist_setf(u->card->proplist, "bluetooth.battery", "%d%%", level);
+    } else {
+        pa_proplist_unset(u->card->proplist, "bluetooth.battery");
+    }
+
+    return PA_HOOK_OK;
+}
+
 /* Run from main thread */
 static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
     pa_assert(t);
@@ -2691,6 +2717,10 @@ int pa__init(pa_module* m) {
         pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
                         PA_HOOK_NORMAL, (pa_hook_cb_t) device_connection_changed_cb, u);
 
+    u->device_battery_level_changed_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_BATTERY_LEVEL_CHANGED),
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) device_battery_level_changed_cb, u);
+
     u->transport_state_changed_slot =
         pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED),
                         PA_HOOK_NORMAL, (pa_hook_cb_t) transport_state_changed_cb, u);
@@ -2767,6 +2797,9 @@ void pa__done(pa_module *m) {
     if (u->device_connection_changed_slot)
         pa_hook_slot_free(u->device_connection_changed_slot);
 
+    if (u->device_battery_level_changed_slot)
+        pa_hook_slot_free(u->device_battery_level_changed_slot);
+
     if (u->transport_state_changed_slot)
         pa_hook_slot_free(u->transport_state_changed_slot);
 



View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/6329a2498eb038f8a9537888280a62b00a93f68e...a246bb77c745ed1d2571120a604a1e9ec6c3f88a

-- 
View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/6329a2498eb038f8a9537888280a62b00a93f68e...a246bb77c745ed1d2571120a604a1e9ec6c3f88a
You're receiving this email because of your account on gitlab.freedesktop.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.freedesktop.org/archives/pulseaudio-commits/attachments/20210730/23f90fe0/attachment-0001.htm>


More information about the pulseaudio-commits mailing list