mirror of https://github.com/cjbassi/gotop.git
319 lines
7.5 KiB
Go
319 lines
7.5 KiB
Go
// battery
|
|
// Copyright (C) 2016-2017 Karol 'Kenji Takahashi' Woźniak
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the "Software"),
|
|
// to deal in the Software without restriction, including without limitation
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
package battery
|
|
|
|
import (
|
|
"errors"
|
|
"math"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
type batteryQueryInformation struct {
|
|
BatteryTag uint32
|
|
InformationLevel int32
|
|
AtRate int32
|
|
}
|
|
|
|
type batteryInformation struct {
|
|
Capabilities uint32
|
|
Technology uint8
|
|
Reserved [3]uint8
|
|
Chemistry [4]uint8
|
|
DesignedCapacity uint32
|
|
FullChargedCapacity uint32
|
|
DefaultAlert1 uint32
|
|
DefaultAlert2 uint32
|
|
CriticalBias uint32
|
|
CycleCount uint32
|
|
}
|
|
|
|
type batteryWaitStatus struct {
|
|
BatteryTag uint32
|
|
Timeout uint32
|
|
PowerState uint32
|
|
LowCapacity uint32
|
|
HighCapacity uint32
|
|
}
|
|
|
|
type batteryStatus struct {
|
|
PowerState uint32
|
|
Capacity uint32
|
|
Voltage uint32
|
|
Rate int32
|
|
}
|
|
|
|
type guid struct {
|
|
Data1 uint32
|
|
Data2 uint16
|
|
Data3 uint16
|
|
Data4 [8]byte
|
|
}
|
|
|
|
type spDeviceInterfaceData struct {
|
|
cbSize uint32
|
|
InterfaceClassGuid guid
|
|
Flags uint32
|
|
Reserved uint
|
|
}
|
|
|
|
var guidDeviceBattery = guid{
|
|
0x72631e54,
|
|
0x78A4,
|
|
0x11d0,
|
|
[8]byte{0xbc, 0xf7, 0x00, 0xaa, 0x00, 0xb7, 0xb3, 0x2a},
|
|
}
|
|
|
|
func int32ToFloat64(num int32) (float64, error) {
|
|
// There is something wrong with this constant, but
|
|
// it appears to work so far...
|
|
if num == -0x80000000 { // BATTERY_UNKNOWN_RATE
|
|
return 0, errors.New("Unknown value received")
|
|
}
|
|
return math.Abs(float64(num)), nil
|
|
}
|
|
|
|
func uint32ToFloat64(num uint32) (float64, error) {
|
|
if num == 0xffffffff { // BATTERY_UNKNOWN_CAPACITY
|
|
return 0, errors.New("Unknown value received")
|
|
}
|
|
return float64(num), nil
|
|
}
|
|
|
|
func setupDiSetup(proc *windows.LazyProc, nargs, a1, a2, a3, a4, a5, a6 uintptr) (uintptr, error) {
|
|
r1, _, errno := syscall.Syscall6(proc.Addr(), nargs, a1, a2, a3, a4, a5, a6)
|
|
if windows.Handle(r1) == windows.InvalidHandle {
|
|
if errno != 0 {
|
|
return 0, error(errno)
|
|
}
|
|
return 0, syscall.EINVAL
|
|
}
|
|
return r1, nil
|
|
}
|
|
|
|
func setupDiCall(proc *windows.LazyProc, nargs, a1, a2, a3, a4, a5, a6 uintptr) syscall.Errno {
|
|
r1, _, errno := syscall.Syscall6(proc.Addr(), nargs, a1, a2, a3, a4, a5, a6)
|
|
if r1 == 0 {
|
|
if errno != 0 {
|
|
return errno
|
|
}
|
|
return syscall.EINVAL
|
|
}
|
|
return 0
|
|
}
|
|
|
|
var setupapi = &windows.LazyDLL{Name: "setupapi.dll", System: true}
|
|
var setupDiGetClassDevsW = setupapi.NewProc("SetupDiGetClassDevsW")
|
|
var setupDiEnumDeviceInterfaces = setupapi.NewProc("SetupDiEnumDeviceInterfaces")
|
|
var setupDiGetDeviceInterfaceDetailW = setupapi.NewProc("SetupDiGetDeviceInterfaceDetailW")
|
|
var setupDiDestroyDeviceInfoList = setupapi.NewProc("SetupDiDestroyDeviceInfoList")
|
|
|
|
func readState(powerState uint32) State {
|
|
switch powerState {
|
|
case 0x00000004:
|
|
return Charging
|
|
case 0x00000008:
|
|
return Empty
|
|
case 0x00000002:
|
|
return Discharging
|
|
case 0x00000001:
|
|
return Full
|
|
default:
|
|
return Unknown
|
|
}
|
|
}
|
|
|
|
func systemGet(idx int) (*Battery, error) {
|
|
hdev, err := setupDiSetup(
|
|
setupDiGetClassDevsW,
|
|
4,
|
|
uintptr(unsafe.Pointer(&guidDeviceBattery)),
|
|
0,
|
|
0,
|
|
2|16, // DIGCF_PRESENT|DIGCF_DEVICEINTERFACE
|
|
0, 0,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer syscall.Syscall(setupDiDestroyDeviceInfoList.Addr(), 1, hdev, 0, 0)
|
|
|
|
var did spDeviceInterfaceData
|
|
did.cbSize = uint32(unsafe.Sizeof(did))
|
|
errno := setupDiCall(
|
|
setupDiEnumDeviceInterfaces,
|
|
5,
|
|
hdev,
|
|
0,
|
|
uintptr(unsafe.Pointer(&guidDeviceBattery)),
|
|
uintptr(idx),
|
|
uintptr(unsafe.Pointer(&did)),
|
|
0,
|
|
)
|
|
if errno == 259 { // ERROR_NO_MORE_ITEMS
|
|
return nil, ErrNotFound
|
|
}
|
|
if errno != 0 {
|
|
return nil, errno
|
|
}
|
|
var cbRequired uint32
|
|
errno = setupDiCall(
|
|
setupDiGetDeviceInterfaceDetailW,
|
|
6,
|
|
hdev,
|
|
uintptr(unsafe.Pointer(&did)),
|
|
0,
|
|
0,
|
|
uintptr(unsafe.Pointer(&cbRequired)),
|
|
0,
|
|
)
|
|
if errno != 0 && errno != 122 { // ERROR_INSUFFICIENT_BUFFER
|
|
return nil, errno
|
|
}
|
|
// The god damn struct with ANYSIZE_ARRAY of utf16 in it is crazy.
|
|
// So... let's emulate it with array of uint16 ;-D.
|
|
// Keep in mind that the first two elements are actually cbSize.
|
|
didd := make([]uint16, cbRequired/2-1)
|
|
cbSize := (*uint32)(unsafe.Pointer(&didd[0]))
|
|
if unsafe.Sizeof(uint(0)) == 8 {
|
|
*cbSize = 8
|
|
} else {
|
|
*cbSize = 6
|
|
}
|
|
errno = setupDiCall(
|
|
setupDiGetDeviceInterfaceDetailW,
|
|
6,
|
|
hdev,
|
|
uintptr(unsafe.Pointer(&did)),
|
|
uintptr(unsafe.Pointer(&didd[0])),
|
|
uintptr(cbRequired),
|
|
uintptr(unsafe.Pointer(&cbRequired)),
|
|
0,
|
|
)
|
|
if errno != 0 {
|
|
return nil, errno
|
|
}
|
|
devicePath := &didd[2:][0]
|
|
|
|
handle, err := windows.CreateFile(
|
|
devicePath,
|
|
windows.GENERIC_READ|windows.GENERIC_WRITE,
|
|
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE,
|
|
nil,
|
|
windows.OPEN_EXISTING,
|
|
windows.FILE_ATTRIBUTE_NORMAL,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer windows.CloseHandle(handle)
|
|
|
|
var dwOut uint32
|
|
|
|
var dwWait uint32
|
|
var bqi batteryQueryInformation
|
|
err = windows.DeviceIoControl(
|
|
handle,
|
|
2703424, // IOCTL_BATTERY_QUERY_TAG
|
|
(*byte)(unsafe.Pointer(&dwWait)),
|
|
uint32(unsafe.Sizeof(dwWait)),
|
|
(*byte)(unsafe.Pointer(&bqi.BatteryTag)),
|
|
uint32(unsafe.Sizeof(bqi.BatteryTag)),
|
|
&dwOut,
|
|
nil,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if bqi.BatteryTag == 0 {
|
|
return nil, errors.New("BatteryTag not returned")
|
|
}
|
|
|
|
b := &Battery{}
|
|
e := ErrPartial{}
|
|
|
|
var bi batteryInformation
|
|
err = windows.DeviceIoControl(
|
|
handle,
|
|
2703428, // IOCTL_BATTERY_QUERY_INFORMATION
|
|
(*byte)(unsafe.Pointer(&bqi)),
|
|
uint32(unsafe.Sizeof(bqi)),
|
|
(*byte)(unsafe.Pointer(&bi)),
|
|
uint32(unsafe.Sizeof(bi)),
|
|
&dwOut,
|
|
nil,
|
|
)
|
|
if err == nil {
|
|
b.Full = float64(bi.FullChargedCapacity)
|
|
b.Design = float64(bi.DesignedCapacity)
|
|
} else {
|
|
e.Full = err
|
|
e.Design = err
|
|
}
|
|
|
|
bws := batteryWaitStatus{BatteryTag: bqi.BatteryTag}
|
|
var bs batteryStatus
|
|
err = windows.DeviceIoControl(
|
|
handle,
|
|
2703436, // IOCTL_BATTERY_QUERY_STATUS
|
|
(*byte)(unsafe.Pointer(&bws)),
|
|
uint32(unsafe.Sizeof(bws)),
|
|
(*byte)(unsafe.Pointer(&bs)),
|
|
uint32(unsafe.Sizeof(bs)),
|
|
&dwOut,
|
|
nil,
|
|
)
|
|
if err == nil {
|
|
b.Current, e.Current = uint32ToFloat64(bs.Capacity)
|
|
b.ChargeRate, e.ChargeRate = int32ToFloat64(bs.Rate)
|
|
b.Voltage, e.Voltage = uint32ToFloat64(bs.Voltage)
|
|
b.Voltage /= 1000
|
|
b.State = readState(bs.PowerState)
|
|
} else {
|
|
e.Current = err
|
|
e.ChargeRate = err
|
|
e.Voltage = err
|
|
e.State = err
|
|
}
|
|
|
|
b.DesignVoltage, e.DesignVoltage = b.Voltage, e.Voltage
|
|
|
|
return b, e
|
|
}
|
|
|
|
func systemGetAll() ([]*Battery, error) {
|
|
var batteries []*Battery
|
|
var errors Errors
|
|
for i := 0; ; i++ {
|
|
b, err := systemGet(i)
|
|
if err == ErrNotFound {
|
|
break
|
|
}
|
|
batteries = append(batteries, b)
|
|
errors = append(errors, err)
|
|
}
|
|
return batteries, errors
|
|
}
|