power_x11.cpp 15 KB

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