[pulseaudio-commits] [Git][pulseaudio/pulseaudio][master] 4 commits: bluez5-util: move pa_bluetooth_discovery to header

PulseAudio Marge Bot (@pulseaudio-merge-bot) gitlab at gitlab.freedesktop.org
Mon Aug 1 19:19:25 UTC 2022



PulseAudio Marge Bot pushed to branch master at PulseAudio / pulseaudio


Commits:
b05e34e0 by Dylan Van Assche at 2022-08-01T19:16:25+00:00
bluez5-util: move pa_bluetooth_discovery to header

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

- - - - -
cca0d693 by Dylan Van Assche at 2022-08-01T19:16:25+00:00
bluetooth: add AT+BIA support

AT+BIA is used to enable/disable CIND indicators by Bluetooth HFP spec.
By default, all indicators are enabled on connection.
AT+BIA will configure which indicators should be disabled then,
the disabled indicators may be enabled later on again with AT+BIA.
When the connection is lost and recovered, all indicators are enabled
again. The HF will reconfigure the indicators again with an AT+BIA
command.

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

- - - - -
36217310 by Dylan Van Assche at 2022-08-01T19:16:25+00:00
bluetooth: add UPower backend

UPower provides information about the power supply and battery
level of the host. Add a backend to retrieve the host battery level.

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

- - - - -
1b031ece by Dylan Van Assche at 2022-08-01T19:16:25+00:00
bluetooth: hook up UPower backend

Hook up the UPower backend to backend-native to report
the host battery level to the HF.

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

- - - - -


7 changed files:

- src/modules/bluetooth/backend-native.c
- src/modules/bluetooth/bluez5-util.c
- src/modules/bluetooth/bluez5-util.h
- src/modules/bluetooth/meson.build
- + src/modules/bluetooth/upower.c
- + src/modules/bluetooth/upower.h
- src/modules/meson.build


Changes:

=====================================
src/modules/bluetooth/backend-native.c
=====================================
@@ -38,15 +38,25 @@
 
 #include "bluez5-util.h"
 #include "bt-codec-msbc.h"
+#include "upower.h"
+
+#define MANDATORY_CALL_INDICATORS \
+        "(\"call\",(0-1))," \
+        "(\"callsetup\",(0-3))," \
+        "(\"callheld\",(0-2))" \
 
 struct pa_bluetooth_backend {
   pa_core *core;
   pa_dbus_connection *connection;
   pa_bluetooth_discovery *discovery;
   pa_hook_slot *adapter_uuids_changed_slot;
+  pa_hook_slot *host_battery_level_changed_slot;
+  pa_upower_backend *upower;
   bool enable_shared_profiles;
   bool enable_hsp_hs;
   bool enable_hfp_hf;
+  bool cmer_indicator_reporting_enabled;
+  uint32_t cind_enabled_indicators;
 
   PA_LLIST_HEAD(pa_dbus_pending, pending);
 };
@@ -97,6 +107,20 @@ enum hfp_ag_features {
     HFP_AG_INDICATORS = 10,
 };
 
+/*
+ * Always keep this struct in sync with indicator discovery of AT+CIND=?
+ * These indicators are used in bitflags and intentionally start at 1
+ * since AT+CIND indicators start at index 1.
+ */
+typedef enum pa_bluetooth_ag_to_hf_indicators {
+    CIND_CALL_INDICATOR = 1,
+    CIND_CALL_SETUP_INDICATOR = 2,
+    CIND_CALL_HELD_INDICATOR = 3,
+    CIND_SERVICE_INDICATOR = 4,
+    CIND_BATT_CHG_INDICATOR = 5,
+    CIND_INDICATOR_MAX = 6
+} pa_bluetooth_ag_to_hf_indicators_t;
+
 /* gateway features we support, which is as little as we can get away with */
 static uint32_t hfp_features =
     /* HFP 1.6 requires this */
@@ -588,12 +612,13 @@ 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 pa_bluetooth_discovery *discovery = t->device->discovery;
     struct hfp_config *c = t->config;
