[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