diff options
author | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2014-03-24 21:57:41 -0700 |
---|---|---|
committer | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2014-05-11 21:29:49 -0700 |
commit | ba5e8ae2724d5069f2f95193ab89310bc7eb4b35 (patch) | |
tree | 1d66973e660d825f2f0c93f2bca7245fa5e6aa3a | |
parent | 1a7964d4d509df88f61054513e3f7f074a627b71 (diff) | |
download | urwid-ba5e8ae2724d5069f2f95193ab89310bc7eb4b35.tar.gz |
Have the Screen call back into MainLoop on new input.
This should be much more readily extended by async loops like asyncio and Twisted.
-rwxr-xr-x | urwid/main_loop.py | 39 | ||||
-rw-r--r-- | urwid/raw_display.py | 155 |
2 files changed, 103 insertions, 91 deletions
diff --git a/urwid/main_loop.py b/urwid/main_loop.py index 9b9cee0..75972c3 100755 --- a/urwid/main_loop.py +++ b/urwid/main_loop.py @@ -124,7 +124,6 @@ class MainLoop(object): event_loop = SelectEventLoop() self.event_loop = event_loop - self._input_timeout = None self._watch_pipes = {} def _set_widget(self, widget): @@ -297,12 +296,12 @@ class MainLoop(object): screen.get_cols_rows() widget.render((20, 10), focus=True) screen.draw_screen((20, 10), 'fake canvas') - screen.get_input_descriptors() - event_loop.watch_file(42, <bound method ...>) + screen.unhook_event_loop(...) + screen.hook_event_loop(...) event_loop.enter_idle(<bound method ...>) event_loop.run() event_loop.remove_enter_idle(1) - event_loop.remove_watch_file(2) + screen.unhook_event_loop(...) >>> scr.started = False >>> ml.run() # doctest:+ELLIPSIS screen.run_wrapper(<bound method ...>) @@ -339,49 +338,25 @@ class MainLoop(object): signals.disconnect_signal(self.screen, INPUT_DESCRIPTORS_CHANGED, reset_input_descriptors) self.screen.unhook_event_loop(self.event_loop) - if self._input_timeout is not None: - self.event_loop.remove_alarm(self._input_timeout) - def _update(self, timeout=False): + def _update(self, keys, raw): """ >>> w = _refl("widget") >>> w.selectable_rval = True >>> w.mouse_event_rval = True >>> scr = _refl("screen") >>> scr.get_cols_rows_rval = (15, 5) - >>> scr.get_input_nonblocking_rval = 1, ['y'], [121] >>> evl = _refl("event_loop") >>> ml = MainLoop(w, [], scr, event_loop=evl) >>> ml._input_timeout = "old timeout" - >>> ml._update() # doctest:+ELLIPSIS - event_loop.remove_alarm('old timeout') - screen.get_input_nonblocking() - event_loop.alarm(1, <function ...>) + >>> ml._update(['y'], [121]) # doctest:+ELLIPSIS screen.get_cols_rows() widget.selectable() widget.keypress((15, 5), 'y') - >>> scr.get_input_nonblocking_rval = None, [("mouse press", 1, 5, 4) - ... ], [] - >>> ml._update() - screen.get_input_nonblocking() + >>> ml._update([("mouse press", 1, 5, 4)], []) widget.mouse_event((15, 5), 'mouse press', 1, 5, 4, focus=True) - >>> scr.get_input_nonblocking_rval = None, [], [] - >>> ml._update() - screen.get_input_nonblocking() + >>> ml._update([], []) """ - if self._input_timeout is not None and not timeout: - # cancel the timeout, something else triggered the update - self.event_loop.remove_alarm(self._input_timeout) - self._input_timeout = None - - max_wait, keys, raw = self.screen.get_input_nonblocking() - - if max_wait is not None: - # if get_input_nonblocking wants to be called back - # make sure it happens with an alarm - self._input_timeout = self.event_loop.alarm(max_wait, - lambda: self._update(timeout=True)) - keys = self.input_filter(keys, raw) if keys: diff --git a/urwid/raw_display.py b/urwid/raw_display.py index 27cf60a..610709e 100644 --- a/urwid/raw_display.py +++ b/urwid/raw_display.py @@ -209,7 +209,6 @@ class Screen(BaseScreen, RealTerminal): self.signal_init() self._alternate_buffer = alternate_buffer - self._input_iter = self._run_input_iter() self._next_timeout = self.max_wait if not self._signal_keys_set: @@ -251,7 +250,6 @@ class Screen(BaseScreen, RealTerminal): + escape.SI + move_cursor + escape.SHOW_CURSOR) - self._input_iter = self._fake_input_iter() if self._old_signal_keys: self.tty_signal_keys(*(self._old_signal_keys + (fd,))) @@ -367,81 +365,120 @@ class Screen(BaseScreen, RealTerminal): _current_event_loop_handles = () def unhook_event_loop(self, event_loop): + """ + Remove any hooks added by hook_event_loop. + """ for handle in self._current_event_loop_handles: event_loop.remove_watch_file(handle) + if self._input_timeout: + event_loop.remove_alarm(self._input_timeout) + self._input_timeout = None + def hook_event_loop(self, event_loop, callback): + """ + Register the given callback with the event loop, to be called with new + input whenever it's available. The callback should be passed a list of + processed keys and a list of unprocessed keycodes. + + Subclasses may wish to use parse_input to wrap the callback. + """ + if hasattr(self, 'get_input_nonblocking'): + wrapper = self._make_legacy_input_wrapper(event_loop, callback) + else: + wrapper = lambda: self.parse_input(event_loop, callback) fds = self.get_input_descriptors() handles = [] for fd in fds: - event_loop.watch_file(fd, callback) + event_loop.watch_file(fd, wrapper) self._current_event_loop_handles = handles - def get_input_nonblocking(self): + _input_timeout = None + _partial_codes = None + + def _make_legacy_input_wrapper(self, event_loop, callback): """ - Return a (next_input_timeout, keys_pressed, raw_keycodes) - tuple. + Support old Screen classes that still have a get_input_nonblocking and + expect it to work. + """ + def wrapper(): + if self._input_timeout: + event_loop.remove_alarm(self._input_timeout) + self._input_timeout = None + timeout, keys, raw = self.get_input_nonblocking() + if timeout is not None: + self._input_timeout = event_loop.alarm(timeout, wrapper) - Use this method if you are implementing your own event loop. + callback(keys, raw) - When there is input waiting on one of the descriptors returned - by get_input_descriptors() this method should be called to - read and process the input. + return wrapper - This method expects to be called in next_input_timeout seconds - (a floating point number) if there is no input waiting. + def get_available_raw_input(self): """ - return self._input_iter.next() + Return any currently-available input. Does not block. - def _run_input_iter(self): - def empty_resize_pipe(): - # clean out the pipe used to signal external event loops - # that a resize has occurred - try: - while True: os.read(self._resize_pipe_rd, 1) - except OSError: - pass + This method is only used by parse_input; you can safely ignore it if + you implement your own parse_input. + """ + codes = self._get_gpm_codes() + self._get_keyboard_codes() - while True: - processed = [] - codes = self._get_gpm_codes() + \ - self._get_keyboard_codes() + if self._partial_codes: + codes = self._partial_codes + codes + self._partial_codes = None - original_codes = codes - try: - while codes: - run, codes = escape.process_keyqueue( - codes, True) - processed.extend(run) - except escape.MoreInputRequired: - k = len(original_codes) - len(codes) - yield (self.complete_wait, processed, - original_codes[:k]) - empty_resize_pipe() - original_codes = codes - processed = [] - - codes += self._get_keyboard_codes() + \ - self._get_gpm_codes() - while codes: - run, codes = escape.process_keyqueue( - codes, False) - processed.extend(run) - - if self._resized: - processed.append('window resize') - self._resized = False - - yield (self.max_wait, processed, original_codes) - empty_resize_pipe() - - def _fake_input_iter(self): - """ - This generator is a placeholder for when the screen is stopped - to always return that no input is available. + # clean out the pipe used to signal external event loops + # that a resize has occurred + try: + while True: os.read(self._resize_pipe_rd, 1) + except OSError: + pass + + return codes + + def parse_input(self, event_loop, callback, wait_for_more=True): """ - while True: - yield (self.max_wait, [], []) + Read any available input from get_available_raw_input, parses it into + keys, and calls the given callback. + + The current implementation tries to avoid any assumptions about what + the screen or event loop look like; it only deals with parsing keycodes + and setting a timeout when an incomplete one is detected. + """ + if self._input_timeout: + event_loop.remove_alarm(self._input_timeout) + self._input_timeout = None + + codes = self.get_available_raw_input() + original_codes = codes + processed = [] + try: + while codes: + run, codes = escape.process_keyqueue( + codes, wait_for_more) + processed.extend(run) + except escape.MoreInputRequired: + # Set a timer to wait for the rest of the input; if it goes off + # without any new input having come in, use the partial input + k = len(original_codes) - len(codes) + processed_codes = original_codes[:k] + self._partial_codes = codes + + def _parse_incomplete_input(): + self._input_timeout = None + self.parse_input( + event_loop, callback, wait_for_more=False) + self._input_timeout = event_loop.alarm( + self.complete_wait, self._parse_incomplete_input) + + else: + processed_codes = original_codes + self._partial_codes = None + + if self._resized: + processed.append('window resize') + self._resized = False + + callback(processed, processed_codes) def _get_keyboard_codes(self): codes = [] |