-    int indicator, val;
+    int indicator, mode, val;
     char str[5];
     const char *r;
     size_t len;
-    const char *state;
+    const char *state = NULL;
 
     /* first-time initialize selected codec to CVSD */
     if (c->selected_codec == 0)
@@ -608,11 +633,38 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
         c->state = 1;
 
         return true;
+    } else if (sscanf(buf, "AT+BIA=%s", str) == 1) {
+        /* Indicators start with index 1 and follow the order of the AT+CIND=? response */
+        indicator = 1;
+
+        while ((r = pa_split_in_place(str, ",", &len, &state))) {
+            /* Ignore updates to mandantory indicators which are always ON */
+            if (indicator == CIND_CALL_INDICATOR 
+                || indicator == CIND_CALL_SETUP_INDICATOR
+                || indicator == CIND_CALL_HELD_INDICATOR) 
+                continue;
+
+            /* Indicators may have no value and should be skipped */
+            if (len == 0)
+                continue;
+
+            if (len == 1 && r[0] == '1')
+                discovery->native_backend->cind_enabled_indicators |= (1 << indicator);
+            else if (len == 1 && r[0] == '0')
+                discovery->native_backend->cind_enabled_indicators &= ~(1 << indicator);
+	    else {
+                pa_log_error("Unable to parse indicator of AT+BIA command: %s", buf);
+                rfcomm_write_response(fd, "ERROR");
+	        return false;
+            }
+
+            indicator++;
+        }
+
+	return true;
     } else if (sscanf(buf, "AT+BAC=%3s", str) == 1) {
         c->support_msbc = false;
 
-        state = NULL;
-
         /* check if codec id 2 (mSBC) is in the list of supported codecs */
         while ((r = pa_split_in_place(str, ",", &len, &state))) {
             if (len == 1 && r[0] == '2') {
@@ -633,24 +685,49 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
 
         return true;
     } else if (c->state == 1 && pa_startswith(buf, "AT+CIND=?")) {
-        /* 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 reply */
-                     "(\"service\",(0-1)),"
-                     "(\"call\",(0-1)),"
-                     "(\"callsetup\",(0-3)),"
-                     "(\"callheld\",(0-2))");
+        /* UPower backend available, declare support for more indicators */
+        if (discovery->native_backend->upower) {
+            rfcomm_write_response(fd, "+CIND: "
+                         MANDATORY_CALL_INDICATORS ","
+                         "(\"service\",(0-1)),"
+                         "(\"battchg\",(0-5))");
+
+        /* Minimal indicators supported without any additional backend */
+        } else {
+            rfcomm_write_response(fd, "+CIND: "
+                         MANDATORY_CALL_INDICATORS ","
+			 "(\"service\",(0-1))");
+        }
         c->state = 2;
 
         return true;
     } else if (c->state == 2 && pa_startswith(buf, "AT+CIND?")) {
-        rfcomm_write_response(fd, "+CIND: 0,0,0,0");
+        if (discovery->native_backend->upower)
+            rfcomm_write_response(fd, "+CIND: 0,0,0,0,%u", pa_upower_get_battery_level(discovery->native_backend->upower));
+        else
+            rfcomm_write_response(fd, "+CIND: 0,0,0,0");
         c->state = 3;
 
         return true;
     } else if ((c->state == 2 || c->state == 3) && pa_startswith(buf, "AT+CMER=")) {
-        rfcomm_write_response(fd, "OK");
+	if (sscanf(buf, "AT+CMER=%d,%*d,%*d,%d", &mode, &val) == 2) {
+            /* Bluetooth HFP spec only defines mode == 3 */
+            if (mode != 3) {
+                pa_log_warn("Unexpected mode for AT+CMER: %d", mode);
+	    }
+
+	    /* Configure CMER event reporting */
+	    discovery->native_backend->cmer_indicator_reporting_enabled = !!val;
+
+            pa_log_debug("Event indications enabled? %s", pa_yes_no(val));
+
+            rfcomm_write_response(fd, "OK");
+        }
+        else {
+            pa_log_error("Unable to parse AT+CMER command: %s", buf);
+            rfcomm_write_response(fd, "ERROR");
+	    return false;
+        }
 
         if (c->support_codec_negotiation) {
             if (c->support_msbc && pa_bluetooth_discovery_get_enable_msbc(t->device->discovery)) {
@@ -738,8 +815,62 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
     return true;
 }
 
+static int get_rfcomm_fd (pa_bluetooth_discovery *discovery) {
+    struct pa_bluetooth_transport *t;
+    struct transport_data *trd = NULL;
+    void *state = NULL;
+
+    /* Find RFCOMM transport by checking if a HSP or HFP profile transport is available */
+    while ((t = pa_hashmap_iterate(discovery->transports, &state, NULL))) {
+        /* Skip non-connected transports */
+        if (!t || t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) {
+            pa_log_debug("Profile disconnected or unavailable");
+            continue;
+        }
+
+        /* Break when an RFCOMM capable transport profile is available */
+        if (t->profile == PA_BLUETOOTH_PROFILE_HFP_HF) {
+            trd = t->userdata;
+            break;
+        }
+    }
+
+    /* Skip if RFCOMM channel is not available yet */
+    if (!trd) {
+        pa_log_info("RFCOMM not available yet, skipping notification");
+        return -1;
+    }
+
+    return trd->rfcomm_fd;
+}
+
+static pa_hook_result_t host_battery_level_changed_cb(pa_bluetooth_discovery *y, const pa_upower_backend *u, pa_bluetooth_backend *b) {
+    int rfcomm_fd;
+
+    pa_assert(y);
+    pa_assert(u);
+    pa_assert(b);
+
+    /* Get RFCOMM channel if available */
+    rfcomm_fd = get_rfcomm_fd (y);
+    if (rfcomm_fd < 0)
+        return PA_HOOK_OK;
+
+    /* Notify HF about AG battery level change over RFCOMM */
+    if (b->cmer_indicator_reporting_enabled && (b->cind_enabled_indicators & (1 << CIND_BATT_CHG_INDICATOR))) {
+    	rfcomm_write_response(rfcomm_fd, "+CIEV: %d,%d", CIND_BATT_CHG_INDICATOR, u->battery_level);
+    	pa_log_debug("HG notified of AG's battery level change");
+    /* Skip notification if indicator is disabled or event reporting is completely disabled */
+    } else
+        pa_log_debug("Battery level change indicator disabled, skipping notification");
+
+    return PA_HOOK_OK;
+}
+
 static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
     pa_bluetooth_transport *t = userdata;
+    pa_bluetooth_discovery *discovery = t->device->discovery;
+    int i;
 
     pa_assert(io);
     pa_assert(t);
@@ -860,6 +991,11 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
     return;
 
 fail:
+    /* Service Connection lost, reset indicators and event reporting to default values */
+    discovery->native_backend->cmer_indicator_reporting_enabled = false;
+    for (i = 1; i < CIND_INDICATOR_MAX; i++)
+        discovery->native_backend->cind_enabled_indicators |= (1 << i);
+
     pa_bluetooth_transport_unlink(t);
     pa_bluetooth_transport_free(t);
 }
@@ -1188,6 +1324,7 @@ void pa_bluetooth_native_backend_enable_shared_profiles(pa_bluetooth_backend *na
 pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_shared_profiles) {
     pa_bluetooth_backend *backend;
     DBusError err;
+    int i;
 
     pa_log_debug("Bluetooth Headset Backend API support using the native backend");
 
@@ -1211,6 +1348,10 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d
         pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_ADAPTER_UUIDS_CHANGED), PA_HOOK_NORMAL,
                         (pa_hook_cb_t) adapter_uuids_changed_cb, backend);
 
+    backend->host_battery_level_changed_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED), PA_HOOK_NORMAL,
+                        (pa_hook_cb_t) host_battery_level_changed_cb, backend);
+
     if (!backend->enable_hsp_hs && !backend->enable_hfp_hf)
         pa_log_warn("Both HSP HS and HFP HF bluetooth profiles disabled in native backend. Native backend will not register for headset connections.");
 
