package monitor import ( "context" "log" "github.com/godbus/dbus/v5" "led-controller/config" ) type Event int const ( DisplayOn Event = iota DisplayOff ) type Monitor struct { cfg *config.Config events chan<- Event } func New(cfg *config.Config, events chan<- Event) (*Monitor, error) { return &Monitor{cfg: cfg, events: events}, nil } func (m *Monitor) Run(ctx context.Context) { method := m.cfg.Monitor.Method if method == "screensaver" || method == "all" { go m.watchScreenSaver(ctx) } if method == "logind" || method == "all" { go m.watchLogind(ctx) } <-ctx.Done() } // watchScreenSaver listens on the session bus for org.freedesktop.ScreenSaver.ActiveChanged // This covers GNOME, KDE, XFCE, and other freedesktop-compliant DEs. func (m *Monitor) watchScreenSaver(ctx context.Context) { conn, err := dbus.ConnectSessionBus() if err != nil { log.Printf("screensaver monitor: cannot connect to session bus: %v", err) return } defer conn.Close() // Match both the freedesktop and GNOME screensaver interfaces rules := []string{ "type='signal',interface='org.freedesktop.ScreenSaver',member='ActiveChanged'", "type='signal',interface='org.gnome.ScreenSaver',member='ActiveChanged'", } for _, rule := range rules { call := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) if call.Err != nil { log.Printf("screensaver monitor: AddMatch failed for %q: %v", rule, call.Err) } } sigCh := make(chan *dbus.Signal, 16) conn.Signal(sigCh) log.Println("screensaver monitor: listening for ActiveChanged signals") for { select { case <-ctx.Done(): return case sig := <-sigCh: if sig == nil { return } if sig.Name == "org.freedesktop.ScreenSaver.ActiveChanged" || sig.Name == "org.gnome.ScreenSaver.ActiveChanged" { if len(sig.Body) < 1 { continue } active, ok := sig.Body[0].(bool) if !ok { continue } if active { log.Println("screensaver monitor: screen locked/blanked") m.events <- DisplayOff } else { log.Println("screensaver monitor: screen unlocked/unblanked") m.events <- DisplayOn } } } } } // watchLogind listens on the system bus for org.freedesktop.login1.Manager.PrepareForSleep. // This catches system suspend/resume events. func (m *Monitor) watchLogind(ctx context.Context) { conn, err := dbus.ConnectSystemBus() if err != nil { log.Printf("logind monitor: cannot connect to system bus: %v", err) return } defer conn.Close() rule := "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'" call := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) if call.Err != nil { log.Printf("logind monitor: AddMatch failed: %v", call.Err) return } sigCh := make(chan *dbus.Signal, 16) conn.Signal(sigCh) log.Println("logind monitor: listening for PrepareForSleep signals") for { select { case <-ctx.Done(): return case sig := <-sigCh: if sig == nil { return } if sig.Name == "org.freedesktop.login1.Manager.PrepareForSleep" { if len(sig.Body) < 1 { continue } preparing, ok := sig.Body[0].(bool) if !ok { continue } if preparing { log.Println("logind monitor: system preparing to sleep") m.events <- DisplayOff } else { log.Println("logind monitor: system woke up") m.events <- DisplayOn } } } } }