// Package displaycontroller steuert das physische Display per xset DPMS. package displaycontroller import ( "fmt" "log/slog" "os/exec" "sync" ) // Controller führt xset-Befehle aus und verfolgt den letzten Display-Zustand. // onStateChange wird nach jeder erfolgreichen Ausführung mit dem neuen Zustand // aufgerufen (z. B. um ihn per MQTT zu publizieren). Darf nil sein. type Controller struct { display string screenSlug string onStateChange func(screenSlug, state string) mu sync.Mutex currentState string } // New erstellt einen Controller. display ist der Wert der DISPLAY-Env-Var (z. B. ":0"). func New(display, screenSlug string, onStateChange func(screenSlug, state string)) *Controller { return &Controller{ display: display, screenSlug: screenSlug, onStateChange: onStateChange, currentState: "unknown", } } // Execute führt eine Display-Aktion aus. Bekannte Aktionen: "display_on", "display_off". func (c *Controller) Execute(action string) { switch action { case "display_on": if err := c.runXset("on"); err != nil { slog.Error("display_on failed", "err", err) return } c.setState("on") case "display_off": if err := c.runXset("off"); err != nil { slog.Error("display_off failed", "err", err) return } c.setState("off") default: slog.Warn("unknown display action", "action", action) } } // State gibt den zuletzt gesetzten Display-Zustand zurück: "on", "off" oder "unknown". func (c *Controller) State() string { c.mu.Lock() defer c.mu.Unlock() return c.currentState } func (c *Controller) runXset(arg string) error { out, err := exec.Command("xset", "-display", c.display, "dpms", "force", arg).CombinedOutput() if err != nil { return fmt.Errorf("xset dpms force %s: %w (output: %s)", arg, err, out) } return nil } func (c *Controller) setState(state string) { c.mu.Lock() c.currentState = state c.mu.Unlock() if c.onStateChange != nil { go c.onStateChange(c.screenSlug, state) } }