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