mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-09-13 02:42:09 +02:00
Battery (Windows): switches to query WMI with NtApi as a fast path
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -1,298 +1,378 @@
|
||||
#define INITGUID
|
||||
|
||||
#include "battery.h"
|
||||
|
||||
#include "common/io.h"
|
||||
#include "common/windows/nt.h"
|
||||
#include "common/windows/unicode.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/mallocHelper.h"
|
||||
#include "common/smbios.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
|
||||
|
||||
#undef WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <batclass.h>
|
||||
#include <devguid.h>
|
||||
#include <cfgmgr32.h>
|
||||
|
||||
static const char* detectWithCmApi(FFBatteryOptions* options, FFlist* results) {
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/drivers/install/using-device-interfaces
|
||||
ULONG cchDeviceInterfaces = 0;
|
||||
if (CM_Get_Device_Interface_List_SizeW(
|
||||
&cchDeviceInterfaces,
|
||||
(LPGUID) &GUID_DEVCLASS_BATTERY,
|
||||
NULL,
|
||||
CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS) {
|
||||
return "CM_Get_Device_Interface_List_SizeW() failed";
|
||||
}
|
||||
#pragma GCC diagnostic ignored "-Wmultichar"
|
||||
|
||||
if (cchDeviceInterfaces <= 1) {
|
||||
return NULL; // Not found
|
||||
}
|
||||
typedef struct FFBatteryWmiEntry {
|
||||
ULONG tag;
|
||||
FFBatteryResult* result;
|
||||
} FFBatteryWmiEntry;
|
||||
|
||||
wchar_t* FF_AUTO_FREE mszDeviceInterfaces = (wchar_t*) malloc(cchDeviceInterfaces * sizeof(wchar_t));
|
||||
if (CM_Get_Device_Interface_ListW(
|
||||
(LPGUID) &GUID_DEVCLASS_BATTERY,
|
||||
NULL,
|
||||
mszDeviceInterfaces,
|
||||
cchDeviceInterfaces,
|
||||
CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS) {
|
||||
return "CM_Get_Device_Interface_ListW() failed";
|
||||
}
|
||||
|
||||
for (const wchar_t* p = mszDeviceInterfaces; *p; p += wcslen(p) + 1) {
|
||||
HANDLE FF_AUTO_CLOSE_FD hBattery =
|
||||
CreateFileW(p, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
|
||||
if (hBattery == INVALID_HANDLE_VALUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BATTERY_QUERY_INFORMATION bqi = { .InformationLevel = BatteryInformation };
|
||||
|
||||
DWORD dwWait = 0;
|
||||
DWORD dwOut;
|
||||
|
||||
if (!DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG, &dwWait, sizeof(dwWait), &bqi.BatteryTag, sizeof(bqi.BatteryTag), &dwOut, NULL) && bqi.BatteryTag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BATTERY_INFORMATION bi = { 0 };
|
||||
if (!DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &bi, sizeof(bi), &dwOut, NULL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(bi.Capabilities & BATTERY_SYSTEM_BATTERY)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results);
|
||||
|
||||
if (memcmp(bi.Chemistry, "PbAc", 4) == 0) {
|
||||
ffStrbufInitStatic(&battery->technology, "Lead Acid");
|
||||
} else if (memcmp(bi.Chemistry, "LION", 4) == 0 || memcmp(bi.Chemistry, "Li-I", 4) == 0) {
|
||||
ffStrbufInitStatic(&battery->technology, "Lithium Ion");
|
||||
} else if (memcmp(bi.Chemistry, "NiCd", 4) == 0) {
|
||||
ffStrbufInitStatic(&battery->technology, "Nickel Cadmium");
|
||||
} else if (memcmp(bi.Chemistry, "NiMH", 4) == 0) {
|
||||
ffStrbufInitStatic(&battery->technology, "Nickel Metal Hydride");
|
||||
} else if (memcmp(bi.Chemistry, "NiZn", 4) == 0) {
|
||||
ffStrbufInitStatic(&battery->technology, "Nickel Zinc");
|
||||
} else if (memcmp(bi.Chemistry, "RAM\0", 4) == 0) {
|
||||
ffStrbufInitStatic(&battery->technology, "Rechargeable Alkaline-Manganese");
|
||||
} else {
|
||||
ffStrbufInitStatic(&battery->technology, "Unknown");
|
||||
}
|
||||
|
||||
{
|
||||
ffStrbufInit(&battery->modelName);
|
||||
bqi.InformationLevel = BatteryDeviceName;
|
||||
wchar_t name[64];
|
||||
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), name, sizeof(name), &dwOut, NULL)) {
|
||||
ffStrbufSetWS(&battery->modelName, name);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ffStrbufInit(&battery->manufacturer);
|
||||
bqi.InformationLevel = BatteryManufactureName;
|
||||
wchar_t name[64];
|
||||
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), name, sizeof(name), &dwOut, NULL)) {
|
||||
ffStrbufSetWS(&battery->manufacturer, name);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ffStrbufInit(&battery->manufactureDate);
|
||||
bqi.InformationLevel = BatteryManufactureDate;
|
||||
BATTERY_MANUFACTURE_DATE date;
|
||||
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &date, sizeof(date), &dwOut, NULL)) {
|
||||
ffStrbufSetF(&battery->manufactureDate, "%.4d-%.2d-%.2d", date.Year < 1000 ? date.Year + 1900 : date.Year, date.Month, date.Day);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ffStrbufInit(&battery->serial);
|
||||
bqi.InformationLevel = BatterySerialNumber;
|
||||
wchar_t name[64];
|
||||
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), name, sizeof(name), &dwOut, NULL)) {
|
||||
ffStrbufSetWS(&battery->serial, name);
|
||||
}
|
||||
}
|
||||
|
||||
battery->cycleCount = bi.CycleCount;
|
||||
|
||||
battery->temperature = FF_BATTERY_TEMP_UNSET;
|
||||
if (options->temp) {
|
||||
bqi.InformationLevel = BatteryTemperature;
|
||||
ULONG temp;
|
||||
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &temp, sizeof(temp), &dwOut, NULL)) {
|
||||
battery->temperature = temp / 10.0 - 273.15;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
bqi.InformationLevel = BatteryEstimatedTime;
|
||||
ULONG time;
|
||||
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &time, sizeof(time), &dwOut, NULL)) {
|
||||
battery->timeRemaining = time == BATTERY_UNKNOWN_TIME ? -1 : (int32_t) time;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
BATTERY_STATUS bs;
|
||||
BATTERY_WAIT_STATUS bws = { .BatteryTag = bqi.BatteryTag };
|
||||
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_STATUS, &bws, sizeof(bws), &bs, sizeof(bs), &dwOut, NULL) && bs.Capacity != BATTERY_UNKNOWN_CAPACITY && bi.FullChargedCapacity != 0) {
|
||||
battery->capacity = bs.Capacity * 100.0 / bi.FullChargedCapacity;
|
||||
|
||||
battery->status = FF_BATTERY_STATUS_NONE;
|
||||
if (bs.PowerState & BATTERY_POWER_ON_LINE) {
|
||||
battery->status |= FF_BATTERY_STATUS_AC_CONNECTED;
|
||||
}
|
||||
if (bs.PowerState & BATTERY_DISCHARGING) {
|
||||
battery->status |= FF_BATTERY_STATUS_DISCHARGING;
|
||||
}
|
||||
if (bs.PowerState & BATTERY_CHARGING) {
|
||||
battery->status |= FF_BATTERY_STATUS_CHARGING;
|
||||
}
|
||||
if (bs.PowerState & BATTERY_CRITICAL) {
|
||||
battery->status |= FF_BATTERY_STATUS_CRITICAL;
|
||||
}
|
||||
} else {
|
||||
battery->status = FF_BATTERY_STATUS_UNKNOWN;
|
||||
battery->capacity = 0;
|
||||
}
|
||||
static FFBatteryWmiEntry* getBatteryEntry(FFlist* entries, FFlist* results, ULONG tag) {
|
||||
FF_LIST_FOR_EACH (FFBatteryWmiEntry, entry, *entries) {
|
||||
if (entry->tag == tag) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
typedef struct FFSmbiosPortableBattery {
|
||||
FFSmbiosHeader Header;
|
||||
|
||||
// 2.1+
|
||||
uint8_t Location; // string
|
||||
uint8_t Manufacturer; // string
|
||||
uint8_t ManufactureDate; // string
|
||||
uint8_t SerialNumber; // string
|
||||
uint8_t DeviceName; // string
|
||||
uint8_t DeviceChemistry; // enum
|
||||
uint16_t DesignCapacity; // varies
|
||||
uint16_t DesignVoltage; // varies
|
||||
uint8_t SbdsVersionNumber; // string
|
||||
uint8_t MaximumErrorInBatteryData; // varies
|
||||
|
||||
// 2.2+
|
||||
uint16_t SbdsSerialNumber; // varies
|
||||
uint16_t SbdsManufactureDate; // varies
|
||||
uint8_t SbdsDeviceChemistry; // string
|
||||
uint8_t DesignCapacityMultiplier; // varies
|
||||
uint16_t OEMSpecific; // varies
|
||||
} FF_A_PACKED FFSmbiosPortableBattery;
|
||||
|
||||
static_assert(offsetof(FFSmbiosPortableBattery, OEMSpecific) == 0x16,
|
||||
"FFSmbiosPortableBattery: Wrong struct alignment");
|
||||
|
||||
static const char* detectBySmbios(FFBatteryResult* battery) {
|
||||
const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
|
||||
if (!smbiosTable) {
|
||||
return "Failed to get SMBIOS data";
|
||||
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";
|
||||
}
|
||||
|
||||
const FFSmbiosPortableBattery* data = (const FFSmbiosPortableBattery*) (*smbiosTable)[FF_SMBIOS_TYPE_PORTABLE_BATTERY];
|
||||
if (!data) {
|
||||
return "Portable battery section is not found in SMBIOS data";
|
||||
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";
|
||||
}
|
||||
|
||||
const char* strings = (const char*) data + data->Header.Length;
|
||||
|
||||
ffStrbufSetStatic(&battery->modelName, ffSmbiosLocateString(strings, data->DeviceName));
|
||||
ffCleanUpSmbiosValue(&battery->modelName);
|
||||
ffStrbufSetStatic(&battery->manufacturer, ffSmbiosLocateString(strings, data->Manufacturer));
|
||||
ffCleanUpSmbiosValue(&battery->manufacturer);
|
||||
|
||||
if (data->ManufactureDate) {
|
||||
ffStrbufSetStatic(&battery->manufactureDate, ffSmbiosLocateString(strings, data->ManufactureDate));
|
||||
ffCleanUpSmbiosValue(&battery->manufactureDate);
|
||||
} else if (data->Header.Length > offsetof(FFSmbiosPortableBattery, SbdsManufactureDate)) {
|
||||
int day = data->SbdsManufactureDate & 0b11111;
|
||||
int month = (data->SbdsManufactureDate >> 5) & 0b1111;
|
||||
int year = (data->SbdsManufactureDate >> 9) + 1800;
|
||||
ffStrbufSetF(&battery->manufactureDate, "%.4d-%.2d-%.2d", year, month, day);
|
||||
if (*pBufferSize == 0) {
|
||||
return "WmiQueryAllDataW(NULL) returned no data";
|
||||
}
|
||||
|
||||
switch (data->DeviceChemistry) {
|
||||
case 0x01:
|
||||
ffStrbufSetStatic(&battery->technology, "Other");
|
||||
break;
|
||||
case 0x02:
|
||||
ffStrbufSetStatic(&battery->technology, "Unknown");
|
||||
break;
|
||||
case 0x03:
|
||||
ffStrbufSetStatic(&battery->technology, "Lead Acid");
|
||||
break;
|
||||
case 0x04:
|
||||
ffStrbufSetStatic(&battery->technology, "Nickel Cadmium");
|
||||
break;
|
||||
case 0x05:
|
||||
ffStrbufSetStatic(&battery->technology, "Nickel metal hydride");
|
||||
break;
|
||||
case 0x06:
|
||||
ffStrbufSetStatic(&battery->technology, "Lithium-ion");
|
||||
break;
|
||||
case 0x07:
|
||||
ffStrbufSetStatic(&battery->technology, "Zinc air");
|
||||
break;
|
||||
case 0x08:
|
||||
ffStrbufSetStatic(&battery->technology, "Lithium Polymer");
|
||||
break;
|
||||
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";
|
||||
}
|
||||
|
||||
if (data->SerialNumber) {
|
||||
ffStrbufSetStatic(&battery->serial, ffSmbiosLocateString(strings, data->SerialNumber));
|
||||
ffCleanUpSmbiosValue(&battery->serial);
|
||||
} else if (data->Header.Length > offsetof(FFSmbiosPortableBattery, SbdsSerialNumber)) {
|
||||
ffStrbufSetF(&battery->serial, "%4X", data->SbdsSerialNumber);
|
||||
*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 const char* detectWithNtApi(FF_A_UNUSED FFBatteryOptions* options, FFlist* results) {
|
||||
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);
|
||||
switch (data->Chemistry) {
|
||||
case 'cAbP':
|
||||
ffStrbufSetStatic(&entry->result->technology, "Lead Acid");
|
||||
break;
|
||||
case 'NOIL':
|
||||
case 'I-iL':
|
||||
ffStrbufSetStatic(&entry->result->technology, "Lithium Ion");
|
||||
break;
|
||||
case 'dCiN':
|
||||
ffStrbufSetStatic(&entry->result->technology, "Nickel Cadmium");
|
||||
break;
|
||||
case 'HMiN':
|
||||
ffStrbufSetStatic(&entry->result->technology, "Nickel Metal Hydride");
|
||||
break;
|
||||
case 'nZiN':
|
||||
ffStrbufSetStatic(&entry->result->technology, "Nickel Zinc");
|
||||
break;
|
||||
case '\0MAR':
|
||||
ffStrbufSetStatic(&entry->result->technology, "Rechargeable Alkaline-Manganese");
|
||||
break;
|
||||
default:
|
||||
ffStrbufSetStatic(&entry->result->technology, data->Technology ? "Rechargeable" : "Non Rechargeable");
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
if (NT_SUCCESS(NtPowerInformation(SystemBatteryState, NULL, 0, &info, sizeof(info))) &&
|
||||
info.BatteryPresent) {
|
||||
FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results);
|
||||
ffStrbufInit(&battery->modelName);
|
||||
ffStrbufInit(&battery->manufacturer);
|
||||
ffStrbufInit(&battery->manufactureDate);
|
||||
ffStrbufInit(&battery->technology);
|
||||
ffStrbufInit(&battery->serial);
|
||||
battery->temperature = FF_BATTERY_TEMP_UNSET;
|
||||
battery->cycleCount = 0;
|
||||
battery->timeRemaining = info.EstimatedTime == BATTERY_UNKNOWN_TIME ? -1 : (int32_t) info.EstimatedTime;
|
||||
battery->status = FF_BATTERY_STATUS_NONE;
|
||||
|
||||
battery->capacity = info.RemainingCapacity * 100.0 / info.MaxCapacity;
|
||||
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;
|
||||
}
|
||||
|
||||
detectBySmbios(battery);
|
||||
|
||||
return NULL;
|
||||
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";
|
||||
}
|
||||
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) {
|
||||
return options->useSetupApi
|
||||
? detectWithCmApi(options, results)
|
||||
: detectWithNtApi(options, 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user