mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 17:24:45 +00:00
feat: Display system, adapter, and battery power metrics in status view
This commit is contained in:
BIN
bin/analyze-go
BIN
bin/analyze-go
Binary file not shown.
BIN
bin/status-go
BIN
bin/status-go
Binary file not shown.
@@ -118,10 +118,13 @@ type BatteryStatus struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ThermalStatus struct {
|
type ThermalStatus struct {
|
||||||
CPUTemp float64
|
CPUTemp float64
|
||||||
GPUTemp float64
|
GPUTemp float64
|
||||||
FanSpeed int
|
FanSpeed int
|
||||||
FanCount int
|
FanCount int
|
||||||
|
SystemPower float64 // System power consumption in Watts
|
||||||
|
AdapterPower float64 // AC adapter max power in Watts
|
||||||
|
BatteryPower float64 // Battery charge/discharge power in Watts (positive = discharging)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SensorReading struct {
|
type SensorReading struct {
|
||||||
|
|||||||
@@ -132,15 +132,13 @@ func getCachedPowerData() (health string, cycles int) {
|
|||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
lower := strings.ToLower(line)
|
lower := strings.ToLower(line)
|
||||||
if strings.Contains(lower, "cycle count") {
|
if strings.Contains(lower, "cycle count") {
|
||||||
parts := strings.Split(line, ":")
|
if _, after, found := strings.Cut(line, ":"); found {
|
||||||
if len(parts) == 2 {
|
cycles, _ = strconv.Atoi(strings.TrimSpace(after))
|
||||||
cycles, _ = strconv.Atoi(strings.TrimSpace(parts[1]))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.Contains(lower, "condition") {
|
if strings.Contains(lower, "condition") {
|
||||||
parts := strings.Split(line, ":")
|
if _, after, found := strings.Cut(line, ":"); found {
|
||||||
if len(parts) == 2 {
|
health = strings.TrimSpace(after)
|
||||||
health = strings.TrimSpace(parts[1])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,44 +173,93 @@ func collectThermal() ThermalStatus {
|
|||||||
|
|
||||||
var thermal ThermalStatus
|
var thermal ThermalStatus
|
||||||
|
|
||||||
// Get fan info from cached system_profiler
|
// Get fan info and adapter power from cached system_profiler
|
||||||
out := getSystemPowerOutput()
|
out := getSystemPowerOutput()
|
||||||
if out != "" {
|
if out != "" {
|
||||||
lines := strings.Split(out, "\n")
|
lines := strings.Split(out, "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
lower := strings.ToLower(line)
|
lower := strings.ToLower(line)
|
||||||
if strings.Contains(lower, "fan") && strings.Contains(lower, "speed") {
|
if strings.Contains(lower, "fan") && strings.Contains(lower, "speed") {
|
||||||
parts := strings.Split(line, ":")
|
if _, after, found := strings.Cut(line, ":"); found {
|
||||||
if len(parts) == 2 {
|
|
||||||
// Extract number from string like "1200 RPM"
|
// Extract number from string like "1200 RPM"
|
||||||
numStr := strings.TrimSpace(parts[1])
|
numStr := strings.TrimSpace(after)
|
||||||
numStr = strings.Split(numStr, " ")[0]
|
numStr, _, _ = strings.Cut(numStr, " ")
|
||||||
thermal.FanSpeed, _ = strconv.Atoi(numStr)
|
thermal.FanSpeed, _ = strconv.Atoi(numStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Try ioreg battery temperature (simple, no sudo needed)
|
// Get power metrics from ioreg (fast, real-time data)
|
||||||
ctxIoreg, cancelIoreg := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
ctxPower, cancelPower := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||||
defer cancelIoreg()
|
defer cancelPower()
|
||||||
if out, err := runCmd(ctxIoreg, "sh", "-c", "ioreg -rn AppleSmartBattery | awk '/\"Temperature\"/ {print $3}'"); err == nil {
|
if out, err := runCmd(ctxPower, "ioreg", "-rn", "AppleSmartBattery"); err == nil {
|
||||||
valStr := strings.TrimSpace(out)
|
lines := strings.Split(out, "\n")
|
||||||
if tempRaw, err := strconv.Atoi(valStr); err == nil && tempRaw > 0 {
|
for _, line := range lines {
|
||||||
thermal.CPUTemp = float64(tempRaw) / 100.0
|
line = strings.TrimSpace(line)
|
||||||
return thermal
|
|
||||||
|
// Get battery temperature
|
||||||
|
// Matches: "Temperature" = 3055 (note: space before =)
|
||||||
|
if _, after, found := strings.Cut(line, "\"Temperature\" = "); found {
|
||||||
|
valStr := strings.TrimSpace(after)
|
||||||
|
if tempRaw, err := strconv.Atoi(valStr); err == nil && tempRaw > 0 {
|
||||||
|
thermal.CPUTemp = float64(tempRaw) / 100.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get adapter power (Watts)
|
||||||
|
// Read from current adapter: "AdapterDetails" = {"Watts"=140...}
|
||||||
|
// Skip historical data: "AppleRawAdapterDetails" = ({Watts=90}, {Watts=140})
|
||||||
|
if strings.Contains(line, "\"AdapterDetails\" = {") && !strings.Contains(line, "AppleRaw") {
|
||||||
|
if _, after, found := strings.Cut(line, "\"Watts\"="); found {
|
||||||
|
valStr := strings.TrimSpace(after)
|
||||||
|
// Remove trailing characters like , or }
|
||||||
|
valStr, _, _ = strings.Cut(valStr, ",")
|
||||||
|
valStr, _, _ = strings.Cut(valStr, "}")
|
||||||
|
valStr = strings.TrimSpace(valStr)
|
||||||
|
if watts, err := strconv.ParseFloat(valStr, 64); err == nil && watts > 0 {
|
||||||
|
thermal.AdapterPower = watts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get system power consumption (mW -> W)
|
||||||
|
// Matches: "SystemPowerIn"=12345
|
||||||
|
if _, after, found := strings.Cut(line, "\"SystemPowerIn\"="); found {
|
||||||
|
valStr := strings.TrimSpace(after)
|
||||||
|
valStr, _, _ = strings.Cut(valStr, ",")
|
||||||
|
valStr, _, _ = strings.Cut(valStr, "}")
|
||||||
|
valStr = strings.TrimSpace(valStr)
|
||||||
|
if powerMW, err := strconv.ParseFloat(valStr, 64); err == nil && powerMW > 0 {
|
||||||
|
thermal.SystemPower = powerMW / 1000.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get battery power (mW -> W, positive = discharging)
|
||||||
|
// Matches: "BatteryPower"=12345
|
||||||
|
if _, after, found := strings.Cut(line, "\"BatteryPower\"="); found {
|
||||||
|
valStr := strings.TrimSpace(after)
|
||||||
|
valStr, _, _ = strings.Cut(valStr, ",")
|
||||||
|
valStr, _, _ = strings.Cut(valStr, "}")
|
||||||
|
valStr = strings.TrimSpace(valStr)
|
||||||
|
if powerMW, err := strconv.ParseFloat(valStr, 64); err == nil {
|
||||||
|
thermal.BatteryPower = powerMW / 1000.0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Try thermal level as a proxy (fallback)
|
// Fallback: Try thermal level as a proxy if temperature not found
|
||||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
if thermal.CPUTemp == 0 {
|
||||||
defer cancel2()
|
ctx2, cancel2 := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||||
out2, err := runCmd(ctx2, "sysctl", "-n", "machdep.xcpm.cpu_thermal_level")
|
defer cancel2()
|
||||||
if err == nil {
|
out2, err := runCmd(ctx2, "sysctl", "-n", "machdep.xcpm.cpu_thermal_level")
|
||||||
level, _ := strconv.Atoi(strings.TrimSpace(out2))
|
if err == nil {
|
||||||
// Estimate temp: level 0-100 roughly maps to 40-100°C
|
level, _ := strconv.Atoi(strings.TrimSpace(out2))
|
||||||
if level >= 0 {
|
// Estimate temp: level 0-100 roughly maps to 40-100°C
|
||||||
thermal.CPUTemp = 45 + float64(level)*0.5
|
if level >= 0 {
|
||||||
|
thermal.CPUTemp = 45 + float64(level)*0.5
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -520,7 +520,7 @@ func renderBatteryCard(batts []BatteryStatus, thermal ThermalStatus) cardData {
|
|||||||
}
|
}
|
||||||
lines = append(lines, fmt.Sprintf("Level %s %s", batteryProgressBar(b.Percent), percentText))
|
lines = append(lines, fmt.Sprintf("Level %s %s", batteryProgressBar(b.Percent), percentText))
|
||||||
|
|
||||||
// Line 2: status
|
// Line 2: status with power info
|
||||||
statusIcon := ""
|
statusIcon := ""
|
||||||
statusStyle := subtleStyle
|
statusStyle := subtleStyle
|
||||||
if statusLower == "charging" || statusLower == "charged" {
|
if statusLower == "charging" || statusLower == "charged" {
|
||||||
@@ -537,6 +537,18 @@ func renderBatteryCard(batts []BatteryStatus, thermal ThermalStatus) cardData {
|
|||||||
if b.TimeLeft != "" {
|
if b.TimeLeft != "" {
|
||||||
statusText += " · " + b.TimeLeft
|
statusText += " · " + b.TimeLeft
|
||||||
}
|
}
|
||||||
|
// Add power information
|
||||||
|
if statusLower == "charging" || statusLower == "charged" {
|
||||||
|
// AC powered - show system power consumption
|
||||||
|
if thermal.SystemPower > 0 {
|
||||||
|
statusText += fmt.Sprintf(" · %.0fW", thermal.SystemPower)
|
||||||
|
} else if thermal.AdapterPower > 0 {
|
||||||
|
statusText += fmt.Sprintf(" · %.0fW Adapter", thermal.AdapterPower)
|
||||||
|
}
|
||||||
|
} else if thermal.BatteryPower > 0 {
|
||||||
|
// Battery powered - show discharge rate
|
||||||
|
statusText += fmt.Sprintf(" · %.0fW", thermal.BatteryPower)
|
||||||
|
}
|
||||||
lines = append(lines, statusStyle.Render(statusText+statusIcon))
|
lines = append(lines, statusStyle.Render(statusText+statusIcon))
|
||||||
|
|
||||||
// Line 3: Health + cycles + temp
|
// Line 3: Health + cycles + temp
|
||||||
|
|||||||
2
mole
2
mole
@@ -22,7 +22,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
source "$SCRIPT_DIR/lib/core/common.sh"
|
source "$SCRIPT_DIR/lib/core/common.sh"
|
||||||
|
|
||||||
# Version info
|
# Version info
|
||||||
VERSION="1.14.1"
|
VERSION="1.14.2"
|
||||||
MOLE_TAGLINE="Deep clean and optimize your Mac."
|
MOLE_TAGLINE="Deep clean and optimize your Mac."
|
||||||
|
|
||||||
# Check TouchID configuration
|
# Check TouchID configuration
|
||||||
|
|||||||
Reference in New Issue
Block a user