mirror of
https://gitlab.freedesktop.org/pipewire/pipewire
synced 2024-10-15 12:22:47 +00:00
bluez5: backend-native: Add battery level indicator support
This connect to the UPower service and update the +CIND battchg indicator
This commit is contained in:
parent
a37aeac273
commit
47700a2214
|
@ -52,6 +52,7 @@
|
|||
#endif
|
||||
|
||||
#include "modemmanager.h"
|
||||
#include "upower.h"
|
||||
|
||||
static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.native");
|
||||
#undef SPA_LOG_TOPIC_DEFAULT
|
||||
|
@ -71,7 +72,7 @@ enum {
|
|||
HFP_AG_INITIAL_CODEC_SETUP_WAIT
|
||||
};
|
||||
|
||||
#define CIND_INDICATORS "(\"service\",(0-1)),(\"call\",(0-1)),(\"callsetup\",(0-3)),(\"callheld\",(0-2)),(\"signal\",(0-5)),(\"roam\",(0-1))"
|
||||
#define CIND_INDICATORS "(\"service\",(0-1)),(\"call\",(0-1)),(\"callsetup\",(0-3)),(\"callheld\",(0-2)),(\"signal\",(0-5)),(\"roam\",(0-1)),\"battchg\",(0-5))"
|
||||
enum {
|
||||
CIND_SERVICE = 1,
|
||||
CIND_CALL,
|
||||
|
@ -79,6 +80,7 @@ enum {
|
|||
CIND_CALLHELD,
|
||||
CIND_SIGNAL,
|
||||
CIND_ROAM,
|
||||
CIND_BATTERY_LEVEL,
|
||||
CIND_MAX
|
||||
};
|
||||
|
||||
|
@ -115,9 +117,11 @@ struct impl {
|
|||
unsigned int defer_setup_enabled:1;
|
||||
|
||||
struct modem modem;
|
||||
unsigned int battery_level;
|
||||
|
||||
void *modemmanager;
|
||||
struct spa_source *ring_timer;
|
||||
void *upower;
|
||||
};
|
||||
|
||||
struct transport_data {
|
||||
|
@ -835,9 +839,9 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf)
|
|||
rfcomm_send_reply(rfcomm, "+CIND:%s", CIND_INDICATORS);
|
||||
rfcomm_send_reply(rfcomm, "OK");
|
||||
} else if (spa_strstartswith(buf, "AT+CIND?")) {
|
||||
rfcomm_send_reply(rfcomm, "+CIND: %d,%d,%d,0,%d,%d", backend->modem.network_has_service,
|
||||
rfcomm_send_reply(rfcomm, "+CIND: %d,%d,%d,0,%d,%d,%d", backend->modem.network_has_service,
|
||||
backend->modem.active_call, backend->modem.call_setup, backend->modem.signal_strength,
|
||||
backend->modem.network_is_roaming);
|
||||
backend->modem.network_is_roaming, backend->battery_level);
|
||||
rfcomm_send_reply(rfcomm, "OK");
|
||||
} else if (spa_strstartswith(buf, "AT+CMER")) {
|
||||
int mode, keyp, disp, ind;
|
||||
|
@ -2530,6 +2534,16 @@ static void set_call_setup(enum call_setup value, void *user_data)
|
|||
}
|
||||
}
|
||||
|
||||
void set_battery_level(unsigned int level, void *user_data)
|
||||
{
|
||||
struct impl *backend = user_data;
|
||||
|
||||
if (backend->battery_level != level) {
|
||||
backend->battery_level = level;
|
||||
send_ciev_for_each_rfcomm(backend, CIND_BATTERY_LEVEL, level);
|
||||
}
|
||||
}
|
||||
|
||||
static void set_modem_operator_name(const char *name, void *user_data)
|
||||
{
|
||||
struct impl *backend = user_data;
|
||||
|
@ -2611,6 +2625,11 @@ static int backend_native_free(void *data)
|
|||
backend->modemmanager = NULL;
|
||||
}
|
||||
|
||||
if (backend->upower) {
|
||||
upower_unregister(backend->upower);
|
||||
backend->upower = NULL;
|
||||
}
|
||||
|
||||
if (backend->ring_timer)
|
||||
spa_loop_utils_destroy_source(backend->loop_utils, backend->ring_timer);
|
||||
|
||||
|
@ -2740,6 +2759,7 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
|
|||
#endif
|
||||
|
||||
backend->modemmanager = mm_register(backend->log, backend->conn, &mm_ops, backend);
|
||||
backend->upower = upower_register(backend->log, backend->conn, set_battery_level, backend);
|
||||
|
||||
return &backend->this;
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ if get_option('bluez5-backend-hsp-native').allowed() or get_option('bluez5-backe
|
|||
bluez5_deps += mm_dep
|
||||
bluez5_sources += ['modemmanager.c']
|
||||
endif
|
||||
bluez5_sources += ['backend-native.c']
|
||||
bluez5_sources += ['backend-native.c', 'upower.c']
|
||||
endif
|
||||
|
||||
if get_option('bluez5-backend-ofono').allowed()
|
||||
|
|
311
spa/plugins/bluez5/upower.c
Normal file
311
spa/plugins/bluez5/upower.c
Normal file
|
@ -0,0 +1,311 @@
|
|||
/* Spa Bluez5 UPower proxy
|
||||
*
|
||||
* Copyright © 2022 Collabora
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <spa/utils/string.h>
|
||||
|
||||
#include "upower.h"
|
||||
|
||||
#define UPOWER_SERVICE "org.freedesktop.UPower"
|
||||
#define UPOWER_DEVICE_INTERFACE UPOWER_SERVICE ".Device"
|
||||
#define UPOWER_DISPLAY_DEVICE_OBJECT "/org/freedesktop/UPower/devices/DisplayDevice"
|
||||
|
||||
struct impl {
|
||||
struct spa_bt_monitor *monitor;
|
||||
|
||||
struct spa_log *log;
|
||||
DBusConnection *conn;
|
||||
|
||||
bool filters_added;
|
||||
|
||||
void *user_data;
|
||||
void (*set_battery_level)(unsigned int level, void *user_data);
|
||||
};
|
||||
|
||||
static DBusHandlerResult upower_parse_percentage(struct impl *this, DBusMessageIter *variant_i)
|
||||
{
|
||||
double percentage;
|
||||
unsigned int battery_level;
|
||||
|
||||
dbus_message_iter_get_basic(variant_i, &percentage);
|
||||
spa_log_debug(this->log, "Battery level: %f %%", percentage);
|
||||
|
||||
battery_level = (unsigned int) round(percentage / 20.0);
|
||||
this->set_battery_level(battery_level, this->user_data);
|
||||
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
}
|
||||
|
||||
static void upower_get_percentage_properties_reply(DBusPendingCall *pending, void *user_data)
|
||||
{
|
||||
struct impl *backend = user_data;
|
||||
DBusMessage *r;
|
||||
DBusMessageIter i, variant_i;
|
||||
|
||||
r = dbus_pending_call_steal_reply(pending);
|
||||
if (r == NULL)
|
||||
return;
|
||||
|
||||
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
|
||||
spa_log_error(backend->log, "Failed to get percentage from UPower: %s",
|
||||
dbus_message_get_error_name(r));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!dbus_message_iter_init(r, &i) || !spa_streq(dbus_message_get_signature(r), "v")) {
|
||||
spa_log_error(backend->log, "Invalid arguments in Get() reply");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
dbus_message_iter_recurse(&i, &variant_i);
|
||||
upower_parse_percentage(backend, &variant_i);
|
||||
|
||||
finish:
|
||||
dbus_message_unref(r);
|
||||
}
|
||||
|
||||
static void upower_clean(struct impl *this)
|
||||
{
|
||||
this->set_battery_level(0, this->user_data);
|
||||
}
|
||||
|
||||
static DBusHandlerResult upower_filter_cb(DBusConnection *bus, DBusMessage *m, void *user_data)
|
||||
{
|
||||
struct impl *this = user_data;
|
||||
DBusError err;
|
||||
|
||||
dbus_error_init(&err);
|
||||
|
||||
if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
|
||||
const char *name, *old_owner, *new_owner;
|
||||
|
||||
spa_log_debug(this->log, "Name owner changed %s", dbus_message_get_path(m));
|
||||
|
||||
if (!dbus_message_get_args(m, &err,
|
||||
DBUS_TYPE_STRING, &name,
|
||||
DBUS_TYPE_STRING, &old_owner,
|
||||
DBUS_TYPE_STRING, &new_owner,
|
||||
DBUS_TYPE_INVALID)) {
|
||||
spa_log_error(this->log, "Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (spa_streq(name, UPOWER_SERVICE)) {
|
||||
if (old_owner && *old_owner) {
|
||||
spa_log_debug(this->log, "UPower daemon disappeared (%s)", old_owner);
|
||||
upower_clean(this);
|
||||
}
|
||||
|
||||
if (new_owner && *new_owner) {
|
||||
DBusPendingCall *call;
|
||||
static const char* upower_device_interface = UPOWER_DEVICE_INTERFACE;
|
||||
static const char* percentage_property = "Percentage";
|
||||
|
||||
spa_log_debug(this->log, "UPower daemon appeared (%s)", new_owner);
|
||||
|
||||
m = dbus_message_new_method_call(UPOWER_SERVICE, UPOWER_DISPLAY_DEVICE_OBJECT, DBUS_INTERFACE_PROPERTIES, "Get");
|
||||
if (m == NULL)
|
||||
goto finish;
|
||||
dbus_message_append_args(m, DBUS_TYPE_STRING, &upower_device_interface,
|
||||
DBUS_TYPE_STRING, &percentage_property, DBUS_TYPE_INVALID);
|
||||
dbus_connection_send_with_reply(this->conn, m, &call, -1);
|
||||
dbus_pending_call_set_notify(call, upower_get_percentage_properties_reply, this, NULL);
|
||||
dbus_message_unref(m);
|
||||
}
|
||||
}
|
||||
} else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, DBUS_SIGNAL_PROPERTIES_CHANGED)) {
|
||||
const char *path;
|
||||
DBusMessageIter iface_i, props_i;
|
||||
const char *interface;
|
||||
|
||||
if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "sa{sv}as")) {
|
||||
spa_log_error(this->log, "Invalid signature found in PropertiesChanged");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
dbus_message_iter_get_basic(&iface_i, &interface);
|
||||
dbus_message_iter_next(&iface_i);
|
||||
spa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY);
|
||||
|
||||
dbus_message_iter_recurse(&iface_i, &props_i);
|
||||
|
||||
path = dbus_message_get_path(m);
|
||||
|
||||
if (spa_streq(interface, UPOWER_DEVICE_INTERFACE)) {
|
||||
spa_log_debug(this->log, "Properties changed on %s", path);
|
||||
|
||||
while (dbus_message_iter_get_arg_type(&props_i) != DBUS_TYPE_INVALID) {
|
||||
DBusMessageIter i, value_i;
|
||||
const char *key;
|
||||
|
||||
dbus_message_iter_recurse(&props_i, &i);
|
||||
|
||||
dbus_message_iter_get_basic(&i, &key);
|
||||
dbus_message_iter_next(&i);
|
||||
dbus_message_iter_recurse(&i, &value_i);
|
||||
|
||||
if(spa_streq(key, "Percentage"))
|
||||
upower_parse_percentage(this, &value_i);
|
||||
|
||||
dbus_message_iter_next(&props_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
finish:
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
|
||||
static int add_filters(struct impl *this)
|
||||
{
|
||||
DBusError err;
|
||||
|
||||
if (this->filters_added)
|
||||
return 0;
|
||||
|
||||
dbus_error_init(&err);
|
||||
|
||||
if (!dbus_connection_add_filter(this->conn, upower_filter_cb, this, NULL)) {
|
||||
spa_log_error(this->log, "failed to add filter function");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dbus_bus_add_match(this->conn,
|
||||
"type='signal',sender='org.freedesktop.DBus',"
|
||||
"interface='org.freedesktop.DBus',member='NameOwnerChanged'," "arg0='" UPOWER_SERVICE "'", &err);
|
||||
dbus_bus_add_match(this->conn,
|
||||
"type='signal',sender='" UPOWER_SERVICE "',"
|
||||
"interface='" DBUS_INTERFACE_PROPERTIES "',member='" DBUS_SIGNAL_PROPERTIES_CHANGED "',"
|
||||
"path='" UPOWER_DISPLAY_DEVICE_OBJECT "',arg0='" UPOWER_DEVICE_INTERFACE "'", &err);
|
||||
|
||||
this->filters_added = true;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
dbus_error_free(&err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static bool is_dbus_service_available(struct impl *this, const char *service)
|
||||
{
|
||||
DBusMessage *m, *r;
|
||||
DBusError err;
|
||||
bool success = false;
|
||||
|
||||
m = dbus_message_new_method_call("org.freedesktop.DBus", "/org/freedesktop/DBus",
|
||||
"org.freedesktop.DBus", "NameHasOwner");
|
||||
if (m == NULL)
|
||||
return false;
|
||||
dbus_message_append_args(m, DBUS_TYPE_STRING, &service, DBUS_TYPE_INVALID);
|
||||
|
||||
dbus_error_init(&err);
|
||||
r = dbus_connection_send_with_reply_and_block(this->conn, m, -1, &err);
|
||||
dbus_message_unref(m);
|
||||
m = NULL;
|
||||
|
||||
if (r == NULL) {
|
||||
spa_log_info(this->log, "NameHasOwner failed for %s", service);
|
||||
dbus_error_free(&err);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
|
||||
spa_log_error(this->log, "NameHasOwner() returned error: %s", dbus_message_get_error_name(r));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!dbus_message_get_args(r, &err,
|
||||
DBUS_TYPE_BOOLEAN, &success,
|
||||
DBUS_TYPE_INVALID)) {
|
||||
spa_log_error(this->log, "Failed to parse NameHasOwner() reply: %s", err.message);
|
||||
dbus_error_free(&err);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
finish:
|
||||
if (r)
|
||||
dbus_message_unref(r);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void *upower_register(struct spa_log *log,
|
||||
void *dbus_connection,
|
||||
void (*set_battery_level)(unsigned int level, void *user_data),
|
||||
void *user_data)
|
||||
{
|
||||
struct impl *this;
|
||||
|
||||
spa_assert(log);
|
||||
spa_assert(dbus_connection);
|
||||
spa_assert(set_battery_level);
|
||||
spa_assert(user_data);
|
||||
|
||||
this = calloc(1, sizeof(struct impl));
|
||||
if (this == NULL)
|
||||
return NULL;
|
||||
|
||||
this->log = log;
|
||||
this->conn = dbus_connection;
|
||||
this->set_battery_level = set_battery_level;
|
||||
this->user_data = user_data;
|
||||
|
||||
if (add_filters(this) < 0) {
|
||||
goto fail4;
|
||||
}
|
||||
|
||||
if (is_dbus_service_available(this, UPOWER_SERVICE)) {
|
||||
DBusMessage *m;
|
||||
DBusPendingCall *call;
|
||||
static const char* upower_device_interface = UPOWER_DEVICE_INTERFACE;
|
||||
static const char* percentage_property = "Percentage";
|
||||
|
||||
m = dbus_message_new_method_call(UPOWER_SERVICE, UPOWER_DISPLAY_DEVICE_OBJECT, DBUS_INTERFACE_PROPERTIES, "Get");
|
||||
if (m == NULL)
|
||||
goto fail4;
|
||||
dbus_message_append_args(m, DBUS_TYPE_STRING, &upower_device_interface,
|
||||
DBUS_TYPE_STRING, &percentage_property, DBUS_TYPE_INVALID);
|
||||
dbus_connection_send_with_reply(this->conn, m, &call, -1);
|
||||
dbus_pending_call_set_notify(call, upower_get_percentage_properties_reply, this, NULL);
|
||||
dbus_message_unref(m);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
fail4:
|
||||
free(this);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void upower_unregister(void *data)
|
||||
{
|
||||
struct impl *this = data;
|
||||
|
||||
if (this->filters_added) {
|
||||
dbus_connection_remove_filter(this->conn, upower_filter_cb, this);
|
||||
this->filters_added = false;
|
||||
}
|
||||
free(this);
|
||||
}
|
36
spa/plugins/bluez5/upower.h
Normal file
36
spa/plugins/bluez5/upower.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/* Spa Bluez5 UPower proxy
|
||||
*
|
||||
* Copyright © 2022 Collabora
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SPA_BLUEZ5_UPOWER_H_
|
||||
#define SPA_BLUEZ5_UPOWER_H_
|
||||
|
||||
#include "defs.h"
|
||||
|
||||
void *upower_register(struct spa_log *log,
|
||||
void *dbus_connection,
|
||||
void (*set_battery_level)(unsigned int level, void *user_data),
|
||||
void *user_data);
|
||||
void upower_unregister(void *data);
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue