// Command groundstation-display renders the Ground Station kiosk layers. It // owns its display lifecycle independently of WFB, the transmitter and the // video launcher: it waits for its target to appear, redraws as needed, and // stops only when it receives SIGINT or SIGTERM. package main import ( "context" "flag" "fmt" "log" "os" "os/signal" "syscall" "git.piksar.eu/piotr/groundstation/internal/display" ) // buildTarget selects the target-specific layers. Each target owns its own // backend and layouts; there is no shared global template. The Viture target // builds two independent layers — the fullscreen fallback/menu canvas below // video and the transparent OSD overlay above it — wired to one shared video // presence signal: the sessions observe the video output lifecycle on the // display server and the templates draw or hide from it. WFB and video // decode never move into this process. func buildTarget(name string) ([]display.Layer, error) { switch name { case "x11": video := display.NewVideoPresence() return []display.Layer{ { Name: "fallback-canvas", Opener: display.X11Opener{Video: video}, Template: display.NewFallbackTemplate(video, "Brak połączenia WFB"), }, { Name: "osd-overlay", Opener: display.X11Opener{Video: video, Role: display.RoleAbove}, Template: display.NewOSDTemplate(video), }, }, nil default: return nil, fmt.Errorf("unknown display target %q (supported: x11)", name) } } // wireDiagnostics gives every layer the shared structured reconnect hook so // each layer reports its own reconnect boundaries, identified by target and // layer name. func wireDiagnostics(target string, layers []display.Layer) { for i := range layers { layers[i].Options = display.Options{ Target: target, Layer: layers[i].Name, Diagnostic: func(d display.Diagnostic) { log.Printf("display reconnect: %s", formatDiagnostic(d)) }, } } } // formatDiagnostic renders one reconnect diagnostic as key=value fields so // reconnects stay greppable and machine-parseable in the journal. func formatDiagnostic(d display.Diagnostic) string { return fmt.Sprintf("target=%s layer=%s event=%s error=%q retry_delay=%s", d.Target, d.Layer, d.Event, d.Err, d.RetryDelay) } func main() { target := flag.String("target", "x11", "display target: x11 (Viture) today; mipi planned") flag.Parse() layers, err := buildTarget(*target) if err != nil { log.Fatal(err) } wireDiagnostics(*target, layers) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() // Temporary X11 unavailability never exits this process; every layer // keeps retrying internally until the context is cancelled, reporting // each reconnect boundary through the structured diagnostic hook. display.RunLayers(ctx, layers) }