feat(agent): displaycontroller Package (xset DPMS)
This commit is contained in:
parent
f985a99ea1
commit
4ef16048ad
2 changed files with 92 additions and 0 deletions
74
player/agent/internal/displaycontroller/controller.go
Normal file
74
player/agent/internal/displaycontroller/controller.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// 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)
|
||||
}
|
||||
}
|
||||
18
player/agent/internal/displaycontroller/controller_test.go
Normal file
18
player/agent/internal/displaycontroller/controller_test.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package displaycontroller
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNew_initialState(t *testing.T) {
|
||||
c := New(":0", "test-screen", nil)
|
||||
if got := c.State(); got != "unknown" {
|
||||
t.Fatalf("initial state = %q, want %q", got, "unknown")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecute_unknownAction(t *testing.T) {
|
||||
c := New(":0", "test-screen", nil)
|
||||
c.Execute("invalid_action")
|
||||
if got := c.State(); got != "unknown" {
|
||||
t.Fatalf("state after unknown action = %q, want %q", got, "unknown")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue