morz-infoboard/player/agent/internal/displaycontroller/controller.go
2026-03-26 23:07:14 +01:00

74 lines
2 KiB
Go

// 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)
}
}