@@ -1220,6 +1361,16 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d
     if (backend->enable_shared_profiles)
         native_backend_apply_profile_registration_change(backend, true);
 
+    backend->upower = pa_upower_backend_new(c, y);
+
+    /* All CIND indicators are enabled by default until overriden by AT+BIA */
+    for (i = 1; i < CIND_INDICATOR_MAX; i++)
+        backend->cind_enabled_indicators |= (1 << i);
+
+    /* While all CIND indicators are enabled, event reporting is not enabled by default */
+    backend->cmer_indicator_reporting_enabled = false;
+
+
     return backend;
 }
 
@@ -1231,12 +1382,18 @@ void pa_bluetooth_native_backend_free(pa_bluetooth_backend *backend) {
     if (backend->adapter_uuids_changed_slot)
         pa_hook_slot_free(backend->adapter_uuids_changed_slot);
 
+    if (backend->host_battery_level_changed_slot)
+        pa_hook_slot_free(backend->host_battery_level_changed_slot);
+
     if (backend->enable_shared_profiles)
         native_backend_apply_profile_registration_change(backend, false);
 
     if (backend->enable_hsp_hs)
         profile_done(backend, PA_BLUETOOTH_PROFILE_HSP_HS);
 
+    if (backend->upower)
+        pa_upower_backend_free(backend->upower);
+
     pa_dbus_connection_unref(backend->connection);
 
     pa_xfree(backend);


=====================================
src/modules/bluetooth/bluez5-util.c
=====================================
@@ -31,7 +31,6 @@
 #include <pulsecore/core.h>
 #include <pulsecore/core-error.h>
 #include <pulsecore/core-util.h>
-#include <pulsecore/dbus-shared.h>
 #include <pulsecore/log.h>
 #include <pulsecore/macro.h>
 #include <pulsecore/refcnt.h>
@@ -128,28 +127,6 @@ static uint16_t volume_to_a2dp_gain(pa_volume_t volume) {
     return gain;
 }
 
-struct pa_bluetooth_discovery {
-    PA_REFCNT_DECLARE;
-
-    pa_core *core;
-    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;
-    pa_hashmap *transports;
-    pa_bluetooth_profile_status_t profiles_status[PA_BLUETOOTH_PROFILE_COUNT];
-
-    int headset_backend;
-    pa_bluetooth_backend *ofono_backend, *native_backend;
-    PA_LLIST_HEAD(pa_dbus_pending, pending);
-    bool enable_native_hsp_hs;
-    bool enable_native_hfp_hf;
-    bool enable_msbc;
-};
-
 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m,
                                                                   DBusPendingCallNotifyFunction func, void *call_data) {
     pa_dbus_pending *p;


=====================================
src/modules/bluetooth/bluez5-util.h
=====================================
@@ -22,6 +22,7 @@
 ***/
 
 #include <pulsecore/core.h>
+#include <pulsecore/dbus-shared.h>
 
 #include "a2dp-codec-util.h"
 
@@ -62,12 +63,14 @@ typedef struct pa_bluetooth_device pa_bluetooth_device;
 typedef struct pa_bluetooth_adapter pa_bluetooth_adapter;
 typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
 typedef struct pa_bluetooth_backend pa_bluetooth_backend;
+typedef struct pa_upower_backend pa_upower_backend;
 
 typedef enum pa_bluetooth_hook {
     PA_BLUETOOTH_HOOK_ADAPTER_UUIDS_CHANGED,            /* Call data: pa_bluetooth_adapter */
     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_HOST_BATTERY_LEVEL_CHANGED,       /* Call data: pa_upower_backend */
     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 */
@@ -134,6 +137,29 @@ struct pa_bluetooth_transport {
     void *userdata;
 };
 
+struct pa_bluetooth_discovery {
+    PA_REFCNT_DECLARE;
+
+    pa_core *core;
+    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;
+    pa_hashmap *transports;
+    pa_bluetooth_profile_status_t profiles_status[PA_BLUETOOTH_PROFILE_COUNT];
+
+    int headset_backend;
+    pa_bluetooth_backend *ofono_backend, *native_backend;
+    PA_LLIST_HEAD(pa_dbus_pending, pending);
+    bool enable_native_hsp_hs;
+    bool enable_native_hfp_hf;
+    bool enable_msbc;
+};
+
+
 struct pa_bluetooth_device {
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_adapter *adapter;


=====================================
src/modules/bluetooth/meson.build
=====================================
@@ -16,6 +16,8 @@ libbluez5_util_headers = [
 
 if get_option('bluez5-native-headset')
   libbluez5_util_sources += [ 'backend-native.c' ]
+  libbluez5_util_sources += [ 'upower.c' ]
+  libbluez5_util_headers += [ 'upower.h' ]
 endif
 
 if get_option('bluez5-ofono-headset')
@@ -35,7 +37,7 @@ libbluez5_util = shared_library('bluez5-util',
   c_args : [pa_c_args, server_c_args],
   link_args : [nodelete_link_args],
   include_directories : [configinc, topinc],
-  dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, bluez_dep, dbus_dep, sbc_dep, libintl_dep, bluez5_gst_dep, bluez5_gstapp_dep],
+  dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, bluez_dep, dbus_dep, sbc_dep, libintl_dep, bluez5_gst_dep, bluez5_gstapp_dep, libm_dep],
   install : true,
   install_rpath : privlibdir,
   install_dir : modlibexecdir,


=====================================
src/modules/bluetooth/upower.c
=====================================
@@ -0,0 +1,300 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2022 Dylan Van Assche <me at dylanvanassche.be>
+
+  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
+  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-shared.h>
+#include <pulsecore/log.h>
+#include <pulse/timeval.h>
+#include <pulse/rtclock.h>
+
+#include "upower.h"
+
+static pa_dbus_pending* send_and_add_to_pending(pa_upower_backend *backend, DBusMessage *m,
+        DBusPendingCallNotifyFunction func, void *call_data) {
+
+    pa_dbus_pending *p;
+    DBusPendingCall *call;
+
+    pa_assert(backend);
+    pa_assert(m);
+
+    pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(backend->connection), m, &call, -1));
+
+    p = pa_dbus_pending_new(pa_dbus_connection_get(backend->connection), m, call, backend, call_data);
+    PA_LLIST_PREPEND(pa_dbus_pending, backend->pending, p);
+    dbus_pending_call_set_notify(call, func, p, NULL);
+
+    return p;
+}
+
+static void parse_percentage(pa_upower_backend *b, DBusMessageIter *i) {
+    double percentage;
+    unsigned int battery_level;
+
+    pa_assert(i);
+    pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_DOUBLE);
+
+    dbus_message_iter_get_basic(i, &percentage);
+    battery_level = (unsigned int) round(percentage / 20.0);
+
+    if (battery_level != b->battery_level) {
+        b->battery_level = battery_level;
+        pa_log_debug("AG battery level updated (%d/5)", b->battery_level);
+        pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED), b);
+    }
+}
+
+static void get_percentage_reply(DBusPendingCall *pending, void *userdata) {
+    pa_dbus_pending *p;
+    pa_upower_backend *b;
+    DBusMessage *r;
+    DBusMessageIter arg_i, variant_i;
+
+    pa_assert(pending);
+    pa_assert_se(p = userdata);
+    pa_assert_se(b = 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("UPower D-Bus Display Device not available");
+        goto finish;
+    }
+
+    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+        pa_log_error("Get() 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), "v")) {
+        pa_log_error("Invalid reply signature for Get()");
+        goto finish;
+    }
+
+    dbus_message_iter_recurse(&arg_i, &variant_i);
+    parse_percentage(b, &variant_i);
+
+finish:
+    dbus_message_unref(r);
+
+    PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
+    pa_dbus_pending_free(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;
+}
+
+static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *data) {
+    DBusError err;
+    DBusMessage *m2;
+    static const char* upower_device_interface = UPOWER_SERVICE UPOWER_DEVICE_INTERFACE;
+    static const char* percentage_property = "Percentage";
+    pa_upower_backend *b = data;
+    const char *path, *interface, *member;
+
+    pa_assert(bus);
+    pa_assert(m);
+    pa_assert(b);
+
+    dbus_error_init(&err);
+
+    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);
+
+    /* UPower D-Bus status change */
+    if (dbus_message_is_signal(m, DBUS_INTERFACE_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 " DBUS_INTERFACE_DBUS ".NameOwnerChanged: %s", err.message);
+            goto fail;
+        }
+
+        if (pa_streq(name, UPOWER_SERVICE)) {
+
+            /* UPower disappeared from D-Bus */
+            if (old_owner && *old_owner) {
+                pa_log_debug("UPower disappeared from D-Bus");
+                b->battery_level = 0;
+                pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED), b);
+            }
+
+            /* UPower appeared on D-Bus */
+            if (new_owner && *new_owner) {
+                pa_log_debug("UPower appeared on D-Bus");
+
+                /* Update battery level */
+                pa_assert_se(m2 = dbus_message_new_method_call(UPOWER_SERVICE, UPOWER_DISPLAY_DEVICE_OBJECT, DBUS_INTERFACE_PROPERTIES, "Get"));
+                pa_assert_se(dbus_message_append_args(m2,
+                    DBUS_TYPE_STRING, &upower_device_interface,
+                    DBUS_TYPE_STRING, &percentage_property,
+                    DBUS_TYPE_INVALID));
+                send_and_add_to_pending(b, m2, get_percentage_reply, NULL);
+            }
+        }
+
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    /* UPower battery level property updates */
+    } else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged")) {
+        DBusMessageIter arg_i, element_i;
+
+        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;
+        }
+
+        /* Skip interface name */
+        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);
+
+        /* Parse UPower property updates */
+        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);
+
+            /* Retrieve property name */
+            key = check_variant_property(&dict_i);
+            if (key == NULL) {
+                pa_log_error("Received invalid property!");
+                break;
+            }
+
+            dbus_message_iter_recurse(&dict_i, &variant_i);
+
+            if(pa_streq(path, UPOWER_DISPLAY_DEVICE_OBJECT)) {
+                pa_log_debug("UPower Device property updated: %s", key);
+
+                if(pa_streq(key, "Percentage"))
+                    parse_percentage(b, &variant_i);
+            }
+
+            dbus_message_iter_next(&element_i);
+        }
+    }
+
+fail:
+    dbus_error_free(&err);
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+unsigned int pa_upower_get_battery_level(pa_upower_backend *backend) {
+    return backend->battery_level;
+}
+
+pa_upower_backend *pa_upower_backend_new(pa_core *c, pa_bluetooth_discovery *d) {
+    pa_upower_backend *backend;
+    DBusError err;
+    DBusMessage *m;
+    static const char* upower_device_interface = UPOWER_SERVICE UPOWER_DEVICE_INTERFACE;
+    static const char* percentage_property = "Percentage";
+
+    pa_log_debug("Native backend enabled UPower battery status reporting");
+
+    backend = pa_xnew0(pa_upower_backend, 1);
+    backend->core = c;
+    backend->discovery = d;
+
+    /* Get DBus connection */
+    dbus_error_init(&err);
+    if (!(backend->connection = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &err))) {
+        pa_log("Failed to get D-Bus connection: %s", err.message);
+        dbus_error_free(&err);
+        pa_xfree(backend);
+        return NULL;
+    }
+
+    /* Add filter callback for DBus connection */
+    if (!dbus_connection_add_filter(pa_dbus_connection_get(backend->connection), filter_cb, backend, NULL)) {
+        pa_log_error("Failed to add filter function");
+        pa_dbus_connection_unref(backend->connection);
+        pa_xfree(backend);
+        return NULL;
+    }
+
+    /* Register for battery level changes from UPower */
+    if (pa_dbus_add_matches(pa_dbus_connection_get(backend->connection), &err,
+            "type='signal',sender='" DBUS_SERVICE_DBUS "',interface='" DBUS_INTERFACE_DBUS "',member='NameOwnerChanged',"
+            "arg0='" UPOWER_SERVICE "'",
+            "type='signal',sender='" UPOWER_SERVICE "',interface='" DBUS_INTERFACE_PROPERTIES "',member='PropertiesChanged'",
+            NULL) < 0) {
+        pa_log("Failed to add UPower D-Bus matches: %s", err.message);
+        dbus_connection_remove_filter(pa_dbus_connection_get(backend->connection), filter_cb, backend);
+        pa_dbus_connection_unref(backend->connection);
+        pa_xfree(backend);
+        return NULL;
+    }
+
+    /* Initialize battery level by requesting it from UPower */
+    pa_assert_se(m = dbus_message_new_method_call(UPOWER_SERVICE, UPOWER_DISPLAY_DEVICE_OBJECT, DBUS_INTERFACE_PROPERTIES, "Get"));
+    pa_assert_se(dbus_message_append_args(m,
+        DBUS_TYPE_STRING, &upower_device_interface,
+        DBUS_TYPE_STRING, &percentage_property,
+        DBUS_TYPE_INVALID));
+    send_and_add_to_pending(backend, m, get_percentage_reply, NULL);
+
+    return backend;
+}
+
+void pa_upower_backend_free(pa_upower_backend *backend) {
+    pa_assert(backend);
+
+    pa_dbus_free_pending_list(&backend->pending);
+
+    pa_dbus_connection_unref(backend->connection);
+
+    pa_xfree(backend);
+}
+


