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