|
1 # |
|
2 # Wake/sleep detection for scheduling adjustments |
|
3 # |
|
4 |
|
5 import Foundation |
|
6 import AppKit |
|
7 import platform |
|
8 import time |
|
9 import threading |
|
10 import weakref |
|
11 import datetime |
|
12 |
|
13 from . import loggers |
|
14 |
|
15 logger=loggers.get(__name__) |
|
16 |
|
17 _dreamtime_monitor=None |
|
18 |
|
19 # |
|
20 # Support classes for dealing with different times |
|
21 # |
|
22 |
|
23 # Return difference (delay) of "dreamtime" to monotonic time |
|
24 def dreamtime_difference(): |
|
25 if _dreamtime_monitor: |
|
26 return _dreamtime_monitor.diff() |
|
27 else: |
|
28 return time.monotonic() |
|
29 |
|
30 # Return "dreamtime" |
|
31 def dreamtime(): |
|
32 return max(0, time.monotonic()-dreamtime_difference()) |
|
33 |
|
34 class Time: |
|
35 def realtime(self): |
|
36 raise NotImplementedError |
|
37 |
|
38 def monotonic(self): |
|
39 raise NotImplementedError |
|
40 |
|
41 @staticmethod |
|
42 def _now(): |
|
43 raise NotImplementedError |
|
44 |
|
45 @classmethod |
|
46 def now(cls): |
|
47 return cls(cls._now()) |
|
48 |
|
49 @classmethod |
|
50 def from_realtime(cls, realtime): |
|
51 return cls(realtime-time.time()+cls._now()) |
|
52 |
|
53 @classmethod |
|
54 def from_monotonic(cls, monotonic): |
|
55 return cls(monotonic-time.monotonic()+cls._now()) |
|
56 |
|
57 @classmethod |
|
58 def after(cls, seconds): |
|
59 return cls(cls._now()+seconds) |
|
60 |
|
61 def datetime(self): |
|
62 return datetime.datetime.fromtimestamp(self.realtime()) |
|
63 |
|
64 def seconds_to(self): |
|
65 return self._value-self._now() |
|
66 |
|
67 def __lt__(self, other): |
|
68 return self.monotonic() < other.monotonic() |
|
69 |
|
70 def __gt__(self, other): |
|
71 return self.monotonic() > other.monotonic() |
|
72 |
|
73 def __le__(self, other): |
|
74 return self.monotonic() <= other.monotonic() |
|
75 |
|
76 def __ge__(self, other): |
|
77 return self.monotonic() >= other.monotonic() |
|
78 |
|
79 def __eq__(self, other): |
|
80 return self.monotonic() == other.realtime() |
|
81 |
|
82 class RealTime(Time): |
|
83 def __init__(self, when): |
|
84 self._value=when |
|
85 |
|
86 def realtime(self): |
|
87 return self._value |
|
88 |
|
89 def monotonic(self): |
|
90 return self._value+(time.monotonic()-time.time()) |
|
91 |
|
92 @staticmethod |
|
93 def _now(): |
|
94 return time.time() |
|
95 |
|
96 class MonotonicTime(Time): |
|
97 def __init__(self, when): |
|
98 self._value=when |
|
99 |
|
100 def realtime(self): |
|
101 return self._value+(time.time()-time.monotonic()) |
|
102 |
|
103 def monotonic(self): |
|
104 return self._value |
|
105 |
|
106 @staticmethod |
|
107 def _now(): |
|
108 return time.monotonic() |
|
109 |
|
110 class DreamTime(Time): |
|
111 def __init__(self, when): |
|
112 self._value=when |
|
113 |
|
114 def realtime(self): |
|
115 return self._value+(time.time()-dreamtime()) |
|
116 |
|
117 # Important: monotonic is "static" within a wakeup period |
|
118 # and does not need to call time.monotonic(), as it gets compared |
|
119 # to a specific time.monotonic() realisation |
|
120 def monotonic(self): |
|
121 return self._value+dreamtime_difference() |
|
122 |
|
123 @staticmethod |
|
124 def _now(): |
|
125 return dreamtime() |
|
126 |
|
127 # |
|
128 # Wake up / sleep handling |
|
129 # |
|
130 |
|
131 class SleepHandler(Foundation.NSObject): |
|
132 """ Handle wake/sleep notifications """ |
|
133 |
|
134 def init(self): |
|
135 self.__sleeptime=None |
|
136 self.__slept=0 |
|
137 self.__epoch=time.monotonic() |
|
138 self.__lock=threading.Lock() |
|
139 self.__callbacks=weakref.WeakKeyDictionary() |
|
140 |
|
141 return self |
|
142 |
|
143 def handleSleepNotification_(self, aNotification): |
|
144 logger.info("System going to sleep") |
|
145 now=time.monotonic() |
|
146 with self.__lock: |
|
147 self.__sleeptime=now |
|
148 |
|
149 def handleWakeNotification_(self, aNotification): |
|
150 logger.info("System waking up from sleep") |
|
151 try: |
|
152 now=time.monotonic() |
|
153 with self.__lock: |
|
154 if self.__sleeptime: |
|
155 slept=max(0, now-self.__sleeptime) |
|
156 logger.info("Slept %f seconds" % slept) |
|
157 self.__slept=self.__slept+slept |
|
158 self.__sleeptime=None |
|
159 callbacks=self.__callbacks.copy() |
|
160 except: |
|
161 logger.exception("Bug in wakeup handler") |
|
162 |
|
163 for callback in callbacks.values(): |
|
164 try: |
|
165 callback() |
|
166 except Exception: |
|
167 logger.exception("Error in wake notification callback") |
|
168 |
|
169 # Return difference to time.monotonic() |
|
170 def diff(self): |
|
171 with self.__lock: |
|
172 diff=self.__epoch+self.__slept |
|
173 return diff |
|
174 |
|
175 # Weirdo (Py)ObjC naming to stop it form choking up |
|
176 def addForObj_aCallback_(self, obj, callback): |
|
177 with self.__lock: |
|
178 self.__callbacks[obj]=callback |
|
179 |
|
180 # obj is to use a a key in a weak key dictionary |
|
181 def add_callback(obj, callback): |
|
182 global _dreamtime_monitor |
|
183 |
|
184 monitor=_dreamtime_monitor |
|
185 if not monitor: |
|
186 raise Exception("Dreamtime monitor not started") |
|
187 else: |
|
188 monitor.addForObj_aCallback_(obj, callback) |
|
189 |
|
190 def start_monitoring(): |
|
191 global _dreamtime_monitor |
|
192 |
|
193 if platform.system()=='Darwin': |
|
194 logger.debug("Starting to monitor system sleep") |
|
195 workspace = AppKit.NSWorkspace.sharedWorkspace() |
|
196 notification_center = workspace.notificationCenter() |
|
197 _dreamtime_monitor = SleepHandler.new() |
|
198 |
|
199 notification_center.addObserver_selector_name_object_( |
|
200 _dreamtime_monitor, |
|
201 "handleSleepNotification:", |
|
202 AppKit.NSWorkspaceWillSleepNotification, |
|
203 None) |
|
204 |
|
205 notification_center.addObserver_selector_name_object_( |
|
206 _dreamtime_monitor, |
|
207 "handleWakeNotification:", |
|
208 AppKit.NSWorkspaceDidWakeNotification, |
|
209 None) |
|
210 else: |
|
211 logger.warning(("No system sleep monitor implemented for '%s'" |
|
212 % platform.system())) |
|
213 |