1 /*************************************************************************/
2 /* power_x11.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
10 /* */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the */
13 /* "Software"), to deal in the Software without restriction, including */
14 /* without limitation the rights to use, copy, modify, merge, publish, */
15 /* distribute, sublicense, and/or sell copies of the Software, and to */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions: */
18 /* */
19 /* The above copyright notice and this permission notice shall be */
20 /* included in all copies or substantial portions of the Software. */
21 /* */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29 /*************************************************************************/
30
31 /*
32 Adapted from corresponding SDL 2.0 code.
33 */
34
35 /*
36 Simple DirectMedia Layer
37 Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
38
39 This software is provided 'as-is', without any express or implied
40 warranty. In no event will the authors be held liable for any damages
41 arising from the use of this software.
42
43 Permission is granted to anyone to use this software for any purpose,
44 including commercial applications, and to alter it and redistribute it
45 freely, subject to the following restrictions:
46
47 1. The origin of this software must not be misrepresented; you must not
48 claim that you wrote the original software. If you use this software
49 in a product, an acknowledgment in the product documentation would be
50 appreciated but is not required.
51 2. Altered source versions must be plainly marked as such, and must not be
52 misrepresented as being the original software.
53 3. This notice may not be removed or altered from any source distribution.
54 */
55
56 #include "power_x11.h"
57
58 #include <stdio.h>
59 #include <unistd.h>
60
61 #include "core/error_macros.h"
62 #include <dirent.h>
63 #include <fcntl.h>
64 #include <sys/stat.h>
65 #include <sys/types.h>
66
67 // CODE CHUNK IMPORTED FROM SDL 2.0
68
69 static const char *proc_apm_path = "/proc/apm";
70 static const char *proc_acpi_battery_path = "/proc/acpi/battery";
71 static const char *proc_acpi_ac_adapter_path = "/proc/acpi/ac_adapter";
72 static const char *sys_class_power_supply_path = "/sys/class/power_supply";
73
open_power_file(const char * base,const char * node,const char * key)74 FileAccessRef PowerX11::open_power_file(const char *base, const char *node, const char *key) {
75 String path = String(base) + String("/") + String(node) + String("/") + String(key);
76 FileAccessRef f = FileAccess::open(path, FileAccess::READ);
77 return f;
78 }
79
read_power_file(const char * base,const char * node,const char * key,char * buf,size_t buflen)80 bool PowerX11::read_power_file(const char *base, const char *node, const char *key, char *buf, size_t buflen) {
81 ssize_t br = 0;
82 FileAccessRef fd = open_power_file(base, node, key);
83 if (!fd) {
84 return false;
85 }
86 br = fd->get_buffer(reinterpret_cast<uint8_t *>(buf), buflen - 1);
87 fd->close();
88 if (br < 0) {
89 return false;
90 }
91 buf[br] = '\0'; // null-terminate the string
92 return true;
93 }
94
make_proc_acpi_key_val(char ** _ptr,char ** _key,char ** _val)95 bool PowerX11::make_proc_acpi_key_val(char **_ptr, char **_key, char **_val) {
96 char *ptr = *_ptr;
97
98 while (*ptr == ' ') {
99 ptr++; /* skip whitespace. */
100 }
101
102 if (*ptr == '\0') {
103 return false; /* EOF. */
104 }
105
106 *_key = ptr;
107
108 while ((*ptr != ':') && (*ptr != '\0')) {
109 ptr++;
110 }
111
112 if (*ptr == '\0') {
113 return false; /* (unexpected) EOF. */
114 }
115
116 *(ptr++) = '\0'; /* terminate the key. */
117
118 while (*ptr == ' ') {
119 ptr++; /* skip whitespace. */
120 }
121
122 if (*ptr == '\0') {
123 return false; /* (unexpected) EOF. */
124 }
125
126 *_val = ptr;
127
128 while ((*ptr != '\n') && (*ptr != '\0')) {
129 ptr++;
130 }
131
132 if (*ptr != '\0') {
133 *(ptr++) = '\0'; /* terminate the value. */
134 }
135
136 *_ptr = ptr; /* store for next time. */
137 return true;
138 }
139
check_proc_acpi_battery(const char * node,bool * have_battery,bool * charging)140 void PowerX11::check_proc_acpi_battery(const char *node, bool *have_battery, bool *charging) {
141 const char *base = proc_acpi_battery_path;
142 char info[1024];
143 char state[1024];
144 char *ptr = NULL;
145 char *key = NULL;
146 char *val = NULL;
147 bool charge = false;
148 bool choose = false;
149 int maximum = -1;
150 int remaining = -1;
151 int secs = -1;
152 int pct = -1;
153
154 if (!read_power_file(base, node, "state", state, sizeof(state))) {
155 return;
156 } else {
157 if (!read_power_file(base, node, "info", info, sizeof(info)))
158 return;
159 }
160
161 ptr = &state[0];
162 while (make_proc_acpi_key_val(&ptr, &key, &val)) {
163 if (String(key) == "present") {
164 if (String(val) == "yes") {
165 *have_battery = true;
166 }
167 } else if (String(key) == "charging state") {
168 /* !!! FIXME: what exactly _does_ charging/discharging mean? */
169 if (String(val) == "charging/discharging") {
170 charge = true;
171 } else if (String(val) == "charging") {
172 charge = true;
173 }
174 } else if (String(key) == "remaining capacity") {
175 String sval = val;
176 const int cvt = sval.to_int();
177 remaining = cvt;
178 }
179 }
180
181 ptr = &info[0];
182 while (make_proc_acpi_key_val(&ptr, &key, &val)) {
183 if (String(key) == "design capacity") {
184 String sval = val;
185 const int cvt = sval.to_int();
186 maximum = cvt;
187 }
188 }
189
190 if ((maximum >= 0) && (remaining >= 0)) {
191 pct = (int)((((float)remaining) / ((float)maximum)) * 100.0f);
192 if (pct < 0) {
193 pct = 0;
194 } else if (pct > 100) {
195 pct = 100;
196 }
197 }
198
199 /* !!! FIXME: calculate (secs). */
200
201 /*
202 * We pick the battery that claims to have the most minutes left.
203 * (failing a report of minutes, we'll take the highest percent.)
204 */
205 // -- GODOT start --
206 //if ((secs < 0) && (this->nsecs_left < 0)) {
207 if (this->nsecs_left < 0) {
208 // -- GODOT end --
209 if ((pct < 0) && (this->percent_left < 0)) {
210 choose = true; /* at least we know there's a battery. */
211 }
212 if (pct > this->percent_left) {
213 choose = true;
214 }
215 } else if (secs > this->nsecs_left) {
216 choose = true;
217 }
218
219 if (choose) {
220 this->nsecs_left = secs;
221 this->percent_left = pct;
222 *charging = charge;
223 }
224 }
225
check_proc_acpi_ac_adapter(const char * node,bool * have_ac)226 void PowerX11::check_proc_acpi_ac_adapter(const char *node, bool *have_ac) {
227 const char *base = proc_acpi_ac_adapter_path;
228 char state[256];
229 char *ptr = NULL;
230 char *key = NULL;
231 char *val = NULL;
232
233 if (!read_power_file(base, node, "state", state, sizeof(state))) {
234 return;
235 }
236
237 ptr = &state[0];
238 while (make_proc_acpi_key_val(&ptr, &key, &val)) {
239 String skey = key;
240 if (skey == "state") {
241 String sval = val;
242 if (sval == "on-line") {
243 *have_ac = true;
244 }
245 }
246 }
247 }
248
GetPowerInfo_Linux_proc_acpi()249 bool PowerX11::GetPowerInfo_Linux_proc_acpi() {
250 String node;
251 DirAccess *dirp = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
252 bool have_battery = false;
253 bool have_ac = false;
254 bool charging = false;
255
256 this->nsecs_left = -1;
257 this->percent_left = -1;
258 this->power_state = OS::POWERSTATE_UNKNOWN;
259
260 dirp->change_dir(proc_acpi_battery_path);
261 Error err = dirp->list_dir_begin();
262
263 if (err != OK) {
264 return false; /* can't use this interface. */
265 } else {
266 node = dirp->get_next();
267 while (node != "") {
268 check_proc_acpi_battery(node.utf8().get_data(), &have_battery, &charging /*, seconds, percent*/);
269 node = dirp->get_next();
270 }
271 }
272 dirp->change_dir(proc_acpi_ac_adapter_path);
273 err = dirp->list_dir_begin();
274 if (err != OK) {
275 return false; /* can't use this interface. */
276 } else {
277 node = dirp->get_next();
278 while (node != "") {
279 check_proc_acpi_ac_adapter(node.utf8().get_data(), &have_ac);
280 node = dirp->get_next();
281 }
282 }
283
284 if (!have_battery) {
285 this->power_state = OS::POWERSTATE_NO_BATTERY;
286 } else if (charging) {
287 this->power_state = OS::POWERSTATE_CHARGING;
288 } else if (have_ac) {
289 this->power_state = OS::POWERSTATE_CHARGED;
290 } else {
291 this->power_state = OS::POWERSTATE_ON_BATTERY;
292 }
293
294 memdelete(dirp);
295 return true; /* definitive answer. */
296 }
297
next_string(char ** _ptr,char ** _str)298 bool PowerX11::next_string(char **_ptr, char **_str) {
299 char *ptr = *_ptr;
300 char *str = *_str;
301
302 while (*ptr == ' ') { /* skip any spaces... */
303 ptr++;
304 }
305
306 if (*ptr == '\0') {
307 return false;
308 }
309
310 str = ptr;
311 while ((*ptr != ' ') && (*ptr != '\n') && (*ptr != '\0'))
312 ptr++;
313
314 if (*ptr != '\0')
315 *(ptr++) = '\0';
316
317 *_str = str;
318 *_ptr = ptr;
319 return true;
320 }
321
int_string(char * str,int * val)322 bool PowerX11::int_string(char *str, int *val) {
323 String sval = str;
324 *val = sval.to_int();
325 return (*str != '\0');
326 }
327
328 /* http://lxr.linux.no/linux+v2.6.29/drivers/char/apm-emulation.c */
GetPowerInfo_Linux_proc_apm()329 bool PowerX11::GetPowerInfo_Linux_proc_apm() {
330 bool need_details = false;
331 int ac_status = 0;
332 int battery_status = 0;
333 int battery_flag = 0;
334 int battery_percent = 0;
335 int battery_time = 0;
336 FileAccessRef fd = FileAccess::open(proc_apm_path, FileAccess::READ);
337 char buf[128];
338 char *ptr = &buf[0];
339 char *str = NULL;
340 ssize_t br;
341
342 if (!fd) {
343 return false; /* can't use this interface. */
344 }
345
346 br = fd->get_buffer(reinterpret_cast<uint8_t *>(buf), sizeof(buf) - 1);
347 fd->close();
348
349 if (br < 0) {
350 return false;
351 }
352
353 buf[br] = '\0'; /* null-terminate the string. */
354 if (!next_string(&ptr, &str)) { /* driver version */
355 return false;
356 }
357 if (!next_string(&ptr, &str)) { /* BIOS version */
358 return false;
359 }
360 if (!next_string(&ptr, &str)) { /* APM flags */
361 return false;
362 }
363
364 if (!next_string(&ptr, &str)) { /* AC line status */
365 return false;
366 } else if (!int_string(str, &ac_status)) {
367 return false;
368 }
369
370 if (!next_string(&ptr, &str)) { /* battery status */
371 return false;
372 } else if (!int_string(str, &battery_status)) {
373 return false;
374 }
375 if (!next_string(&ptr, &str)) { /* battery flag */
376 return false;
377 } else if (!int_string(str, &battery_flag)) {
378 return false;
379 }
380 if (!next_string(&ptr, &str)) { /* remaining battery life percent */
381 return false;
382 }
383 String sstr = str;
384 if (sstr[sstr.length() - 1] == '%') {
385 sstr[sstr.length() - 1] = '\0';
386 }
387 if (!int_string(str, &battery_percent)) {
388 return false;
389 }
390
391 if (!next_string(&ptr, &str)) { /* remaining battery life time */
392 return false;
393 } else if (!int_string(str, &battery_time)) {
394 return false;
395 }
396
397 if (!next_string(&ptr, &str)) { /* remaining battery life time units */
398 return false;
399 } else if (String(str) == "min") {
400 battery_time *= 60;
401 }
402
403 if (battery_flag == 0xFF) { /* unknown state */
404 this->power_state = OS::POWERSTATE_UNKNOWN;
405 } else if (battery_flag & (1 << 7)) { /* no battery */
406 this->power_state = OS::POWERSTATE_NO_BATTERY;
407 } else if (battery_flag & (1 << 3)) { /* charging */
408 this->power_state = OS::POWERSTATE_CHARGING;
409 need_details = true;
410 } else if (ac_status == 1) {
411 this->power_state = OS::POWERSTATE_CHARGED; /* on AC, not charging. */
412 need_details = true;
413 } else {
414 this->power_state = OS::POWERSTATE_ON_BATTERY;
415 need_details = true;
416 }
417
418 this->percent_left = -1;
419 this->nsecs_left = -1;
420 if (need_details) {
421 const int pct = battery_percent;
422 const int secs = battery_time;
423
424 if (pct >= 0) { /* -1 == unknown */
425 this->percent_left = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
426 }
427 if (secs >= 0) { /* -1 == unknown */
428 this->nsecs_left = secs;
429 }
430 }
431
432 return true;
433 }
434
435 /* !!! FIXME: implement d-bus queries to org.freedesktop.UPower. */
436
GetPowerInfo_Linux_sys_class_power_supply()437 bool PowerX11::GetPowerInfo_Linux_sys_class_power_supply(/*PowerState *state, int *seconds, int *percent*/) {
438 const char *base = sys_class_power_supply_path;
439 String name;
440
441 DirAccess *dirp = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
442 dirp->change_dir(base);
443 Error err = dirp->list_dir_begin();
444
445 if (err != OK) {
446 return false;
447 }
448
449 this->power_state = OS::POWERSTATE_NO_BATTERY; /* assume we're just plugged in. */
450 this->nsecs_left = -1;
451 this->percent_left = -1;
452
453 name = dirp->get_next();
454
455 while (name != "") {
456 bool choose = false;
457 char str[64];
458 OS::PowerState st;
459 int secs;
460 int pct;
461
462 if ((name == ".") || (name == "..")) {
463 name = dirp->get_next();
464 continue; //skip these, of course.
465 } else {
466 if (!read_power_file(base, name.utf8().get_data(), "type", str, sizeof(str))) {
467 name = dirp->get_next();
468 continue; // Don't know _what_ we're looking at. Give up on it.
469 } else {
470 if (String(str) != "Battery\n") {
471 name = dirp->get_next();
472 continue; // we don't care about UPS and such.
473 }
474 }
475 }
476
477 /* some drivers don't offer this, so if it's not explicitly reported assume it's present. */
478 if (read_power_file(base, name.utf8().get_data(), "present", str, sizeof(str)) && (String(str) == "0\n")) {
479 st = OS::POWERSTATE_NO_BATTERY;
480 } else if (!read_power_file(base, name.utf8().get_data(), "status", str, sizeof(str))) {
481 st = OS::POWERSTATE_UNKNOWN; /* uh oh */
482 } else if (String(str) == "Charging\n") {
483 st = OS::POWERSTATE_CHARGING;
484 } else if (String(str) == "Discharging\n") {
485 st = OS::POWERSTATE_ON_BATTERY;
486 } else if ((String(str) == "Full\n") || (String(str) == "Not charging\n")) {
487 st = OS::POWERSTATE_CHARGED;
488 } else {
489 st = OS::POWERSTATE_UNKNOWN; /* uh oh */
490 }
491
492 if (!read_power_file(base, name.utf8().get_data(), "capacity", str, sizeof(str))) {
493 pct = -1;
494 } else {
495 pct = String(str).to_int();
496 pct = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
497 }
498
499 if (!read_power_file(base, name.utf8().get_data(), "time_to_empty_now", str, sizeof(str))) {
500 secs = -1;
501 } else {
502 secs = String(str).to_int();
503 secs = (secs <= 0) ? -1 : secs; /* 0 == unknown */
504 }
505
506 /*
507 * We pick the battery that claims to have the most minutes left.
508 * (failing a report of minutes, we'll take the highest percent.)
509 */
510 if ((secs < 0) && (this->nsecs_left < 0)) {
511 if ((pct < 0) && (this->percent_left < 0)) {
512 choose = true; /* at least we know there's a battery. */
513 } else if (pct > this->percent_left) {
514 choose = true;
515 }
516 } else if (secs > this->nsecs_left) {
517 choose = true;
518 }
519
520 if (choose) {
521 this->nsecs_left = secs;
522 this->percent_left = pct;
523 this->power_state = st;
524 }
525
526 name = dirp->get_next();
527 }
528
529 memdelete(dirp);
530 return true; /* don't look any further*/
531 }
532
UpdatePowerInfo()533 bool PowerX11::UpdatePowerInfo() {
534 if (GetPowerInfo_Linux_sys_class_power_supply()) { // try method 1
535 return true;
536 }
537 if (GetPowerInfo_Linux_proc_acpi()) { // try further
538 return true;
539 }
540 if (GetPowerInfo_Linux_proc_apm()) { // try even further
541 return true;
542 }
543 return false;
544 }
545
PowerX11()546 PowerX11::PowerX11() :
547 nsecs_left(-1),
548 percent_left(-1),
549 power_state(OS::POWERSTATE_UNKNOWN) {
550 }
551
~PowerX11()552 PowerX11::~PowerX11() {
553 }
554
get_power_state()555 OS::PowerState PowerX11::get_power_state() {
556 if (UpdatePowerInfo()) {
557 return power_state;
558 } else {
559 return OS::POWERSTATE_UNKNOWN;
560 }
561 }
562
get_power_seconds_left()563 int PowerX11::get_power_seconds_left() {
564 if (UpdatePowerInfo()) {
565 return nsecs_left;
566 } else {
567 return -1;
568 }
569 }
570
get_power_percent_left()571 int PowerX11::get_power_percent_left() {
572 if (UpdatePowerInfo()) {
573 return percent_left;
574 } else {
575 return -1;
576 }
577 }
578