=====================================
src/modules/bluetooth/upower.h
=====================================
@@ -0,0 +1,39 @@
+#pragma once
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2022 Dylan Van Assche <me at dylanvanassche.be>
+
+  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
+  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "bluez5-util.h"
+
+#define UPOWER_SERVICE "org.freedesktop.UPower"
+#define UPOWER_DEVICE_INTERFACE ".Device"
+#define UPOWER_DISPLAY_DEVICE_OBJECT "/org/freedesktop/UPower/devices/DisplayDevice"
+
+struct pa_upower_backend {
+    pa_core *core;
+    pa_dbus_connection *connection;
+    pa_bluetooth_discovery *discovery;
+    unsigned int battery_level;
+
+    PA_LLIST_HEAD(pa_dbus_pending, pending);
+};
+
+pa_upower_backend *pa_upower_backend_new(pa_core *c, pa_bluetooth_discovery *d);
+void pa_upower_backend_free(pa_upower_backend *backend);
+unsigned int pa_upower_get_battery_level(pa_upower_backend *backend);


=====================================
src/modules/meson.build
=====================================
@@ -123,7 +123,7 @@ if cdata.has('HAVE_BLUEZ_5')
   all_modules += [
     [ 'module-bluetooth-discover', 'bluetooth/module-bluetooth-discover.c' ],
     [ 'module-bluetooth-policy', 'bluetooth/module-bluetooth-policy.c', [], [], [dbus_dep] ],
-    [ 'module-bluez5-device', 'bluetooth/module-bluez5-device.c', [], [], [], libbluez5_util ],
+    [ 'module-bluez5-device', 'bluetooth/module-bluez5-device.c', [], [], [dbus_dep], libbluez5_util ],
     [ 'module-bluez5-discover', 'bluetooth/module-bluez5-discover.c', [], [], [dbus_dep], libbluez5_util ],
   ]
 endif



View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/ff6010b80f1cf8ebeabaea8b8fc3fc53aa2bd7a1...1b031ecee69142c4b0ff6ea9767c0cabb61af144

-- 
View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/ff6010b80f1cf8ebeabaea8b8fc3fc53aa2bd7a1...1b031ecee69142c4b0ff6ea9767c0cabb61af144
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/20220801/d9d080c7/attachment-0001.htm>


More information about the pulseaudio-commits mailing list