making lots of progress!
This commit is contained in:
35
cmd/fbhello/README.md
Normal file
35
cmd/fbhello/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# fbhello
|
||||
|
||||
A simple "Hello World" framebuffer application demonstrating the hdmistat carousel and layout APIs.
|
||||
|
||||
## Features
|
||||
|
||||
- Displays "Hello World" centered on the screen
|
||||
- Shows current time updating at 1 FPS
|
||||
- Shows uptime counter
|
||||
- Decorative border around the display
|
||||
- Falls back to terminal display if framebuffer is unavailable
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Run with framebuffer (requires appropriate permissions)
|
||||
sudo ./fbhello
|
||||
|
||||
# Run with terminal display (if framebuffer fails)
|
||||
./fbhello
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
The application demonstrates:
|
||||
|
||||
1. Creating a custom screen that implements `FrameGenerator`
|
||||
2. Using the layout API to draw text and borders
|
||||
3. Setting up a carousel (though with only one screen)
|
||||
4. Proper signal handling for clean shutdown
|
||||
5. Fallback to terminal display when framebuffer is unavailable
|
||||
|
||||
## Exit
|
||||
|
||||
Press Ctrl+C to exit the application.
|
||||
BIN
cmd/fbhello/fbhello
Executable file
BIN
cmd/fbhello/fbhello
Executable file
Binary file not shown.
176
cmd/fbhello/main.go
Normal file
176
cmd/fbhello/main.go
Normal file
@@ -0,0 +1,176 @@
|
||||
//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")
|
||||
}
|
||||
Reference in New Issue
Block a user