-
Notifications
You must be signed in to change notification settings - Fork 21
/
sleepcallback.go
183 lines (157 loc) · 5.71 KB
/
sleepcallback.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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package main
/*
#cgo LDFLAGS: -framework IOKit
#include "hook.h"
*/
import "C"
import (
"fmt"
"time"
"github.com/sirupsen/logrus"
"github.com/charlie0129/batt/smc"
)
var (
preSleepLoopDelaySeconds = 300
postSleepLoopDelaySeconds = 120
)
var (
lastWakeTime = time.Now()
lastSleepTime = time.Now().Add(-time.Duration(preSleepLoopDelaySeconds) * time.Second)
)
//export canSystemSleepCallback
func canSystemSleepCallback() {
/* Idle sleep is about to kick in. This message will not be sent for forced sleep.
Applications have a chance to prevent sleep by calling IOCancelPowerChange.
Most applications should not prevent idle sleep.
Power Management waits up to 30 seconds for you to either allow or deny idle
sleep. If you don't acknowledge this power change by calling either
IOAllowPowerChange or IOCancelPowerChange, the system will wait 30
seconds then go to sleep.
*/
logrus.Debugln("received kIOMessageCanSystemSleep notification, idle sleep is about to kick in")
if !config.PreventIdleSleep {
logrus.Debugln("PreventIdleSleep is disabled, allow idle sleep")
C.AllowPowerChange()
return
}
// We won't allow idle sleep if the system has just waked up,
// because there may still be a maintain loop waiting (see the wg.Wait() in loop.go).
// So decisions may not be made yet. We need to wait.
// Actually, we wait the larger of preSleepLoopDelaySeconds and postSleepLoopDelaySeconds. This is not implemented yet.
if timeAfterWokenUp := time.Since(lastWakeTime); timeAfterWokenUp < time.Duration(preSleepLoopDelaySeconds)*time.Second {
logrus.Debugf("system has just waked up (%fs ago), deny idle sleep", timeAfterWokenUp.Seconds())
C.CancelPowerChange()
return
}
// Run a loop immediately to update `maintainedChargingInProgress` variable.
maintainLoopForced()
if maintainedChargingInProgress {
logrus.Debugln("maintained charging is in progress, deny idle sleep")
C.CancelPowerChange()
return
}
logrus.Debugln("no maintained charging is in progress, allow idle sleep")
C.AllowPowerChange()
}
//export systemWillSleepCallback
func systemWillSleepCallback() {
/* The system WILL go to sleep. If you do not call IOAllowPowerChange or
IOCancelPowerChange to acknowledge this message, sleep will be
delayed by 30 seconds.
NOTE: If you call IOCancelPowerChange to deny sleep it returns
kIOReturnSuccess, however the system WILL still go to sleep.
*/
logrus.Debugln("received kIOMessageSystemWillSleep notification, system will go to sleep")
lastSleepTime = time.Now()
if !config.DisableChargingPreSleep {
logrus.Debugln("DisableChargingPreSleep is disabled, allow sleep")
C.AllowPowerChange()
return
}
// If charge limit is enabled (limit<100), no matter if maintained charging is in progress,
// we disable charging just before sleep.
// Previously, we only disabled charging if maintained charging was in progress. But we find
// out this is not required, because if there is no maintained charging in progress, disabling
// charging will not cause any problem.
// By always disabling charging before sleep (if charge limit is enabled), we can prevent
// some rare cases.
if config.Limit < 100 {
logrus.Infof("charge limit is enabled, disabling charging, delaying next loop by %d seconds, and allowing sleep", preSleepLoopDelaySeconds)
// Delay next loop to prevent charging to be re-enabled after we disabled it.
// macOS will wait 30s before going to sleep, there is a chance that a maintain loop is
// executed during that time and it enables charging.
// So we delay more than that, just to be sure.
// No need to prevent duplicated runs.
wg.Add(1)
go func() {
// Use sleep instead of time.After because when the computer sleeps, we
// actually want the sleep to prolong as well.
sleep(preSleepLoopDelaySeconds)
wg.Done()
}()
err := smcConn.DisableCharging()
if err != nil {
logrus.Errorf("DisableCharging failed: %v", err)
return
}
err = smcConn.SetMagSafeLedState(smc.LEDOff)
if err != nil {
logrus.Errorf("SetMagSafeLedState failed: %v", err)
}
} else {
logrus.Debugln("no maintained charging is in progress, allow sleep")
}
C.AllowPowerChange()
}
//export systemWillPowerOnCallback
func systemWillPowerOnCallback() {
// System has started the wake-up process...
}
//export systemHasPoweredOnCallback
func systemHasPoweredOnCallback() {
// System has finished waking up...
logrus.Debugln("received kIOMessageSystemHasPoweredOn notification, system has finished waking up")
lastWakeTime = time.Now()
if config.Limit < 100 {
logrus.Debugf("delaying next loop by %d seconds", postSleepLoopDelaySeconds)
wg.Add(1)
go func() {
// Use sleep instead of time.After because when the computer sleeps, we
// actually want the sleep to prolong as well.
sleep(postSleepLoopDelaySeconds)
if config.DisableChargingPreSleep && config.ControlMagSafeLED {
err := smcConn.SetMagSafeLedState(smc.LEDOff)
if err != nil {
logrus.Errorf("SetMagSafeLedState failed: %v", err)
}
}
wg.Done()
}()
}
}
// Use sleep instead of time.After or time.Sleep because when the computer sleeps, we
// actually want the sleep to prolong as well.
func sleep(seconds int) {
tl := 250 // ms
t := time.NewTicker(time.Duration(tl) * time.Millisecond)
ticksWanted := seconds * 1000 / tl
ticksElapsed := 0
for range t.C {
ticksElapsed++
if ticksElapsed > ticksWanted {
break
}
}
t.Stop()
}
func listenNotifications() error {
logrus.Info("registered and listening system sleep notifications")
if int(C.ListenNotifications()) != 0 {
return fmt.Errorf("IORegisterForSystemPower failed")
}
return nil
}
func stopListeningNotifications() {
C.StopListeningNotifications()
logrus.Info("stopped listening system sleep notifications")
}