• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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