-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpoll_dbus_linux.go
126 lines (111 loc) · 2.94 KB
/
poll_dbus_linux.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Copyright ©2024 Dan Kortschak. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"encoding/json"
"errors"
"sync"
"time"
"github.com/godbus/dbus/v5"
watcher "github.com/kortschak/dex/cmd/watcher/api"
)
func init() {
detailers[(&mutterDetailer{}).strategy()] = newMutterDetailer
}
func newMutterDetailer() (detailer, error) {
conn, err := dbus.ConnectSessionBus()
if err != nil {
return noDetails{}, err
}
return &mutterDetailer{conn: conn, last: time.Now()}, nil
}
type mutterDetailer struct {
mu sync.Mutex
conn *dbus.Conn
last time.Time
}
func (*mutterDetailer) strategy() string { return "gnome/mutter" }
func (d *mutterDetailer) Close() error {
d.mu.Lock()
var err error
if d.conn != nil {
err = d.conn.Close()
d.conn = nil
}
d.mu.Unlock()
return err
}
func (d *mutterDetailer) details() (watcher.Details, error) {
d.mu.Lock()
defer d.mu.Unlock()
if d.conn == nil {
return watcher.Details{}, errors.New("closed")
}
locked, errLocked := dbusCall[bool](d.conn,
"org.gnome.ScreenSaver",
"/org/gnome/ScreenSaver",
"org.gnome.ScreenSaver.GetActive",
)
if locked {
// Extensions are not available when locked, so
// don't try; just get idle time from Mutter.
last, errTime := d.mutterIdleMonitor()
if !last.IsZero() {
d.last = last
}
return watcher.Details{
LastInput: d.last,
Locked: true,
}, errors.Join(errLocked, errTime)
}
detMsg, errWindow := dbusCall[string](d.conn,
"org.gnome.Shell",
"/org/gnome/Shell/Extensions/UserActivity",
"org.gnome.Shell.Extensions.UserActivity.Details",
)
if errWindow != nil {
// If the user activity call failed, get last
// activity from Mutter. This will either be
// because of a race between locking and the
// user activity call, or not having the user
// activity extension installed. Assume the
// extension is installed, and so that the
// screen has been locked.
last, errTime := d.mutterIdleMonitor()
if !last.IsZero() {
d.last = last
}
return watcher.Details{
LastInput: d.last,
Locked: true,
}, errors.Join(errLocked, errWindow, errTime)
}
var det watcher.Details
errWindow = json.Unmarshal([]byte(detMsg), &det)
if !det.LastInput.IsZero() {
d.last = det.LastInput
}
det.Locked = false
return det, errors.Join(errLocked, errWindow)
}
func (d *mutterDetailer) mutterIdleMonitor() (time.Time, error) {
idle, err := dbusCall[int64](d.conn,
"org.gnome.Mutter.IdleMonitor",
"/org/gnome/Mutter/IdleMonitor/Core",
"org.gnome.Mutter.IdleMonitor.GetIdletime",
)
if idle < 0 {
return time.Time{}, err
}
return time.Now().Add(time.Duration(idle) * -time.Millisecond).Round(time.Second / 10), err
}
func dbusCall[T any](conn *dbus.Conn, dest, path, method string) (T, error) {
var v T
c := conn.Object(dest, dbus.ObjectPath(path)).Call(method, 0)
err := c.Store(&v)
if err != nil {
return v, err
}
return v, nil
}