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}