From 17857db340617ba7e444992a23c288f5fdba8a90 Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Tue, 5 Jul 2022 16:27:25 +0200 Subject: [PATCH] feat: include battery lib resolves #2479 --- src/environment/battery/battery.go | 60 ++++ src/environment/battery/battery_darwin.go | 56 +++ .../battery_darwin_test.go} | 20 +- src/environment/battery/battery_linux.go | 156 +++++++++ src/environment/battery/battery_windows.go | 321 ++++++++++++++++++ .../battery/battery_windows_nix.go | 65 ++++ .../battery/battery_windows_nix_test.go | 51 +++ src/environment/battery/errors.go | 42 +++ src/environment/battery/errors_test.go | 48 +++ src/environment/cmd/run.go | 27 ++ src/environment/shell.go | 46 +-- .../{darwin.go => shell_darwin.go} | 18 +- src/environment/{unix.go => shell_unix.go} | 0 .../{windows.go => shell_windows.go} | 2 - src/environment/shell_windows_nix.go | 18 + src/environment/windows_unix.go | 106 ------ src/environment/windows_unix_test.go | 31 -- src/go.mod | 4 - src/go.sum | 9 - src/mock/environment.go | 5 +- src/segments/battery.go | 13 +- 21 files changed, 880 insertions(+), 218 deletions(-) create mode 100644 src/environment/battery/battery.go create mode 100644 src/environment/battery/battery_darwin.go rename src/environment/{darwin_test.go => battery/battery_darwin_test.go} (72%) create mode 100644 src/environment/battery/battery_linux.go create mode 100644 src/environment/battery/battery_windows.go create mode 100644 src/environment/battery/battery_windows_nix.go create mode 100644 src/environment/battery/battery_windows_nix_test.go create mode 100644 src/environment/battery/errors.go create mode 100644 src/environment/battery/errors_test.go create mode 100644 src/environment/cmd/run.go rename src/environment/{darwin.go => shell_darwin.go} (78%) rename src/environment/{unix.go => shell_unix.go} (100%) rename src/environment/{windows.go => shell_windows.go} (99%) create mode 100644 src/environment/shell_windows_nix.go delete mode 100644 src/environment/windows_unix.go delete mode 100644 src/environment/windows_unix_test.go diff --git a/src/environment/battery/battery.go b/src/environment/battery/battery.go new file mode 100644 index 00000000..9396a6f5 --- /dev/null +++ b/src/environment/battery/battery.go @@ -0,0 +1,60 @@ +// 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 + +type Info struct { + Percentage int + State State +} + +type NoBatteryError struct{} + +func (m *NoBatteryError) Error() string { + return "no battery" +} + +// State type enumerates possible battery states. +type State int + +var states = [...]string{ + Unknown: "Unknown", + Empty: "Empty", + Full: "Full", + Charging: "Charging", + Discharging: "Discharging", + NotCharging: "Not Charging", +} + +func (s State) String() string { + return states[s] +} + +// Possible state values. +// Unknown can mean either controller returned unknown, or +// not able to retrieve state due to some error. +const ( + Unknown State = iota + Empty + Full + Charging + Discharging + NotCharging +) diff --git a/src/environment/battery/battery_darwin.go b/src/environment/battery/battery_darwin.go new file mode 100644 index 00000000..a7fdbf2c --- /dev/null +++ b/src/environment/battery/battery_darwin.go @@ -0,0 +1,56 @@ +package battery + +import ( + "errors" + "oh-my-posh/environment/cmd" + "oh-my-posh/regex" + "strconv" + "strings" +) + +func mapMostLogicalState(state string) State { + switch state { + case "charging": + return Charging + case "discharging": + return Discharging + case "AC attached": + return NotCharging + case "full": + return Full + case "empty": + return Empty + case "charged": + return Full + default: + return Unknown + } +} + +func parseBatteryOutput(output string) (*Info, error) { + matches := regex.FindNamedRegexMatch(`(?P[0-9]{1,3})%; (?P[a-zA-Z\s]+);`, output) + if len(matches) != 2 { + msg := "Unable to find battery state based on output" + return nil, errors.New(msg) + } + var percentage int + var err error + if percentage, err = strconv.Atoi(matches["PERCENTAGE"]); err != nil { + return nil, errors.New("Unable to parse battery percentage") + } + return &Info{ + Percentage: percentage, + State: mapMostLogicalState(matches["STATE"]), + }, nil +} + +func Get() (*Info, error) { + output, err := cmd.Run("pmset", "-g", "batt") + if err != nil { + return nil, err + } + if !strings.Contains(output, "Battery") { + return nil, ErrNotFound + } + return parseBatteryOutput(output) +} diff --git a/src/environment/darwin_test.go b/src/environment/battery/battery_darwin_test.go similarity index 72% rename from src/environment/darwin_test.go rename to src/environment/battery/battery_darwin_test.go index ca3ddd60..a9160e7e 100644 --- a/src/environment/darwin_test.go +++ b/src/environment/battery/battery_darwin_test.go @@ -1,11 +1,8 @@ -//go:build darwin - -package environment +package battery import ( "testing" - "github.com/distatus/battery" "github.com/stretchr/testify/assert" ) @@ -13,44 +10,43 @@ func TestParseBatteryOutput(t *testing.T) { cases := []struct { Case string Output string - ExpectedState battery.State + ExpectedState State ExpectedPercentage int ExpectError bool }{ { Case: "charging", Output: "99%; charging;", - ExpectedState: battery.Charging, + ExpectedState: Charging, ExpectedPercentage: 99, }, { Case: "charging 1%", Output: "1%; charging;", - ExpectedState: battery.Charging, + ExpectedState: Charging, ExpectedPercentage: 1, }, { Case: "not charging 80%", Output: "81%; AC attached;", - ExpectedState: battery.NotCharging, + ExpectedState: NotCharging, ExpectedPercentage: 81, }, { Case: "charged", Output: "100%; charged;", - ExpectedState: battery.Full, + ExpectedState: Full, ExpectedPercentage: 100, }, { Case: "discharging", Output: "100%; discharging;", - ExpectedState: battery.Discharging, + ExpectedState: Discharging, ExpectedPercentage: 100, }, } for _, tc := range cases { - env := ShellEnvironment{} - info, err := env.parseBatteryOutput(tc.Output) + info, err := parseBatteryOutput(tc.Output) if tc.ExpectError { assert.Error(t, err, tc.Case) return diff --git a/src/environment/battery/battery_linux.go b/src/environment/battery/battery_linux.go new file mode 100644 index 00000000..be0c13c1 --- /dev/null +++ b/src/environment/battery/battery_linux.go @@ -0,0 +1,156 @@ +// 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" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" +) + +const sysfs = "/sys/class/power_supply" + +func newState(name string) (State, error) { + for i, state := range states { + if strings.EqualFold(name, state) { + return State(i), nil + } + } + return Unknown, fmt.Errorf("Invalid state `%s`", name) +} + +func readFloat(path, filename string) (float64, error) { + str, err := ioutil.ReadFile(filepath.Join(path, filename)) + if err != nil { + return 0, err + } + if len(str) == 0 { + return 0, ErrNotFound + } + num, err := strconv.ParseFloat(string(str[:len(str)-1]), 64) + if err != nil { + return 0, err + } + return num / 1000, nil // Convert micro->milli +} + +func readAmp(path, filename string, volts float64) (float64, error) { + val, err := readFloat(path, filename) + if err != nil { + return 0, err + } + return val * volts, nil +} + +func isBattery(path string) bool { + t, err := ioutil.ReadFile(filepath.Join(path, "type")) + return err == nil && string(t) == "Battery\n" +} + +func getBatteryFiles() ([]string, error) { + files, err := ioutil.ReadDir(sysfs) + if err != nil { + return nil, err + } + + var bFiles []string + for _, file := range files { + path := filepath.Join(sysfs, file.Name()) + if isBattery(path) { + bFiles = append(bFiles, path) + } + } + + if len(bFiles) == 0 { + return nil, &NoBatteryError{} + } + + return bFiles, nil +} + +func getByPath(path string) (*battery, error) { + b := &battery{} + var err error + + if b.Current, err = readFloat(path, "energy_now"); err == nil { + if b.Full, err = readFloat(path, "energy_full"); err != nil { + return nil, errors.New("unable to parse energy_full") + } + } else { + currentDoesNotExist := os.IsNotExist(err) + if b.Voltage, err = readFloat(path, "voltage_now"); err != nil { + return nil, errors.New("unable to parse voltage_now") + } + b.Voltage /= 1000 + if currentDoesNotExist { + if b.Current, err = readAmp(path, "charge_now", b.Voltage); err != nil { + return nil, errors.New("unable to parse charge_now") + } + if b.Full, err = readAmp(path, "charge_full", b.Voltage); err != nil { + return nil, errors.New("unable to parse charge_full") + } + } else { + if b.Full, err = readFloat(path, "energy_full"); err != nil { + return nil, errors.New("unable to parse energy_full") + } + } + } + + state, err := ioutil.ReadFile(filepath.Join(path, "status")) + if err != nil || len(state) == 0 { + return nil, errors.New("unable to parse or invalid status") + } + if b.State, err = newState(string(state[:len(state)-1])); err != nil { + return nil, errors.New("unable to map to new state") + } + + return b, nil +} + +func systemGetAll() ([]*battery, error) { + bFiles, err := getBatteryFiles() + if err != nil { + return nil, err + } + + var batteries []*battery + var errs Errors + + for _, bFile := range bFiles { + b, err := getByPath(bFile) + if err != nil { + errs = append(errs, err) + continue + } + batteries = append(batteries, b) + } + + if len(batteries) == 0 { + return nil, errs + } + + return batteries, nil +} diff --git a/src/environment/battery/battery_windows.go b/src/environment/battery/battery_windows.go new file mode 100644 index 00000000..dcfc7207 --- /dev/null +++ b/src/environment/battery/battery_windows.go @@ -0,0 +1,321 @@ +// 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" + "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 // nolint:revive + Flags uint32 + Reserved uint +} + +var guidDeviceBattery = guid{ + 0x72631e54, + 0x78A4, + 0x11d0, + [8]byte{0xbc, 0xf7, 0x00, 0xaa, 0x00, 0xb7, 0xb3, 0x2a}, +} + +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 { + case powerState&0x00000004 != 0: + return Charging + case powerState&0x00000008 != 0: + return Empty + case powerState&0x00000002 != 0: + return Discharging + case powerState&0x00000001 != 0: + 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 func() { + _, _, _ = 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) + 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 func() { + _ = 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{} + + 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 { + return nil, err + } + + b.Full = float64(bi.FullChargedCapacity) + + 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 { + return nil, err + } + + if b.Current, err = uint32ToFloat64(bs.Capacity); err != nil { + return nil, err + } + if b.Voltage, err = uint32ToFloat64(bs.Voltage); err != nil { + return nil, err + } + b.Voltage /= 1000 + b.State = readState(bs.PowerState) + + return b, nil +} + +func systemGetAll() ([]*battery, error) { + var batteries []*battery + var i int + var errs Errors + + for i = 0; ; i++ { + b, err := systemGet(i) + if err == ErrNotFound { + break + } + if err != nil { + errs = append(errs, err) + continue + } + batteries = append(batteries, b) + } + + if i == 0 { + return nil, &NoBatteryError{} + } + + if len(batteries) == 0 { + return nil, errs + } + + return batteries, nil +} diff --git a/src/environment/battery/battery_windows_nix.go b/src/environment/battery/battery_windows_nix.go new file mode 100644 index 00000000..e9e397ea --- /dev/null +++ b/src/environment/battery/battery_windows_nix.go @@ -0,0 +1,65 @@ +//go:build !darwin + +package battery + +import ( + "math" +) + +// battery type represents a single battery entry information. +type battery struct { + // Current battery state. + State State + // Current (momentary) capacity (in mWh). + Current float64 + // Last known full capacity (in mWh). + Full float64 + // Current voltage (in V). + Voltage float64 +} + +func mapMostLogicalState(currentState, newState State) State { + switch currentState { + case Discharging, NotCharging: + return Discharging + case Empty: + return newState + case Charging: + if newState == Discharging { + return Discharging + } + return Charging + case Unknown: + return newState + case Full: + return newState + } + return newState +} + +// GetAll returns information about all batteries in the system. +// +// If error != nil, it will be either ErrFatal or Errors. +// If error is of type Errors, it is guaranteed that length of both returned slices is the same and that i-th error coresponds with i-th battery structure. +func Get() (*Info, error) { + parseBatteryInfo := func(batteries []*battery) *Info { + var info Info + var current, total float64 + var state State + for _, bt := range batteries { + current += bt.Current + total += bt.Full + state = mapMostLogicalState(state, bt.State) + } + batteryPercentage := current / total * 100 + info.Percentage = int(math.Min(100, batteryPercentage)) + info.State = state + return &info + } + + batteries, err := systemGetAll() + if err != nil { + return nil, err + } + return parseBatteryInfo(batteries), nil +} diff --git a/src/environment/battery/battery_windows_nix_test.go b/src/environment/battery/battery_windows_nix_test.go new file mode 100644 index 00000000..8eb1b5f5 --- /dev/null +++ b/src/environment/battery/battery_windows_nix_test.go @@ -0,0 +1,51 @@ +//go:build !darwin + +// 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 ( + "testing" + + "github.com/alecthomas/assert" +) + +func TestMapBatteriesState(t *testing.T) { + cases := []struct { + Case string + ExpectedState State + CurrentState State + NewState State + }{ + {Case: "charging > charged", ExpectedState: Charging, CurrentState: Full, NewState: Charging}, + {Case: "charging < discharging", ExpectedState: Discharging, CurrentState: Discharging, NewState: Charging}, + {Case: "charging == charging", ExpectedState: Charging, CurrentState: Charging, NewState: Charging}, + {Case: "discharging > charged", ExpectedState: Discharging, CurrentState: Full, NewState: Discharging}, + {Case: "discharging > unknown", ExpectedState: Discharging, CurrentState: Unknown, NewState: Discharging}, + {Case: "discharging > full", ExpectedState: Discharging, CurrentState: Full, NewState: Discharging}, + {Case: "discharging > charging 2", ExpectedState: Discharging, CurrentState: Charging, NewState: Discharging}, + {Case: "discharging > empty", ExpectedState: Discharging, CurrentState: Empty, NewState: Discharging}, + } + for _, tc := range cases { + assert.Equal(t, tc.ExpectedState, mapMostLogicalState(tc.CurrentState, tc.NewState), tc.Case) + } +} diff --git a/src/environment/battery/errors.go b/src/environment/battery/errors.go new file mode 100644 index 00000000..7dd0fd48 --- /dev/null +++ b/src/environment/battery/errors.go @@ -0,0 +1,42 @@ +// 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 "fmt" + +var ErrNotFound = fmt.Errorf("Not found") + +type Errors []error + +func (e Errors) Error() string { + var s string + for _, err := range e { + if err != nil { + s += err.Error() + ", " + } + } + // strip trailing colon/space + if len(s) > 1 { + s = s[:len(s)-2] + } + return s +} diff --git a/src/environment/battery/errors_test.go b/src/environment/battery/errors_test.go new file mode 100644 index 00000000..adef1abd --- /dev/null +++ b/src/environment/battery/errors_test.go @@ -0,0 +1,48 @@ +// 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" + "testing" +) + +func TestErrors(t *testing.T) { + cases := []struct { + in Errors + str string + }{ + {Errors{nil}, ""}, + {Errors{errors.New("")}, ""}, + {Errors{errors.New("t1")}, "t1"}, + {Errors{errors.New("t2"), errors.New("t3")}, "t2, t3"}, + {Errors{errors.New("t4"), errors.New("t5")}, "t4, t5"}, + } + + for i, c := range cases { + str := c.in.Error() + + if str != c.str { + t.Errorf("%d: %v != %v", i, str, c.str) + } + } +} diff --git a/src/environment/cmd/run.go b/src/environment/cmd/run.go new file mode 100644 index 00000000..d817087b --- /dev/null +++ b/src/environment/cmd/run.go @@ -0,0 +1,27 @@ +package cmd + +import ( + "bytes" + "os/exec" + "strings" +) + +func Run(command string, args ...string) (string, error) { + cmd := exec.Command(command, args...) + var out bytes.Buffer + var err bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &err + cmdErr := cmd.Run() + if cmdErr != nil { + output := err.String() + return output, cmdErr + } + // some silly commands return 0 and the output is in stderr instead of stdout + result := out.String() + if len(result) == 0 { + result = err.String() + } + output := strings.TrimSpace(result) + return output, nil +} diff --git a/src/environment/shell.go b/src/environment/shell.go index da001c70..cfeefecc 100644 --- a/src/environment/shell.go +++ b/src/environment/shell.go @@ -9,6 +9,8 @@ import ( "io/fs" "log" "net/http" + "oh-my-posh/environment/battery" + "oh-my-posh/environment/cmd" "oh-my-posh/regex" "os" "os/exec" @@ -19,7 +21,6 @@ import ( "sync" "time" - "github.com/distatus/battery" process "github.com/shirou/gopsutil/v3/process" "golang.org/x/text/cases" "golang.org/x/text/language" @@ -61,12 +62,6 @@ func (e *CommandError) Error() string { return e.Err } -type NoBatteryError struct{} - -func (m *NoBatteryError) Error() string { - return "no battery" -} - type FileInfo struct { ParentFolder string Path string @@ -140,11 +135,6 @@ func (t *TemplateCache) AddSegmentData(key string, value interface{}) { t.Segments[key] = value } -type BatteryInfo struct { - Percentage int - State battery.State -} - type Environment interface { Getenv(key string) string Pwd() string @@ -173,7 +163,7 @@ type Environment interface { RunShellCommand(shell, command string) string ExecutionTime() float64 Flags() *Flags - BatteryState() (*BatteryInfo, error) + BatteryState() (*battery.Info, error) QueryWindowTitles(processName, windowTitleRegex string) (string, error) WindowsRegistryKeyValue(path string) (*WindowsRegistryValue, error) HTTPRequest(url string, timeout int, requestModifiers ...HTTPRequestModifier) ([]byte, error) @@ -203,11 +193,11 @@ func (c *commandCache) set(command, path string) { } func (c *commandCache) get(command string) (string, bool) { - cmd, found := c.commands.get(command) + cacheCommand, found := c.commands.get(command) if !found { return "", false } - command, ok := cmd.(string) + command, ok := cacheCommand.(string) return command, ok } @@ -494,29 +484,15 @@ func (env *ShellEnvironment) GOOS() string { func (env *ShellEnvironment) RunCommand(command string, args ...string) (string, error) { defer env.Trace(time.Now(), "RunCommand", append([]string{command}, args...)...) - if cmd, ok := env.cmdCache.get(command); ok { - command = cmd + if cacheCommand, ok := env.cmdCache.get(command); ok { + command = cacheCommand } - cmd := exec.Command(command, args...) - var out bytes.Buffer - var err bytes.Buffer - cmd.Stdout = &out - cmd.Stderr = &err - cmdErr := cmd.Run() - if cmdErr != nil { - output := err.String() - errorStr := fmt.Sprintf("cmd.Start() failed with '%s'", output) - env.Log(Error, "RunCommand", errorStr) - return output, cmdErr + output, err := cmd.Run(command, args...) + if err != nil { + env.Log(Error, "RunCommand", "cmd.Run() failed") } - // some silly commands return 0 and the output is in stderr instead of stdout - result := out.String() - if len(result) == 0 { - result = err.String() - } - output := strings.TrimSpace(result) env.Log(Debug, "RunCommand", output) - return output, nil + return output, err } func (env *ShellEnvironment) RunShellCommand(shell, command string) string { diff --git a/src/environment/darwin.go b/src/environment/shell_darwin.go similarity index 78% rename from src/environment/darwin.go rename to src/environment/shell_darwin.go index d7788c5d..7be230bd 100644 --- a/src/environment/darwin.go +++ b/src/environment/shell_darwin.go @@ -1,5 +1,3 @@ -//go:build darwin - package environment import ( @@ -9,7 +7,7 @@ import ( "strings" "time" - "github.com/distatus/battery" + "oh-my-posh/environment/battery" ) func mapMostLogicalState(state string) battery.State { @@ -31,30 +29,30 @@ func mapMostLogicalState(state string) battery.State { } } -func (env *ShellEnvironment) parseBatteryOutput(output string) (*BatteryInfo, error) { +func (env *ShellEnvironment) parseBatteryOutput(output string) (*battery.Info, error) { matches := regex.FindNamedRegexMatch(`(?P[0-9]{1,3})%; (?P[a-zA-Z\s]+);`, output) if len(matches) != 2 { msg := "Unable to find battery state based on output" - env.Log(Error, "BatteryInfo", msg) + env.Log(Error, "BatteryState", msg) return nil, errors.New(msg) } var percentage int var err error if percentage, err = strconv.Atoi(matches["PERCENTAGE"]); err != nil { - env.Log(Error, "BatteryInfo", err.Error()) + env.Log(Error, "BatteryState", err.Error()) return nil, errors.New("Unable to parse battery percentage") } - return &BatteryInfo{ + return &battery.Info{ Percentage: percentage, State: mapMostLogicalState(matches["STATE"]), }, nil } -func (env *ShellEnvironment) BatteryState() (*BatteryInfo, error) { - defer env.Trace(time.Now(), "BatteryInfo") +func (env *ShellEnvironment) BatteryState() (*battery.Info, error) { + defer env.Trace(time.Now(), "BatteryState") output, err := env.RunCommand("pmset", "-g", "batt") if err != nil { - env.Log(Error, "BatteryInfo", err.Error()) + env.Log(Error, "BatteryState", err.Error()) return nil, err } if !strings.Contains(output, "Battery") { diff --git a/src/environment/unix.go b/src/environment/shell_unix.go similarity index 100% rename from src/environment/unix.go rename to src/environment/shell_unix.go diff --git a/src/environment/windows.go b/src/environment/shell_windows.go similarity index 99% rename from src/environment/windows.go rename to src/environment/shell_windows.go index fd7dd0f0..91abe18c 100644 --- a/src/environment/windows.go +++ b/src/environment/shell_windows.go @@ -1,5 +1,3 @@ -//go:build windows - package environment import ( diff --git a/src/environment/shell_windows_nix.go b/src/environment/shell_windows_nix.go new file mode 100644 index 00000000..e0ba8a95 --- /dev/null +++ b/src/environment/shell_windows_nix.go @@ -0,0 +1,18 @@ +//go:build !darwin + +package environment + +import ( + "oh-my-posh/environment/battery" + "time" +) + +func (env *ShellEnvironment) BatteryState() (*battery.Info, error) { + defer env.Trace(time.Now(), "BatteryState") + info, err := battery.Get() + if err != nil { + env.Log(Error, "BatteryState", err.Error()) + return nil, err + } + return info, nil +} diff --git a/src/environment/windows_unix.go b/src/environment/windows_unix.go deleted file mode 100644 index 6601c561..00000000 --- a/src/environment/windows_unix.go +++ /dev/null @@ -1,106 +0,0 @@ -//go:build !darwin - -package environment - -import ( - "math" - "strings" - "time" - - "github.com/distatus/battery" -) - -func mapMostLogicalState(currentState, newState battery.State) battery.State { - switch currentState { - case battery.Discharging, battery.NotCharging: - return battery.Discharging - case battery.Empty: - return newState - case battery.Charging: - if newState == battery.Discharging { - return battery.Discharging - } - return battery.Charging - case battery.Unknown: - return newState - case battery.Full: - return newState - } - return newState -} - -func (env *ShellEnvironment) BatteryState() (*BatteryInfo, error) { - defer env.Trace(time.Now(), "BatteryInfo") - - parseBatteryInfo := func(batteries []*battery.Battery) *BatteryInfo { - var info BatteryInfo - var current, total float64 - var state battery.State - for _, bt := range batteries { - current += bt.Current - total += bt.Full - state = mapMostLogicalState(state, bt.State) - } - batteryPercentage := current / total * 100 - info.Percentage = int(math.Min(100, batteryPercentage)) - info.State = state - return &info - } - - batteries, err := battery.GetAll() - // actual error, return it - if err != nil && len(batteries) == 0 { - env.Log(Error, "BatteryInfo", err.Error()) - return nil, err - } - // there are no batteries found - if len(batteries) == 0 { - return nil, &NoBatteryError{} - } - // some batteries fail to get retrieved, filter them out if present - validBatteries := []*battery.Battery{} - for _, batt := range batteries { - if batt != nil { - validBatteries = append(validBatteries, batt) - } - } - // clean minor errors - unableToRetrieveBatteryInfo := "A device which does not exist was specified." - unknownChargeRate := "Unknown value received" - var fatalErr battery.Errors - ignoreErr := func(err error) bool { - if e, ok := err.(battery.ErrPartial); ok { - // ignore unknown charge rate value error - if e.Current == nil && - e.Design == nil && - e.DesignVoltage == nil && - e.Full == nil && - e.State == nil && - e.Voltage == nil && - e.ChargeRate != nil && - e.ChargeRate.Error() == unknownChargeRate { - return true - } - } - return false - } - if batErr, ok := err.(battery.Errors); ok { - for _, err := range batErr { - if !ignoreErr(err) { - fatalErr = append(fatalErr, err) - } - } - } - - // when battery info fails to get retrieved but there is at least one valid battery, return it without error - if len(validBatteries) > 0 && fatalErr != nil && strings.Contains(fatalErr.Error(), unableToRetrieveBatteryInfo) { - return parseBatteryInfo(validBatteries), nil - } - // another error occurred (possibly unmapped use-case), return it - if fatalErr != nil { - env.Log(Error, "BatteryInfo", fatalErr.Error()) - return nil, fatalErr - } - // everything is fine - return parseBatteryInfo(validBatteries), nil -} diff --git a/src/environment/windows_unix_test.go b/src/environment/windows_unix_test.go deleted file mode 100644 index 1b278cf1..00000000 --- a/src/environment/windows_unix_test.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build !darwin - -package environment - -import ( - "testing" - - "github.com/distatus/battery" - "github.com/stretchr/testify/assert" -) - -func TestMapBatteriesState(t *testing.T) { - cases := []struct { - Case string - ExpectedState battery.State - CurrentState battery.State - NewState battery.State - }{ - {Case: "charging > charged", ExpectedState: battery.Charging, CurrentState: battery.Full, NewState: battery.Charging}, - {Case: "charging < discharging", ExpectedState: battery.Discharging, CurrentState: battery.Discharging, NewState: battery.Charging}, - {Case: "charging == charging", ExpectedState: battery.Charging, CurrentState: battery.Charging, NewState: battery.Charging}, - {Case: "discharging > charged", ExpectedState: battery.Discharging, CurrentState: battery.Full, NewState: battery.Discharging}, - {Case: "discharging > unknown", ExpectedState: battery.Discharging, CurrentState: battery.Unknown, NewState: battery.Discharging}, - {Case: "discharging > full", ExpectedState: battery.Discharging, CurrentState: battery.Full, NewState: battery.Discharging}, - {Case: "discharging > charging 2", ExpectedState: battery.Discharging, CurrentState: battery.Charging, NewState: battery.Discharging}, - {Case: "discharging > empty", ExpectedState: battery.Discharging, CurrentState: battery.Empty, NewState: battery.Discharging}, - } - for _, tc := range cases { - assert.Equal(t, tc.ExpectedState, mapMostLogicalState(tc.CurrentState, tc.NewState), tc.Case) - } -} diff --git a/src/go.mod b/src/go.mod index d2dfc6e6..bd5f5437 100644 --- a/src/go.mod +++ b/src/go.mod @@ -8,7 +8,6 @@ require ( github.com/alecthomas/assert v1.0.0 github.com/alecthomas/colour v0.1.0 // indirect github.com/alecthomas/repr v0.1.0 // indirect - github.com/distatus/battery v0.10.0 github.com/esimov/stackblur-go v1.1.0 github.com/fogleman/gg v1.3.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 @@ -29,7 +28,6 @@ require ( golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b golang.org/x/text v0.3.7 gopkg.in/ini.v1 v1.66.6 - howett.net/plist v1.0.0 // indirect ) require ( @@ -90,6 +88,4 @@ require ( golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect ) -replace github.com/distatus/battery v0.10.0 => github.com/JanDeDobbeleer/battery v0.10.0-5 - replace github.com/atotto/clipboard v0.1.4 => github.com/jandedobbeleer/clipboard v0.1.4-1 diff --git a/src/go.sum b/src/go.sum index cefb5d23..b94d5e5e 100644 --- a/src/go.sum +++ b/src/go.sum @@ -6,8 +6,6 @@ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/ConradIrwin/font v0.0.0-20210318200717-ce8d41cc0732 h1:0EDePskeF4vNFCk70ATaFHQzjmwXsk+VImnMJttecNU= github.com/ConradIrwin/font v0.0.0-20210318200717-ce8d41cc0732/go.mod h1:krTLO7JWu6g8RMxG8sl+T1Hf8W93XQacBKJmqFZ2MFY= -github.com/JanDeDobbeleer/battery v0.10.0-5 h1:RdZlM4ioJRVrt0JFAgLASbECb7xlBifvmgwYFdwp55I= -github.com/JanDeDobbeleer/battery v0.10.0-5/go.mod h1:STnSvFLX//eEpkaN7qWRxCWxrWOcssTDgnG4yqq9BRE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= @@ -91,7 +89,6 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jandedobbeleer/clipboard v0.1.4-1 h1:rJehm5W0a3hvjcxyB3snqLBV4yvMBBc12JyMP7ngNQw= github.com/jandedobbeleer/clipboard v0.1.4-1/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -231,7 +228,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -271,8 +267,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -282,6 +276,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= -howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= -howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= diff --git a/src/mock/environment.go b/src/mock/environment.go index 36b1f35c..8c445f24 100644 --- a/src/mock/environment.go +++ b/src/mock/environment.go @@ -3,6 +3,7 @@ package mock import ( "io/fs" "oh-my-posh/environment" + "oh-my-posh/environment/battery" "time" mock "github.com/stretchr/testify/mock" @@ -122,9 +123,9 @@ func (env *MockedEnvironment) Flags() *environment.Flags { return arguments.Get(0).(*environment.Flags) } -func (env *MockedEnvironment) BatteryState() (*environment.BatteryInfo, error) { +func (env *MockedEnvironment) BatteryState() (*battery.Info, error) { args := env.Called() - return args.Get(0).(*environment.BatteryInfo), args.Error(1) + return args.Get(0).(*battery.Info), args.Error(1) } func (env *MockedEnvironment) Shell() string { diff --git a/src/segments/battery.go b/src/segments/battery.go index e13c1065..608a4de3 100644 --- a/src/segments/battery.go +++ b/src/segments/battery.go @@ -2,16 +2,15 @@ package segments import ( "oh-my-posh/environment" + "oh-my-posh/environment/battery" "oh-my-posh/properties" - - "github.com/distatus/battery" ) type Battery struct { props properties.Properties env environment.Environment - *environment.BatteryInfo + *battery.Info Error string Icon string } @@ -38,18 +37,18 @@ func (b *Battery) Enabled() bool { } var err error - b.BatteryInfo, err = b.env.BatteryState() + b.Info, err = b.env.BatteryState() if !b.enabledWhileError(err) { return false } // case on computer without batteries(no error, empty array) - if err == nil && b.BatteryInfo == nil { + if err == nil && b.Info == nil { return false } - switch b.BatteryInfo.State { + switch b.Info.State { case battery.Discharging: b.Icon = b.props.GetString(DischargingIcon, "") case battery.NotCharging: @@ -68,7 +67,7 @@ func (b *Battery) enabledWhileError(err error) bool { if err == nil { return true } - if _, ok := err.(*environment.NoBatteryError); ok { + if _, ok := err.(*battery.NoBatteryError); ok { return false } displayError := b.props.GetBool(properties.DisplayError, false)