diff options
Diffstat (limited to 'src/detection/battery')
| -rw-r--r-- | src/detection/battery/battery.h | 32 | ||||
| -rw-r--r-- | src/detection/battery/battery_android.c | 160 | ||||
| -rw-r--r-- | src/detection/battery/battery_apple.c | 117 | ||||
| -rw-r--r-- | src/detection/battery/battery_bsd.c | 90 | ||||
| -rw-r--r-- | src/detection/battery/battery_haiku.c | 71 | ||||
| -rw-r--r-- | src/detection/battery/battery_linux.c | 203 | ||||
| -rw-r--r-- | src/detection/battery/battery_nbsd.c | 109 | ||||
| -rw-r--r-- | src/detection/battery/battery_nosupport.c | 7 | ||||
| -rw-r--r-- | src/detection/battery/battery_obsd.c | 60 | ||||
| -rw-r--r-- | src/detection/battery/battery_windows.c | 384 |
10 files changed, 1233 insertions, 0 deletions
diff --git a/src/detection/battery/battery.h b/src/detection/battery/battery.h new file mode 100644 index 0000000..1a3b4e9 --- /dev/null +++ b/src/detection/battery/battery.h @@ -0,0 +1,32 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/battery/option.h" + +#define FF_BATTERY_TEMP_UNSET (-DBL_MAX) + +typedef enum FFBatteryStatus { + FF_BATTERY_STATUS_NONE = 0, + FF_BATTERY_STATUS_UNKNOWN = 1 << 0, + FF_BATTERY_STATUS_AC_CONNECTED = 1 << 1, + FF_BATTERY_STATUS_USB_CONNECTED = 1 << 2, + FF_BATTERY_STATUS_WIRELESS_CONNECTED = 1 << 3, + FF_BATTERY_STATUS_CHARGING = 1 << 4, + FF_BATTERY_STATUS_DISCHARGING = 1 << 5, + FF_BATTERY_STATUS_CRITICAL = 1 << 6, +} FFBatteryStatus; + +typedef struct FFBatteryResult { + FFstrbuf manufacturer; + FFstrbuf manufactureDate; + FFstrbuf modelName; + FFstrbuf technology; + FFstrbuf serial; + FFBatteryStatus status; + double capacity; + double temperature; + uint32_t cycleCount; + int32_t timeRemaining; // in seconds, -1 if unknown +} FFBatteryResult; + +const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results); diff --git a/src/detection/battery/battery_android.c b/src/detection/battery/battery_android.c new file mode 100644 index 0000000..d76c4ad --- /dev/null +++ b/src/detection/battery/battery_android.c @@ -0,0 +1,160 @@ +#include "fastfetch.h" +#include "battery.h" +#include "common/strutil.h" +#include "common/processing.h" +#include "common/properties.h" + +#define FF_TERMUX_API_PATH FASTFETCH_TARGET_DIR_ROOT "/libexec/termux-api" +#define FF_TERMUX_API_PARAM "BatteryStatus" + +static inline void wrapYyjsonFree(yyjson_doc** doc) { + assert(doc); + if (*doc) { + yyjson_doc_free(*doc); + } +} + +static const char* parseTermuxApi(FFBatteryOptions* options, FFlist* results) { + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + + if (ffProcessAppendStdOut(&buffer, (char* const[]) { FF_TERMUX_API_PATH, FF_TERMUX_API_PARAM, NULL })) { + return "Starting `" FF_TERMUX_API_PATH " " FF_TERMUX_API_PARAM "` failed"; + } + + yyjson_doc* FF_A_CLEANUP(wrapYyjsonFree) doc = yyjson_read_opts(buffer.chars, buffer.length, 0, NULL, NULL); + if (!doc) { + return "Failed to parse battery info"; + } + + yyjson_val* root = yyjson_doc_get_root(doc); + if (!yyjson_is_obj(root)) { + return "Battery info result is not a JSON object"; + } + + FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + battery->timeRemaining = -1; + battery->status = FF_BATTERY_STATUS_NONE; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->manufactureDate); + + battery->capacity = yyjson_get_num(yyjson_obj_get(root, "percentage")); + const char* acStatus = yyjson_get_str(yyjson_obj_get(root, "plugged")); + if (acStatus) { + if (ffStrEquals(acStatus, "PLUGGED_AC")) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } else if (ffStrEquals(acStatus, "PLUGGED_USB")) { + battery->status |= FF_BATTERY_STATUS_USB_CONNECTED; + } else if (ffStrEquals(acStatus, "PLUGGED_WIRELESS")) { + battery->status |= FF_BATTERY_STATUS_WIRELESS_CONNECTED; + } + } + const char* status = yyjson_get_str(yyjson_obj_get(root, "status")); + if (status) { + if (ffStrEquals(status, "CHARGING")) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } else if (ffStrEquals(status, "DISCHARGING")) { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + } + } + + if (options->temp) { + battery->temperature = yyjson_get_num(yyjson_obj_get(root, "temperature")); + } + + return NULL; +} + +static const char* parseDumpsys(FFBatteryOptions* options, FFlist* results) { + FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); + if (ffProcessAppendStdOut(&buf, (char*[]) { + "/system/bin/dumpsys", + "battery", + NULL, + }) != NULL || + buf.length == 0) { + return "Executing `/system/bin/dumpsys battery` failed"; // Only works in `adb shell`, or when rooted + } + + if (!ffStrbufStartsWithS(&buf, "Current Battery Service state:\n")) { + return "Invalid `/system/bin/dumpsys battery` result"; + } + + const char* start = buf.chars + strlen("Current Battery Service state:\n"); + + FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate(); + if (!ffParsePropLines(start, "present: ", &temp) || !ffStrbufEqualS(&temp, "true")) { + return NULL; + } + ffStrbufClear(&temp); + + FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + battery->timeRemaining = -1; + battery->capacity = 0; + battery->status = FF_BATTERY_STATUS_NONE; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->manufactureDate); + + if (ffParsePropLines(start, "AC powered: ", &temp) && ffStrbufEqualS(&temp, "true")) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + ffStrbufClear(&temp); + + if (ffParsePropLines(start, "USB powered: ", &temp) && ffStrbufEqualS(&temp, "true")) { + battery->status |= FF_BATTERY_STATUS_USB_CONNECTED; + } + ffStrbufClear(&temp); + + if (ffParsePropLines(start, "Wireless powered: ", &temp) && ffStrbufEqualS(&temp, "true")) { + battery->status |= FF_BATTERY_STATUS_WIRELESS_CONNECTED; + } + ffStrbufClear(&temp); + + { + double level = 0, scale = 0; + if (ffParsePropLines(start, "level: ", &temp)) { + level = ffStrbufToDouble(&temp, -DBL_MAX); + } + ffStrbufClear(&temp); + + if (ffParsePropLines(start, "scale: ", &temp)) { + scale = ffStrbufToDouble(&temp, -DBL_MAX); + } + ffStrbufClear(&temp); + + if (level > 0 && scale > 0) { + battery->capacity = level * 100 / scale; + } + } + + if (options->temp) { + if (ffParsePropLines(start, "temperature: ", &temp)) { + battery->temperature = ffStrbufToDouble(&temp, FF_BATTERY_TEMP_UNSET); + if (battery->temperature != FF_BATTERY_TEMP_UNSET) { + battery->temperature /= 10.0; // Android returns temperature in tenths of a degree + } + } + ffStrbufClear(&temp); + } + + ffParsePropLines(start, "technology: ", &battery->technology); + + return NULL; +} + +const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { + const char* error = parseTermuxApi(options, results); + if (error && parseDumpsys(options, results) == NULL) { + return NULL; + } + return error; +} diff --git a/src/detection/battery/battery_apple.c b/src/detection/battery/battery_apple.c new file mode 100644 index 0000000..c22aa6d --- /dev/null +++ b/src/detection/battery/battery_apple.c @@ -0,0 +1,117 @@ +#include "fastfetch.h" +#include "battery.h" +#include "common/apple/cf_helpers.h" +#include "common/apple/smc_temps.h" + +#include <IOKit/IOKitLib.h> +#include <IOKit/pwr_mgt/IOPM.h> + +const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { + FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL; + if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("AppleSmartBattery"), &iterator) != kIOReturnSuccess) { + return "IOServiceGetMatchingServices() failed"; + } + + io_registry_entry_t registryEntry; + while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) { + FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryBattery = registryEntry; + FF_CFTYPE_AUTO_RELEASE CFMutableDictionaryRef properties = NULL; + if (IORegistryEntryCreateCFProperties(entryBattery, &properties, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) { + continue; + } + + int currentCapacity, maxCapacity; + + if (ffCfDictGetInt(properties, CFSTR(kIOPMPSMaxCapacityKey), &maxCapacity) != NULL || maxCapacity <= 0) { + continue; + } + + if (ffCfDictGetInt(properties, CFSTR(kIOPMPSCurrentCapacityKey), ¤tCapacity) != NULL || currentCapacity <= 0) { + continue; + } + + bool boolValue; + + FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); + battery->temperature = FF_BATTERY_TEMP_UNSET; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->manufactureDate); + battery->status = FF_BATTERY_STATUS_NONE; + battery->capacity = currentCapacity * 100.0 / maxCapacity; + battery->cycleCount = 0; + battery->timeRemaining = -1; + + ffCfDictGetString(properties, CFSTR(kIOPMDeviceNameKey), &battery->modelName); + ffCfDictGetString(properties, CFSTR(kIOPMPSSerialKey), &battery->serial); + ffCfDictGetString(properties, CFSTR(kIOPMPSManufacturerKey), &battery->manufacturer); + + if (!ffCfDictGetBool(properties, CFSTR("built-in"), &boolValue) && boolValue) { + if (!battery->manufacturer.length) { + ffStrbufAppendS(&battery->manufacturer, "Apple Inc."); + } + ffStrbufAppendS(&battery->technology, "Lithium"); + if (!battery->modelName.length) { + ffStrbufAppendS(&battery->modelName, "Built-in"); + } + } + + int32_t cycleCount = 0; + ffCfDictGetInt(properties, CFSTR(kIOPMPSCycleCountKey), &cycleCount); + battery->cycleCount = cycleCount < 0 ? 0 : (uint32_t) cycleCount; + + battery->timeRemaining = -1; + if (ffCfDictGetBool(properties, CFSTR(kIOPMPSExternalConnectedKey), &boolValue) == NULL) { + if (boolValue) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } else { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + ffCfDictGetInt(properties, CFSTR("AvgTimeToEmpty"), &battery->timeRemaining); // in minutes + if (battery->timeRemaining < 0 || battery->timeRemaining >= 0xFFFF) { + battery->timeRemaining = -1; + } else { + battery->timeRemaining *= 60; + } + } + } + if (ffCfDictGetBool(properties, CFSTR(kIOPMPSIsChargingKey), &boolValue) == NULL && boolValue) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } + if (ffCfDictGetBool(properties, CFSTR(kIOPMPSAtCriticalLevelKey), &boolValue) == NULL && boolValue) { + battery->status |= FF_BATTERY_STATUS_CRITICAL; + } + + int sbdsManufactureDate = 0; + if (ffCfDictGetInt(properties, CFSTR(kIOPMPSManufactureDateKey), &sbdsManufactureDate) == NULL) { + int day = sbdsManufactureDate & 0b11111; + int month = (sbdsManufactureDate >> 5) & 0b1111; + int year = (sbdsManufactureDate >> 9) + 1800; + ffStrbufSetF(&battery->manufactureDate, "%.4d-%.2d-%.2d", year, month, day); + } else { + CFDictionaryRef batteryData; + if (ffCfDictGetDict(properties, CFSTR("BatteryData"), &batteryData) == NULL) { + char manufactureDate[sizeof(uint64_t)]; + if (ffCfDictGetInt64(batteryData, CFSTR(kIOPMPSManufactureDateKey), (int64_t*) manufactureDate) == NULL) { + // https://github.com/AsahiLinux/linux/blob/b5c05cbffb0488c7618106926d522cc3b43d93d5/drivers/power/supply/macsmc_power.c#L410-L419 + int year = (manufactureDate[0] - '0') * 10 + (manufactureDate[1] - '0') + 2000 - 8; + int month = (manufactureDate[2] - '0') * 10 + (manufactureDate[3] - '0'); + int day = (manufactureDate[4] - '0') * 10 + (manufactureDate[3] - '0'); + ffStrbufSetF(&battery->manufactureDate, "%.4d-%.2d-%.2d", year, month, day); + } + } + } + + if (options->temp) { + int64_t temp; + if (!ffCfDictGetInt64(properties, CFSTR(kIOPMPSBatteryTemperatureKey), &temp)) { + battery->temperature = (double) temp / 10 - 273.15; + } else { + ffDetectSmcTemps(FF_TEMP_BATTERY, &battery->temperature); + } + } + } + + return NULL; +} diff --git a/src/detection/battery/battery_bsd.c b/src/detection/battery/battery_bsd.c new file mode 100644 index 0000000..336a17e --- /dev/null +++ b/src/detection/battery/battery_bsd.c @@ -0,0 +1,90 @@ +#include "fastfetch.h" +#include "common/sysctl.h" +#include "common/io.h" +#include "battery.h" + +#include <dev/acpica/acpiio.h> +#include <sys/ioctl.h> +#include <sys/fcntl.h> +#include <unistd.h> + +const char* ffDetectBattery(FF_A_UNUSED FFBatteryOptions* options, FFlist* results) { + // https://www.freebsd.org/cgi/man.cgi?acpi_battery(4) + // https://gitlab.xfce.org/panel-plugins/xfce4-battery-plugin/-/blob/master/panel-plugin/libacpi.c + + int units = ffSysctlGetInt("hw.acpi.battery.units", -100); + if (units < 0) { + return "sysctlbyname(\"hw.acpi.battery.units\") failed"; + } + + if (units == 0) { + return NULL; + } + + FF_AUTO_CLOSE_FD int acpifd = open("/dev/acpi", O_RDONLY | O_CLOEXEC); + if (acpifd < 0) { + return "open(\"/dev/acpi\", O_RDONLY | O_CLOEXEC) failed"; + } + + for (int i = 0; i < units; ++i) { + union acpi_battery_ioctl_arg battio; + battio.unit = i; + + if (ioctl(acpifd, ACPIIO_BATT_GET_BATTINFO, &battio) < 0 || (battio.battinfo.state == ACPI_BATT_STAT_NOT_PRESENT)) { + continue; + } + + FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->manufactureDate); + battery->status = FF_BATTERY_STATUS_NONE; + battery->timeRemaining = -1; + if (battio.battinfo.min > 0) { + battery->timeRemaining = battio.battinfo.min * 60; + } + battery->capacity = battio.battinfo.cap; + if (battio.battinfo.state == ACPI_BATT_STAT_INVALID) { + battery->status |= FF_BATTERY_STATUS_UNKNOWN; + } else { + if (battio.battinfo.state & ACPI_BATT_STAT_DISCHARG) { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + } + if (battio.battinfo.state & ACPI_BATT_STAT_CHARGING) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } + if (battio.battinfo.state & ACPI_BATT_STAT_CRITICAL) { + battery->status |= FF_BATTERY_STATUS_CRITICAL; + } + } + + int acadStatus; + if (ioctl(acpifd, ACPIIO_ACAD_GET_STATUS, &acadStatus) >= 0 && acadStatus) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + +#ifdef ACPIIO_BATT_GET_BIX + battio.unit = i; + if (ioctl(acpifd, ACPIIO_BATT_GET_BIX, &battio) >= 0) { + ffStrbufAppendS(&battery->manufacturer, battio.bix.oeminfo); + ffStrbufAppendS(&battery->modelName, battio.bix.model); + ffStrbufAppendS(&battery->technology, battio.bix.type); + ffStrbufAppendS(&battery->serial, battio.bix.serial); + battery->cycleCount = battio.bix.cycles; + } +#elif defined(ACPIIO_BATT_GET_BIF) + battio.unit = i; + if (ioctl(acpifd, ACPIIO_BATT_GET_BIF, &battio) >= 0) { + ffStrbufAppendS(&battery->manufacturer, battio.bif.oeminfo); + ffStrbufAppendS(&battery->modelName, battio.bif.model); + ffStrbufAppendS(&battery->technology, battio.bif.type); + ffStrbufAppendS(&battery->serial, battio.bif.serial); + } +#endif + } + return NULL; +} diff --git a/src/detection/battery/battery_haiku.c b/src/detection/battery/battery_haiku.c new file mode 100644 index 0000000..aa620e6 --- /dev/null +++ b/src/detection/battery/battery_haiku.c @@ -0,0 +1,71 @@ +#include "fastfetch.h" +#include "battery.h" +#include "common/io.h" + +#include <private/device/power_managment.h> +#include <sys/ioctl.h> +#include <fcntl.h> + +const char* parseBattery(int dfd, const char* battId, FFlist* results) { + FF_AUTO_CLOSE_FD int fd = openat(dfd, battId, O_RDWR); + if (fd < 0) { + return "openat() failed"; + } + + acpi_battery_info basic = {}; + if (ioctl(fd, GET_BATTERY_INFO, &basic, sizeof(basic)) != 0) { + return "ioctl(GET_BATTERY_INFO) failed"; + } + acpi_extended_battery_info extended = {}; + if (ioctl(fd, GET_EXTENDED_BATTERY_INFO, &extended, sizeof(extended)) != 0) { + return "ioctl(GET_EXTENDED_BATTERY_INFO) failed"; + } + + if (extended.last_full_charge == (uint32) -1) { + return "Skipped"; + } + + FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); + ffStrbufInitS(&battery->modelName, extended.model_number); + ffStrbufInitS(&battery->manufacturer, extended.oem_info); + ffStrbufInit(&battery->manufactureDate); + ffStrbufInitS(&battery->technology, extended.type); // extended.technology? + ffStrbufInitS(&battery->serial, extended.serial_number); + battery->status = FF_BATTERY_STATUS_NONE; + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = extended.cycles; + battery->timeRemaining = -1; + battery->capacity = (double) basic.capacity * 100. / (double) extended.last_full_charge; + + if (basic.state & BATTERY_DISCHARGING) { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + } + if (basic.state & BATTERY_CHARGING) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } + if (basic.state & BATTERY_CRITICAL_STATE) { + battery->status |= FF_BATTERY_STATUS_CRITICAL; + } + if (basic.state & BATTERY_NOT_CHARGING || basic.state & BATTERY_CHARGING) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + + return NULL; +} + +const char* ffDetectBattery(FF_A_UNUSED FFBatteryOptions* options, FFlist* results) { + FF_AUTO_CLOSE_DIR DIR* dir = opendir("/dev/power/acpi_battery/"); + if (!dir) { + return "opendir(/dev/power/acpi_battery) failed"; + } + + struct dirent* entry; + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') { + continue; + } + parseBattery(dirfd(dir), entry->d_name, results); + } + + return NULL; +} diff --git a/src/detection/battery/battery_linux.c b/src/detection/battery/battery_linux.c new file mode 100644 index 0000000..6c1f711 --- /dev/null +++ b/src/detection/battery/battery_linux.c @@ -0,0 +1,203 @@ +#include "battery.h" +#include "common/io.h" +#include "common/strutil.h" +#include "common/debug.h" + +#include <dirent.h> +#include <unistd.h> +#include <fcntl.h> + +// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power + +static bool parseBattery(int dfd, const char* id, FFBatteryOptions* options, FFlist* results, bool* acConnected) { + FF_STRBUF_AUTO_DESTROY tmpBuffer = ffStrbufCreate(); + + { + char present = '\0'; + if (ffReadFileDataRelative(dfd, "present", 1, &present) && present == '0') { + FF_DEBUG("Battery \"%s\": Not present", id); + return false; + } + } + + // type must exist + if (!ffReadFileBufferRelative(dfd, "type", &tmpBuffer)) { + FF_DEBUG("Battery \"%s\": No type file", id); + return false; + } + ffStrbufTrimRightSpace(&tmpBuffer); + if (ffStrbufEqualS(&tmpBuffer, "Mains")) { + if (*acConnected) { + FF_DEBUG("Battery \"%s\": Type is Mains, but AC is already connected", id); + return false; + } + + char online = '\0'; + if (ffReadFileDataRelative(dfd, "online", 1, &online) == 1 && online == '1') { + *acConnected = true; + } + FF_DEBUG("Battery \"%s\": Type is Mains, AC Connected: %s", id, *acConnected ? "Yes" : "No"); + return false; + } else if (!ffStrbufEqualS(&tmpBuffer, "Battery")) { + FF_DEBUG("Battery \"%s\": Type is not Battery or Mains, but \"%s\"", id, tmpBuffer.chars); + return false; + } + + // scope may not exist or must not be "Device" + if (ffReadFileBufferRelative(dfd, "scope", &tmpBuffer)) { + ffStrbufTrimRightSpace(&tmpBuffer); + + FF_DEBUG("Battery \"%s\": Scope is \"%s\"", id, tmpBuffer.chars); + if (ffStrbufEqualS(&tmpBuffer, "Device")) { + return false; + } + } + + // `capacity` must exist + // This is expensive in my laptop + if (!ffReadFileBufferRelative(dfd, "capacity", &tmpBuffer)) { + FF_DEBUG("Battery \"%s\": No capacity file", id); + return false; + } + + FFBatteryResult* result = FF_LIST_ADD(FFBatteryResult, *results); + ffStrbufInit(&result->manufacturer); + ffStrbufInit(&result->modelName); + ffStrbufInit(&result->technology); + ffStrbufInit(&result->serial); + ffStrbufInit(&result->manufactureDate); + result->status = FF_BATTERY_STATUS_NONE; + result->capacity = ffStrbufToDouble(&tmpBuffer, 0); + result->cycleCount = 0; + result->temperature = FF_BATTERY_TEMP_UNSET; + result->timeRemaining = -1; + + // At this point, we have a battery. Try to get as much values as possible. + + if (ffReadFileBufferRelative(dfd, "manufacturer", &result->manufacturer)) { + ffStrbufTrimRightSpace(&result->manufacturer); + } else if (ffStrEquals(id, "macsmc-battery")) { // asahi + ffStrbufSetStatic(&result->manufacturer, "Apple Inc."); + } + + if (ffReadFileBufferRelative(dfd, "model_name", &result->modelName)) { + ffStrbufTrimRightSpace(&result->modelName); + } + + if (ffReadFileBufferRelative(dfd, "technology", &result->technology)) { + ffStrbufTrimRightSpace(&result->technology); + } + + if (ffReadFileBufferRelative(dfd, "status", &tmpBuffer)) { + ffStrbufTrimRightSpace(&tmpBuffer); + } + + // Unknown, Charging, Discharging, Not charging, Full + + if (ffStrbufEqualS(&tmpBuffer, "Discharging")) { + result->status |= FF_BATTERY_STATUS_DISCHARGING; + FF_STRBUF_AUTO_DESTROY now = ffStrbufCreate(); + if (ffReadFileBufferRelative(dfd, "time_to_empty_now", &now)) { + result->timeRemaining = (int32_t) ffStrbufToSInt(&now, 0); + } else { + if (ffReadFileBufferRelative(dfd, "charge_now", &now)) { + int64_t chargeNow = ffStrbufToSInt(&now, 0); + if (chargeNow > 0) { + if (ffReadFileBufferRelative(dfd, "current_now", &now)) { + int64_t currentNow = ffStrbufToSInt(&now, INT64_MIN); + if (currentNow < 0) { + currentNow = -currentNow; + } + if (currentNow > 0) { + result->timeRemaining = (int32_t) ((chargeNow * 3600) / currentNow); + } + } + } + } + } + } else if (ffStrbufEqualS(&tmpBuffer, "Charging")) { + result->status |= FF_BATTERY_STATUS_CHARGING; + } else if (ffStrbufEqualS(&tmpBuffer, "Unknown")) { + result->status |= FF_BATTERY_STATUS_UNKNOWN; + } + + if (ffReadFileBufferRelative(dfd, "capacity_level", &tmpBuffer)) { + ffStrbufTrimRightSpace(&tmpBuffer); + if (ffStrbufEqualS(&tmpBuffer, "Critical")) { + result->status |= FF_BATTERY_STATUS_CRITICAL; + } + } + + if (ffReadFileBufferRelative(dfd, "serial_number", &result->serial)) { + ffStrbufTrimRightSpace(&result->serial); + } + + if (ffReadFileBufferRelative(dfd, "cycle_count", &tmpBuffer)) { + int64_t cycleCount = ffStrbufToSInt(&tmpBuffer, 0); + result->cycleCount = cycleCount < 0 || cycleCount > UINT32_MAX ? 0 : (uint32_t) cycleCount; + } + + if (ffReadFileBufferRelative(dfd, "manufacture_year", &tmpBuffer)) { + int year = (int) ffStrbufToSInt(&tmpBuffer, 0); + if (year > 0) { + if (ffReadFileBufferRelative(dfd, "manufacture_month", &tmpBuffer)) { + int month = (int) ffStrbufToSInt(&tmpBuffer, 0); + if (month > 0) { + if (ffReadFileBufferRelative(dfd, "manufacture_day", &tmpBuffer)) { + int day = (int) ffStrbufToSInt(&tmpBuffer, 0); + if (day > 0) { + ffStrbufSetF(&result->manufactureDate, "%.4d-%.2d-%.2d", year, month, day); + } + } + } + } + } + } + + if (options->temp) { + if (ffReadFileBufferRelative(dfd, "temp", &tmpBuffer)) { + result->temperature = ffStrbufToDouble(&tmpBuffer, FF_BATTERY_TEMP_UNSET); + if (result->temperature != FF_BATTERY_TEMP_UNSET) { + result->temperature /= 10; + } + } + } + + FF_DEBUG("Battery \"%s\": Capacity: %.2f%%, Status: \"%x\", Time Remaining: %d seconds, Temperature: %.1f°C, Cycle Count: %u", + id, + result->capacity, + result->status, + result->timeRemaining, + result->temperature, + result->cycleCount); + return true; +} + +const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { + FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/power_supply/"); + if (dirp == NULL) { + return "opendir(\"/sys/class/power_supply/\") == NULL"; + } + + bool acConnected = false; + + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + + FF_AUTO_CLOSE_FD int dfd = openat(dirfd(dirp), entry->d_name, O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY); + if (dfd >= 0) { + parseBattery(dfd, entry->d_name, options, results, &acConnected); + } + } + + if (acConnected) { + FF_LIST_FOR_EACH (FFBatteryResult, batt, *results) { + batt->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + } + + return NULL; +} diff --git a/src/detection/battery/battery_nbsd.c b/src/detection/battery/battery_nbsd.c new file mode 100644 index 0000000..2e8b426 --- /dev/null +++ b/src/detection/battery/battery_nbsd.c @@ -0,0 +1,109 @@ +#include "battery.h" +#include "common/io.h" +#include "common/FFstrbuf.h" +#include "common/strutil.h" + +#include <prop/prop_array.h> +#include <prop/prop_bool.h> +#include <prop/prop_dictionary.h> +#include <prop/prop_object.h> +#include <sys/envsys.h> +#include <prop/proplib.h> +#include <paths.h> +#include <time.h> +#include <unistd.h> +#include <fcntl.h> + +const char* ffDetectBattery(FF_A_UNUSED FFBatteryOptions* options, FFlist* results) { + FF_AUTO_CLOSE_FD int fd = open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + return "open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC) failed"; + } + + prop_dictionary_t root = NULL; + if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &root) < 0) { + return "prop_dictionary_recv_ioctl(ENVSYS_GETDICTIONARY) failed"; + } + + bool acConnected = false; + { + prop_array_t acad = prop_dictionary_get(root, "acpiacad0"); + if (acad) { + prop_dictionary_t dict = prop_array_get(acad, 0); + prop_dictionary_get_uint8(dict, "cur-value", (uint8_t*) &acConnected); + } + } + + prop_object_iterator_t itKey = prop_dictionary_iterator(root); + for (prop_dictionary_keysym_t key; (key = prop_object_iterator_next(itKey)) != NULL;) { + if (!ffStrStartsWith(prop_dictionary_keysym_value(key), "acpibat")) { + continue; + } + + prop_array_t bat = prop_dictionary_get_keysym(root, key); + uint32_t max = 0, curr = 0, dischargeRate = 0; + bool charging = false, critical = false; + prop_object_iterator_t iter = prop_array_iterator(bat); + for (prop_dictionary_t dict; (dict = prop_object_iterator_next(iter)) != NULL;) { + if (prop_object_type(dict) != PROP_TYPE_DICTIONARY) { + continue; + } + + const char* desc = NULL; + if (!prop_dictionary_get_string(dict, "description", &desc)) { + continue; + } + + if (ffStrEquals(desc, "present")) { + int value = 0; + if (prop_dictionary_get_int(dict, "cur-value", &value) && value == 0) { + continue; + } + } else if (ffStrEquals(desc, "charging")) { + prop_dictionary_get_uint8(dict, "cur-value", (uint8_t*) &charging); + } else if (ffStrEquals(desc, "charge")) { + prop_dictionary_get_uint32(dict, "max-value", &max); + prop_dictionary_get_uint32(dict, "cur-value", &curr); + const char* state = NULL; + if (prop_dictionary_get_string(dict, "state", &state) && ffStrEquals(state, "critical")) { + critical = true; + } + } else if (ffStrEquals(desc, "discharge rate")) { + prop_dictionary_get_uint(dict, "cur-value", &dischargeRate); + } + } + + if (max > 0) { + FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->manufactureDate); + battery->status = FF_BATTERY_STATUS_NONE; + battery->timeRemaining = -1; + + battery->capacity = (double) curr / (double) max * 100.; + if (charging) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } else if (dischargeRate) { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + battery->timeRemaining = (int32_t) ((double) curr / dischargeRate * 3600); + } + if (critical) { + battery->status |= FF_BATTERY_STATUS_CRITICAL; + } + if (acConnected) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + } + + prop_object_iterator_release(iter); + } + prop_object_iterator_release(itKey); + prop_object_release(root); + + return NULL; +} diff --git a/src/detection/battery/battery_nosupport.c b/src/detection/battery/battery_nosupport.c new file mode 100644 index 0000000..efd0f73 --- /dev/null +++ b/src/detection/battery/battery_nosupport.c @@ -0,0 +1,7 @@ +#include "fastfetch.h" +#include "battery.h" + +const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { + FF_UNUSED(options, results) + return "Not supported on this platform"; +} diff --git a/src/detection/battery/battery_obsd.c b/src/detection/battery/battery_obsd.c new file mode 100644 index 0000000..ee664ce --- /dev/null +++ b/src/detection/battery/battery_obsd.c @@ -0,0 +1,60 @@ +#include "battery.h" +#include "common/io.h" + +#include <machine/apmvar.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <fcntl.h> + +const char* ffDetectBattery(FF_A_UNUSED FFBatteryOptions* options, FFlist* result) { + FF_AUTO_CLOSE_FD int devfd = open("/dev/apm", O_RDONLY | O_CLOEXEC); + + if (devfd < 0) { + return "open(dev/apm, O_RDONLY | O_CLOEXEC) failed"; + } + + struct apm_power_info info = {}; + + if (ioctl(devfd, APM_IOC_GETPOWER, &info) < 0) { + return "ioctl(APM_IOC_GETPOWER) failed"; + } + + if (info.battery_state == APM_BATTERY_ABSENT) { + return NULL; + } + + FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *result); + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + battery->timeRemaining = -1; + battery->capacity = info.battery_life; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->manufactureDate); + battery->status = FF_BATTERY_STATUS_NONE; + + if (info.ac_state == APM_AC_ON || info.ac_state == APM_AC_BACKUP) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } else if (info.ac_state == APM_AC_OFF) { + battery->timeRemaining = (int) info.minutes_left * 60; + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + } + + if (info.battery_state == APM_BATT_CRITICAL || info.battery_state == APM_BATT_CHARGING || info.battery_state == APM_BATT_UNKNOWN) { + switch (info.battery_state) { + case APM_BATT_UNKNOWN: + battery->status |= FF_BATTERY_STATUS_UNKNOWN; + break; + case APM_BATT_CHARGING: + battery->status |= FF_BATTERY_STATUS_CHARGING; + break; + case APM_BATT_CRITICAL: + battery->status |= FF_BATTERY_STATUS_CRITICAL; + break; + } + } + + return NULL; +} diff --git a/src/detection/battery/battery_windows.c b/src/detection/battery/battery_windows.c new file mode 100644 index 0000000..84f2a90 --- /dev/null +++ b/src/detection/battery/battery_windows.c @@ -0,0 +1,384 @@ +#define INITGUID + +#include "battery.h" + +#include "common/debug.h" +#include "common/mallocHelper.h" +#include "common/windows/unicode.h" +#include "common/windows/wmi.h" + +#include <winerror.h> + +typedef void(WINAPI* PINTERFACE_REFERENCE)(PVOID Context); +typedef void(WINAPI* PINTERFACE_DEREFERENCE)(PVOID Context); +typedef struct _DEVICE_OBJECT* PDEVICE_OBJECT; +typedef struct _IRP* PIRP; +#ifdef _WINDOWS_ + #undef _WINDOWS_ +#endif + +#include <batclass.h> + +#pragma GCC diagnostic ignored "-Wmultichar" + +typedef struct FFBatteryWmiEntry { + ULONG tag; + FFBatteryResult* result; +} FFBatteryWmiEntry; + +static FFBatteryWmiEntry* getBatteryEntry(FFlist* entries, FFlist* results, ULONG tag) { + FF_LIST_FOR_EACH (FFBatteryWmiEntry, entry, *entries) { + if (entry->tag == tag) { + return entry; + } + } + + FFBatteryWmiEntry* entry = FF_LIST_ADD(FFBatteryWmiEntry, *entries); + entry->tag = tag; + FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); + entry->result = battery; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->manufactureDate); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + battery->status = FF_BATTERY_STATUS_NONE; + battery->capacity = -1; + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + battery->timeRemaining = -1; + return entry; +} + +static const char* queryWmiAllData(const GUID* guid, const char* guidStr, PWNODE_ALL_DATA* pAllData, ULONG* pBufferSize) { + FF_AUTO_CLOSE_WMI_BLOCK HANDLE hBlock = NULL; + ULONG status = WmiOpenBlock(guid, WMIGUID_QUERY, &hBlock); + if (status != ERROR_SUCCESS) { + FF_DEBUG("WMI: WmiOpenBlock() failed for %s: %s", guidStr, ffDebugWin32Error(status)); + return "WmiOpenBlock() failed"; + } + + status = WmiQueryAllDataW(hBlock, pBufferSize, NULL); + if (status != ERROR_SUCCESS && status != ERROR_INSUFFICIENT_BUFFER) { + FF_DEBUG("WMI: first WmiQueryAllDataW() failed: %s", ffDebugWin32Error(status)); + return "WmiQueryAllDataW(NULL) failed"; + } + + if (*pBufferSize == 0) { + return "WmiQueryAllDataW(NULL) returned no data"; + } + + if (*pBufferSize < sizeof(WNODE_ALL_DATA)) { + FF_DEBUG("WMI: WmiQueryAllDataW() returned insufficient buffer size: %lu", *pBufferSize); + return "WmiQueryAllDataW() returned insufficient data for WNODE_ALL_DATA"; + } + + *pAllData = (PWNODE_ALL_DATA) malloc(*pBufferSize); + + status = WmiQueryAllDataW(hBlock, pBufferSize, *pAllData); + if (status != ERROR_SUCCESS) { + FF_DEBUG("WMI: second WmiQueryAllDataW failed: %s", ffDebugWin32Error(status)); + free(*pAllData); + *pAllData = NULL; + return "WmiQueryAllDataW(*pAllData) failed"; + } + + return NULL; +} + +static bool getInstanceData(const PWNODE_ALL_DATA allData, ULONG bufferSize, ULONG index, const uint8_t** instanceData, ULONG* instanceLength) { + ULONG dataOffset = 0; + ULONG dataLength = 0; + + if (allData->WnodeHeader.Flags & WNODE_FLAG_FIXED_INSTANCE_SIZE) { + dataLength = allData->FixedInstanceSize; + dataOffset = allData->DataBlockOffset + index * dataLength; + } else { + dataOffset = allData->OffsetInstanceDataAndLength[index].OffsetInstanceData; + dataLength = allData->OffsetInstanceDataAndLength[index].LengthInstanceData; + } + + if (dataLength == 0 || dataOffset >= bufferSize || dataLength > bufferSize - dataOffset) { + return false; + } + + *instanceData = (const uint8_t*) allData + dataOffset; + *instanceLength = dataLength; + return true; +} + +static void detectStaticData(FFlist* entries, FFlist* results) { + FF_DEBUG("detectStaticData"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_STATIC_DATA_WMI_GUID, "BATTERY_STATIC_DATA_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < offsetof(BATTERY_WMI_STATIC_DATA, Strings)) { + continue; + } + + const BATTERY_WMI_STATIC_DATA* data = (const BATTERY_WMI_STATIC_DATA*) instanceData; + FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); + + FF_DEBUG("chemistry: %.4s", (const char*) &data->Chemistry); +#if __BIG_ENDIAN__ + #define htobe32(x) (x) // Should not happen on Windows, but just in case +#else + #define htobe32(x) __builtin_bswap32(x) +#endif + switch (data->Chemistry) { + case htobe32('PbAc'): + ffStrbufSetStatic(&entry->result->technology, "Lead Acid"); + break; + case htobe32('LION'): + case htobe32('Li-I'): + ffStrbufSetStatic(&entry->result->technology, "Lithium Ion"); + break; + case htobe32('NiCd'): + ffStrbufSetStatic(&entry->result->technology, "Nickel Cadmium"); + break; + case htobe32('NiMH'): + ffStrbufSetStatic(&entry->result->technology, "Nickel Metal Hydride"); + break; + case htobe32('NiZn'): + ffStrbufSetStatic(&entry->result->technology, "Nickel Zinc"); + break; + case htobe32('RAM\0'): + ffStrbufSetStatic(&entry->result->technology, "Rechargeable Alkaline-Manganese"); + break; + default: + ffStrbufSetStatic(&entry->result->technology, data->Technology ? "Rechargeable" : "Non Rechargeable"); + break; + } +#undef htobe32 + + const BATTERY_MANUFACTURE_DATE* manufactureDate = (const BATTERY_MANUFACTURE_DATE*) data->ManufactureDate; + if (manufactureDate->Year > 0 && manufactureDate->Month >= 1 && manufactureDate->Month <= 12 && manufactureDate->Day >= 1 && manufactureDate->Day <= 31) { + uint16_t year = manufactureDate->Year; + ffStrbufSetF(&entry->result->manufactureDate, "%.4u-%.2u-%.2u", (unsigned) (year < 1000 ? (year + 1900) : year), (unsigned) manufactureDate->Month, (unsigned) manufactureDate->Day); + } + + // Device Name, Manufacture Name, Serial Number, UniqueID + const struct { + uint16_t size; // in bytes, including the null terminator + wchar_t value[]; + }* cursor = (const void*) data->Strings; + + FFstrbuf* strings[] = { + &entry->result->modelName, + &entry->result->manufacturer, + &entry->result->serial, + }; + + for (size_t i = 0; i < ARRAY_SIZE(strings); ++i) { + if (cursor->size > sizeof(wchar_t)) { + ffStrbufSetNWS(strings[i], cursor->size / sizeof(wchar_t) - 1, cursor->value); + } + cursor = (const void*) ((const uint8_t*) cursor + sizeof(uint16_t) + cursor->size); + } + } +} + +static void detectStatus(FFlist* entries, FFlist* results) { + FF_DEBUG("detectStatus"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_STATUS_WMI_GUID, "BATTERY_STATUS_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_STATUS)) { + continue; + } + + const BATTERY_WMI_STATUS* data = (const BATTERY_WMI_STATUS*) instanceData; + FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); + if (data->RemainingCapacity != BATTERY_UNKNOWN_CAPACITY) { + entry->result->capacity = data->RemainingCapacity; + } + + entry->result->status = FF_BATTERY_STATUS_NONE; + if (data->PowerOnline) { + entry->result->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + if (data->Charging) { + entry->result->status |= FF_BATTERY_STATUS_CHARGING; + } + if (data->Discharging) { + entry->result->status |= FF_BATTERY_STATUS_DISCHARGING; + } + if (data->Critical) { + entry->result->status |= FF_BATTERY_STATUS_CRITICAL; + } + } +} + +static void detectRuntime(FFlist* entries, FFlist* results) { + FF_DEBUG("detectRuntime"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_RUNTIME_WMI_GUID, "BATTERY_RUNTIME_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_RUNTIME)) { + continue; + } + + const BATTERY_WMI_RUNTIME* data = (const BATTERY_WMI_RUNTIME*) instanceData; + FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); + if (data->EstimatedRuntime != BATTERY_UNKNOWN_TIME) { + entry->result->timeRemaining = (int32_t) data->EstimatedRuntime; + } + } +} + +static void detectFullChargedCapacity(FFlist* entries, FFlist* results) { + FF_DEBUG("detectFullChargedCapacity"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_FULL_CHARGED_CAPACITY_WMI_GUID, "BATTERY_FULL_CHARGED_CAPACITY_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_FULL_CHARGED_CAPACITY)) { + continue; + } + + const BATTERY_WMI_FULL_CHARGED_CAPACITY* data = (const BATTERY_WMI_FULL_CHARGED_CAPACITY*) instanceData; + FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); + + if (data->FullChargedCapacity != BATTERY_UNKNOWN_CAPACITY && entry->result->capacity >= 0) { + entry->result->capacity *= 100; + entry->result->capacity /= data->FullChargedCapacity; + } + } +} + +static void detectCycleCount(FFlist* entries, FFlist* results) { + FF_DEBUG("detectCycleCount"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_CYCLE_COUNT_WMI_GUID, "BATTERY_CYCLE_COUNT_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_CYCLE_COUNT)) { + continue; + } + + const BATTERY_WMI_CYCLE_COUNT* data = (const BATTERY_WMI_CYCLE_COUNT*) instanceData; + getBatteryEntry(entries, results, data->Tag)->result->cycleCount = data->CycleCount; + } +} + +static void detectTemperature(FFlist* entries, FFlist* results) { + FF_DEBUG("detectTemperature"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_TEMPERATURE_WMI_GUID, "BATTERY_TEMPERATURE_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_TEMPERATURE)) { + continue; + } + + const BATTERY_WMI_TEMPERATURE* data = (const BATTERY_WMI_TEMPERATURE*) instanceData; + getBatteryEntry(entries, results, data->Tag)->result->temperature = data->Temperature / 10.0 - 273.15; + } +} + +static const char* detectWithNtApi(FFBatteryResult* battery) { + // Reports summary battery information, not per battery + FF_DEBUG("NtApi: start detection"); + SYSTEM_BATTERY_STATE info; + NTSTATUS status = NtPowerInformation(SystemBatteryState, NULL, 0, &info, sizeof(info)); + if (!NT_SUCCESS(status)) { + FF_DEBUG("NtApi: NtPowerInformation(SystemBatteryState) failed: %s", ffDebugNtStatus(status)); + return "NtPowerInformation(SystemBatteryState) failed"; + } + if (!info.BatteryPresent) { + FF_DEBUG("NtApi reports no battery present"); + return "No battery present"; + } + + if (info.MaxCapacity != BATTERY_UNKNOWN_CAPACITY && info.RemainingCapacity != BATTERY_UNKNOWN_CAPACITY) { + battery->capacity = info.RemainingCapacity * 100.0 / info.MaxCapacity; + } + battery->status = FF_BATTERY_STATUS_NONE; + if (info.AcOnLine) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + if (info.Charging) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } + if (info.Discharging) { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + } + if (info.DefaultAlert1 > 0 && info.RemainingCapacity <= info.DefaultAlert1) { + battery->status |= FF_BATTERY_STATUS_CRITICAL; + } + battery->timeRemaining = info.EstimatedTime == BATTERY_UNKNOWN_TIME ? -1 : (int32_t) info.EstimatedTime; + return NULL; +} + +const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { + FF_DEBUG("WMI: start detection"); + + FF_LIST_AUTO_DESTROY entries = ffListCreate(); + detectStaticData(&entries, results); + if (results->length == 0) { + return NULL; + } else if (results->length == 1) { + // Fast path for single battery + detectWithNtApi(FF_LIST_FIRST(FFBatteryWmiEntry, entries)->result); + } else { + detectStatus(&entries, results); + detectFullChargedCapacity(&entries, results); + detectRuntime(&entries, results); + } + detectCycleCount(&entries, results); + if (options->temp) { + detectTemperature(&entries, results); + } + + FF_LIST_FOR_EACH (FFBatteryWmiEntry, entry, entries) { + FF_DEBUG( + "WMI: detected battery tag=%lu, name='%s', charge=%.2f%%, status=0x%x, runtime=%d seconds", + entry->tag, + entry->result->modelName.length ? entry->result->modelName.chars : "<unknown>", + entry->result->capacity, + entry->result->status, + entry->result->timeRemaining); + } + + FF_DEBUG("WMI: finished detection, total results=%u", results->length); + return NULL; +} |