Verified Commit c66ebd35 authored by Dylan Van Assche's avatar Dylan Van Assche
Browse files

bluetooth: report AG battery level

oFono backend reports the AG's battery level to the connected
headset, but this is not the case for the native backend.
Add UPower backend to report the AG's battery when available.
parent c94a3a9f
Pipeline #425686 failed with stages
in 1 minute and 1 second
......@@ -36,27 +36,40 @@
#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 transport_data {
int rfcomm_fd;
pa_io_event *rfcomm_io;
int sco_fd;
pa_io_event *sco_io;
pa_mainloop_api *mainloop;
pa_bluetooth_backend *backend;
};
struct pa_bluetooth_backend {
pa_core *core;
pa_dbus_connection *connection;
pa_bluetooth_discovery *discovery;
pa_bluetooth_transport *transport;
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;
transport_data *trd;
PA_LLIST_HEAD(pa_dbus_pending, pending);
};
struct transport_data {
int rfcomm_fd;
pa_io_event *rfcomm_io;
int sco_fd;
pa_io_event *sco_io;
pa_mainloop_api *mainloop;
};
struct hfp_config {
uint32_t capabilities;
int state;
......@@ -95,6 +108,19 @@ 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,
} 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 */
......@@ -572,8 +598,9 @@ 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 transport_data *trd = t->userdata;
struct hfp_config *c = t->config;
int indicator, val;
int indicator, mode, val;
char str[5];
const char *r;
size_t len;
......@@ -592,6 +619,33 @@ 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')
trd->backend->cind_enabled_indicators |= (1 << indicator);
else if (len == 1 && r[0] == '0')
trd->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++;
}
} else if (sscanf(buf, "AT+BAC=%3s", str) == 1) {
c->support_msbc = false;
......@@ -617,24 +671,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 (trd->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 (trd->backend->upower)
rfcomm_write_response(fd, "+CIND: 0,0,0,0,%u", pa_upower_get_battery_level(trd->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) {
if (mode != 3) {
pa_log_warn("Unexpected mode for AT+CMER: %d", mode);
}
/* Configure CMER event reporting */
trd->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)) {
......@@ -722,8 +801,31 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
return true;
}
static pa_hook_result_t host_battery_level_changed_cb(pa_bluetooth_discovery *y, const pa_upower_backend *u, pa_bluetooth_backend *b) {
pa_assert(y);
pa_assert(u);
pa_assert(b);
/* Skip if RFCOMM channel is not available yet */
if (!b->trd || b->trd->rfcomm_fd < 0) {
pa_log_debug("RFCOMM not available yet, skipping notification");
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(trd->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;
struct transport_data *trd = t->userdata;
pa_assert(io);
pa_assert(t);
......@@ -844,6 +946,14 @@ 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 */
trd->backend->cmer_indicator_reporting_enabled = false;
trd->backend->cind_enabled_indicators |= (1 << CIND_CALL_INDICATOR);
trd->backend->cind_enabled_indicators |= (1 << CIND_CALL_SETUP_INDICATOR);
trd->backend->cind_enabled_indicators |= (1 << CIND_CALL_HELD_INDICATOR);
trd->backend->cind_enabled_indicators |= (1 << CIND_SERVICE_INDICATOR);
trd->backend->cind_enabled_indicators |= (1 << CIND_BATT_CHG_INDICATOR);
pa_bluetooth_transport_unlink(t);
pa_bluetooth_transport_free(t);
}
......@@ -851,6 +961,8 @@ fail:
static void transport_destroy(pa_bluetooth_transport *t) {
struct transport_data *trd = t->userdata;
trd->backend->trd = NULL;
if (trd->sco_io) {
trd->mainloop->io_free(trd->sco_io);
shutdown(trd->sco_fd, SHUT_RDWR);
......@@ -1005,10 +1117,12 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
trd = pa_xnew0(struct transport_data, 1);
trd->rfcomm_fd = fd;
trd->backend = b;
trd->mainloop = b->core->mainloop;
trd->rfcomm_io = trd->mainloop->io_new(b->core->mainloop, fd, PA_IO_EVENT_INPUT,
rfcomm_io_callback, t);
t->userdata = trd;
t->userdata = trd;
b->trd = trd;
sco_listen(t);
......@@ -1195,6 +1309,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.");
......@@ -1204,6 +1322,18 @@ 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 */
trd->backend->cind_enabled_indicators |= (1 << CIND_CALL_INDICATOR);
trd->backend->cind_enabled_indicators |= (1 << CIND_CALL_SETUP_INDICATOR);
trd->backend->cind_enabled_indicators |= (1 << CIND_CALL_HELD_INDICATOR);
trd->backend->cind_enabled_indicators |= (1 << CIND_SERVICE_INDICATOR);
trd->backend->cind_enabled_indicators |= (1 << CIND_BATT_CHG_INDICATOR);
/* While all CIND indicators are enabled, event reporting is not enabled by default */
trd->backend->cmer_indicator_reporting_enabled = false;
return backend;
}
......@@ -1215,12 +1345,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);
......
......@@ -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>
......@@ -45,8 +44,6 @@
#define WAIT_FOR_PROFILES_TIMEOUT_USEC (3 * PA_USEC_PER_SEC)
#define DBUS_INTERFACE_OBJECT_MANAGER DBUS_INTERFACE_DBUS ".ObjectManager"
#define A2DP_OBJECT_MANAGER_PATH "/MediaEndpoint"
#define A2DP_SOURCE_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSource"
#define A2DP_SINK_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSink"
......
......@@ -22,6 +22,7 @@
***/
#include <pulsecore/core.h>
#include <pulsecore/dbus-shared.h>
#include "a2dp-codec-util.h"
......@@ -40,6 +41,8 @@
#define BLUEZ_ERROR_NOT_AVAILABLE BLUEZ_SERVICE ".Error.NotAvailable"
#define BLUEZ_ERROR_NOT_SUPPORTED BLUEZ_SERVICE ".Error.NotSupported"
#define DBUS_INTERFACE_OBJECT_MANAGER DBUS_INTERFACE_DBUS "org.freedesktop.DBus.ObjectManager"
#define PA_BLUETOOTH_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb"
#define PA_BLUETOOTH_UUID_A2DP_SINK "0000110b-0000-1000-8000-00805f9b34fb"
......@@ -62,12 +65,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 */
......@@ -244,4 +249,5 @@ void pa_bluetooth_discovery_set_ofono_running(pa_bluetooth_discovery *y, bool is
bool pa_bluetooth_discovery_get_enable_native_hsp_hs(pa_bluetooth_discovery *y);
bool pa_bluetooth_discovery_get_enable_native_hfp_hf(pa_bluetooth_discovery *y);
bool pa_bluetooth_discovery_get_enable_msbc(pa_bluetooth_discovery *y);
#endif
......@@ -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,
......
/***
This file is part of PulseAudio.
Copyrigth 2021 Dylan Van Assche <me@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 "bluez5-util.h"
#include "upower.h"
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);
};
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";