diff options
Diffstat (limited to 'spa/plugins/bluez5/upower.c')
-rw-r--r-- | spa/plugins/bluez5/upower.c | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/spa/plugins/bluez5/upower.c b/spa/plugins/bluez5/upower.c new file mode 100644 index 0000000..23a637a --- /dev/null +++ b/spa/plugins/bluez5/upower.c @@ -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); +} |