Skip to content

Commit e422e3d

Browse files
committed
Reuse winpowrprof callback
1 parent fa81eab commit e422e3d

3 files changed

Lines changed: 102 additions & 47 deletions

File tree

common/winpowrprof/event.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const (
66
EVENT_RESUME_AUTOMATIC // Because the user is not present, most applications should do nothing.
77
)
88

9+
type EventCallback = func(event int)
10+
911
type EventListener interface {
1012
Start() error
1113
Close() error

common/winpowrprof/event_stub.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ import (
66
"os"
77
)
88

9-
func NewEventListener(callback func(event int)) (EventListener, error) {
9+
func NewEventListener(callback EventCallback) (EventListener, error) {
1010
return nil, os.ErrInvalid
1111
}

common/winpowrprof/event_windows.go

Lines changed: 99 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,81 +3,134 @@ package winpowrprof
33
// modify from https://github.com/golang/go/blob/b634f6fdcbebee23b7da709a243f3db217b64776/src/runtime/os_windows.go#L257
44

55
import (
6+
"sync"
67
"syscall"
78
"unsafe"
89

10+
"github.com/sagernet/sing/common/x/list"
11+
912
"golang.org/x/sys/windows"
1013
)
1114

15+
type powerEventListener struct {
16+
element *list.Element[EventCallback]
17+
}
18+
19+
func NewEventListener(callback EventCallback) (EventListener, error) {
20+
err := initCallback()
21+
if err != nil {
22+
return nil, err
23+
}
24+
access.Lock()
25+
defer access.Unlock()
26+
return &powerEventListener{
27+
element: callbackList.PushBack(callback),
28+
}, nil
29+
}
30+
31+
func (l *powerEventListener) Start() error {
32+
access.Lock()
33+
defer access.Unlock()
34+
if handle != 0 {
35+
return nil
36+
}
37+
return startListener()
38+
}
39+
40+
func (l *powerEventListener) Close() error {
41+
access.Lock()
42+
defer access.Unlock()
43+
if l.element != nil {
44+
callbackList.Remove(l.element)
45+
}
46+
if callbackList.Len() > 0 {
47+
return nil
48+
}
49+
return closeListener()
50+
}
51+
1252
var (
1353
modpowerprof = windows.NewLazySystemDLL("powrprof.dll")
1454
procPowerRegisterSuspendResumeNotification = modpowerprof.NewProc("PowerRegisterSuspendResumeNotification")
1555
procPowerUnregisterSuspendResumeNotification = modpowerprof.NewProc("PowerUnregisterSuspendResumeNotification")
1656
)
1757

18-
const (
19-
PBT_APMSUSPEND uint32 = 4
20-
PBT_APMRESUMESUSPEND uint32 = 7
21-
PBT_APMRESUMEAUTOMATIC uint32 = 18
22-
)
23-
24-
const (
25-
_DEVICE_NOTIFY_CALLBACK = 2
58+
var (
59+
access sync.Mutex
60+
callbackList list.List[EventCallback]
61+
initCallbackOnce sync.Once
62+
rawCallback uintptr
63+
handle uintptr
2664
)
2765

28-
type _DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS struct {
29-
callback uintptr
30-
context uintptr
31-
}
32-
33-
type eventListener struct {
34-
params _DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
35-
handle uintptr
36-
}
37-
38-
func NewEventListener(callback func(event int)) (EventListener, error) {
39-
if err := procPowerRegisterSuspendResumeNotification.Find(); err != nil {
40-
return nil, err // Running on Windows 7, where we don't need it anyway.
66+
func initCallback() error {
67+
err := procPowerRegisterSuspendResumeNotification.Find()
68+
if err != nil {
69+
return err // Running on Windows 7, where we don't need it anyway.
4170
}
42-
if err := procPowerUnregisterSuspendResumeNotification.Find(); err != nil {
43-
return nil, err // Running on Windows 7, where we don't need it anyway.
71+
err = procPowerUnregisterSuspendResumeNotification.Find()
72+
if err != nil {
73+
return err // Running on Windows 7, where we don't need it anyway.
4474
}
45-
46-
var fn interface{} = func(context uintptr, changeType uint32, setting uintptr) uintptr {
47-
switch changeType {
48-
case PBT_APMSUSPEND:
49-
callback(EVENT_SUSPEND)
50-
case PBT_APMRESUMESUSPEND:
51-
callback(EVENT_RESUME)
52-
case PBT_APMRESUMEAUTOMATIC:
53-
callback(EVENT_RESUME_AUTOMATIC)
54-
}
55-
return 0
56-
}
57-
return &eventListener{
58-
params: _DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS{
59-
callback: windows.NewCallback(fn),
60-
},
61-
}, nil
75+
initCallbackOnce.Do(func() {
76+
rawCallback = windows.NewCallback(func(context uintptr, changeType uint32, setting uintptr) uintptr {
77+
const (
78+
PBT_APMSUSPEND uint32 = 4
79+
PBT_APMRESUMESUSPEND uint32 = 7
80+
PBT_APMRESUMEAUTOMATIC uint32 = 18
81+
)
82+
var event int
83+
switch changeType {
84+
case PBT_APMSUSPEND:
85+
event = EVENT_SUSPEND
86+
case PBT_APMRESUMESUSPEND:
87+
event = EVENT_RESUME
88+
case PBT_APMRESUMEAUTOMATIC:
89+
event = EVENT_RESUME_AUTOMATIC
90+
default:
91+
return 0
92+
}
93+
access.Lock()
94+
callbacks := callbackList.Array()
95+
access.Unlock()
96+
for _, callback := range callbacks {
97+
callback(event)
98+
}
99+
return 0
100+
})
101+
})
102+
return nil
62103
}
63104

64-
func (l *eventListener) Start() error {
105+
func startListener() error {
106+
type DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS struct {
107+
callback uintptr
108+
context uintptr
109+
}
110+
const DEVICE_NOTIFY_CALLBACK = 2
111+
params := DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS{
112+
callback: rawCallback,
113+
}
65114
_, _, errno := syscall.SyscallN(
66115
procPowerRegisterSuspendResumeNotification.Addr(),
67-
_DEVICE_NOTIFY_CALLBACK,
68-
uintptr(unsafe.Pointer(&l.params)),
69-
uintptr(unsafe.Pointer(&l.handle)),
116+
DEVICE_NOTIFY_CALLBACK,
117+
uintptr(unsafe.Pointer(&params)),
118+
uintptr(unsafe.Pointer(&handle)),
70119
)
71120
if errno != 0 {
72121
return errno
73122
}
74123
return nil
75124
}
76125

77-
func (l *eventListener) Close() error {
78-
_, _, errno := syscall.SyscallN(procPowerUnregisterSuspendResumeNotification.Addr(), uintptr(unsafe.Pointer(&l.handle)))
126+
func closeListener() error {
127+
if handle == 0 {
128+
return nil
129+
}
130+
_, _, errno := syscall.SyscallN(procPowerUnregisterSuspendResumeNotification.Addr(), uintptr(unsafe.Pointer(&handle)))
79131
if errno != 0 {
80132
return errno
81133
}
134+
handle = 0
82135
return nil
83136
}

0 commit comments

Comments
 (0)