--- a/sleep.py Sat Jan 27 12:19:39 2018 +0000 +++ b/sleep.py Sun Jan 28 00:11:18 2018 +0000 @@ -4,34 +4,199 @@ import Foundation import AppKit +import platform +import time +import threading import borgend -import platform +import weakref +import datetime logger=borgend.logger.getChild(__name__) +_dreamtime_monitor=[None] + +# +# Support classes for dealing with different times +# + +def dreamtime(): + if _dreamtime_monitor[0]: + return max(0, time.monotonic()-_dreamtime_monitor[0].diff()) + else: + return time.monotonic() + +class Time: + def realtime(self): + raise NotImplementedError + + def monotonic(self): + raise NotImplementedError + + @staticmethod + def _now(): + raise NotImplementedError + + @classmethod + def now(cls): + return cls(cls._now()) + + @classmethod + def from_realtime(cls, realtime): + return cls(realtime-time.time()+cls._now()) + + @classmethod + def from_monotonic(cls, monotonic): + return cls(monotonic-time.monotonic()+cls._now()) + + @classmethod + def after(cls, seconds): + return cls(cls._now()+seconds) + + def datetime(self): + return datetime.datetime.fromtimestamp(self.realtime()) + + def seconds_to(self): + return self._value-self._now() + + def __lt__(self, other): + return self.monotonic() < other.monotonic() + + def __gt__(self, other): + return self.monotonic() > other.monotonic() + + def __le__(self, other): + return self.monotonic() <= other.monotonic() + + def __ge__(self, other): + return self.monotonic() >= other.monotonic() + + def __eq__(self, other): + return self.monotonic() == other.realtime() + +class RealTime(Time): + def __init__(self, when): + self._value=when + + def realtime(self): + return self._value + + def monotonic(self): + return self._value+(time.monotonic()-time.time()) + + @staticmethod + def _now(): + return time.time() + +class MonotonicTime(Time): + def __init__(self, when): + self._value=when + + def realtime(self): + return self._value+(time.time()-time.monotonic()) + + def monotonic(self): + return self._value + + @staticmethod + def _now(): + return time.monotonic() + +class DreamTime(Time): + def __init__(self, when): + self._value=when + + def realtime(self): + return self._value+(time.time()-dreamtime()) + + def monotonic(self): + return self._value+(time.monotonic()-dreamtime()) + + @staticmethod + def _now(): + return dreamtime() + +# +# Wake up / sleep handling +# + class SleepHandler(Foundation.NSObject): """ Handle wake/sleep notifications """ - def handleSleepNotification(self, aNotification): + def init(self): + self.__sleeptime=None + self.__slept=0 + self.__epoch=time.monotonic() + self._lock=threading.Lock() + self._callbacks=weakref.WeakKeyDictionary() + + return self + + def handleSleepNotification_(self, aNotification): logger.info("System going to sleep") + now=time.monotonic() + with self._lock: + self.__sleeptime=now - def handleWakeNotification(self, aNotification): + def handleWakeNotification_(self, aNotification): logger.info("System waking up from sleep") + try: + now=time.monotonic() + with self._lock: + if self.__sleeptime: + slept=max(0, now-self.__sleeptime) + logger.info("Slept %f seconds" % slept) + self.__slept=self.__slept+slept + self.__sleeptime=None + callbacks=self._callbacks.copy() + except: + logger.exception("Bug in wakeup handler") -if platform.system()=='Darwin': - workspace = AppKit.NSWorkspace.sharedWorkspace() - notification_center = workspace.notificationCenter() - sleep_handler = SleepHandler.new() + for callback in callbacks.values(): + try: + callback() + except Exception: + logger.exception("Error in wake notification callback") - notification_center.addObserver_selector_name_object_( - sleep_handler, - "handleSleepNotification", - AppKit.NSWorkspaceWillSleepNotification, - None) + # Return difference to time.monotonic() + def diff(self): + with self._lock: + diff=self.__epoch+self.__slept + return diff + + # Obj-C hates this + # def add_callback(self, obj, callback): + # with self.lock: + # self.__callbacks[obj]=callback + +# obj is to use a a key in a weak key dictionary +def add_callback(obj, callback): + monitor=_dreamtime_monitor[0] + if not monitor: + raise Exception("Dreamtime monitor not started") + else: + #monitor.add_my_callback(obj, callback) + with monitor._lock: + monitor._callbacks[obj]=callback - notification_center.addObserver_selector_name_object_( - sleep_handler, - "handleWakeNotification", - AppKit.NSWorkspaceDidWakeNotification, - None) +def start_monitoring(): + if platform.system()=='Darwin': + logger.debug("Starting to monitor system sleep") + workspace = AppKit.NSWorkspace.sharedWorkspace() + notification_center = workspace.notificationCenter() + _dreamtime_monitor[0] = SleepHandler.new() + notification_center.addObserver_selector_name_object_( + _dreamtime_monitor[0], + "handleSleepNotification:", + AppKit.NSWorkspaceWillSleepNotification, + None) + + notification_center.addObserver_selector_name_object_( + _dreamtime_monitor[0], + "handleWakeNotification:", + AppKit.NSWorkspaceDidWakeNotification, + None) + else: + logger.warning(("No system sleep monitor implemented for '%s'" + % platform.system())) +