hdmistat/internal/framebufferdisplay
2025-07-24 14:32:50 +02:00
..
README.md checkpointing, heavy dev 2025-07-24 14:32:50 +02:00

Framebuffer Display API

A high-level Go package for easily creating text-based status displays on Linux framebuffers. Perfect for system monitors, embedded displays, IoT dashboards, and more.

API Design Concepts

Below are four different API design approaches for creating framebuffer displays. Each example shows how you might implement a system status display.

Concept 1: Builder Pattern

package main

import (
    "time"
    fb "github.com/example/framebufferdisplay"
)

func main() {
    // Create and configure display with fluent interface
    display := fb.New().
        AutoDetect().                    // Find first available framebuffer
        WithFont("IBM Plex Mono", 14).   // Default font
        WithUpdateInterval(time.Second). // Auto-refresh rate
        Build()
    
    defer display.Close()
    
    // Define the layout
    display.Layout(func(canvas *fb.Canvas) {
        // Header section
        canvas.Section("header", fb.TopCenter).
            Font("IBM Plex Mono", 24).
            Color(fb.White).
            Text("System Status")
        
        // System info section
        canvas.Section("info", fb.TopLeft).
            Margin(20).
            Rows(
                fb.Row().Label("Hostname:").Value(getHostname()),
                fb.Row().Label("Uptime:").Value(getUptime()),
                fb.Row().Label("Load:").Value(getLoad()).Color(fb.Red),
            )
        
        // CPU meters
        canvas.Section("cpu", fb.CenterLeft).
            Title("CPU Usage").
            Meters(getCPUMeters()...)
        
        // Memory bar
        canvas.Section("memory", fb.BottomLeft).
            Title("Memory").
            ProgressBar(getMemoryPercent(), fb.Green)
    })
    
    // Start the display loop
    display.Run()
}

Concept 2: Declarative/React-like

package main

import (
    fb "github.com/example/framebufferdisplay"
)

type SystemStatus struct {
    fb.Component
    hostname string
    uptime   time.Duration
}

func (s *SystemStatus) Render() fb.Element {
    return fb.Screen(
        fb.Header(
            fb.Text("System Status").
                Font("IBM Plex Mono", 48).
                Color(fb.RGB(100, 200, 255)),
        ),
        
        fb.Grid(fb.GridOptions{Columns: 2, Gap: 20},
            // Left column
            fb.Column(
                fb.Card(
                    fb.Title("System Info"),
                    fb.List(
                        fb.ListItem("Hostname", s.hostname),
                        fb.ListItem("Uptime", formatDuration(s.uptime)),
                        fb.ListItem("OS", getOS()),
                    ),
                ),
                fb.Card(
                    fb.Title("Network"),
                    fb.List(getNetworkInfo()...),
                ),
            ),
            
            // Right column
            fb.Column(
                fb.Card(
                    fb.Title("CPU Usage"),
                    fb.BarChart(getCPUData(), fb.ChartOptions{
                        Height: 200,
                        Color:  fb.Gradient(fb.Green, fb.Red),
                    }),
                ),
                fb.Card(
                    fb.Title("Memory"),
                    fb.CircularProgress(getMemoryPercent(), fb.Blue),
                    fb.Text(getMemoryDetails()).Size(12),
                ),
            ),
        ),
    )
}

func main() {
    fb.Run(&SystemStatus{})
}

Concept 3: Immediate Mode

package main

import (
    fb "github.com/example/framebufferdisplay"
)

