@@ -29,310 +29,66 @@ import (
2929 "golang.org/x/sys/windows"
3030)
3131
32- type batteryQueryInformation struct {
33- BatteryTag uint32
34- InformationLevel int32
35- AtRate int32
32+ // systemPowerStatus mirrors the Win32 SYSTEM_POWER_STATUS struct. GetSystemPowerStatus
33+ // reads a value the OS already maintains, so unlike the battery miniclass IOCTLs
34+ // (IOCTL_BATTERY_QUERY_TAG/INFORMATION/STATUS) it never round-trips to the embedded
35+ // controller, which is what made the previous SetupAPI-based implementation slow.
36+ type systemPowerStatus struct {
37+ ACLineStatus byte
38+ BatteryFlag byte
39+ BatteryLifePercent byte
40+ SystemStatusFlag byte
41+ BatteryLifeTime uint32
42+ BatteryFullLifeTime uint32
3643}
3744
38- type batteryInformation struct {
39- Capabilities uint32
40- Technology uint8
41- Reserved [3 ]uint8
42- Chemistry [4 ]uint8
43- DesignedCapacity uint32
44- FullChargedCapacity uint32
45- DefaultAlert1 uint32
46- DefaultAlert2 uint32
47- CriticalBias uint32
48- CycleCount uint32
49- }
50-
51- type batteryWaitStatus struct {
52- BatteryTag uint32
53- Timeout uint32
54- PowerState uint32
55- LowCapacity uint32
56- HighCapacity uint32
57- }
58-
59- type batteryStatus struct {
60- PowerState uint32
61- Capacity uint32
62- Voltage uint32
63- Rate int32
64- }
45+ const (
46+ batteryFlagCharging = 0x08
47+ batteryFlagCritical = 0x04
48+ batteryFlagNoBattery = 0x80
49+ batteryFlagUnknown = 0xFF
6550
66- type guid struct {
67- Data1 uint32
68- Data2 uint16
69- Data3 uint16
70- Data4 [8 ]byte
71- }
72-
73- type spDeviceInterfaceData struct {
74- cbSize uint32
75- InterfaceClassGuid guid
76- Flags uint32
77- Reserved uint
78- }
51+ batteryLifePercentUnknown = 0xFF
7952
80- var guidDeviceBattery = guid {
81- 0x72631e54 ,
82- 0x78A4 ,
83- 0x11d0 ,
84- [8 ]byte {0xbc , 0xf7 , 0x00 , 0xaa , 0x00 , 0xb7 , 0xb3 , 0x2a },
85- }
53+ acLineOnline = 1
54+ )
8655
87- func uint32ToFloat64 (num uint32 ) (float64 , error ) {
88- if num == 0xffffffff { // BATTERY_UNKNOWN_CAPACITY
89- return 0 , errors .New ("unknown value received" )
90- }
91- return float64 (num ), nil
92- }
56+ var kernel32 = & windows.LazyDLL {Name : "kernel32.dll" , System : true }
57+ var getSystemPowerStatus = kernel32 .NewProc ("GetSystemPowerStatus" )
9358
94- func setupDiSetup (proc * windows.LazyProc , args ... uintptr ) (uintptr , error ) {
95- r1 , _ , errno := syscall .SyscallN (proc .Addr (), args ... )
96- if windows .Handle (r1 ) == windows .InvalidHandle {
97- if errno != 0 {
98- return 0 , error (errno )
99- }
100- return 0 , syscall .EINVAL
101- }
102- return r1 , nil
103- }
59+ func systemGetAll () ([]* battery , error ) {
60+ var sps systemPowerStatus
10461
105- func setupDiCall (proc * windows.LazyProc , args ... uintptr ) syscall.Errno {
106- r1 , _ , errno := syscall .SyscallN (proc .Addr (), args ... )
62+ r1 , _ , errno := syscall .SyscallN (getSystemPowerStatus .Addr (), uintptr (unsafe .Pointer (& sps )))
10763 if r1 == 0 {
10864 if errno != 0 {
109- return errno
65+ return nil , error ( errno )
11066 }
111- return syscall .EINVAL
67+ return nil , syscall .EINVAL
11268 }
113- return 0
114- }
11569
116- var setupapi = & windows.LazyDLL {Name : "setupapi.dll" , System : true }
117- var setupDiGetClassDevsW = setupapi .NewProc ("SetupDiGetClassDevsW" )
118- var setupDiEnumDeviceInterfaces = setupapi .NewProc ("SetupDiEnumDeviceInterfaces" )
119- var setupDiGetDeviceInterfaceDetailW = setupapi .NewProc ("SetupDiGetDeviceInterfaceDetailW" )
120- var setupDiDestroyDeviceInfoList = setupapi .NewProc ("SetupDiDestroyDeviceInfoList" )
121-
122- func readState (powerState uint32 ) State {
123- switch {
124- case powerState & 0x00000004 != 0 :
125- return Charging
126- case powerState & 0x00000008 != 0 :
127- return Empty
128- case powerState & 0x00000002 != 0 :
129- return Discharging
130- case powerState & 0x00000001 != 0 :
131- return Full
132- default :
133- return Unknown
134- }
135- }
136-
137- func systemGet (idx int ) (* battery , error ) {
138- hdev , err := setupDiSetup (
139- setupDiGetClassDevsW ,
140- uintptr (unsafe .Pointer (& guidDeviceBattery )),
141- 0 ,
142- 0 ,
143- 2 | 16 , // DIGCF_PRESENT|DIGCF_DEVICEINTERFACE
144- )
145-
146- if err != nil {
147- return nil , err
148- }
149-
150- defer func () {
151- _ , _ , _ = syscall .SyscallN (setupDiDestroyDeviceInfoList .Addr (), hdev )
152- }()
153-
154- var did spDeviceInterfaceData
155- did .cbSize = uint32 (unsafe .Sizeof (did ))
156- errno := setupDiCall (
157- setupDiEnumDeviceInterfaces ,
158- hdev ,
159- 0 ,
160- uintptr (unsafe .Pointer (& guidDeviceBattery )),
161- uintptr (idx ),
162- uintptr (unsafe .Pointer (& did )),
163- )
164-
165- if errno == 259 { // ERROR_NO_MORE_ITEMS
166- return nil , ErrNotFound
167- }
168-
169- if errno != 0 {
170- return nil , errno
171- }
172-
173- var cbRequired uint32
174- errno = setupDiCall (
175- setupDiGetDeviceInterfaceDetailW ,
176- hdev ,
177- uintptr (unsafe .Pointer (& did )),
178- 0 ,
179- 0 ,
180- uintptr (unsafe .Pointer (& cbRequired )),
181- 0 ,
182- )
183-
184- if errno != 0 && errno != 122 { // ERROR_INSUFFICIENT_BUFFER
185- return nil , errno
186- }
187-
188- if cbRequired == 0 {
189- return nil , errors .New ("no buffer information returned" )
190- }
191-
192- // The god damn struct with ANYSIZE_ARRAY of utf16 in it is crazy.
193- // So... let's emulate it with array of uint16 ;-D.
194- // Keep in mind that the first two elements are actually cbSize.
195- didd := make ([]uint16 , cbRequired / 2 )
196- cbSize := (* uint32 )(unsafe .Pointer (& didd [0 ]))
197- if unsafe .Sizeof (uint (0 )) == 8 {
198- * cbSize = 8
199- } else {
200- * cbSize = 6
201- }
202-
203- errno = setupDiCall (
204- setupDiGetDeviceInterfaceDetailW ,
205- hdev ,
206- uintptr (unsafe .Pointer (& did )),
207- uintptr (unsafe .Pointer (& didd [0 ])),
208- uintptr (cbRequired ),
209- uintptr (unsafe .Pointer (& cbRequired )),
210- 0 ,
211- )
212-
213- if errno != 0 {
214- return nil , errno
215- }
216-
217- devicePath := & didd [2 :][0 ]
218-
219- handle , err := windows .CreateFile (
220- devicePath ,
221- windows .GENERIC_READ | windows .GENERIC_WRITE ,
222- windows .FILE_SHARE_READ | windows .FILE_SHARE_WRITE ,
223- nil ,
224- windows .OPEN_EXISTING ,
225- windows .FILE_ATTRIBUTE_NORMAL ,
226- 0 ,
227- )
228-
229- if err != nil {
230- return nil , err
231- }
232-
233- defer func () {
234- _ = windows .CloseHandle (handle )
235- }()
236-
237- var dwOut uint32
238-
239- var dwWait uint32
240- var bqi batteryQueryInformation
241- err = windows .DeviceIoControl (
242- handle ,
243- 2703424 , // IOCTL_BATTERY_QUERY_TAG
244- (* byte )(unsafe .Pointer (& dwWait )),
245- uint32 (unsafe .Sizeof (dwWait )),
246- (* byte )(unsafe .Pointer (& bqi .BatteryTag )),
247- uint32 (unsafe .Sizeof (bqi .BatteryTag )),
248- & dwOut ,
249- nil ,
250- )
251-
252- if err != nil {
253- return nil , err
254- }
255-
256- if bqi .BatteryTag == 0 {
257- return nil , errors .New ("battery tag not returned" )
258- }
259-
260- b := & battery {}
261-
262- var bi batteryInformation
263- err = windows .DeviceIoControl (
264- handle ,
265- 2703428 , // IOCTL_BATTERY_QUERY_INFORMATION
266- (* byte )(unsafe .Pointer (& bqi )),
267- uint32 (unsafe .Sizeof (bqi )),
268- (* byte )(unsafe .Pointer (& bi )),
269- uint32 (unsafe .Sizeof (bi )),
270- & dwOut ,
271- nil ,
272- )
273-
274- if err != nil {
275- return nil , err
276- }
277-
278- b .Full = float64 (bi .FullChargedCapacity )
279-
280- bws := batteryWaitStatus {BatteryTag : bqi .BatteryTag }
281-
282- var bs batteryStatus
283- err = windows .DeviceIoControl (
284- handle ,
285- 2703436 , // IOCTL_BATTERY_QUERY_STATUS
286- (* byte )(unsafe .Pointer (& bws )),
287- uint32 (unsafe .Sizeof (bws )),
288- (* byte )(unsafe .Pointer (& bs )),
289- uint32 (unsafe .Sizeof (bs )),
290- & dwOut ,
291- nil ,
292- )
293-
294- if err != nil {
295- return nil , err
296- }
297-
298- if b .Current , err = uint32ToFloat64 (bs .Capacity ); err != nil {
299- return nil , err
70+ if sps .BatteryFlag & batteryFlagNoBattery != 0 || sps .BatteryFlag == batteryFlagUnknown {
71+ return nil , & NoBatteryError {}
30072 }
30173
302- if b . Voltage , err = uint32ToFloat64 ( bs . Voltage ); err != nil {
303- return nil , err
74+ if sps . BatteryLifePercent == batteryLifePercentUnknown {
75+ return nil , errors . New ( "unknown value received" )
30476 }
30577
306- b .Voltage /= 1000
307- b .State = readState (bs .PowerState )
308-
309- return b , nil
310- }
311-
312- func systemGetAll () ([]* battery , error ) {
313- var batteries []* battery
314- var i int
315- var errs Errors
78+ b := & battery {Full : 100 , Current : float64 (min (sps .BatteryLifePercent , 100 ))}
31679
317- for i = 0 ; ; i ++ {
318- b , err := systemGet (i )
319- if err == ErrNotFound {
320- break
321- }
322- if err != nil {
323- errs = append (errs , err )
324- continue
325- }
326- batteries = append (batteries , b )
327- }
328-
329- if i == 0 {
330- return nil , & NoBatteryError {}
331- }
332-
333- if len (batteries ) == 0 {
334- return nil , errs
80+ switch {
81+ case sps .BatteryFlag & batteryFlagCharging != 0 :
82+ b .State = Charging
83+ case sps .BatteryLifePercent == 100 && sps .ACLineStatus == acLineOnline :
84+ b .State = Full
85+ case sps .ACLineStatus == acLineOnline :
86+ b .State = NotCharging
87+ case sps .BatteryFlag & batteryFlagCritical != 0 :
88+ b .State = Empty
89+ default :
90+ b .State = Discharging
33591 }
33692
337- return batteries , nil
93+ return [] * battery { b } , nil
33894}
0 commit comments