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