Bob Ippolito avatar Bob Ippolito committed adefcd0

Add a method to stop console event loops

Comments (0)

Files changed (4)

pyobjc/Examples/Scripts/HelloWorld.py

 import objc
 from Foundation import *
 from AppKit import *
+from PyObjCTools import AppHelper
 
 class AppDelegate (NSObject):
     def applicationDidFinishLaunching_(self, aNotification):
     win.display()
     win.orderFrontRegardless()          ## but this one does
 
-    app.run()
+    AppHelper.runEventLoop()
 
 
 if __name__ == '__main__' : main()

pyobjc/Examples/Scripts/autoreadme.py

 import os
 import re
 
-from AppKit import NSWorkspace, NSWorkspaceDidMountNotification
+from AppKit import *
 from Foundation import *
+from PyObjCTools import AppHelper
 
 
 readTheseFiles = re.compile('(.*read\s*me.*|.*release.*note.*|^about.*)', re.I)
     None)
 
 NSLog("Listening for mount notifications....")
-NSRunLoop.currentRunLoop().run() # never exits
+AppHelper.runConsoleEventLoop()

pyobjc/Examples/Scripts/debugging.py

 """
 from PyObjCTools import AppHelper
 from PyObjCTools import Debugging
-from Foundation import NSTimer, NSObject, NSInvocation
+from Foundation import *
 
 class FooTester(NSObject):
     def doBadThingsNow_(self, aTimer):
+        AppHelper.stopEventLoop()
         raise ValueError, "doing bad things"
 
 foo = FooTester.alloc().init()
 NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
-    1.0, foo, 'doBadThingsNow:', None, False
+    0.0, foo, 'doBadThingsNow:', None, False
 )
 # we need to catch everything, because NSTimer handles this one
 Debugging.installVerboseExceptionHandler()

pyobjc/Lib/PyObjCTools/AppHelper.py

 
 Exported functions:
 * runEventLoop - run NSApplicationMain in a safer way
+* runConsoleEventLoop - run NSRunLoop.run() in a stoppable manner
+* stopEventLoop - stops the event loop or terminates the application
 * endSheetMethod - set correct signature for NSSheet callbacks
 """
 
-__all__ = ( 'runEventLoop', 'runConsoleEventLoop', 'endSheetMethod' )
+__all__ = ( 'runEventLoop', 'runConsoleEventLoop', 'stopEventLoop', 'endSheetMethod' )
 
-from AppKit import NSApplicationMain, NSApp, NSRunAlertPanel
-from Foundation import NSLog, NSRunLoop
+from AppKit import *
+from Foundation import *
 import os
 import sys
 import traceback
 import objc
 
