diff --git a/cmd/status/metrics.go b/cmd/status/metrics.go index 1ee928d..2c65e4a 100644 --- a/cmd/status/metrics.go +++ b/cmd/status/metrics.go @@ -37,11 +37,12 @@ type MetricsSnapshot struct { } type HardwareInfo struct { - Model string // MacBook Pro 14-inch, 2021 - CPUModel string // Apple M1 Pro / Intel Core i7 - TotalRAM string // 16GB - DiskSize string // 512GB - OSVersion string // macOS Sonoma 14.5 + Model string // MacBook Pro 14-inch, 2021 + CPUModel string // Apple M1 Pro / Intel Core i7 + TotalRAM string // 16GB + DiskSize string // 512GB + OSVersion string // macOS Sonoma 14.5 + RefreshRate string // 120Hz / 60Hz } type DiskIOStatus struct { diff --git a/cmd/status/metrics_hardware.go b/cmd/status/metrics_hardware.go index 86ad00d..731d6a7 100644 --- a/cmd/status/metrics_hardware.go +++ b/cmd/status/metrics_hardware.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "runtime" "strings" "time" @@ -10,11 +11,12 @@ import ( func collectHardware(totalRAM uint64, disks []DiskStatus) HardwareInfo { if runtime.GOOS != "darwin" { return HardwareInfo{ - Model: "Unknown", - CPUModel: runtime.GOARCH, - TotalRAM: humanBytes(totalRAM), - DiskSize: "Unknown", - OSVersion: runtime.GOOS, + Model: "Unknown", + CPUModel: runtime.GOARCH, + TotalRAM: humanBytes(totalRAM), + DiskSize: "Unknown", + OSVersion: runtime.GOOS, + RefreshRate: "", } } @@ -22,7 +24,7 @@ func collectHardware(totalRAM uint64, disks []DiskStatus) HardwareInfo { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - var model, cpuModel, osVersion string + var model, cpuModel, osVersion, refreshRate string out, err := runCmd(ctx, "system_profiler", "SPHardwareDataType") if err == nil { @@ -58,16 +60,79 @@ func collectHardware(totalRAM uint64, disks []DiskStatus) HardwareInfo { osVersion = "macOS " + strings.TrimSpace(out2) } + // Get refresh rate from display info (use mini detail to keep it fast). + ctx3, cancel3 := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel3() + out3, err := runCmd(ctx3, "system_profiler", "-detailLevel", "mini", "SPDisplaysDataType") + if err == nil { + refreshRate = parseRefreshRate(out3) + } + diskSize := "Unknown" if len(disks) > 0 { diskSize = humanBytes(disks[0].Total) } return HardwareInfo{ - Model: model, - CPUModel: cpuModel, - TotalRAM: humanBytes(totalRAM), - DiskSize: diskSize, - OSVersion: osVersion, + Model: model, + CPUModel: cpuModel, + TotalRAM: humanBytes(totalRAM), + DiskSize: diskSize, + OSVersion: osVersion, + RefreshRate: refreshRate, } } + +// parseRefreshRate extracts the highest refresh rate from system_profiler display output. +func parseRefreshRate(output string) string { + lines := strings.Split(output, "\n") + maxHz := 0 + + for _, line := range lines { + lower := strings.ToLower(line) + // Look for patterns like "@ 60Hz", "@ 60.00Hz", or "Refresh Rate: 120 Hz". + if strings.Contains(lower, "hz") { + fields := strings.Fields(lower) + for i, field := range fields { + if field == "hz" && i > 0 { + if hz := parseInt(fields[i-1]); hz > maxHz && hz < 500 { + maxHz = hz + } + continue + } + if strings.HasSuffix(field, "hz") { + numStr := strings.TrimSuffix(field, "hz") + if numStr == "" && i > 0 { + numStr = fields[i-1] + } + if hz := parseInt(numStr); hz > maxHz && hz < 500 { + maxHz = hz + } + } + } + } + } + + if maxHz > 0 { + return fmt.Sprintf("%dHz", maxHz) + } + return "" +} + +// parseInt safely parses an integer from a string. +func parseInt(s string) int { + // Trim away non-numeric padding, keep digits and '.' for decimals. + cleaned := strings.TrimSpace(s) + cleaned = strings.TrimLeftFunc(cleaned, func(r rune) bool { + return (r < '0' || r > '9') && r != '.' + }) + cleaned = strings.TrimRightFunc(cleaned, func(r rune) bool { + return (r < '0' || r > '9') && r != '.' + }) + if cleaned == "" { + return 0 + } + var num int + fmt.Sscanf(cleaned, "%d", &num) + return num +} diff --git a/cmd/status/view.go b/cmd/status/view.go index 8eb0f4e..a5ef8ea 100644 --- a/cmd/status/view.go +++ b/cmd/status/view.go @@ -125,6 +125,9 @@ func renderHeader(m MetricsSnapshot, errMsg string, animFrame int, termWidth int if len(specs) > 0 { infoParts = append(infoParts, strings.Join(specs, "/")) } + if m.Hardware.RefreshRate != "" { + infoParts = append(infoParts, m.Hardware.RefreshRate) + } if m.Hardware.OSVersion != "" { infoParts = append(infoParts, m.Hardware.OSVersion) }