hdmistat/internal/renderer/process_screen.go
sneak 2f8256b310 Refactor hdmistat to use dependency injection and fix linter issues
Major changes:
- Converted all cobra commands from global variables to CLI struct methods
- Eliminated global logger variable in favor of dependency injection
- Fixed all errcheck linter issues by properly handling errors
- Fixed Makefile to check formatting instead of modifying files
- Integrated smartconfig library for configuration management
- Added CLAUDE.md with project-specific development guidelines

Key improvements:
- All commands (daemon, install, status, info) now use CLI struct methods
- Logger is injected as dependency through fx providers
- Proper error handling for all DrawText and file.Close() calls
- Configuration loading now uses smartconfig with proper defaults
- Fixed formatting check in Makefile (make test no longer modifies files)

Technical details:
- Created CLI struct with log field (renamed from logger per request)
- All command constructors return *cobra.Command from CLI methods
- Config package uses smartconfig.NewFromAppName() correctly
- Fixed all critical errcheck issues throughout the codebase
- Maintained backward compatibility with existing functionality

All tests passing, code formatted, and ready for deployment.
2025-07-23 15:01:51 +02:00

140 lines
3.8 KiB
Go

package renderer
import (
"fmt"
"image/color"
"sort"
"git.eeqj.de/sneak/hdmistat/internal/layout"
"git.eeqj.de/sneak/hdmistat/internal/statcollector"
)
// ProcessScreen displays top processes
type ProcessScreen struct {
SortBy string // "cpu" or "memory"
}
func NewProcessScreenCPU() *ProcessScreen {
return &ProcessScreen{SortBy: "cpu"}
}
func NewProcessScreenMemory() *ProcessScreen {
return &ProcessScreen{SortBy: "memory"}
}
func (s *ProcessScreen) Name() string {
if s.SortBy == "cpu" {
return "Top Processes by CPU"
}
return "Top Processes by Memory"
}
func (s *ProcessScreen) Render(canvas *layout.Canvas, info *statcollector.SystemInfo) error {
width, _ := canvas.Size()
// Colors
textColor := color.RGBA{255, 255, 255, 255}
headerColor := color.RGBA{100, 200, 255, 255}
dimColor := color.RGBA{150, 150, 150, 255}
// Styles
titleStyle := layout.TextStyle{Size: 36, Color: headerColor}
headerStyle := layout.TextStyle{Size: 20, Color: headerColor}
normalStyle := layout.TextStyle{Size: 16, Color: textColor}
smallStyle := layout.TextStyle{Size: 14, Color: dimColor}
y := 50
// Title
_ = canvas.DrawText(s.Name(), layout.Point{X: width / 2, Y: y}, layout.TextStyle{
Size: titleStyle.Size,
Color: titleStyle.Color,
Alignment: layout.AlignCenter,
})
y += 70
// Sort processes
processes := make([]statcollector.ProcessInfo, len(info.Processes))
copy(processes, info.Processes)
if s.SortBy == "cpu" {
sort.Slice(processes, func(i, j int) bool {
return processes[i].CPUPercent > processes[j].CPUPercent
})
} else {
sort.Slice(processes, func(i, j int) bool {
return processes[i].MemoryRSS > processes[j].MemoryRSS
})
}
// Table headers
x := 50
_ = canvas.DrawText("PID", layout.Point{X: x, Y: y}, headerStyle)
_ = canvas.DrawText("USER", layout.Point{X: x + 100, Y: y}, headerStyle)
_ = canvas.DrawText("PROCESS", layout.Point{X: x + 250, Y: y}, headerStyle)
_ = canvas.DrawText("CPU %", layout.Point{X: x + 600, Y: y}, headerStyle)
_ = canvas.DrawText("MEMORY", layout.Point{X: x + 700, Y: y}, headerStyle)
y += 30
canvas.DrawHLine(x, y, width-100, color.RGBA{100, 100, 100, 255})
y += 20
// Display top 20 processes
for i, proc := range processes {
if i >= 20 {
break
}
// Truncate long names
name := proc.Name
if len(name) > 30 {
name = name[:27] + "..."
}
user := proc.Username
if len(user) > 12 {
user = user[:9] + "..."
}
_ = canvas.DrawText(fmt.Sprintf("%d", proc.PID), layout.Point{X: x, Y: y}, normalStyle)
_ = canvas.DrawText(user, layout.Point{X: x + 100, Y: y}, normalStyle)
_ = canvas.DrawText(name, layout.Point{X: x + 250, Y: y}, normalStyle)
_ = canvas.DrawText(fmt.Sprintf("%.1f", proc.CPUPercent), layout.Point{X: x + 600, Y: y}, normalStyle)
_ = canvas.DrawText(layout.FormatBytes(proc.MemoryRSS), layout.Point{X: x + 700, Y: y}, normalStyle)
// Highlight bar for high usage
if s.SortBy == "cpu" && proc.CPUPercent > 50 {
canvas.DrawBox(x-5, y-15, width-90, 20, color.RGBA{100, 50, 50, 100})
} else if s.SortBy == "memory" && float64(proc.MemoryRSS)/float64(info.MemoryTotal) > 0.1 {
canvas.DrawBox(x-5, y-15, width-90, 20, color.RGBA{50, 50, 100, 100})
}
y += 25
}
// Footer with system totals
y = 950
canvas.DrawHLine(50, y, width-100, color.RGBA{100, 100, 100, 255})
y += 30
totalCPU := 0.0
for _, cpu := range info.CPUPercent {
totalCPU += cpu
}
avgCPU := totalCPU / float64(len(info.CPUPercent))
footerText := fmt.Sprintf("System: CPU %.1f%% | Memory: %s / %s (%.1f%%)",
avgCPU,
layout.FormatBytes(info.MemoryUsed),
layout.FormatBytes(info.MemoryTotal),
float64(info.MemoryUsed)/float64(info.MemoryTotal)*100)
_ = canvas.DrawText(footerText, layout.Point{X: width / 2, Y: y}, layout.TextStyle{
Size: smallStyle.Size,
Color: smallStyle.Color,
Alignment: layout.AlignCenter,
})
return nil
}