-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(darwin): manually parse battery info
resolves #2139
- Loading branch information
1 parent
5bf0c76
commit 07d6f74
Showing
7 changed files
with
265 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
//go:build darwin | ||
|
||
package environment | ||
|
||
import ( | ||
"errors" | ||
"oh-my-posh/regex" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/distatus/battery" | ||
) | ||
|
||
func mapMostLogicalState(state string) battery.State { | ||
switch state { | ||
case "charging": | ||
return battery.Charging | ||
case "discharging": | ||
return battery.Discharging | ||
case "AC attached": | ||
return battery.NotCharging | ||
case "full": | ||
return battery.Full | ||
case "empty": | ||
return battery.Empty | ||
case "charged": | ||
return battery.Full | ||
default: | ||
return battery.Unknown | ||
} | ||
} | ||
|
||
func (env *ShellEnvironment) parseBatteryOutput(output string) (*BatteryInfo, error) { | ||
matches := regex.FindNamedRegexMatch(`(?P<PERCENTAGE>[0-9]{1,3})%; (?P<STATE>[a-zA-Z\s]+);`, output) | ||
if len(matches) != 2 { | ||
msg := "Unable to find battery state based on output" | ||
env.log(Error, "BatteryInfo", 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()) | ||
return nil, errors.New("Unable to parse battery percentage") | ||
} | ||
return &BatteryInfo{ | ||
Percentage: percentage, | ||
State: mapMostLogicalState(matches["STATE"]), | ||
}, nil | ||
} | ||
|
||
func (env *ShellEnvironment) BatteryState() (*BatteryInfo, error) { | ||
defer env.trace(time.Now(), "BatteryInfo") | ||
output, err := env.RunCommand("pmset", "-g", "batt") | ||
if err != nil { | ||
env.log(Error, "BatteryInfo", err.Error()) | ||
return nil, err | ||
} | ||
if !strings.Contains(output, "Battery") { | ||
return nil, errors.New("No battery found") | ||
} | ||
return env.parseBatteryOutput(output) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
//go:build darwin | ||
|
||
package environment | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/distatus/battery" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestParseBatteryOutput(t *testing.T) { | ||
cases := []struct { | ||
Case string | ||
Output string | ||
ExpectedState battery.State | ||
ExpectedPercentage int | ||
ExpectError bool | ||
}{ | ||
{ | ||
Case: "charging", | ||
Output: "99%; charging;", | ||
ExpectedState: battery.Charging, | ||
ExpectedPercentage: 99, | ||
}, | ||
{ | ||
Case: "charging 1%", | ||
Output: "1%; charging;", | ||
ExpectedState: battery.Charging, | ||
ExpectedPercentage: 1, | ||
}, | ||
{ | ||
Case: "not charging 80%", | ||
Output: "81%; AC attached;", | ||
ExpectedState: battery.NotCharging, | ||
ExpectedPercentage: 81, | ||
}, | ||
{ | ||
Case: "charged", | ||
Output: "100%; charged;", | ||
ExpectedState: battery.Full, | ||
ExpectedPercentage: 100, | ||
}, | ||
{ | ||
Case: "discharging", | ||
Output: "100%; discharging;", | ||
ExpectedState: battery.Discharging, | ||
ExpectedPercentage: 100, | ||
}, | ||
} | ||
for _, tc := range cases { | ||
env := ShellEnvironment{} | ||
info, err := env.parseBatteryOutput(tc.Output) | ||
if tc.ExpectError { | ||
assert.Error(t, err, tc.Case) | ||
return | ||
} | ||
assert.Equal(t, tc.ExpectedState, info.State, tc.Case) | ||
assert.Equal(t, tc.ExpectedPercentage, info.Percentage, tc.Case) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
//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 | ||
} |
Oops, something went wrong.