1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00
Files
DankMaterialShell/core/internal/server/freedesktop/screensaver.go
bbedward 848991cf5b idle: implement screensaver interface
- Mainly used to create the idle inhibitor when an app requests
  screensaver inhibit
2025-12-14 16:49:59 -05:00

251 lines
6.5 KiB
Go

package freedesktop
import (
"path/filepath"
"strings"
"sync/atomic"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5/introspect"
)
type screensaverHandler struct {
manager *Manager
}
func (m *Manager) initializeScreensaver() error {
if m.sessionConn == nil {
m.stateMutex.Lock()
m.state.Screensaver.Available = false
m.stateMutex.Unlock()
return nil
}
reply, err := m.sessionConn.RequestName(dbusScreensaverName, dbus.NameFlagDoNotQueue)
if err != nil {
log.Warnf("Failed to request screensaver name: %v", err)
m.stateMutex.Lock()
m.state.Screensaver.Available = false
m.stateMutex.Unlock()
return nil
}
if reply != dbus.RequestNameReplyPrimaryOwner {
log.Warnf("Screensaver name already owned by another process")
m.stateMutex.Lock()
m.state.Screensaver.Available = false
m.stateMutex.Unlock()
return nil
}
handler := &screensaverHandler{manager: m}
if err := m.sessionConn.Export(handler, dbusScreensaverPath, dbusScreensaverInterface); err != nil {
log.Warnf("Failed to export screensaver on %s: %v", dbusScreensaverPath, err)
return nil
}
if err := m.sessionConn.Export(handler, dbusScreensaverPath2, dbusScreensaverInterface); err != nil {
log.Warnf("Failed to export screensaver on %s: %v", dbusScreensaverPath2, err)
return nil
}
introNode := &introspect.Node{
Name: dbusScreensaverPath,
Interfaces: []introspect.Interface{
introspect.IntrospectData,
{Name: dbusScreensaverInterface},
},
}
if err := m.sessionConn.Export(introspect.NewIntrospectable(introNode), dbusScreensaverPath, "org.freedesktop.DBus.Introspectable"); err != nil {
log.Warnf("Failed to export introspectable on %s: %v", dbusScreensaverPath, err)
}
introNode2 := &introspect.Node{
Name: dbusScreensaverPath2,
Interfaces: []introspect.Interface{
introspect.IntrospectData,
{Name: dbusScreensaverInterface},
},
}
if err := m.sessionConn.Export(introspect.NewIntrospectable(introNode2), dbusScreensaverPath2, "org.freedesktop.DBus.Introspectable"); err != nil {
log.Warnf("Failed to export introspectable on %s: %v", dbusScreensaverPath2, err)
}
go m.watchPeerDisconnects()
m.stateMutex.Lock()
m.state.Screensaver.Available = true
m.state.Screensaver.Inhibited = false
m.state.Screensaver.Inhibitors = []ScreensaverInhibitor{}
m.stateMutex.Unlock()
log.Info("Screensaver inhibit listener initialized")
return nil
}
func (h *screensaverHandler) Inhibit(sender dbus.Sender, appName, reason string) (uint32, *dbus.Error) {
if appName == "" {
return 0, dbus.NewError("org.freedesktop.DBus.Error.InvalidArgs", []any{"application name required"})
}
if reason == "" {
return 0, dbus.NewError("org.freedesktop.DBus.Error.InvalidArgs", []any{"reason required"})
}
if strings.Contains(strings.ToLower(reason), "audio") && !strings.Contains(strings.ToLower(reason), "video") {
log.Debugf("Ignoring audio-only inhibit from %s: %s", appName, reason)
return 0, nil
}
if idx := strings.LastIndex(appName, "/"); idx != -1 && idx < len(appName)-1 {
appName = appName[idx+1:]
}
appName = filepath.Base(appName)
cookie := atomic.AddUint32(&h.manager.screensaverCookieCounter, 1)
inhibitor := ScreensaverInhibitor{
Cookie: cookie,
AppName: appName,
Reason: reason,
Peer: string(sender),
StartTime: time.Now().Unix(),
}
h.manager.stateMutex.Lock()
h.manager.state.Screensaver.Inhibitors = append(h.manager.state.Screensaver.Inhibitors, inhibitor)
h.manager.state.Screensaver.Inhibited = len(h.manager.state.Screensaver.Inhibitors) > 0
h.manager.stateMutex.Unlock()
log.Infof("Screensaver inhibited by %s (%s): %s -> cookie %08X", appName, sender, reason, cookie)
h.manager.NotifyScreensaverSubscribers()
return cookie, nil
}
func (h *screensaverHandler) UnInhibit(sender dbus.Sender, cookie uint32) *dbus.Error {
h.manager.stateMutex.Lock()
defer h.manager.stateMutex.Unlock()
found := false
inhibitors := h.manager.state.Screensaver.Inhibitors
for i, inh := range inhibitors {
if inh.Cookie != cookie {
continue
}
log.Infof("Screensaver uninhibited by %s (%s) cookie %08X", inh.AppName, sender, cookie)
h.manager.state.Screensaver.Inhibitors = append(inhibitors[:i], inhibitors[i+1:]...)
found = true
break
}
if !found {
log.Debugf("UnInhibit: no match for cookie %08X", cookie)
return nil
}
h.manager.state.Screensaver.Inhibited = len(h.manager.state.Screensaver.Inhibitors) > 0
go h.manager.NotifyScreensaverSubscribers()
return nil
}
func (m *Manager) watchPeerDisconnects() {
if m.sessionConn == nil {
return
}
if err := m.sessionConn.AddMatchSignal(
dbus.WithMatchInterface("org.freedesktop.DBus"),
dbus.WithMatchMember("NameOwnerChanged"),
); err != nil {
log.Warnf("Failed to watch peer disconnects: %v", err)
return
}
signals := make(chan *dbus.Signal, 64)
m.sessionConn.Signal(signals)
for sig := range signals {
if sig.Name != "org.freedesktop.DBus.NameOwnerChanged" {
continue
}
if len(sig.Body) < 3 {
continue
}
name, ok1 := sig.Body[0].(string)
newOwner, ok2 := sig.Body[2].(string)
if !ok1 || !ok2 {
continue
}
if newOwner != "" {
continue
}
m.removeInhibitorsByPeer(name)
}
}
func (m *Manager) removeInhibitorsByPeer(peer string) {
m.stateMutex.Lock()
defer m.stateMutex.Unlock()
var remaining []ScreensaverInhibitor
var removed []ScreensaverInhibitor
for _, inh := range m.state.Screensaver.Inhibitors {
if inh.Peer == peer {
removed = append(removed, inh)
continue
}
remaining = append(remaining, inh)
}
if len(removed) == 0 {
return
}
for _, inh := range removed {
log.Infof("Screensaver: peer %s died, removing inhibitor from %s (cookie %08X)", peer, inh.AppName, inh.Cookie)
}
m.state.Screensaver.Inhibitors = remaining
m.state.Screensaver.Inhibited = len(remaining) > 0
go m.NotifyScreensaverSubscribers()
}
func (m *Manager) GetScreensaverState() ScreensaverState {
m.stateMutex.RLock()
defer m.stateMutex.RUnlock()
return m.state.Screensaver
}
func (m *Manager) SubscribeScreensaver(id string) chan ScreensaverState {
ch := make(chan ScreensaverState, 64)
m.screensaverSubscribers.Store(id, ch)
return ch
}
func (m *Manager) UnsubscribeScreensaver(id string) {
if val, ok := m.screensaverSubscribers.LoadAndDelete(id); ok {
close(val)
}
}
func (m *Manager) NotifyScreensaverSubscribers() {
state := m.GetScreensaverState()
m.screensaverSubscribers.Range(func(key string, ch chan ScreensaverState) bool {
select {
case ch <- state:
default:
}
return true
})
}