Atlas - SDL_syspower.c

Home / ext / SDL / src / power / linux Lines: 10 | Size: 20912 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#ifndef SDL_POWER_DISABLED 24#ifdef SDL_POWER_LINUX 25 26#include <stdio.h> 27#include <unistd.h> 28 29#include <sys/types.h> 30#include <sys/stat.h> 31#include <dirent.h> 32#include <fcntl.h> 33 34#include "../SDL_syspower.h" 35 36#include "../../core/linux/SDL_dbus.h" 37 38static const char *proc_apm_path = "/proc/apm"; 39static const char *proc_acpi_battery_path = "/proc/acpi/battery"; 40static const char *proc_acpi_ac_adapter_path = "/proc/acpi/ac_adapter"; 41static const char *sys_class_power_supply_path = "/sys/class/power_supply"; 42 43static int open_power_file(const char *base, const char *node, const char *key) 44{ 45 int fd; 46 const size_t pathlen = SDL_strlen(base) + SDL_strlen(node) + SDL_strlen(key) + 3; 47 char *path = SDL_stack_alloc(char, pathlen); 48 if (!path) { 49 return -1; // oh well. 50 } 51 52 (void)SDL_snprintf(path, pathlen, "%s/%s/%s", base, node, key); 53 fd = open(path, O_RDONLY | O_CLOEXEC); 54 SDL_stack_free(path); 55 return fd; 56} 57 58static bool read_power_file(const char *base, const char *node, const char *key, 59 char *buf, size_t buflen) 60{ 61 ssize_t br = 0; 62 const int fd = open_power_file(base, node, key); 63 if (fd == -1) { 64 return false; 65 } 66 br = read(fd, buf, buflen - 1); 67 close(fd); 68 if (br < 0) { 69 return false; 70 } 71 buf[br] = '\0'; // null-terminate the string. 72 return true; 73} 74 75static bool make_proc_acpi_key_val(char **_ptr, char **_key, char **_val) 76{ 77 char *ptr = *_ptr; 78 79 while (*ptr == ' ') { 80 ptr++; // skip whitespace. 81 } 82 83 if (*ptr == '\0') { 84 return false; // EOF. 85 } 86 87 *_key = ptr; 88 89 while ((*ptr != ':') && (*ptr != '\0')) { 90 ptr++; 91 } 92 93 if (*ptr == '\0') { 94 return false; // (unexpected) EOF. 95 } 96 97 *(ptr++) = '\0'; // terminate the key. 98 99 while (*ptr == ' ') { 100 ptr++; // skip whitespace. 101 } 102 103 if (*ptr == '\0') { 104 return false; // (unexpected) EOF. 105 } 106 107 *_val = ptr; 108 109 while ((*ptr != '\n') && (*ptr != '\0')) { 110 ptr++; 111 } 112 113 if (*ptr != '\0') { 114 *(ptr++) = '\0'; // terminate the value. 115 } 116 117 *_ptr = ptr; // store for next time. 118 return true; 119} 120 121static void check_proc_acpi_battery(const char *node, bool *have_battery, 122 bool *charging, int *seconds, int *percent) 123{ 124 const char *base = proc_acpi_battery_path; 125 char info[1024]; 126 char state[1024]; 127 char *ptr = NULL; 128 char *key = NULL; 129 char *val = NULL; 130 bool charge = false; 131 bool choose = false; 132 int maximum = -1; 133 int remaining = -1; 134 int secs = -1; 135 int pct = -1; 136 137 if (!read_power_file(base, node, "state", state, sizeof(state))) { 138 return; 139 } else if (!read_power_file(base, node, "info", info, sizeof(info))) { 140 return; 141 } 142 143 ptr = &state[0]; 144 while (make_proc_acpi_key_val(&ptr, &key, &val)) { 145 if (SDL_strcasecmp(key, "present") == 0) { 146 if (SDL_strcasecmp(val, "yes") == 0) { 147 *have_battery = true; 148 } 149 } else if (SDL_strcasecmp(key, "charging state") == 0) { 150 // !!! FIXME: what exactly _does_ charging/discharging mean? 151 if (SDL_strcasecmp(val, "charging/discharging") == 0) { 152 charge = true; 153 } else if (SDL_strcasecmp(val, "charging") == 0) { 154 charge = true; 155 } 156 } else if (SDL_strcasecmp(key, "remaining capacity") == 0) { 157 char *endptr = NULL; 158 const int cvt = (int)SDL_strtol(val, &endptr, 10); 159 if (*endptr == ' ') { 160 remaining = cvt; 161 } 162 } 163 } 164 165 ptr = &info[0]; 166 while (make_proc_acpi_key_val(&ptr, &key, &val)) { 167 if (SDL_strcasecmp(key, "design capacity") == 0) { 168 char *endptr = NULL; 169 const int cvt = (int)SDL_strtol(val, &endptr, 10); 170 if (*endptr == ' ') { 171 maximum = cvt; 172 } 173 } 174 } 175 176 if ((maximum >= 0) && (remaining >= 0)) { 177 pct = (int)((((float)remaining) / ((float)maximum)) * 100.0f); 178 if (pct < 0) { 179 pct = 0; 180 } else if (pct > 100) { 181 pct = 100; 182 } 183 } 184 185 // !!! FIXME: calculate (secs). 186 187 /* 188 * We pick the battery that claims to have the most minutes left. 189 * (failing a report of minutes, we'll take the highest percent.) 190 */ 191 if ((secs < 0) && (*seconds < 0)) { 192 if ((pct < 0) && (*percent < 0)) { 193 choose = true; // at least we know there's a battery. 194 } 195 if (pct > *percent) { 196 choose = true; 197 } 198 } else if (secs > *seconds) { 199 choose = true; 200 } 201 202 if (choose) { 203 *seconds = secs; 204 *percent = pct; 205 *charging = charge; 206 } 207} 208 209static void check_proc_acpi_ac_adapter(const char *node, bool *have_ac) 210{ 211 const char *base = proc_acpi_ac_adapter_path; 212 char state[256]; 213 char *ptr = NULL; 214 char *key = NULL; 215 char *val = NULL; 216 217 if (!read_power_file(base, node, "state", state, sizeof(state))) { 218 return; 219 } 220 221 ptr = &state[0]; 222 while (make_proc_acpi_key_val(&ptr, &key, &val)) { 223 if (SDL_strcasecmp(key, "state") == 0) { 224 if (SDL_strcasecmp(val, "on-line") == 0) { 225 *have_ac = true; 226 } 227 } 228 } 229} 230 231bool SDL_GetPowerInfo_Linux_proc_acpi(SDL_PowerState *state, int *seconds, int *percent) 232{ 233 struct dirent *dent = NULL; 234 DIR *dirp = NULL; 235 bool have_battery = false; 236 bool have_ac = false; 237 bool charging = false; 238 239 *seconds = -1; 240 *percent = -1; 241 *state = SDL_POWERSTATE_UNKNOWN; 242 243 dirp = opendir(proc_acpi_battery_path); 244 if (!dirp) { 245 return false; // can't use this interface. 246 } else { 247 while ((dent = readdir(dirp)) != NULL) { 248 const char *node = dent->d_name; 249 check_proc_acpi_battery(node, &have_battery, &charging, 250 seconds, percent); 251 } 252 closedir(dirp); 253 } 254 255 dirp = opendir(proc_acpi_ac_adapter_path); 256 if (!dirp) { 257 return false; // can't use this interface. 258 } else { 259 while ((dent = readdir(dirp)) != NULL) { 260 const char *node = dent->d_name; 261 check_proc_acpi_ac_adapter(node, &have_ac); 262 } 263 closedir(dirp); 264 } 265 266 if (!have_battery) { 267 *state = SDL_POWERSTATE_NO_BATTERY; 268 } else if (charging) { 269 *state = SDL_POWERSTATE_CHARGING; 270 } else if (have_ac) { 271 *state = SDL_POWERSTATE_CHARGED; 272 } else { 273 *state = SDL_POWERSTATE_ON_BATTERY; 274 } 275 276 return true; // definitive answer. 277} 278 279static bool next_string(char **_ptr, char **_str) 280{ 281 char *ptr = *_ptr; 282 char *str; 283 284 while (*ptr == ' ') { // skip any spaces... 285 ptr++; 286 } 287 288 if (*ptr == '\0') { 289 return false; 290 } 291 292 str = ptr; 293 while ((*ptr != ' ') && (*ptr != '\n') && (*ptr != '\0')) { 294 ptr++; 295 } 296 297 if (*ptr != '\0') { 298 *(ptr++) = '\0'; 299 } 300 301 *_str = str; 302 *_ptr = ptr; 303 return true; 304} 305 306static bool int_string(char *str, int *val) 307{ 308 char *endptr = NULL; 309 *val = (int)SDL_strtol(str, &endptr, 0); 310 return (*str != '\0') && (*endptr == '\0'); 311} 312 313// http://lxr.linux.no/linux+v2.6.29/drivers/char/apm-emulation.c 314bool SDL_GetPowerInfo_Linux_proc_apm(SDL_PowerState *state, int *seconds, int *percent) 315{ 316 bool need_details = false; 317 int ac_status = 0; 318 int battery_status = 0; 319 int battery_flag = 0; 320 int battery_percent = 0; 321 int battery_time = 0; 322 const int fd = open(proc_apm_path, O_RDONLY | O_CLOEXEC); 323 char buf[128]; 324 char *ptr = &buf[0]; 325 char *str = NULL; 326 ssize_t br; 327 328 if (fd == -1) { 329 return false; // can't use this interface. 330 } 331 332 br = read(fd, buf, sizeof(buf) - 1); 333 close(fd); 334 335 if (br < 0) { 336 return false; 337 } 338 339 buf[br] = '\0'; // null-terminate the string. 340 if (!next_string(&ptr, &str)) { // driver version 341 return false; 342 } 343 if (!next_string(&ptr, &str)) { // BIOS version 344 return false; 345 } 346 if (!next_string(&ptr, &str)) { // APM flags 347 return false; 348 } 349 350 if (!next_string(&ptr, &str)) { // AC line status 351 return false; 352 } else if (!int_string(str, &ac_status)) { 353 return false; 354 } 355 356 if (!next_string(&ptr, &str)) { // battery status 357 return false; 358 } else if (!int_string(str, &battery_status)) { 359 return false; 360 } 361 if (!next_string(&ptr, &str)) { // battery flag 362 return false; 363 } else if (!int_string(str, &battery_flag)) { 364 return false; 365 } 366 if (!next_string(&ptr, &str)) { // remaining battery life percent 367 return false; 368 } 369 if (str[SDL_strlen(str) - 1] == '%') { 370 str[SDL_strlen(str) - 1] = '\0'; 371 } 372 if (!int_string(str, &battery_percent)) { 373 return false; 374 } 375 376 if (!next_string(&ptr, &str)) { // remaining battery life time 377 return false; 378 } else if (!int_string(str, &battery_time)) { 379 return false; 380 } 381 382 if (!next_string(&ptr, &str)) { // remaining battery life time units 383 return false; 384 } else if (SDL_strcasecmp(str, "min") == 0) { 385 battery_time *= 60; 386 } 387 388 if (battery_flag == 0xFF) { // unknown state 389 *state = SDL_POWERSTATE_UNKNOWN; 390 } else if (battery_flag & (1 << 7)) { // no battery 391 *state = SDL_POWERSTATE_NO_BATTERY; 392 } else if (battery_flag & (1 << 3)) { // charging 393 *state = SDL_POWERSTATE_CHARGING; 394 need_details = true; 395 } else if (ac_status == 1) { 396 *state = SDL_POWERSTATE_CHARGED; // on AC, not charging. 397 need_details = true; 398 } else { 399 *state = SDL_POWERSTATE_ON_BATTERY; 400 need_details = true; 401 } 402 403 *percent = -1; 404 *seconds = -1; 405 if (need_details) { 406 const int pct = battery_percent; 407 const int secs = battery_time; 408 409 if (pct >= 0) { // -1 == unknown 410 *percent = (pct > 100) ? 100 : pct; // clamp between 0%, 100% 411 } 412 if (secs >= 0) { // -1 == unknown 413 *seconds = secs; 414 } 415 } 416 417 return true; 418} 419 420bool SDL_GetPowerInfo_Linux_sys_class_power_supply(SDL_PowerState *state, int *seconds, int *percent) 421{ 422 const char *base = sys_class_power_supply_path; 423 struct dirent *dent; 424 DIR *dirp; 425 426 dirp = opendir(base); 427 if (!dirp) { 428 return false; 429 } 430 431 *state = SDL_POWERSTATE_NO_BATTERY; // assume we're just plugged in. 432 *seconds = -1; 433 *percent = -1; 434 435 while ((dent = readdir(dirp)) != NULL) { 436 const char *name = dent->d_name; 437 bool choose = false; 438 char str[64]; 439 SDL_PowerState st; 440 int secs; 441 int pct; 442 int energy; 443 int power; 444 445 if ((SDL_strcmp(name, ".") == 0) || (SDL_strcmp(name, "..") == 0)) { 446 continue; // skip these, of course. 447 } else if (!read_power_file(base, name, "type", str, sizeof(str))) { 448 continue; // Don't know _what_ we're looking at. Give up on it. 449 } else if (SDL_strcasecmp(str, "Battery\n") != 0) { 450 continue; // we don't care about UPS and such. 451 } 452 453 /* if the scope is "device," it might be something like a PS4 454 controller reporting its own battery, and not something that powers 455 the system. Most system batteries don't list a scope at all; we 456 assume it's a system battery if not specified. */ 457 if (read_power_file(base, name, "scope", str, sizeof(str))) { 458 if (SDL_strcasecmp(str, "Device\n") == 0) { 459 continue; // skip external devices with their own batteries. 460 } 461 } 462 463 // some drivers don't offer this, so if it's not explicitly reported assume it's present. 464 if (read_power_file(base, name, "present", str, sizeof(str)) && (SDL_strcmp(str, "0\n") == 0)) { 465 st = SDL_POWERSTATE_NO_BATTERY; 466 } else if (!read_power_file(base, name, "status", str, sizeof(str))) { 467 st = SDL_POWERSTATE_UNKNOWN; // uh oh 468 } else if (SDL_strcasecmp(str, "Charging\n") == 0) { 469 st = SDL_POWERSTATE_CHARGING; 470 } else if (SDL_strcasecmp(str, "Discharging\n") == 0) { 471 st = SDL_POWERSTATE_ON_BATTERY; 472 } else if ((SDL_strcasecmp(str, "Full\n") == 0) || (SDL_strcasecmp(str, "Not charging\n") == 0)) { 473 st = SDL_POWERSTATE_CHARGED; 474 } else { 475 st = SDL_POWERSTATE_UNKNOWN; // uh oh 476 } 477 478 if (!read_power_file(base, name, "capacity", str, sizeof(str))) { 479 pct = -1; 480 } else { 481 pct = SDL_atoi(str); 482 pct = (pct > 100) ? 100 : pct; // clamp between 0%, 100% 483 } 484 485 if (read_power_file(base, name, "time_to_empty_now", str, sizeof(str))) { 486 secs = SDL_atoi(str); 487 secs = (secs <= 0) ? -1 : secs; // 0 == unknown 488 } else if (st == SDL_POWERSTATE_ON_BATTERY) { 489 /* energy is Watt*hours and power is Watts */ 490 energy = (read_power_file(base, name, "energy_now", str, sizeof(str))) ? SDL_atoi(str) : -1; 491 power = (read_power_file(base, name, "power_now", str, sizeof(str))) ? SDL_atoi(str) : -1; 492 secs = (energy >= 0 && power > 0) ? (3600LL * energy) / power : -1; 493 } else { 494 secs = -1; 495 } 496 497 /* 498 * We pick the battery that claims to have the most minutes left. 499 * (failing a report of minutes, we'll take the highest percent.) 500 */ 501 if ((secs < 0) && (*seconds < 0)) { 502 if ((pct < 0) && (*percent < 0)) { 503 choose = true; // at least we know there's a battery. 504 } else if (pct > *percent) { 505 choose = true; 506 } 507 } else if (secs > *seconds) { 508 choose = true; 509 } 510 511 if (choose) { 512 *seconds = secs; 513 *percent = pct; 514 *state = st; 515 } 516 } 517 518 closedir(dirp); 519 return true; // don't look any further. 520} 521 522// d-bus queries to org.freedesktop.UPower. 523#ifdef SDL_USE_LIBDBUS 524#define UPOWER_DBUS_NODE "org.freedesktop.UPower" 525#define UPOWER_DBUS_PATH "/org/freedesktop/UPower" 526#define UPOWER_DBUS_INTERFACE "org.freedesktop.UPower" 527#define UPOWER_DEVICE_DBUS_INTERFACE "org.freedesktop.UPower.Device" 528 529static void check_upower_device(DBusConnection *conn, const char *path, SDL_PowerState *state, int *seconds, int *percent) 530{ 531 bool choose = false; 532 SDL_PowerState st; 533 int secs; 534 int pct; 535 Uint32 ui32 = 0; 536 Sint64 si64 = 0; 537 double d = 0.0; 538 539 if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Type", DBUS_TYPE_UINT32, &ui32)) { 540 return; // Don't know _what_ we're looking at. Give up on it. 541 } else if (ui32 != 2) { // 2==Battery 542 return; // we don't care about UPS and such. 543 } else if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "PowerSupply", DBUS_TYPE_BOOLEAN, &ui32)) { 544 return; 545 } else if (!ui32) { 546 return; // we don't care about random devices with batteries, like wireless controllers, etc 547 } 548 549 if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "IsPresent", DBUS_TYPE_BOOLEAN, &ui32)) { 550 return; 551 } 552 if (!ui32) { 553 st = SDL_POWERSTATE_NO_BATTERY; 554 } else { 555 /* Get updated information on the battery status 556 * This can occasionally fail, and we'll just return slightly stale data in that case 557 */ 558 SDL_DBus_CallMethodOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Refresh", 559 DBUS_TYPE_INVALID, 560 DBUS_TYPE_INVALID); 561 562 if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "State", DBUS_TYPE_UINT32, &ui32)) { 563 st = SDL_POWERSTATE_UNKNOWN; // uh oh 564 } else if (ui32 == 1) { // 1 == charging 565 st = SDL_POWERSTATE_CHARGING; 566 } else if ((ui32 == 2) || (ui32 == 3) || (ui32 == 6)) { 567 /* 2 == discharging; 568 * 3 == empty; 569 * 6 == "pending discharge" which GNOME interprets as equivalent 570 * to discharging */ 571 st = SDL_POWERSTATE_ON_BATTERY; 572 } else if ((ui32 == 4) || (ui32 == 5)) { 573 /* 4 == full; 574 * 5 == "pending charge" which GNOME shows as "Not charging", 575 * used when a battery is configured to stop charging at a 576 * lower than 100% threshold */ 577 st = SDL_POWERSTATE_CHARGED; 578 } else { 579 st = SDL_POWERSTATE_UNKNOWN; // uh oh 580 } 581 } 582 583 if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Percentage", DBUS_TYPE_DOUBLE, &d)) { 584 pct = -1; // some old/cheap batteries don't set this property. 585 } else { 586 pct = (int)d; 587 pct = (pct > 100) ? 100 : pct; // clamp between 0%, 100% 588 } 589 590 if (!SDL_DBus_QueryPropertyOnConnection(conn, NULL, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "TimeToEmpty", DBUS_TYPE_INT64, &si64)) { 591 secs = -1; 592 } else { 593 secs = (int)si64; 594 secs = (secs <= 0) ? -1 : secs; // 0 == unknown 595 } 596 597 /* 598 * We pick the battery that claims to have the most minutes left. 599 * (failing a report of minutes, we'll take the highest percent.) 600 */ 601 if ((secs < 0) && (*seconds < 0)) { 602 if ((pct < 0) && (*percent < 0)) { 603 choose = true; // at least we know there's a battery. 604 } else if (pct > *percent) { 605 choose = true; 606 } 607 } else if (secs > *seconds) { 608 choose = true; 609 } 610 611 if (choose) { 612 *seconds = secs; 613 *percent = pct; 614 *state = st; 615 } 616} 617#endif 618 619bool SDL_GetPowerInfo_Linux_org_freedesktop_upower(SDL_PowerState *state, int *seconds, int *percent) 620{ 621 bool result = false; 622 623#ifdef SDL_USE_LIBDBUS 624 SDL_DBusContext *dbus = SDL_DBus_GetContext(); 625 DBusMessage *reply = NULL; 626 char **paths = NULL; 627 char *path = NULL; 628 int i, numpaths = 0; 629 630 if (!dbus) { 631 return false; // try a different approach than UPower. 632 } 633 634 if (SDL_DBus_CallMethodOnConnection(dbus->system_conn, &reply, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "GetDisplayDevice", 635 DBUS_TYPE_INVALID, 636 DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { 637 result = true; // Clearly we can use this interface. 638 *state = SDL_POWERSTATE_NO_BATTERY; // assume we're just plugged in. 639 *seconds = -1; 640 *percent = -1; 641 642 check_upower_device(dbus->system_conn, path, state, seconds, percent); 643 SDL_DBus_FreeReply(&reply); 644 645 } else if (SDL_DBus_CallMethodOnConnection(dbus->system_conn, NULL, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "EnumerateDevices", 646 DBUS_TYPE_INVALID, 647 DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &numpaths, DBUS_TYPE_INVALID)) { 648 result = true; // Clearly we can use this interface. 649 *state = SDL_POWERSTATE_NO_BATTERY; // assume we're just plugged in. 650 *seconds = -1; 651 *percent = -1; 652 653 for (i = 0; i < numpaths; i++) { 654 check_upower_device(dbus->system_conn, paths[i], state, seconds, percent); 655 } 656 657 dbus->free_string_array(paths); 658 } 659 660#endif // SDL_USE_LIBDBUS 661 662 return result; 663} 664 665#endif // SDL_POWER_LINUX 666#endif // SDL_POWER_DISABLED 667
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.