From ef05b48a1cd4d0071873022d1a31d2a076911eb0 Mon Sep 17 00:00:00 2001 From: "Eevee (Alex Munroe)" Date: Sun, 11 May 2014 18:58:04 -0700 Subject: Split up MainLoop._run, so the loop can be managed outside urwid. --- urwid/main_loop.py | 68 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/urwid/main_loop.py b/urwid/main_loop.py index 84ed2d6..da57f0d 100755 --- a/urwid/main_loop.py +++ b/urwid/main_loop.py @@ -50,6 +50,9 @@ class ExitMainLoop(Exception): """ pass +class CantUseExternalLoop(Exception): + pass + class MainLoop(object): """ This is the standard main loop implementation for a single interactive @@ -269,6 +272,10 @@ class MainLoop(object): This method will use :attr:`screen`'s run_wrapper() method if :attr:`screen`'s start() method has not already been called. + + If you would prefer to manage the event loop yourself, don't use this + method. Instead, call :meth:`start` before starting the event loop, + and :meth:`stop` once it's finished. """ try: if self.screen.started: @@ -307,38 +314,59 @@ class MainLoop(object): screen.run_wrapper() """ - def _run(self): + def start(self): + """ + Sets up the main loop, hooking into the event loop where necessary. + + If you want to control starting and stopping the event loop yourself, + you should call this method before starting, and call `stop` once the + loop has finished. + + Note that some event loop implementations don't handle exceptions + specially if you manage the event loop yourself. In particular, the + Twisted and asyncio loops won't stop automatically when + :exc:`ExitMainLoop` is raised. + """ if self.handle_mouse: self.screen.set_mouse_tracking() if not hasattr(self.screen, 'hook_event_loop'): - return self._run_screen_event_loop() - - self.draw_screen() - - fd_handles = [] - def reset_input_descriptors(only_remove=False): - self.screen.unhook_event_loop(self.event_loop) - self.screen.hook_event_loop(self.event_loop, self._update) + raise CantUseExternalLoop( + "Screen {0!r} doesn't support external event loops") try: signals.connect_signal(self.screen, INPUT_DESCRIPTORS_CHANGED, - reset_input_descriptors) + self._reset_input_descriptors) except NameError: pass # watch our input descriptors - reset_input_descriptors() - idle_handle = self.event_loop.enter_idle(self.entering_idle) - - # Go.. - self.event_loop.run() + self._reset_input_descriptors() + self.idle_handle = self.event_loop.enter_idle(self.entering_idle) - # tidy up - self.event_loop.remove_enter_idle(idle_handle) + def stop(self): + """ + Cleans up any hooks added to the event loop. Only call this if you're + managing the event loop yourself, after the loop stops. + """ + self.event_loop.remove_enter_idle(self.idle_handle) + del self.idle_handle signals.disconnect_signal(self.screen, INPUT_DESCRIPTORS_CHANGED, - reset_input_descriptors) + self._reset_input_descriptors) self.screen.unhook_event_loop(self.event_loop) + def _reset_input_descriptors(self): + self.screen.unhook_event_loop(self.event_loop) + self.screen.hook_event_loop(self.event_loop, self._update) + + def _run(self): + try: + self.start() + except CantUseExternalLoop: + return self._run_screen_event_loop() + + self.event_loop.run() + self.stop() + def _update(self, keys, raw): """ >>> w = _refl("widget") @@ -1013,6 +1041,10 @@ class TwistedEventLoop(object): ``manage_reactor=False`` and take care of running/stopping the reactor at the beginning/ending of your program yourself. + You can also forego using :class:`MainLoop`'s run() entirely, and + instead call start() and stop() before and after starting the + reactor. + .. _Twisted: http://twistedmatrix.com/trac/ """ if reactor is None: -- cgit v1.2.1