+class PyObjCAppHelperRunLoopStopper(NSObject):
+    singletons = {}
+
+    def currentRunLoopStopper(cls):
+        runLoop = NSRunLoop.currentRunLoop()
+        return cls.singletons.get(runLoop)
+    currentRunLoopStopper = classmethod(currentRunLoopStopper)
+            
+    def init(self):
+        self = super(PyObjCAppHelperRunLoopStopper, self).init()
+        self.shouldStop = False
+        return self
+
+    def shouldRun(self):
+        return not self.shouldStop
+
+    def addRunLoopStopper_toRunLoop_(cls, runLoopStopper, runLoop):
+        if runLoop in cls.singletons:
+            raise ValueError, "Stopper already registered for this runLoop"
+        cls.singletons[runLoop] = runLoopStopper
+    addRunLoopStopper_toRunLoop_ = classmethod(addRunLoopStopper_toRunLoop_)
+        
+    def removeRunLoopStopperFromRunLoop_(cls, runLoop):
+        if runLoop not in cls.singletons:
+            raise ValueError, "Stopper not registered for this runLoop"
+        del cls.singletons[runLoop]
+    removeRunLoopStopperFromRunLoop_ = classmethod(removeRunLoopStopperFromRunLoop_)
+        
+    def stop(self):
+        self.shouldStop = True
+        # this should go away when/if runEventLoop uses
+        # runLoop iteration
+        if NSApp() is not None:
+            NSApp().terminate_(self)
+
+    def performStop_(self, sender):
+        self.stop()
+
+
+def stopEventLoop():
+    """
+    Stop the current event loop if possible
+    returns True if it expects that it was successful, False otherwise
+    """
+    stopper = PyObjCAppHelperRunLoopStopper.currentRunLoopStopper()
+    if stopper is None:
+        if NSApp() is not None:
+            NSApp().terminate_(None)
+            return True
+        return False
+    NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
+        0.0,
+        stopper,
+        'performStop:',
+        None,
+        False)
+    return True
+
 
 def endSheetMethod(meth):
     """
     return objc.selector(meth, signature='v@:@ii')
 
 
-
 def unexpectedErrorAlertPanel():
     exceptionInfo = traceback.format_exception_only(
         *sys.exc_info()[:2])[0].strip()
             "(%s)" % exceptionInfo,
             "Continue", "Quit", None)
 
+
 def unexpectedErrorAlertPdb():
     import pdb
     traceback.print_exc()
     pdb.post_mortem(sys.exc_info()[2])
     return True
 
+
 def machInterrupt(signum):
-    app = NSApp()
-    if app:
-        app.terminate_(None)
+    stopper = PyObjCAppHelperRunLoopStopper.currentRunLoopStopper()
+    if stopper is not None:
+        stopper.stop()
+    elif NSApp() is not None:
+        NSApp().terminate_(None)
     else:
         import os
         os._exit(1)
 
+
 def installMachInterrupt():
     try:
         import signal
         return
     MachSignals.signal(signal.SIGINT, machInterrupt)
 
-def runConsoleEventLoop(argv=None, installInterrupt=False):
+
+def runConsoleEventLoop(argv=None, installInterrupt=False, mode=NSDefaultRunLoopMode):
     if argv is None:
         argv = sys.argv
     if installInterrupt:
         installMachInterrupt()
-    NSRunLoop.currentRunLoop().run()
+    runLoop = NSRunLoop.currentRunLoop()
+    stopper = PyObjCAppHelperRunLoopStopper.alloc().init()
+    PyObjCAppHelperRunLoopStopper.addRunLoopStopper_toRunLoop_(stopper, runLoop)
+    try:
+
+        while stopper.shouldRun():
+            nextfire = runLoop.limitDateForMode_(mode)
+            if not stopper.shouldRun():
+                break
+            if not runLoop.runMode_beforeDate_(mode, nextfire):
+                stopper.stop()
+
+    finally:
+        PyObjCAppHelperRunLoopStopper.removeRunLoopStopperFromRunLoop_(runLoop)
+
 
 RAISETHESE = (SystemExit, MemoryError, KeyboardInterrupt)
 
+
 def runEventLoop(argv=None, unexpectedErrorAlert=None, installInterrupt=False, pdb=None):
     """Run the event loop, ask the user if we should continue if an
     exception is caught. Use this function instead of NSApplicationMain().
         else:
             unexpectedErrorAlert = unexpectedErrorAlertPanel
 
+    runLoop = NSRunLoop.currentRunLoop()
+    stopper = PyObjCAppHelperRunLoopStopper.alloc().init()
+    PyObjCAppHelperRunLoopStopper.addRunLoopStopper_toRunLoop_(stopper, runLoop)
+
     firstRun = NSApp() is None
-    while True:
-        try:
-            if firstRun:
-                firstRun = False
-                if installInterrupt:
-                    installMachInterrupt()
-                NSApplicationMain(argv)
+    try:
+
+        while stopper.shouldRun():
+            try:
+                if firstRun:
+                    firstRun = False
+                    if installInterrupt:
+                        installMachInterrupt()
+                    NSApplicationMain(argv)
+                else:
+                    NSApp().run()
+            except RAISETHESE:
+                traceback.print_exc()
+                break
+            except:
+                if not unexpectedErrorAlert():
+                    NSLog("An exception has occured:")
+                    raise
+                else:
+                    NSLog("An exception has occured:")
+                    traceback.print_exc()
             else:
-                NSApp().run()
-        except RAISETHESE:
-            traceback.print_exc()
-            break
-        except:
-            if not unexpectedErrorAlert():
-                NSLog("An exception has occured:")
-                raise
-            else:
-                NSLog("An exception has occured:")
-                traceback.print_exc()
-        else:
-            break
+                break
+
+    finally:
+        PyObjCAppHelperRunLoopStopper.removeRunLoopStopperFromRunLoop_(runLoop)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.