230 lines
4.9 KiB
Go
230 lines
4.9 KiB
Go
// Package app contains the main application logic for hdmistat
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/hdmistat/internal/config"
|
|
"git.eeqj.de/sneak/hdmistat/internal/display"
|
|
"git.eeqj.de/sneak/hdmistat/internal/renderer"
|
|
"git.eeqj.de/sneak/hdmistat/internal/statcollector"
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
// App is the main application
|
|
type App struct {
|
|
display display.Display
|
|
collector statcollector.Collector
|
|
renderer *renderer.Renderer
|
|
screens []renderer.Screen
|
|
log *slog.Logger
|
|
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
wg sync.WaitGroup
|
|
|
|
currentScreen int
|
|
rotationInterval time.Duration
|
|
updateInterval time.Duration
|
|
}
|
|
|
|
// Options contains all dependencies for the App
|
|
type Options struct {
|
|
fx.In
|
|
|
|
Lifecycle fx.Lifecycle
|
|
Display display.Display
|
|
Collector statcollector.Collector
|
|
Renderer *renderer.Renderer
|
|
Logger *slog.Logger
|
|
Context context.Context
|
|
Config *config.Config
|
|
}
|
|
|
|
// NewApp creates a new application instance
|
|
func NewApp(opts Options) *App {
|
|
app := &App{
|
|
display: opts.Display,
|
|
collector: opts.Collector,
|
|
renderer: opts.Renderer,
|
|
log: opts.Logger,
|
|
currentScreen: 0,
|
|
rotationInterval: opts.Config.GetRotationDuration(),
|
|
updateInterval: opts.Config.GetUpdateDuration(),
|
|
}
|
|
|
|
// Initialize screens
|
|
app.screens = []renderer.Screen{
|
|
renderer.NewStatusScreen(), // New status screen
|
|
renderer.NewOverviewScreen(), // Old overview screen
|
|
renderer.NewProcessScreenCPU(),
|
|
renderer.NewProcessScreenMemory(),
|
|
}
|
|
|
|
// Use the injected context, not the lifecycle context
|
|
app.ctx, app.cancel = context.WithCancel(opts.Context)
|
|
|
|
opts.Lifecycle.Append(fx.Hook{
|
|
OnStart: func(_ context.Context) error {
|
|
app.Start()
|
|
return nil
|
|
},
|
|
OnStop: func(_ context.Context) error {
|
|
return app.Stop()
|
|
},
|
|
})
|
|
|
|
return app
|
|
}
|
|
|
|
// Start begins the application main loop
|
|
func (a *App) Start() {
|
|
a.log.Info("starting hdmistat app",
|
|
"screens", len(a.screens),
|
|
"rotation_interval", a.rotationInterval,
|
|
"update_interval", a.updateInterval)
|
|
|
|
// Start update loop
|
|
a.wg.Add(1)
|
|
go a.updateLoop()
|
|
|
|
// Start rotation loop
|
|
a.wg.Add(1)
|
|
go a.rotationLoop()
|
|
}
|
|
|
|
// Stop stops the application
|
|
func (a *App) Stop() error {
|
|
a.log.Info("stopping hdmistat app")
|
|
|
|
a.cancel()
|
|
a.wg.Wait()
|
|
|
|
// Clear display
|
|
if err := a.display.Clear(); err != nil {
|
|
a.log.Error("clearing display", "error", err)
|
|
}
|
|
|
|
// Close display
|
|
if err := a.display.Close(); err != nil {
|
|
a.log.Error("closing display", "error", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// updateLoop continuously updates the current screen
|
|
func (a *App) updateLoop() {
|
|
defer a.wg.Done()
|
|
defer func() {
|
|
a.log.Info("updateLoop exiting")
|
|
if r := recover(); r != nil {
|
|
a.log.Error("updateLoop panic", "error", r)
|
|
}
|
|
}()
|
|
|
|
a.log.Debug("updateLoop started")
|
|
|
|
// DISABLED FOR DEBUGGING - Only render once on screen switch
|
|
// ticker := time.NewTicker(a.updateInterval)
|
|
// defer ticker.Stop()
|
|
|
|
// Initial render
|
|
a.renderCurrentScreen()
|
|
|
|
// Just wait for context cancellation
|
|
a.log.Debug("updateLoop waiting for context cancellation")
|
|
<-a.ctx.Done()
|
|
a.log.Debug("updateLoop context cancelled")
|
|
|
|
/* COMMENTED OUT FOR DEBUGGING
|
|
for {
|
|
select {
|
|
case <-a.ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
a.renderCurrentScreen()
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
// rotationLoop rotates through screens
|
|
func (a *App) rotationLoop() {
|
|
defer a.wg.Done()
|
|
defer func() {
|
|
a.log.Info("rotationLoop exiting")
|
|
if r := recover(); r != nil {
|
|
a.log.Error("rotationLoop panic", "error", r)
|
|
}
|
|
}()
|
|
|
|
a.log.Debug("rotationLoop started", "interval", a.rotationInterval)
|
|
|
|
ticker := time.NewTicker(a.rotationInterval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-a.ctx.Done():
|
|
a.log.Debug("rotationLoop context cancelled")
|
|
return
|
|
case <-ticker.C:
|
|
a.log.Debug("rotationLoop ticker fired")
|
|
a.nextScreen()
|
|
}
|
|
}
|
|
}
|
|
|
|
// renderCurrentScreen renders and displays the current screen
|
|
func (a *App) renderCurrentScreen() {
|
|
if len(a.screens) == 0 {
|
|
return
|
|
}
|
|
|
|
// Get current screen
|
|
screen := a.screens[a.currentScreen]
|
|
a.log.Debug("rendering screen",
|
|
"index", a.currentScreen,
|
|
"name", screen.Name())
|
|
|
|
// Collect system info
|
|
info, err := a.collector.Collect()
|
|
if err != nil {
|
|
a.log.Error("collecting system info", "error", err)
|
|
return
|
|
}
|
|
|
|
// Render screen
|
|
img, err := a.renderer.RenderScreen(screen, info)
|
|
if err != nil {
|
|
a.log.Error("rendering screen",
|
|
"screen", screen.Name(),
|
|
"error", err)
|
|
return
|
|
}
|
|
|
|
// Display image
|
|
if err := a.display.Show(img); err != nil {
|
|
a.log.Error("displaying image", "error", err)
|
|
}
|
|
}
|
|
|
|
// nextScreen advances to the next screen
|
|
func (a *App) nextScreen() {
|
|
if len(a.screens) == 0 {
|
|
return
|
|
}
|
|
|
|
a.currentScreen = (a.currentScreen + 1) % len(a.screens)
|
|
a.log.Info("switching screen",
|
|
"index", a.currentScreen,
|
|
"name", a.screens[a.currentScreen].Name())
|
|
|
|
// Render the new screen immediately
|
|
a.renderCurrentScreen()
|
|
}
|