1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2022 Dylan Van Assche <me@dylanvanassche.be>
5
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2.1 of the
9 License, or (at your option) any later version.
10
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
18 ***/
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include <math.h>
24 #include <pulsecore/core-error.h>
25 #include <pulsecore/core-util.h>
26 #include <pulsecore/dbus-shared.h>
27 #include <pulsecore/log.h>
28 #include <pulse/timeval.h>
29 #include <pulse/rtclock.h>
30
31 #include "upower.h"
32
send_and_add_to_pending(pa_upower_backend * backend,DBusMessage * m,DBusPendingCallNotifyFunction func,void * call_data)33 static pa_dbus_pending* send_and_add_to_pending(pa_upower_backend *backend, DBusMessage *m,
34 DBusPendingCallNotifyFunction func, void *call_data) {
35
36 pa_dbus_pending *p;
37 DBusPendingCall *call;
38
39 pa_assert(backend);
40 pa_assert(m);
41
42 pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(backend->connection), m, &call, -1));
43
44 p = pa_dbus_pending_new(pa_dbus_connection_get(backend->connection), m, call, backend, call_data);
45 PA_LLIST_PREPEND(pa_dbus_pending, backend->pending, p);
46 dbus_pending_call_set_notify(call, func, p, NULL);
47
48 return p;
49 }
50
parse_percentage(pa_upower_backend * b,DBusMessageIter * i)51 static void parse_percentage(pa_upower_backend *b, DBusMessageIter *i) {
52 double percentage;
53 unsigned int battery_level;
54
55 pa_assert(i);
56 pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_DOUBLE);
57
58 dbus_message_iter_get_basic(i, &percentage);
59 battery_level = (unsigned int) round(percentage / 20.0);
60
61 if (battery_level != b->battery_level) {
62 b->battery_level = battery_level;
63 pa_log_debug("AG battery level updated (%d/5)", b->battery_level);
64 pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED), b);
65 }
66 }
67
get_percentage_reply(DBusPendingCall * pending,void * userdata)68 static void get_percentage_reply(DBusPendingCall *pending, void *userdata) {
69 pa_dbus_pending *p;
70 pa_upower_backend *b;
71 DBusMessage *r;
72 DBusMessageIter arg_i, variant_i;
73
74 pa_assert(pending);
75 pa_assert_se(p = userdata);
76 pa_assert_se(b = p->context_data);
77 pa_assert_se(r = dbus_pending_call_steal_reply(pending));
78
79 if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
80 pa_log_warn("UPower D-Bus Display Device not available");
81 goto finish;
82 }
83
84 if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
85 pa_log_error("Get() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
86 goto finish;
87 }
88
89 if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "v")) {
90 pa_log_error("Invalid reply signature for Get()");
91 goto finish;
92 }
93
94 dbus_message_iter_recurse(&arg_i, &variant_i);
95 parse_percentage(b, &variant_i);
96
97 finish:
98 dbus_message_unref(r);
99
100 PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
101 pa_dbus_pending_free(p);
102 }
103
check_variant_property(DBusMessageIter * i)104 static const char *check_variant_property(DBusMessageIter *i) {
105 const char *key;
106
107 pa_assert(i);
108
109 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
110 pa_log_error("Property name not a string.");
111 return NULL;
112 }
113
114 dbus_message_iter_get_basic(i, &key);
115
116 if (!dbus_message_iter_next(i)) {
117 pa_log_error("Property value missing");
118 return NULL;
119 }
120
121 if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
122 pa_log_error("Property value not a variant.");
123 return NULL;
124 }
125
126 return key;
127 }
128
filter_cb(DBusConnection * bus,DBusMessage * m,void * data)129 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *data) {
130 DBusError err;
131 DBusMessage *m2;
132 static const char* upower_device_interface = UPOWER_SERVICE UPOWER_DEVICE_INTERFACE;
133 static const char* percentage_property = "Percentage";
134 pa_upower_backend *b = data;
135 const char *path, *interface, *member;
136
137 pa_assert(bus);
138 pa_assert(m);
139 pa_assert(b);
140
141 dbus_error_init(&err);
142
143 path = dbus_message_get_path(m);
144 interface = dbus_message_get_interface(m);
145 member = dbus_message_get_member(m);
146
147 pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
148
149 /* UPower D-Bus status change */
150 if (dbus_message_is_signal(m, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) {
151 const char *name, *old_owner, *new_owner;
152
153 if (!dbus_message_get_args(m, &err,
154 DBUS_TYPE_STRING, &name,
155 DBUS_TYPE_STRING, &old_owner,
156 DBUS_TYPE_STRING, &new_owner,
157 DBUS_TYPE_INVALID)) {
158 pa_log_error("Failed to parse " DBUS_INTERFACE_DBUS ".NameOwnerChanged: %s", err.message);
159 goto fail;
160 }
161
162 if (pa_streq(name, UPOWER_SERVICE)) {
163
164 /* UPower disappeared from D-Bus */
165 if (old_owner && *old_owner) {
166 pa_log_debug("UPower disappeared from D-Bus");
167 b->battery_level = 0;
168 pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery, PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED), b);
169 }
170
171 /* UPower appeared on D-Bus */
172 if (new_owner && *new_owner) {
173 pa_log_debug("UPower appeared on D-Bus");
174
175 /* Update battery level */
176 pa_assert_se(m2 = dbus_message_new_method_call(UPOWER_SERVICE, UPOWER_DISPLAY_DEVICE_OBJECT, DBUS_INTERFACE_PROPERTIES, "Get"));
177 pa_assert_se(dbus_message_append_args(m2,
178 DBUS_TYPE_STRING, &upower_device_interface,
179 DBUS_TYPE_STRING, &percentage_property,
180 DBUS_TYPE_INVALID));
181 send_and_add_to_pending(b, m2, get_percentage_reply, NULL);
182 }
183 }
184
185 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
186 /* UPower battery level property updates */
187 } else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged")) {
188 DBusMessageIter arg_i, element_i;
189
190 if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "sa{sv}as")) {
191 pa_log_error("Invalid signature found in PropertiesChanged");
192 goto fail;
193 }
194
195 /* Skip interface name */
196 pa_assert_se(dbus_message_iter_next(&arg_i));
197 pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
198
199 dbus_message_iter_recurse(&arg_i, &element_i);
200
201 /* Parse UPower property updates */
202 while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
203 DBusMessageIter dict_i, variant_i;
204 const char *key;
205
206 dbus_message_iter_recurse(&element_i, &dict_i);
207
208 /* Retrieve property name */
209 key = check_variant_property(&dict_i);
210 if (key == NULL) {
211 pa_log_error("Received invalid property!");
212 break;
213 }
214
215 dbus_message_iter_recurse(&dict_i, &variant_i);
216
217 if(pa_streq(path, UPOWER_DISPLAY_DEVICE_OBJECT)) {
218 pa_log_debug("UPower Device property updated: %s", key);
219
220 if(pa_streq(key, "Percentage"))
221 parse_percentage(b, &variant_i);
222 }
223
224 dbus_message_iter_next(&element_i);
225 }
226 }
227
228 fail:
229 dbus_error_free(&err);
230 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
231 }
232
pa_upower_get_battery_level(pa_upower_backend * backend)233 unsigned int pa_upower_get_battery_level(pa_upower_backend *backend) {
234 return backend->battery_level;
235 }
236
pa_upower_backend_new(pa_core * c,pa_bluetooth_discovery * d)237 pa_upower_backend *pa_upower_backend_new(pa_core *c, pa_bluetooth_discovery *d) {
238 pa_upower_backend *backend;
239 DBusError err;
240 DBusMessage *m;
241 static const char* upower_device_interface = UPOWER_SERVICE UPOWER_DEVICE_INTERFACE;
242 static const char* percentage_property = "Percentage";
243
244 pa_log_debug("Native backend enabled UPower battery status reporting");
245
246 backend = pa_xnew0(pa_upower_backend, 1);
247 backend->core = c;
248 backend->discovery = d;
249
250 /* Get DBus connection */
251 dbus_error_init(&err);
252 if (!(backend->connection = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &err))) {
253 pa_log("Failed to get D-Bus connection: %s", err.message);
254 dbus_error_free(&err);
255 pa_xfree(backend);
256 return NULL;
257 }
258
259 /* Add filter callback for DBus connection */
260 if (!dbus_connection_add_filter(pa_dbus_connection_get(backend->connection), filter_cb, backend, NULL)) {
261 pa_log_error("Failed to add filter function");
262 pa_dbus_connection_unref(backend->connection);
263 pa_xfree(backend);
264 return NULL;
265 }
266
267 /* Register for battery level changes from UPower */
268 if (pa_dbus_add_matches(pa_dbus_connection_get(backend->connection), &err,
269 "type='signal',sender='" DBUS_SERVICE_DBUS "',interface='" DBUS_INTERFACE_DBUS "',member='NameOwnerChanged',"
270 "arg0='" UPOWER_SERVICE "'",
271 "type='signal',sender='" UPOWER_SERVICE "',interface='" DBUS_INTERFACE_PROPERTIES "',member='PropertiesChanged'",
272 NULL) < 0) {
273 pa_log("Failed to add UPower D-Bus matches: %s", err.message);
274 dbus_connection_remove_filter(pa_dbus_connection_get(backend->connection), filter_cb, backend);
275 pa_dbus_connection_unref(backend->connection);
276 pa_xfree(backend);
277 return NULL;
278 }
279
280 /* Initialize battery level by requesting it from UPower */
281 pa_assert_se(m = dbus_message_new_method_call(UPOWER_SERVICE, UPOWER_DISPLAY_DEVICE_OBJECT, DBUS_INTERFACE_PROPERTIES, "Get"));
282 pa_assert_se(dbus_message_append_args(m,
283 DBUS_TYPE_STRING, &upower_device_interface,
284 DBUS_TYPE_STRING, &percentage_property,
285 DBUS_TYPE_INVALID));
286 send_and_add_to_pending(backend, m, get_percentage_reply, NULL);
287
288 return backend;
289 }
290
pa_upower_backend_free(pa_upower_backend * backend)291 void pa_upower_backend_free(pa_upower_backend *backend) {
292 pa_assert(backend);
293
294 pa_dbus_free_pending_list(&backend->pending);
295
296 pa_dbus_connection_unref(backend->connection);
297
298 pa_xfree(backend);
299 }
300
301