func main() {
    // Auto-detect and initialize
    ctx := fb.Init()
    defer ctx.Close()
    
    // Main render loop
    ctx.Loop(func(d *fb.Draw) {
        // Clear with background
        d.Clear(fb.Black)
        
        // Draw header
        d.SetFont("IBM Plex Mono Bold", 36)
        d.SetColor(fb.White)
        d.TextCenter(d.Width/2, 50, "System Monitor")
        
        // System info box
        d.SetFont("IBM Plex Mono", 14)
        d.Box(20, 100, 400, 200, fb.Gray)
        d.SetColor(fb.Green)
        d.Text(30, 120, "Hostname: %s", getHostname())
        d.Text(30, 140, "Uptime: %s", getUptime())
        d.Text(30, 160, "Load: %.2f %.2f %.2f", getLoad())
        
        // CPU visualization
        cpus := getCPUPercents()
        for i, cpu := range cpus {
            y := 320 + i*30
            d.Text(30, y, "CPU%d", i)
            d.ProgressBar(80, y-10, 300, 20, cpu, fb.Heat(cpu))
        }
        
        // Memory meter
        mem := getMemoryPercent()
        d.SetFont("IBM Plex Mono", 18)
        d.Text(30, 500, "Memory: %.1f%%", mem)
        d.Gauge(30, 520, 350, 40, mem, fb.Blue)
        
        // Update display
        d.Present()
    })
}

Concept 4: Template/Widget-based

package main

import (
    fb "github.com/example/framebufferdisplay"
)

func main() {
    // Create display with auto-detection
    display := fb.NewDisplay()
    
    // Create a dashboard with predefined widgets
    dashboard := fb.Dashboard{
        Title: "System Status",
        Theme: fb.Themes.Dark,
        
        Layout: fb.GridLayout(3, 3), // 3x3 grid
        
        Widgets: []fb.Widget{
            // Row 1
            fb.BigNumber{
                GridPos: fb.Pos(0, 0),
                Label:   "CPU Temp",
                Value:   getCPUTemp,
                Unit:    "°C",
                Color:   fb.TempColor, // Auto-colors based on value
            },
            fb.LineGraph{
                GridPos:  fb.Pos(1, 0).Span(2, 1), // Spans 2 columns
                Title:    "CPU History",
                Duration: 5 * time.Minute,
                Source:   streamCPUData,
            },
            
            // Row 2
            fb.InfoTable{
                GridPos: fb.Pos(0, 1),
                Rows: []fb.TableRow{
                    {"Host", getHostname},
                    {"Kernel", getKernel},
                    {"Uptime", getUptime},
                },
            },
            fb.MultiMeter{
                GridPos: fb.Pos(1, 1),
                Title:   "CPU Cores",
                Meters:  getCPUCoreMeters,
                Compact: true,
            },
            fb.PieChart{
                GridPos: fb.Pos(2, 1),
                Title:   "Disk Usage",
                Data:    getDiskUsage,
            },
            
            // Row 3
            fb.MemoryWidget{
                GridPos:     fb.Pos(0, 2).Span(2, 1),
                ShowDetails: true,
            },
            fb.NetworkTraffic{
                GridPos:   fb.Pos(2, 2),
                Interface: "eth0",
            },
        },
        
        // Optional: Add alerts
        Alerts: []fb.Alert{
            fb.Alert{
                Condition: func() bool { return getCPUTemp() > 80 },
                Message:   "High CPU Temperature!",
                Color:     fb.Red,
            },
        },
    }
    
    // Run the dashboard
    display.RunDashboard(dashboard)
}

Key Features Across All Concepts

  • Auto-detection: Automatically finds and configures the first available framebuffer
  • Resolution independence: Layouts adapt to the detected resolution
  • Font management: Easy font loading and sizing
  • Color utilities: Named colors, RGB, gradients, and conditional coloring
  • Common widgets: Progress bars, meters, graphs, tables, etc.
  • Refresh control: Configurable update intervals or manual control
  • Error handling: Graceful fallbacks for missing fonts, permissions, etc.

Design Considerations

Each approach offers different benefits:

  1. Builder Pattern: Familiar to Go developers, good for static layouts
  2. Declarative: Clean separation of data and presentation, easy to test
  3. Immediate Mode: Simple and direct, good for dynamic content
  4. Widget-based: Highest level abstraction, fastest to build common dashboards

The final API could combine elements from multiple approaches, such as using the widget system from Concept 4 with the immediate mode drawing primitives from Concept 3 for custom widgets.