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