mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
idle: implement screensaver interface
- Mainly used to create the idle inhibitor when an app requests screensaver inhibit
This commit is contained in:
@@ -11,4 +11,9 @@ const (
|
||||
dbusPortalSettingsInterface = "org.freedesktop.portal.Settings"
|
||||
|
||||
dbusPropsInterface = "org.freedesktop.DBus.Properties"
|
||||
|
||||
dbusScreensaverName = "org.freedesktop.ScreenSaver"
|
||||
dbusScreensaverPath = "/ScreenSaver"
|
||||
dbusScreensaverPath2 = "/org/freedesktop/ScreenSaver"
|
||||
dbusScreensaverInterface = "org.freedesktop.ScreenSaver"
|
||||
)
|
||||
|
||||
@@ -22,8 +22,9 @@ func NewManager() (*Manager, error) {
|
||||
|
||||
m := &Manager{
|
||||
state: &FreedeskState{
|
||||
Accounts: AccountsState{},
|
||||
Settings: SettingsState{},
|
||||
Accounts: AccountsState{},
|
||||
Settings: SettingsState{},
|
||||
Screensaver: ScreensaverState{},
|
||||
},
|
||||
stateMutex: sync.RWMutex{},
|
||||
systemConn: systemConn,
|
||||
@@ -33,6 +34,7 @@ func NewManager() (*Manager, error) {
|
||||
|
||||
m.initializeAccounts()
|
||||
m.initializeSettings()
|
||||
m.initializeScreensaver()
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
250
core/internal/server/freedesktop/screensaver.go
Normal file
250
core/internal/server/freedesktop/screensaver.go
Normal file
@@ -0,0 +1,250 @@
|
||||
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
|
||||
})
|
||||
}
|
||||
@@ -29,18 +29,35 @@ type SettingsState struct {
|
||||
ColorScheme uint32 `json:"colorScheme"`
|
||||
}
|
||||
|
||||
type ScreensaverInhibitor struct {
|
||||
Cookie uint32 `json:"cookie"`
|
||||
AppName string `json:"appName"`
|
||||
Reason string `json:"reason"`
|
||||
Peer string `json:"peer"`
|
||||
StartTime int64 `json:"startTime"`
|
||||
}
|
||||
|
||||
type ScreensaverState struct {
|
||||
Available bool `json:"available"`
|
||||
Inhibited bool `json:"inhibited"`
|
||||
Inhibitors []ScreensaverInhibitor `json:"inhibitors"`
|
||||
}
|
||||
|
||||
type FreedeskState struct {
|
||||
Accounts AccountsState `json:"accounts"`
|
||||
Settings SettingsState `json:"settings"`
|
||||
Accounts AccountsState `json:"accounts"`
|
||||
Settings SettingsState `json:"settings"`
|
||||
Screensaver ScreensaverState `json:"screensaver"`
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
state *FreedeskState
|
||||
stateMutex sync.RWMutex
|
||||
systemConn *dbus.Conn
|
||||
sessionConn *dbus.Conn
|
||||
accountsObj dbus.BusObject
|
||||
settingsObj dbus.BusObject
|
||||
currentUID uint64
|
||||
subscribers syncmap.Map[string, chan FreedeskState]
|
||||
state *FreedeskState
|
||||
stateMutex sync.RWMutex
|
||||
systemConn *dbus.Conn
|
||||
sessionConn *dbus.Conn
|
||||
accountsObj dbus.BusObject
|
||||
settingsObj dbus.BusObject
|
||||
currentUID uint64
|
||||
subscribers syncmap.Map[string, chan FreedeskState]
|
||||
screensaverSubscribers syncmap.Map[string, chan ScreensaverState]
|
||||
screensaverCookieCounter uint32
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||
)
|
||||
|
||||
const APIVersion = 23
|
||||
const APIVersion = 24
|
||||
|
||||
var CLIVersion = "dev"
|
||||
|
||||
@@ -702,6 +702,38 @@ func handleSubscribe(conn net.Conn, req models.Request) {
|
||||
}()
|
||||
}
|
||||
|
||||
if shouldSubscribe("freedesktop.screensaver") && freedesktopManager != nil {
|
||||
wg.Add(1)
|
||||
screensaverChan := freedesktopManager.SubscribeScreensaver(clientID + "-screensaver")
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer freedesktopManager.UnsubscribeScreensaver(clientID + "-screensaver")
|
||||
|
||||
initialState := freedesktopManager.GetScreensaverState()
|
||||
select {
|
||||
case eventChan <- ServiceEvent{Service: "freedesktop.screensaver", Data: initialState}:
|
||||
case <-stopChan:
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case state, ok := <-screensaverChan:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case eventChan <- ServiceEvent{Service: "freedesktop.screensaver", Data: state}:
|
||||
case <-stopChan:
|
||||
return
|
||||
}
|
||||
case <-stopChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if shouldSubscribe("gamma") && waylandManager != nil {
|
||||
wg.Add(1)
|
||||
waylandChan := waylandManager.Subscribe(clientID + "-gamma")
|
||||
|
||||
Reference in New Issue
Block a user