master ohmu/fastfetch-openbsd / src / detection / battery / battery_windows.c
ohmu  ·  2026-10-15
  1#define INITGUID
  2
  3#include "battery.h"
  4#include "common/debug.h"
  5#include "common/endian.h"
  6#include "common/mallocHelper.h"
  7#include "common/windows/nt.h"
  8#include "common/windows/unicode.h"
  9#include "common/windows/wmi.h"
 10
 11#include <winerror.h>
 12
 13typedef void(WINAPI* PINTERFACE_REFERENCE)(PVOID Context);
 14typedef void(WINAPI* PINTERFACE_DEREFERENCE)(PVOID Context);
 15typedef struct _DEVICE_OBJECT* PDEVICE_OBJECT;
 16typedef struct _IRP* PIRP;
 17#ifdef _WINDOWS_
 18    #undef _WINDOWS_
 19#endif
 20
 21#include <batclass.h>
 22
 23#pragma GCC diagnostic ignored "-Wmultichar"
 24
 25typedef struct FFBatteryWmiEntry {
 26    ULONG tag;
 27    FFBatteryResult* result;
 28} FFBatteryWmiEntry;
 29
 30static FFBatteryWmiEntry* getBatteryEntry(FFlist* entries, FFlist* results, ULONG tag) {
 31    FF_LIST_FOR_EACH (FFBatteryWmiEntry, entry, *entries) {
 32        if (entry->tag == tag) {
 33            return entry;
 34        }
 35    }
 36
 37    FFBatteryWmiEntry* entry = FF_LIST_ADD(FFBatteryWmiEntry, *entries);
 38    entry->tag = tag;
 39    FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results);
 40    entry->result = battery;
 41    ffStrbufInit(&battery->manufacturer);
 42    ffStrbufInit(&battery->manufactureDate);
 43    ffStrbufInit(&battery->modelName);
 44    ffStrbufInit(&battery->technology);
 45    ffStrbufInit(&battery->serial);
 46    battery->status = FF_BATTERY_STATUS_NONE;
 47    battery->capacity = -1;
 48    battery->temperature = FF_BATTERY_TEMP_UNSET;
 49    battery->cycleCount = 0;
 50    battery->timeRemaining = -1;
 51    return entry;
 52}
 53
 54static const char* queryWmiAllData(const GUID* guid, [[maybe_unused]] const char* guidStr, PWNODE_ALL_DATA* pAllData, ULONG* pBufferSize) {
 55    [[gnu::cleanup(ffCloseWmiBlock)]] HANDLE hBlock = nullptr;
 56    ULONG status = WmiOpenBlock(guid, WMIGUID_QUERY, &hBlock);
 57    if (status != ERROR_SUCCESS) {
 58        FF_DEBUG("WMI: WmiOpenBlock() failed for %s: %s", guidStr, ffDebugWin32Error(status));
 59        return "WmiOpenBlock() failed";
 60    }
 61
 62    status = WmiQueryAllDataW(hBlock, pBufferSize, nullptr);
 63    if (status != ERROR_SUCCESS && status != ERROR_INSUFFICIENT_BUFFER) {
 64        FF_DEBUG("WMI: first WmiQueryAllDataW() failed: %s", ffDebugWin32Error(status));
 65        return "WmiQueryAllDataW(nullptr) failed";
 66    }
 67
 68    if (*pBufferSize == 0) {
 69        FF_DEBUG("WMI: WmiQueryAllDataW(nullptr) returned no data");
 70        return "WmiQueryAllDataW(nullptr) returned no data";
 71    }
 72
 73    if (*pBufferSize < sizeof(WNODE_ALL_DATA)) {
 74        FF_DEBUG("WMI: WmiQueryAllDataW() returned insufficient buffer size: %lu", *pBufferSize);
 75        return "WmiQueryAllDataW() returned insufficient data for WNODE_ALL_DATA";
 76    }
 77
 78    *pAllData = (PWNODE_ALL_DATA) malloc(*pBufferSize);
 79
 80    status = WmiQueryAllDataW(hBlock, pBufferSize, *pAllData);
 81    if (status != ERROR_SUCCESS) {
 82        FF_DEBUG("WMI: second WmiQueryAllDataW failed: %s", ffDebugWin32Error(status));
 83        free(*pAllData);
 84        *pAllData = nullptr;
 85        return "WmiQueryAllDataW(*pAllData) failed";
 86    }
 87
 88    return nullptr;
 89}
 90
 91static bool getInstanceData(const PWNODE_ALL_DATA allData, ULONG bufferSize, ULONG index, const uint8_t** instanceData, ULONG* instanceLength) {
 92    ULONG dataOffset = 0;
 93    ULONG dataLength = 0;
 94
 95    if (allData->WnodeHeader.Flags & WNODE_FLAG_FIXED_INSTANCE_SIZE) {
 96        dataLength = allData->FixedInstanceSize;
 97        dataOffset = allData->DataBlockOffset + index * dataLength;
 98    } else {
 99        dataOffset = allData->OffsetInstanceDataAndLength[index].OffsetInstanceData;
100        dataLength = allData->OffsetInstanceDataAndLength[index].LengthInstanceData;
101    }
102
103    if (dataLength == 0 || dataOffset >= bufferSize || dataLength > bufferSize - dataOffset) {
104        return false;
105    }
106
107    *instanceData = (const uint8_t*) allData + dataOffset;
108    *instanceLength = dataLength;
109    return true;
110}
111
112static void detectStaticData(FFlist* entries, FFlist* results) {
113    FF_DEBUG("detectStaticData");
114    FF_AUTO_FREE PWNODE_ALL_DATA allData = nullptr;
115    ULONG bufferSize = 0;
116    const char* error = queryWmiAllData(&BATTERY_STATIC_DATA_WMI_GUID, "BATTERY_STATIC_DATA_WMI_GUID", &allData, &bufferSize);
117    if (error) {
118        return;
119    }
120
121    for (ULONG i = 0; i < allData->InstanceCount; ++i) {
122        const uint8_t* instanceData = nullptr;
123        ULONG instanceLength = 0;
124        if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < offsetof(BATTERY_WMI_STATIC_DATA, Strings)) {
125            continue;
126        }
127
128        const BATTERY_WMI_STATIC_DATA* data = (const BATTERY_WMI_STATIC_DATA*) instanceData;
129        FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag);
130
131        FF_DEBUG("chemistry: %.4s", (const char*) &data->Chemistry);
132        switch (data->Chemistry) {
133            case FF_READ_BE((uint32_t) 'PbAc'):
134                ffStrbufSetStatic(&entry->result->technology, "Lead Acid");
135                break;
136            case FF_READ_BE((uint32_t) 'LION'):
137            case FF_READ_BE((uint32_t) 'Li-I'):
138                ffStrbufSetStatic(&entry->result->technology, "Lithium Ion");
139                break;
140            case FF_READ_BE((uint32_t) 'NiCd'):
141                ffStrbufSetStatic(&entry->result->technology, "Nickel Cadmium");
142                break;
143            case FF_READ_BE((uint32_t) 'NiMH'):
144                ffStrbufSetStatic(&entry->result->technology, "Nickel Metal Hydride");
145                break;
146            case FF_READ_BE((uint32_t) 'NiZn'):
147                ffStrbufSetStatic(&entry->result->technology, "Nickel Zinc");
148                break;
149            case FF_READ_BE((uint32_t) 'RAM\0'):
150                ffStrbufSetStatic(&entry->result->technology, "Rechargeable Alkaline-Manganese");
151                break;
152            default:
153                ffStrbufSetStatic(&entry->result->technology, data->Technology ? "Rechargeable" : "Non Rechargeable");
154                break;
155        }
156#undef htobe32
157
158        const BATTERY_MANUFACTURE_DATE* manufactureDate = (const BATTERY_MANUFACTURE_DATE*) data->ManufactureDate;
159        if (manufactureDate->Year > 0 && manufactureDate->Month >= 1 && manufactureDate->Month <= 12 && manufactureDate->Day >= 1 && manufactureDate->Day <= 31) {
160            uint16_t year = manufactureDate->Year;
161            ffStrbufSetF(&entry->result->manufactureDate, "%.4u-%.2u-%.2u", (unsigned) (year < 1000 ? (year + 1900) : year), (unsigned) manufactureDate->Month, (unsigned) manufactureDate->Day);
162        }
163
164        // Device Name, Manufacture Name, Serial Number, UniqueID
165        const struct {
166            uint16_t size; // in bytes, including the null terminator
167            wchar_t value[];
168        }* cursor = (const void*) data->Strings;
169
170        FFstrbuf* strings[] = {
171            &entry->result->modelName,
172            &entry->result->manufacturer,
173            &entry->result->serial,
174        };
175
176        for (size_t i = 0; i < ARRAY_SIZE(strings); ++i) {
177            if (cursor->size > sizeof(wchar_t)) {
178                ffStrbufSetNWS(strings[i], cursor->size / sizeof(wchar_t) - 1, cursor->value);
179            }
180            cursor = (const void*) ((const uint8_t*) cursor + sizeof(uint16_t) + cursor->size);
181        }
182    }
183}
184
185static void detectStatus(FFlist* entries, FFlist* results) {
186    FF_DEBUG("detectStatus");
187    FF_AUTO_FREE PWNODE_ALL_DATA allData = nullptr;
188    ULONG bufferSize = 0;
189    const char* error = queryWmiAllData(&BATTERY_STATUS_WMI_GUID, "BATTERY_STATUS_WMI_GUID", &allData, &bufferSize);
190    if (error) {
191        return;
192    }
193
194    for (ULONG i = 0; i < allData->InstanceCount; ++i) {
195        const uint8_t* instanceData = nullptr;
196        ULONG instanceLength = 0;
197        if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_STATUS)) {
198            continue;
199        }
200
201        const BATTERY_WMI_STATUS* data = (const BATTERY_WMI_STATUS*) instanceData;
202        FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag);
203        if (data->RemainingCapacity != BATTERY_UNKNOWN_CAPACITY) {
204            entry->result->capacity = data->RemainingCapacity;
205        }
206
207        entry->result->status = FF_BATTERY_STATUS_NONE;
208        if (data->PowerOnline) {
209            entry->result->status |= FF_BATTERY_STATUS_AC_CONNECTED;
210        }
211        if (data->Charging) {
212            entry->result->status |= FF_BATTERY_STATUS_CHARGING;
213        }
214        if (data->Discharging) {
215            entry->result->status |= FF_BATTERY_STATUS_DISCHARGING;
216        }
217        if (data->Critical) {
218            entry->result->status |= FF_BATTERY_STATUS_CRITICAL;
219        }
220    }
221}
222
223static void detectRuntime(FFlist* entries, FFlist* results) {
224    FF_DEBUG("detectRuntime");
225    FF_AUTO_FREE PWNODE_ALL_DATA allData = nullptr;
226    ULONG bufferSize = 0;
227    const char* error = queryWmiAllData(&BATTERY_RUNTIME_WMI_GUID, "BATTERY_RUNTIME_WMI_GUID", &allData, &bufferSize);
228    if (error) {
229        return;
230    }
231
232    for (ULONG i = 0; i < allData->InstanceCount; ++i) {
233        const uint8_t* instanceData = nullptr;
234        ULONG instanceLength = 0;
235        if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_RUNTIME)) {
236            continue;
237        }
238
239        const BATTERY_WMI_RUNTIME* data = (const BATTERY_WMI_RUNTIME*) instanceData;
240        FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag);
241        if (data->EstimatedRuntime != BATTERY_UNKNOWN_TIME) {
242            entry->result->timeRemaining = (int32_t) data->EstimatedRuntime;
243        }
244    }
245}
246
247static void detectFullChargedCapacity(FFlist* entries, FFlist* results) {
248    FF_DEBUG("detectFullChargedCapacity");
249    FF_AUTO_FREE PWNODE_ALL_DATA allData = nullptr;
250    ULONG bufferSize = 0;
251    const char* error = queryWmiAllData(&BATTERY_FULL_CHARGED_CAPACITY_WMI_GUID, "BATTERY_FULL_CHARGED_CAPACITY_WMI_GUID", &allData, &bufferSize);
252    if (error) {
253        return;
254    }
255
256    for (ULONG i = 0; i < allData->InstanceCount; ++i) {
257        const uint8_t* instanceData = nullptr;
258        ULONG instanceLength = 0;
259        if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_FULL_CHARGED_CAPACITY)) {
260            continue;
261        }
262
263        const BATTERY_WMI_FULL_CHARGED_CAPACITY* data = (const BATTERY_WMI_FULL_CHARGED_CAPACITY*) instanceData;
264        FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag);
265
266        if (data->FullChargedCapacity != BATTERY_UNKNOWN_CAPACITY && entry->result->capacity >= 0) {
267            entry->result->capacity *= 100;
268            entry->result->capacity /= data->FullChargedCapacity;
269        }
270    }
271}
272
273static void detectCycleCount(FFlist* entries, FFlist* results) {
274    FF_DEBUG("detectCycleCount");
275    FF_AUTO_FREE PWNODE_ALL_DATA allData = nullptr;
276    ULONG bufferSize = 0;
277    const char* error = queryWmiAllData(&BATTERY_CYCLE_COUNT_WMI_GUID, "BATTERY_CYCLE_COUNT_WMI_GUID", &allData, &bufferSize);
278    if (error) {
279        return;
280    }
281
282    for (ULONG i = 0; i < allData->InstanceCount; ++i) {
283        const uint8_t* instanceData = nullptr;
284        ULONG instanceLength = 0;
285        if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_CYCLE_COUNT)) {
286            continue;
287        }
288
289        const BATTERY_WMI_CYCLE_COUNT* data = (const BATTERY_WMI_CYCLE_COUNT*) instanceData;
290        getBatteryEntry(entries, results, data->Tag)->result->cycleCount = data->CycleCount;
291    }
292}
293
294static void detectTemperature(FFlist* entries, FFlist* results) {
295    FF_DEBUG("detectTemperature");
296    FF_AUTO_FREE PWNODE_ALL_DATA allData = nullptr;
297    ULONG bufferSize = 0;
298    const char* error = queryWmiAllData(&BATTERY_TEMPERATURE_WMI_GUID, "BATTERY_TEMPERATURE_WMI_GUID", &allData, &bufferSize);
299    if (error) {
300        return;
301    }
302
303    for (ULONG i = 0; i < allData->InstanceCount; ++i) {
304        const uint8_t* instanceData = nullptr;
305        ULONG instanceLength = 0;
306        if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_TEMPERATURE)) {
307            continue;
308        }
309
310        const BATTERY_WMI_TEMPERATURE* data = (const BATTERY_WMI_TEMPERATURE*) instanceData;
311        getBatteryEntry(entries, results, data->Tag)->result->temperature = data->Temperature / 10.0 - 273.15;
312    }
313}
314
315static const char* detectWithNtApi(FFBatteryResult* battery) {
316    // Reports summary battery information, not per battery
317    FF_DEBUG("NtApi: start detection");
318    SYSTEM_BATTERY_STATE info;
319    NTSTATUS status = NtPowerInformation(SystemBatteryState, nullptr, 0, &info, sizeof(info));
320    if (!NT_SUCCESS(status)) {
321        FF_DEBUG("NtApi: NtPowerInformation(SystemBatteryState) failed: %s", ffDebugNtStatus(status));
322        return "NtPowerInformation(SystemBatteryState) failed";
323    }
324    if (!info.BatteryPresent) {
325        FF_DEBUG("NtApi reports no battery present");
326        return "No battery present";
327    }
328
329    if (info.MaxCapacity != BATTERY_UNKNOWN_CAPACITY && info.RemainingCapacity != BATTERY_UNKNOWN_CAPACITY) {
330        battery->capacity = info.RemainingCapacity * 100.0 / info.MaxCapacity;
331    }
332    battery->status = FF_BATTERY_STATUS_NONE;
333    if (info.AcOnLine) {
334        battery->status |= FF_BATTERY_STATUS_AC_CONNECTED;
335    }
336    if (info.Charging) {
337        battery->status |= FF_BATTERY_STATUS_CHARGING;
338    }
339    if (info.Discharging) {
340        battery->status |= FF_BATTERY_STATUS_DISCHARGING;
341    }
342    if (info.DefaultAlert1 > 0 && info.RemainingCapacity <= info.DefaultAlert1) {
343        battery->status |= FF_BATTERY_STATUS_CRITICAL;
344    }
345    battery->timeRemaining = info.EstimatedTime == BATTERY_UNKNOWN_TIME ? -1 : (int32_t) info.EstimatedTime;
346    return nullptr;
347}
348
349const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
350    FF_DEBUG("WMI: start detection");
351
352    FF_LIST_AUTO_DESTROY entries = ffListCreate();
353    detectStaticData(&entries, results);
354    if (results->length == 0) {
355        return nullptr;
356    } else if (results->length == 1) {
357        // Fast path for single battery
358        detectWithNtApi(FF_LIST_FIRST(FFBatteryWmiEntry, entries)->result);
359    } else {
360        detectStatus(&entries, results);
361        detectFullChargedCapacity(&entries, results);
362        detectRuntime(&entries, results);
363    }
364    detectCycleCount(&entries, results);
365    if (options->temp) {
366        detectTemperature(&entries, results);
367    }
368
369    FF_LIST_FOR_EACH (FFBatteryWmiEntry, entry, entries) {
370        FF_DEBUG(
371            "WMI: detected battery tag=%lu, name='%s', charge=%.2f%%, status=0x%x, runtime=%d seconds",
372            entry->tag,
373            entry->result->modelName.length ? entry->result->modelName.chars : "<unknown>",
374            entry->result->capacity,
375            entry->result->status,
376            entry->result->timeRemaining);
377    }
378
379    FF_DEBUG("WMI: finished detection, total results=%u", results->length);
380    return nullptr;
381}