177 lines
4.0 KiB
Go
177 lines
4.0 KiB
Go
//nolint:mnd
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/hdmistat/internal/fbdraw"
|
|
"git.eeqj.de/sneak/hdmistat/internal/layout"
|
|
)
|
|
|
|
const (
|
|
// DefaultFontSize is the font size used throughout the application
|
|
DefaultFontSize = 24
|
|
)
|
|
|
|
// HelloWorldScreen implements the FrameGenerator interface
|
|
type HelloWorldScreen struct {
|
|
width int
|
|
height int
|
|
}
|
|
|
|
// NewHelloWorldScreen creates a new hello world screen
|
|
func NewHelloWorldScreen() *HelloWorldScreen {
|
|
return &HelloWorldScreen{}
|
|
}
|
|
|
|
// Init initializes the screen with the display dimensions
|
|
func (h *HelloWorldScreen) Init(width, height int) error {
|
|
h.width = width
|
|
h.height = height
|
|
return nil
|
|
}
|
|
|
|
// getSystemUptime returns the system uptime
|
|
func getSystemUptime() (time.Duration, error) {
|
|
data, err := os.ReadFile("/proc/uptime")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
fields := strings.Fields(string(data))
|
|
if len(fields) < 1 {
|
|
return 0, fmt.Errorf("invalid /proc/uptime format")
|
|
}
|
|
|
|
seconds, err := strconv.ParseFloat(fields[0], 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return time.Duration(seconds * float64(time.Second)), nil
|
|
}
|
|
|
|
// GenerateFrame generates a frame with "Hello World" and a timestamp
|
|
func (h *HelloWorldScreen) GenerateFrame(grid *fbdraw.CharGrid) error {
|
|
// Create a draw context that works directly on the provided grid
|
|
draw := layout.NewDraw(grid)
|
|
|
|
// Clear the screen with a dark background
|
|
draw.Clear()
|
|
|
|
// Calculate center position
|
|
centerY := grid.Height / 2
|
|
|
|
// Draw "Hello World" in the center
|
|
draw.Color(layout.Color("cyan")).Bold()
|
|
draw.TextCenter(0, centerY-2, "Hello World")
|
|
|
|
// Draw current time below in RFC format
|
|
draw.Color(layout.Color("white")).Plain()
|
|
currentTime := time.Now().Format(time.RFC1123)
|
|
draw.TextCenter(0, centerY, "%s", currentTime)
|
|
|
|
// Draw system uptime below that
|
|
uptime, err := getSystemUptime()
|
|
if err != nil {
|
|
uptime = 0
|
|
}
|
|
draw.Color(layout.Color("gray60"))
|
|
draw.TextCenter(
|
|
0,
|
|
centerY+2,
|
|
"System Uptime: %s",
|
|
formatDuration(uptime),
|
|
)
|
|
|
|
// Add a decorative border
|
|
borderGrid := draw.Grid(2, 2, grid.Width-4, grid.Height-4)
|
|
borderGrid.Border(layout.Color("gray30"))
|
|
|
|
return nil
|
|
}
|
|
|
|
// FramesPerSecond returns the desired frame rate (1 FPS for clock updates)
|
|
func (h *HelloWorldScreen) FramesPerSecond() float64 {
|
|
return 1.0
|
|
}
|
|
|
|
// formatDuration formats a duration in Go duration string format
|
|
func formatDuration(d time.Duration) string {
|
|
return d.Round(time.Second).String()
|
|
}
|
|
|
|
func main() {
|
|
// Set up signal handling
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
<-sigChan
|
|
log.Println("Received shutdown signal")
|
|
cancel()
|
|
}()
|
|
|
|
// Create framebuffer display
|
|
display, err := fbdraw.NewFBDisplayAuto()
|
|
if err != nil {
|
|
log.Fatalf("Failed to open framebuffer: %v", err)
|
|
}
|
|
defer func() {
|
|
if err := display.Close(); err != nil {
|
|
log.Printf("Failed to close display: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Create carousel with no rotation (single screen)
|
|
carousel := fbdraw.NewCarousel(display, 0) // 0 means no rotation
|
|
|
|
// Set font size
|
|
if err := carousel.SetFontSize(DefaultFontSize); err != nil {
|
|
log.Fatalf("Failed to set font size: %v", err)
|
|
}
|
|
|
|
// Add our hello world screen with header
|
|
helloScreen := NewHelloWorldScreen()
|
|
wrappedScreen := fbdraw.NewHeaderWrapper(helloScreen)
|
|
|
|
if err := carousel.AddScreen("Hello World", wrappedScreen); err != nil {
|
|
log.Fatalf("Failed to add screen: %v", err)
|
|
}
|
|
|
|
// Run the carousel
|
|
log.Println("Starting fbhello...")
|
|
log.Println("Press Ctrl+C to exit")
|
|
|
|
// Run carousel in a goroutine
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- carousel.Run()
|
|
}()
|
|
|
|
// Wait for either context cancellation or carousel to finish
|
|
select {
|
|
case <-ctx.Done():
|
|
log.Println("Stopping carousel...")
|
|
carousel.Stop()
|
|
<-done // Wait for carousel to finish
|
|
case err := <-done:
|
|
if err != nil {
|
|
log.Printf("Carousel error: %v", err)
|
|
}
|
|
}
|
|
|
|
log.Println("fbhello exited cleanly")
|
|
}
|