diff --git a/agent/battery/battery.go b/agent/battery/battery.go index 05a9f19c..62049425 100644 --- a/agent/battery/battery.go +++ b/agent/battery/battery.go @@ -1,14 +1,11 @@ -//go:build !freebsd && !darwin && !linux && !windows - // Package battery provides functions to check if the system has a battery and to get the battery stats. package battery -import "errors" - -func HasReadableBattery() bool { - return false -} - -func GetBatteryStats() (uint8, uint8, error) { - return 0, 0, errors.ErrUnsupported -} +const ( + stateUnknown uint8 = iota + stateEmpty + stateFull + stateCharging + stateDischarging + stateIdle +) diff --git a/agent/battery/battery_darwin.go b/agent/battery/battery_darwin.go index 6b11c5c2..f8d0d2ea 100644 --- a/agent/battery/battery_darwin.go +++ b/agent/battery/battery_darwin.go @@ -7,19 +7,11 @@ import ( "log/slog" "math" "os/exec" + "sync" "howett.net/plist" ) -const ( - stateUnknown uint8 = 0 - stateEmpty uint8 = 1 - stateFull uint8 = 2 - stateCharging uint8 = 3 - stateDischarging uint8 = 4 - stateIdle uint8 = 5 -) - type macBattery struct { CurrentCapacity int `plist:"CurrentCapacity"` MaxCapacity int `plist:"MaxCapacity"` @@ -28,11 +20,6 @@ type macBattery struct { ExternalConnected bool `plist:"ExternalConnected"` } -var ( - systemHasBattery = false - haveCheckedBattery = false -) - func readMacBatteries() ([]macBattery, error) { out, err := exec.Command("ioreg", "-n", "AppleSmartBattery", "-r", "-a").Output() if err != nil { @@ -49,11 +36,8 @@ func readMacBatteries() ([]macBattery, error) { } // HasReadableBattery checks if the system has a battery and returns true if it does. -func HasReadableBattery() bool { - if haveCheckedBattery { - return systemHasBattery - } - haveCheckedBattery = true +var HasReadableBattery = sync.OnceValue(func() bool { + systemHasBattery := false batteries, err := readMacBatteries() for _, bat := range batteries { if bat.MaxCapacity > 0 { @@ -65,7 +49,7 @@ func HasReadableBattery() bool { slog.Debug("No battery found", "err", err) } return systemHasBattery -} +}) // GetBatteryStats returns the current battery percent and charge state. // Uses CurrentCapacity/MaxCapacity to match the value macOS displays. diff --git a/agent/battery/battery_linux.go b/agent/battery/battery_linux.go index 7ea255a6..7ca56b9b 100644 --- a/agent/battery/battery_linux.go +++ b/agent/battery/battery_linux.go @@ -9,27 +9,14 @@ import ( "os" "path/filepath" "strconv" + "sync" "github.com/henrygd/beszel/agent/utils" ) -const ( - stateUnknown uint8 = 0 - stateEmpty uint8 = 1 - stateFull uint8 = 2 - stateCharging uint8 = 3 - stateDischarging uint8 = 4 - stateIdle uint8 = 5 -) - const sysfsPowerSupply = "/sys/class/power_supply" -var ( - systemHasBattery = false - haveCheckedBattery = false -) - -func getBatteryPaths() ([]string, error) { +var getBatteryPaths = sync.OnceValues(func() ([]string, error) { entries, err := os.ReadDir(sysfsPowerSupply) if err != nil { return nil, err @@ -42,7 +29,7 @@ func getBatteryPaths() ([]string, error) { } } return paths, nil -} +}) func parseSysfsState(status string) uint8 { switch status { @@ -62,11 +49,8 @@ func parseSysfsState(status string) uint8 { } // HasReadableBattery checks if the system has a battery and returns true if it does. -func HasReadableBattery() bool { - if haveCheckedBattery { - return systemHasBattery - } - haveCheckedBattery = true +var HasReadableBattery = sync.OnceValue(func() bool { + systemHasBattery := false paths, err := getBatteryPaths() for _, path := range paths { if _, ok := utils.ReadStringFileOK(filepath.Join(path, "capacity")); ok { @@ -78,7 +62,7 @@ func HasReadableBattery() bool { slog.Debug("No battery found", "err", err) } return systemHasBattery -} +}) // GetBatteryStats returns the current battery percent and charge state. // Reads /sys/class/power_supply/*/capacity directly so the kernel-reported diff --git a/agent/battery/battery_freebsd.go b/agent/battery/battery_stub.go similarity index 80% rename from agent/battery/battery_freebsd.go rename to agent/battery/battery_stub.go index dfbd5ff8..08cee52d 100644 --- a/agent/battery/battery_freebsd.go +++ b/agent/battery/battery_stub.go @@ -1,4 +1,4 @@ -//go:build freebsd +//go:build !darwin && !linux && !windows package battery diff --git a/agent/battery/battery_windows.go b/agent/battery/battery_windows.go index 5d49339b..1dc1f9a7 100644 --- a/agent/battery/battery_windows.go +++ b/agent/battery/battery_windows.go @@ -6,21 +6,13 @@ import ( "errors" "log/slog" "math" + "sync" "syscall" "unsafe" "golang.org/x/sys/windows" ) -const ( - stateUnknown uint8 = 0 - stateEmpty uint8 = 1 - stateFull uint8 = 2 - stateCharging uint8 = 3 - stateDischarging uint8 = 4 - stateIdle uint8 = 5 -) - type batteryQueryInformation struct { BatteryTag uint32 InformationLevel int32 @@ -77,15 +69,16 @@ var guidDeviceBattery = winGUID{ } var ( - setupapi = &windows.LazyDLL{Name: "setupapi.dll", System: true} - setupDiGetClassDevsW = setupapi.NewProc("SetupDiGetClassDevsW") - setupDiEnumDeviceInterfaces = setupapi.NewProc("SetupDiEnumDeviceInterfaces") + setupapi = &windows.LazyDLL{Name: "setupapi.dll", System: true} + setupDiGetClassDevsW = setupapi.NewProc("SetupDiGetClassDevsW") + setupDiEnumDeviceInterfaces = setupapi.NewProc("SetupDiEnumDeviceInterfaces") setupDiGetDeviceInterfaceDetailW = setupapi.NewProc("SetupDiGetDeviceInterfaceDetailW") - setupDiDestroyDeviceInfoList = setupapi.NewProc("SetupDiDestroyDeviceInfoList") + setupDiDestroyDeviceInfoList = setupapi.NewProc("SetupDiDestroyDeviceInfoList") ) 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) + _ = nargs + r1, _, errno := syscall.SyscallN(proc.Addr(), a1, a2, a3, a4, a5, a6) if windows.Handle(r1) == windows.InvalidHandle { if errno != 0 { return 0, error(errno) @@ -96,7 +89,8 @@ func setupDiSetup(proc *windows.LazyProc, nargs, a1, a2, a3, a4, a5, a6 uintptr) } 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) + _ = nargs + r1, _, errno := syscall.SyscallN(proc.Addr(), a1, a2, a3, a4, a5, a6) if r1 == 0 { if errno != 0 { return errno @@ -137,7 +131,7 @@ func winBatteryGet(idx int) (full, current uint32, state uint8, err error) { if err != nil { return 0, 0, stateUnknown, err } - defer syscall.Syscall(setupDiDestroyDeviceInfoList.Addr(), 1, hdev, 0, 0) + defer syscall.SyscallN(setupDiDestroyDeviceInfoList.Addr(), hdev) var did spDeviceInterfaceData did.cbSize = uint32(unsafe.Sizeof(did)) @@ -256,17 +250,9 @@ func winBatteryGet(idx int) (full, current uint32, state uint8, err error) { return bi.FullChargedCapacity, bs.Capacity, readWinBatteryState(bs.PowerState), nil } -var ( - systemHasBattery = false - haveCheckedBattery = false -) - // HasReadableBattery checks if the system has a battery and returns true if it does. -func HasReadableBattery() bool { - if haveCheckedBattery { - return systemHasBattery - } - haveCheckedBattery = true +var HasReadableBattery = sync.OnceValue(func() bool { + systemHasBattery := false full, _, _, err := winBatteryGet(0) if err == nil && full > 0 { systemHasBattery = true @@ -275,7 +261,7 @@ func HasReadableBattery() bool { slog.Debug("No battery found", "err", err) } return systemHasBattery -} +}) // GetBatteryStats returns the current battery percent and charge state. func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) { diff --git a/go.mod b/go.mod index dbf94976..497d844a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.26.1 require ( github.com/blang/semver v3.5.1+incompatible github.com/coreos/go-systemd/v22 v22.7.0 -github.com/ebitengine/purego v0.10.0 + github.com/ebitengine/purego v0.10.0 github.com/fxamacker/cbor/v2 v2.9.0 github.com/gliderlabs/ssh v0.3.8 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index aaa90387..8f951805 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= -github.com/distatus/battery v0.11.0 h1:KJk89gz90Iq/wJtbjjM9yUzBXV+ASV/EG2WOOL7N8lc= -github.com/distatus/battery v0.11.0/go.mod h1:KmVkE8A8hpIX4T78QRdMktYpEp35QfOL8A8dwZBxq2k= github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8= github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=