summaryrefslogtreecommitdiff
path: root/internal/display
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-18 10:10:39 +0200
committerPaul Buetow <paul@buetow.org>2026-02-18 10:10:39 +0200
commit69f5017434298f1ffd4cdc30c30b95d0f4bd344f (patch)
treec41a378bc8c4c7338f392cde7a4185658d4dfb12 /internal/display
parentf1951f2ee1e83d802030c257d4a1df099ec08976 (diff)
refactor: enforce Go best practices (function size, ordering, formatting)
- config: split set() (62L) into setSizeAndTuning() + setDisplayFlags() - collector: split Run() (70L) into Run + startLocalScanner + startRemoteScanner + parseCollectorStream + dispatchCollectorLine; each <30L - collector: remove unused script_embed.go (RemoteScript was dead code) - display: move newRunState constructor before Run() per constructor-first rule - display: replace loadPeak IIFE with a plain initLoadPeak variable - display: split handleKey() (114L) into handleToggleKeys + handleAdjustAndSave + handleResizeKeys; add nil guard in handleResizeKeys for test safety - display: split drawNetBarSmoothed() (75L) into drawNetBarSmoothed + smoothNetUtilization + drawNetHalves; each <30L - all: gofmt -w to fix formatting drift in display and config files All tests pass (go test ./...). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/display')
-rw-r--r--internal/display/display.go172
-rw-r--r--internal/display/display_test.go22
-rw-r--r--internal/display/font.go4
-rw-r--r--internal/display/tooltip.go6
-rw-r--r--internal/display/tooltip_test.go8
5 files changed, 128 insertions, 84 deletions
diff --git a/internal/display/display.go b/internal/display/display.go
index 555e904..874abe3 100644
--- a/internal/display/display.go
+++ b/internal/display/display.go
@@ -49,6 +49,37 @@ type runState struct {
mouseLastMove time.Time // timestamp of last mouse movement; tooltip hidden after 3s idle
}
+// newRunState builds initial run state from config.
+// When cfg.LoadMax > 0 the load bar uses a fixed scale; otherwise it
+// starts at the auto-scale floor of 2.0 and tracks the live maximum.
+func newRunState(cfg *config.Config, winW, winH int32) *runState {
+ initLoadPeak := 2.0
+ if cfg.LoadMax > 0 {
+ initLoadPeak = cfg.LoadMax
+ }
+ return &runState{
+ showAvgLine: cfg.ShowAvgLine,
+ showIOAvgLine: cfg.ShowIOAvgLine,
+ cpuMode: cfg.CPUMode,
+ showMem: cfg.ShowMem,
+ showNet: cfg.ShowNet,
+ showLoad: cfg.ShowLoad,
+ loadPeak: initLoadPeak,
+ showSeparators: cfg.ShowSeparators,
+ extended: cfg.Extended,
+ winW: winW,
+ winH: winH,
+ prevCPU: make(map[string]collector.CPULine),
+ smoothedCPU: make(map[string]*[10]float64),
+ smoothedMem: make(map[string]*struct{ ramUsed, swapUsed float64 }),
+ smoothedNet: make(map[string]*struct{ rxPct, txPct float64 }),
+ prevNet: make(map[string]stats.NetStamp),
+ peakHistory: make(map[string][]float64),
+ mouseX: -1, // off-screen until first mouse move
+ mouseY: -1,
+ }
+}
+
// Run runs the SDL display loop until ctx is cancelled or user presses 'q'.
func Run(ctx context.Context, cfg *config.Config, src stats.Source) error {
if err := sdl.Init(sdl.INIT_VIDEO); err != nil {
@@ -97,37 +128,6 @@ func Run(ctx context.Context, cfg *config.Config, src stats.Source) error {
}
}
-// newRunState builds initial run state from config.
-func newRunState(cfg *config.Config, winW, winH int32) *runState {
- return &runState{
- showAvgLine: cfg.ShowAvgLine,
- showIOAvgLine: cfg.ShowIOAvgLine,
- cpuMode: cfg.CPUMode,
- showMem: cfg.ShowMem,
- showNet: cfg.ShowNet,
- showLoad: cfg.ShowLoad,
- // Use the fixed cap when set; otherwise start at the auto-scale floor of 2.0.
- loadPeak: func() float64 {
- if cfg.LoadMax > 0 {
- return cfg.LoadMax
- }
- return 2.0
- }(),
- showSeparators: cfg.ShowSeparators,
- extended: cfg.Extended,
- winW: winW,
- winH: winH,
- prevCPU: make(map[string]collector.CPULine),
- smoothedCPU: make(map[string]*[10]float64),
- smoothedMem: make(map[string]*struct{ ramUsed, swapUsed float64 }),
- smoothedNet: make(map[string]*struct{ rxPct, txPct float64 }),
- prevNet: make(map[string]stats.NetStamp),
- peakHistory: make(map[string][]float64),
- mouseX: -1, // off-screen until first mouse move
- mouseY: -1,
- }
-}
-
func clampInt(v, min, max int) int {
if v < min {
return min
@@ -164,10 +164,21 @@ func handleEvents(window *sdl.Window, cfg *config.Config, state *runState) bool
}
// handleKey handles one key press; returns true to quit.
+// handleKey handles one key press; returns true to quit.
+// It delegates to focused helpers for toggle, adjust/save, and resize keys.
func handleKey(sym sdl.Keycode, window *sdl.Window, cfg *config.Config, state *runState) bool {
- switch sym {
- case sdl.K_q:
+ if sym == sdl.K_q {
return true
+ }
+ handleToggleKeys(sym, cfg, state)
+ handleAdjustAndSave(sym, cfg, state)
+ handleResizeKeys(sym, window, cfg, state)
+ return false
+}
+
+// handleToggleKeys processes display-toggle hotkeys (1, 2/m, 3/n, 4/l, r, e, g, i, s).
+func handleToggleKeys(sym sdl.Keycode, cfg *config.Config, state *runState) {
+ switch sym {
case sdl.K_1:
// Cycle through three CPU display modes: average → all cores → off → average
state.cpuMode = (state.cpuMode + 1) % constants.CPUModeCount
@@ -209,6 +220,12 @@ func handleKey(sym sdl.Keycode, window *sdl.Window, cfg *config.Config, state *r
case sdl.K_s:
state.showSeparators = !state.showSeparators
fmt.Println("==> Toggled host separators:", state.showSeparators)
+ }
+}
+
+// handleAdjustAndSave processes sampling-adjust and config-write hotkeys (a, y, d, c, f, v, h, w).
+func handleAdjustAndSave(sym sdl.Keycode, cfg *config.Config, state *runState) {
+ switch sym {
case sdl.K_a:
cfg.CPUAverage++
fmt.Println("==> CPU average samples:", cfg.CPUAverage)
@@ -232,6 +249,7 @@ func handleKey(sym sdl.Keycode, window *sdl.Window, cfg *config.Config, state *r
case sdl.K_h:
printHotkeys()
case sdl.K_w:
+ // Copy mutable display state back to config before persisting.
cfg.ShowAvgLine = state.showAvgLine
cfg.ShowIOAvgLine = state.showIOAvgLine
cfg.CPUMode = state.cpuMode
@@ -245,6 +263,16 @@ func handleKey(sym sdl.Keycode, window *sdl.Window, cfg *config.Config, state *r
} else {
fmt.Println("==> Config written to ~/.loadbarsrc")
}
+ }
+}
+
+// handleResizeKeys processes window-resize hotkeys (arrow keys).
+// window may be nil in tests; the guard prevents a nil-pointer panic.
+func handleResizeKeys(sym sdl.Keycode, window *sdl.Window, cfg *config.Config, state *runState) {
+ if window == nil {
+ return
+ }
+ switch sym {
case sdl.K_LEFT:
state.winW -= 100
if state.winW < 1 {
@@ -267,7 +295,6 @@ func handleKey(sym sdl.Keycode, window *sdl.Window, cfg *config.Config, state *r
state.winH += 100
window.SetSize(state.winW, state.winH)
}
- return false
}
// barBounds calculates the x position and width for a bar at the given index.
@@ -657,16 +684,16 @@ func drawCPUBarFromPcts(renderer *sdl.Renderer, s *[10]float64, barW int32, x, y
renderer.SetDrawColor(r, g, b, 255)
renderer.FillRect(&sdl.Rect{X: x, Y: int32(curY), W: barW, H: hh})
}
- fill(constants.Blue.R, constants.Blue.G, constants.Blue.B, (*s)[0]) // system
- fill(constants.Yellow.R, constants.Yellow.G, constants.Yellow.B, (*s)[1]) // user
- fill(constants.Green.R, constants.Green.G, constants.Green.B, (*s)[2]) // nice
+ fill(constants.Blue.R, constants.Blue.G, constants.Blue.B, (*s)[0]) // system
+ fill(constants.Yellow.R, constants.Yellow.G, constants.Yellow.B, (*s)[1]) // user
+ fill(constants.Green.R, constants.Green.G, constants.Green.B, (*s)[2]) // nice
fill(constants.LimeGreen.R, constants.LimeGreen.G, constants.LimeGreen.B, (*s)[9]) // guestnice
- fill(constants.Black.R, constants.Black.G, constants.Black.B, (*s)[3]) // idle
- fill(constants.Purple.R, constants.Purple.G, constants.Purple.B, (*s)[4]) // iowait
- fill(constants.White.R, constants.White.G, constants.White.B, (*s)[5]) // irq
- fill(constants.White.R, constants.White.G, constants.White.B, (*s)[6]) // softirq
- fill(constants.Red.R, constants.Red.G, constants.Red.B, (*s)[7]) // guest
- fill(constants.Red.R, constants.Red.G, constants.Red.B, (*s)[8]) // steal
+ fill(constants.Black.R, constants.Black.G, constants.Black.B, (*s)[3]) // idle
+ fill(constants.Purple.R, constants.Purple.G, constants.Purple.B, (*s)[4]) // iowait
+ fill(constants.White.R, constants.White.G, constants.White.B, (*s)[5]) // irq
+ fill(constants.White.R, constants.White.G, constants.White.B, (*s)[6]) // softirq
+ fill(constants.Red.R, constants.Red.G, constants.Red.B, (*s)[7]) // guest
+ fill(constants.Red.R, constants.Red.G, constants.Red.B, (*s)[8]) // steal
// Extended: 1px peak line at max (system+user) over history
if extended && peakPct > 0 {
peakY := y + barH - int32(peakPct*pxPerPct)
@@ -828,6 +855,12 @@ func sumNonLoNet(h *stats.HostStats) (sum stats.NetStamp, hasIface bool) {
// Smoothed values and prevNet are only updated when new collector data arrives
// (cur.Stamp > prev.Stamp), so the bar holds steady between collector cycles
// instead of decaying toward zero on frames with no new data.
+// drawNetBarSmoothed sums RX/TX across all non-lo interfaces, computes utilization
+// vs link speed, smooths toward target, and draws one net bar (RX left from top, TX right from bottom).
+// The bar occupies the region (x, y) with dimensions (barW, barH).
+// Smoothed values and prevNet are only updated when new collector data arrives
+// (cur.Stamp > prev.Stamp), so the bar holds steady between collector cycles
+// instead of decaying toward zero on frames with no new data.
func drawNetBarSmoothed(renderer *sdl.Renderer, h *stats.HostStats, cfg *config.Config, smoothed *struct{ rxPct, txPct float64 }, prev stats.NetStamp, factor float64, barW int32, x, y, barH int32) stats.NetStamp {
// Clear this slot so we never leave previous (e.g. CPU/mem) content visible
renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255)
@@ -845,31 +878,43 @@ func drawNetBarSmoothed(renderer *sdl.Renderer, h *stats.HostStats, cfg *config.
// target to 0 (no delta) and smooth the bar toward zero, making real
// traffic invisible.
if cur.Stamp > prev.Stamp && prev.Stamp > 0 {
- linkBps := netLinkBytesPerSec(cfg)
- if linkBps <= 0 {
- linkBps = int64(constants.BytesGbit)
- }
- dt := cur.Stamp - prev.Stamp
- if dt > 0 {
- deltaB := cur.B - prev.B
- deltaTb := cur.Tb - prev.Tb
- if deltaB < 0 {
- deltaB = 0
- }
- if deltaTb < 0 {
- deltaTb = 0
- }
- targetRx := 100 * float64(deltaB) / (float64(linkBps) * dt)
- targetTx := 100 * float64(deltaTb) / (float64(linkBps) * dt)
- smoothed.rxPct += (targetRx - smoothed.rxPct) * factor
- smoothed.txPct += (targetTx - smoothed.txPct) * factor
- }
- prev = cur // only advance prev when we consumed new data
+ prev = smoothNetUtilization(cur, prev, cfg, smoothed, factor)
} else if prev.Stamp == 0 {
// First sample: record it but don't draw yet (no delta available)
prev = cur
}
+ drawNetHalves(renderer, smoothed, x, y, barW, barH)
+ return prev
+}
+
+// smoothNetUtilization computes RX/TX utilization deltas and blends them into smoothed.
+// Returns the updated previous stamp (cur) so callers can advance the baseline.
+func smoothNetUtilization(cur, prev stats.NetStamp, cfg *config.Config, smoothed *struct{ rxPct, txPct float64 }, factor float64) stats.NetStamp {
+ linkBps := netLinkBytesPerSec(cfg)
+ if linkBps <= 0 {
+ linkBps = int64(constants.BytesGbit)
+ }
+ dt := cur.Stamp - prev.Stamp
+ if dt > 0 {
+ deltaB := cur.B - prev.B
+ deltaTb := cur.Tb - prev.Tb
+ if deltaB < 0 {
+ deltaB = 0
+ }
+ if deltaTb < 0 {
+ deltaTb = 0
+ }
+ targetRx := 100 * float64(deltaB) / (float64(linkBps) * dt)
+ targetTx := 100 * float64(deltaTb) / (float64(linkBps) * dt)
+ smoothed.rxPct += (targetRx - smoothed.rxPct) * factor
+ smoothed.txPct += (targetTx - smoothed.txPct) * factor
+ }
+ return cur // advance the baseline to the consumed sample
+}
+// drawNetHalves renders the RX (left half, from top) and TX (right half, from bottom)
+// filled rectangles for one network bar using pre-smoothed utilization percentages.
+func drawNetHalves(renderer *sdl.Renderer, smoothed *struct{ rxPct, txPct float64 }, x, y, barW, barH int32) {
halfW := barW / 2
pxPerPct := float64(barH) / 100.0
halfH := barH / 2
@@ -899,7 +944,6 @@ func drawNetBarSmoothed(renderer *sdl.Renderer, h *stats.HostStats, cfg *config.
renderer.SetDrawColor(constants.Black.R, constants.Black.G, constants.Black.B, 255)
renderer.FillRect(&sdl.Rect{X: x + halfW, Y: y, W: halfW, H: barH - txH})
}
- return prev
}
// updateLoadPeak maintains the load scale used by the bar renderer.
diff --git a/internal/display/display_test.go b/internal/display/display_test.go
index 7d3e82e..16a7700 100644
--- a/internal/display/display_test.go
+++ b/internal/display/display_test.go
@@ -224,8 +224,8 @@ func TestMemBar_RamAndSwap(t *testing.T) {
"cpu": {User: 100, System: 100, Idle: 800}, // needed so countBars > 0
},
Mem: map[string]int64{
- "MemTotal": 1000,
- "MemFree": 400, // 60% used
+ "MemTotal": 1000,
+ "MemFree": 400, // 60% used
"SwapTotal": 1000,
"SwapFree": 600, // 40% used
},
@@ -385,13 +385,13 @@ func TestMultiHost_BarCount(t *testing.T) {
data: map[string]*stats.HostStats{
"alpha": {
CPU: map[string]collector.CPULine{"cpu": alphaCur},
- Mem: map[string]int64{"MemTotal": 100, "MemFree": 50, "SwapTotal": 0, "SwapFree": 0},
- Net: map[string]stats.NetStamp{"eth0": {B: 0, Tb: 0, Stamp: 1.0}},
+ Mem: map[string]int64{"MemTotal": 100, "MemFree": 50, "SwapTotal": 0, "SwapFree": 0},
+ Net: map[string]stats.NetStamp{"eth0": {B: 0, Tb: 0, Stamp: 1.0}},
},
"beta": {
CPU: map[string]collector.CPULine{"cpu": betaCur},
- Mem: map[string]int64{"MemTotal": 100, "MemFree": 50, "SwapTotal": 0, "SwapFree": 0},
- Net: map[string]stats.NetStamp{"eth0": {B: 0, Tb: 0, Stamp: 1.0}},
+ Mem: map[string]int64{"MemTotal": 100, "MemFree": 50, "SwapTotal": 0, "SwapFree": 0},
+ Net: map[string]stats.NetStamp{"eth0": {B: 0, Tb: 0, Stamp: 1.0}},
},
},
}
@@ -631,7 +631,7 @@ func newHotkeyTestEnv(t *testing.T, cpuMode int, showMem, showNet bool) (
"SwapFree": 600,
},
Net: map[string]stats.NetStamp{
- "eth0": {B: 12500000, Tb: 6250000, Stamp: 2.0},
+ "eth0": {B: 12500000, Tb: 6250000, Stamp: 2.0},
"wlan0": {B: 1000000, Tb: 500000, Stamp: 2.0},
},
},
@@ -1216,7 +1216,7 @@ func TestGlobalIOAvgLine_MultiHost(t *testing.T) {
defer surface.Free()
prev1, cur1 := makeCPUPairWithIO(10, 10, 20, 20, 5, 5) // 30% IO
- prev2, cur2 := makeCPUPair(40, 40, 20) // 0% IO
+ prev2, cur2 := makeCPUPair(40, 40, 20) // 0% IO
cfg := defaultTestConfig()
@@ -1554,9 +1554,9 @@ func TestMultiRow_DrawFrame(t *testing.T) {
cfg.MaxBarsPerRow = 2
prev1, cur1 := makeCPUPair(100, 0, 0) // all system → blue
- prev2, cur2 := makeCPUPair(0, 100, 0) // all user → yellow
- prev3, cur3 := makeCPUPair(0, 0, 100) // all idle → black
- prev4, cur4 := makeCPUPair(100, 0, 0) // all system → blue
+ prev2, cur2 := makeCPUPair(0, 100, 0) // all user → yellow
+ prev3, cur3 := makeCPUPair(0, 0, 100) // all idle → black
+ prev4, cur4 := makeCPUPair(100, 0, 0) // all system → blue
src := &mockSource{
data: map[string]*stats.HostStats{
diff --git a/internal/display/font.go b/internal/display/font.go
index d6dae18..e936c4c 100644
--- a/internal/display/font.go
+++ b/internal/display/font.go
@@ -6,8 +6,8 @@ import "github.com/veandco/go-sdl2/sdl"
// Each glyph is 7 rows of 5 bits (MSB = leftmost pixel).
const (
- glyphW = 5 // pixels per character width
- glyphH = 7 // pixels per character height
+ glyphW = 5 // pixels per character width
+ glyphH = 7 // pixels per character height
charGap = 1 // horizontal gap between characters
)
diff --git a/internal/display/tooltip.go b/internal/display/tooltip.go
index 77ee365..e612b2c 100644
--- a/internal/display/tooltip.go
+++ b/internal/display/tooltip.go
@@ -16,9 +16,9 @@ import (
const mouseIdleTimeout = 3 * time.Second
const (
- tooltipScale int32 = 2 // pixel scale for bitmap font
- tooltipPadX int32 = 6 // horizontal padding inside tooltip box
- tooltipPadY int32 = 4 // vertical padding inside tooltip box
+ tooltipScale int32 = 2 // pixel scale for bitmap font
+ tooltipPadX int32 = 6 // horizontal padding inside tooltip box
+ tooltipPadY int32 = 4 // vertical padding inside tooltip box
tooltipOffsetX int32 = 12 // offset from cursor to tooltip
tooltipOffsetY int32 = 12
)
diff --git a/internal/display/tooltip_test.go b/internal/display/tooltip_test.go
index e4f6afc..0d4dc57 100644
--- a/internal/display/tooltip_test.go
+++ b/internal/display/tooltip_test.go
@@ -261,10 +261,10 @@ func TestTooltipLines_Mem(t *testing.T) {
"myhost": {
CPU: map[string]collector.CPULine{"cpu": {}},
Mem: map[string]int64{
- "MemTotal": 8*1024*1024, // 8 GB in KB
- "MemFree": 2*1024*1024,
- "SwapTotal": 4*1024*1024,
- "SwapFree": 3*1024*1024,
+ "MemTotal": 8 * 1024 * 1024, // 8 GB in KB
+ "MemFree": 2 * 1024 * 1024,
+ "SwapTotal": 4 * 1024 * 1024,
+ "SwapFree": 3 * 1024 * 1024,
},
},
}