feat: independent fullscreen kiosk display foundation (#1)
Kiosk process owns its display lifecycle: waits for X11, keeps the fullscreen black canvas below video, stops deterministically on signal. Supervisor starts it before video, restarts only on process death, and shuts the whole process group down; X11 downtime never restarts the supervisor. Per-target opener/template abstractions for future MIPI.
This commit is contained in:
@@ -6,8 +6,18 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// DisplayConfig configures the independent kiosk canvas process.
|
||||
type DisplayConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Command string `yaml:"command"`
|
||||
User string `yaml:"user"`
|
||||
Display string `yaml:"display"`
|
||||
Xauthority string `yaml:"xauthority"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Serial struct {
|
||||
Display DisplayConfig `yaml:"display"`
|
||||
Serial struct {
|
||||
Path string `yaml:"path"`
|
||||
} `yaml:"serial"`
|
||||
Video struct {
|
||||
@@ -24,6 +34,7 @@ type Config struct {
|
||||
|
||||
func Default() Config {
|
||||
var c Config
|
||||
c.Display = DefaultDisplay()
|
||||
c.Serial.Path = "/dev/ttyS0"
|
||||
c.Video.Enabled = true
|
||||
c.Video.AuxIndex = 5 // AUX2, zero-based
|
||||
@@ -33,6 +44,18 @@ func Default() Config {
|
||||
return c
|
||||
}
|
||||
|
||||
// DefaultDisplay starts the kiosk canvas before video on the desktop X
|
||||
// server owned by the unprivileged display user.
|
||||
func DefaultDisplay() DisplayConfig {
|
||||
return DisplayConfig{
|
||||
Enabled: true,
|
||||
Command: "/usr/local/bin/groundstation-display",
|
||||
User: "orangepi",
|
||||
Display: ":0",
|
||||
Xauthority: "/home/orangepi/.Xauthority",
|
||||
}
|
||||
}
|
||||
|
||||
func Load(path string) (Config, error) {
|
||||
config := Default()
|
||||
data, err := os.ReadFile(path)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultConfigStartsKioskDisplay(t *testing.T) {
|
||||
cfg := Default()
|
||||
|
||||
if !cfg.Display.Enabled {
|
||||
t.Fatal("kiosk display must be enabled by default")
|
||||
}
|
||||
if cfg.Display.Command != "/usr/local/bin/groundstation-display" {
|
||||
t.Fatalf("command=%q", cfg.Display.Command)
|
||||
}
|
||||
if cfg.Display.User != "orangepi" {
|
||||
t.Fatalf("user=%q", cfg.Display.User)
|
||||
}
|
||||
if cfg.Display.Display != ":0" {
|
||||
t.Fatalf("display=%q want :0", cfg.Display.Display)
|
||||
}
|
||||
if cfg.Display.Xauthority != "/home/orangepi/.Xauthority" {
|
||||
t.Fatalf("xauthority=%q", cfg.Display.Xauthority)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadOverridesDisplaySettings(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "config.yml")
|
||||
body := "display:\n enabled: false\n command: /bin/kiosk\n"
|
||||
if err := os.WriteFile(path, []byte(body), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cfg, err := Load(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if cfg.Display.Enabled {
|
||||
t.Fatal("display override must disable the kiosk")
|
||||
}
|
||||
if cfg.Display.Command != "/bin/kiosk" {
|
||||
t.Fatalf("command=%q want /bin/kiosk", cfg.Display.Command)
|
||||
}
|
||||
if cfg.Display.Display != ":0" {
|
||||
t.Fatalf("display=%q want default :0", cfg.Display.Display)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user