Exemple #1
0
    def run(self):
        """
        Run the event loop for the interface.
        This starts the interaction.
        """
        # Make sure everything is in sync, before starting.
        self.sync_with_prompt_toolkit()

        def pre_run():
            # Start in navigation mode.
            self.application.vi_state.input_mode = InputMode.NAVIGATION

        # Run eventloop of prompt_toolkit.
        def run():
            with _auto_refresh_context(self.application, .3):
                try:
                    self.application.run(pre_run=pre_run)
                except BaseException as e:
                    import traceback
                    traceback.print_exc()
                    print(e)
        self.thread = threading.Thread(target=run)
        self.thread.start()

        has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread()
        if has_sigwinch:
            loop = get_event_loop()
            loop.add_signal_handler(signal.SIGWINCH,
                                    self.application.invalidate)
Exemple #2
0
    def __enter__(self):
        # Create UI Application.
        title_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.title),
                   height=1,
                   style='class:progressbar,title'),
            filter=Condition(lambda: self.title is not None))

        bottom_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.bottom_toolbar,
                                        style='class:bottom-toolbar.text'),
                   style='class:bottom-toolbar',
                   height=1),
            filter=~is_done & renderer_height_is_known
            & Condition(lambda: self.bottom_toolbar is not None))

        def width_for_formatter(formatter):
            return formatter.get_width(progress_bar=self)

        progress_controls = [
            Window(content=_ProgressControl(self, f),
                   width=width_for_formatter(f)) for f in self.formatters
        ]

        self.app = Application(
            min_redraw_interval=.05,
            layout=Layout(
                HSplit([
                    title_toolbar,
                    VSplit(progress_controls,
                           height=lambda: D(preferred=len(self.counters),
                                            max=len(self.counters))),
                    Window(),
                    bottom_toolbar,
                ])),
            style=self.style,
            key_bindings=self.key_bindings,
            output=self.output,
            input=self.input)

        # Run application in different thread.
        def run():
            with _auto_refresh_context(self.app, .3):
                try:
                    self.app.run()
                except Exception as e:
                    traceback.print_exc()
                    print(e)

        self._thread = threading.Thread(target=run)
        self._thread.start()

        # Attach WINCH signal handler in main thread.
        # (Interrupt that we receive during resize events.)
        self._has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread()
        if self._has_sigwinch:
            self._previous_winch_handler = self._loop.add_signal_handler(
                signal.SIGWINCH, self.app.invalidate)

        return self
Exemple #3
0
    def __enter__(self):
        # Create UI Application.
        title_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.title), height=1),
            filter=Condition(lambda: self.title is not None))

        bottom_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.bottom_toolbar,
                                        style='class:bottom-toolbar.text'),
                   style='class:bottom-toolbar',
                   height=1),
            filter=~is_done & renderer_height_is_known
            & Condition(lambda: self.bottom_toolbar is not None))

        self.app = Application(min_redraw_interval=.05,
                               layout=Layout(
                                   HSplit([
                                       title_toolbar,
                                       Window(
                                           content=_ProgressControl(self),
                                           height=lambda: len(self.counters)),
                                       Window(),
                                       bottom_toolbar,
                                   ])),
                               style=self.style)

        # Run application in different thread.
        def run():
            try:
                self.app.run()
            except Exception as e:
                import traceback
                traceback.print_exc()
                print(e)

        self._thread = threading.Thread(target=run)
        self._thread.start()

        # Attach WINCH signal handler in main thread.
        # (Interrupt that we receive during resize events.)
        self._has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread()
        if self._has_sigwinch:
            self._previous_winch_handler = self._loop.add_signal_handler(
                signal.SIGWINCH, self.app.invalidate)

        return self
def attach_winch_signal_handler(
    handler: Callable[[], None]
) -> Generator[None, None, None]:
    """
    Attach the given callback as a WINCH signal handler within the context
    manager. Restore the original signal handler when done.

    The `Application.run` method will register SIGWINCH, so that it will
    properly repaint when the terminal window resizes. However, using
    `run_in_terminal`, we can temporarily send an application to the
    background, and run an other app in between, which will then overwrite the
    SIGWINCH. This is why it's important to restore the handler when the app
    terminates.
    """
    # The tricky part here is that signals are registered in the Unix event
    # loop with a wakeup fd, but another application could have registered
    # signals using signal.signal directly. For now, the implementation is
    # hard-coded for the `asyncio.unix_events._UnixSelectorEventLoop`.

    # No WINCH? Then don't do anything.
    sigwinch = getattr(signal, "SIGWINCH", None)
    if sigwinch is None or not in_main_thread():
        yield
        return

    # Keep track of the previous handler.
    # (Only UnixSelectorEventloop has `_signal_handlers`.)
    loop = asyncio.get_event_loop()
    previous_winch_handler = getattr(loop, "_signal_handlers", {}).get(sigwinch)

    try:
        loop.add_signal_handler(sigwinch, handler)
        yield
    finally:
        # Restore the previous signal handler.
        loop.remove_signal_handler(sigwinch)
        if previous_winch_handler is not None:
            loop.add_signal_handler(
                sigwinch,
                previous_winch_handler._callback,
                *previous_winch_handler._args,
            )
Exemple #5
0
    def create_ui(self):

        self._create_app()

        # Run application in different thread.
        def run() -> None:
            set_event_loop(self._app_loop)
            try:
                self.app.run()
            except BaseException as e:
                traceback.print_exc()
                print(e)

        ctx: contextvars.Context = contextvars.copy_context()

        self._thread = threading.Thread(target=ctx.run, args=(run,))
        self._thread.start()

        # Attach WINCH signal handler in main thread.
        # (Interrupt that we receive during resize events.)
        self._has_sigwinch = _SIGWINCH is not None and in_main_thread()
        if self._has_sigwinch:
            self._previous_winch_handler = signal.getsignal(_SIGWINCH)
            self._loop.add_signal_handler(_SIGWINCH, self.invalidate)
        def _run_async():
            " Coroutine. "
            loop = get_event_loop()
            f = loop.create_future()
            self.future = f  # XXX: make sure to set this before calling '_redraw'.

            # Counter for cancelling 'flush' timeouts. Every time when a key is
            # pressed, we start a 'flush' timer for flushing our escape key. But
            # when any subsequent input is received, a new timer is started and
            # the current timer will be ignored.
            flush_counter = [0]  # Non local.

            # Reset.
            self.reset()
            self._pre_run(pre_run)

            # Feed type ahead input first.
            self.key_processor.feed_multiple(get_typeahead(self.input))
            self.key_processor.process_keys()

            def read_from_input():
                # Ignore when we aren't running anymore. This callback will
                # removed from the loop next time. (It could be that it was
                # still in the 'tasks' list of the loop.)
                # Except: if we need to process incoming CPRs.
                if not self._is_running and not self.renderer.waiting_for_cpr:
                    return

                # Get keys from the input object.
                keys = self.input.read_keys()

                # Feed to key processor.
                self.key_processor.feed_multiple(keys)
                self.key_processor.process_keys()

                # Quit when the input stream was closed.
                if self.input.closed:
                    f.set_exception(EOFError)
                else:
                    # Increase this flush counter.
                    flush_counter[0] += 1
                    counter = flush_counter[0]

                    # Automatically flush keys.
                    # (_daemon needs to be set, otherwise, this will hang the
                    # application for .5 seconds before exiting.)
                    run_in_executor(
                        lambda: auto_flush_input(counter), _daemon=True)

            def auto_flush_input(counter):
                # Flush input after timeout.
                # (Used for flushing the enter key.)
                time.sleep(self.ttimeoutlen)

                if flush_counter[0] == counter:
                    call_from_executor(flush_input)

            def flush_input():
                if not self.is_done:
                    # Get keys, and feed to key processor.
                    keys = self.input.flush_keys()
                    self.key_processor.feed_multiple(keys)
                    self.key_processor.process_keys()

                    if self.input.closed:
                        f.set_exception(EOFError)

            # Enter raw mode.
            with self.input.raw_mode():
                with self.input.attach(read_from_input):
                    # Draw UI.
                    self._request_absolute_cursor_position()
                    self._redraw()

                    has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread()
                    if has_sigwinch:
                        previous_winch_handler = loop.add_signal_handler(
                            signal.SIGWINCH, self._on_resize)

                    # Wait for UI to finish.
                    try:
                        result = yield From(f)
                    finally:
                        # In any case, when the application finishes. (Successful,
                        # or because of an error.)
                        try:
                            self._redraw(render_as_done=True)
                        finally:
                            # _redraw has a good chance to fail if it calls widgets
                            # with bad code. Make sure to reset the renderer anyway.
                            self.renderer.reset()

                            # Unset `is_running`, this ensures that possibly
                            # scheduled draws won't paint during the following
                            # yield.
                            self._is_running = False

                            # Detach event handlers for invalidate events.
                            # (Important when a UIControl is embedded in
                            # multiple applications, like ptterm in pymux. An
                            # invalidate should not trigger a repaint in
                            # terminated applications.)
                            for ev in self._invalidate_events:
                                ev -= self.invalidate
                            self._invalidate_events = []

                            # Wait for CPR responses.
                            if self.input.responds_to_cpr:
                                yield From(self.renderer.wait_for_cpr_responses())

                            if has_sigwinch:
                                loop.add_signal_handler(signal.SIGWINCH, previous_winch_handler)

                            # Wait for the run-in-terminals to terminate.
                            previous_run_in_terminal_f = self._running_in_terminal_f

                            if previous_run_in_terminal_f:
                                yield From(previous_run_in_terminal_f)

                            # Store unprocessed input as typeahead for next time.
                            store_typeahead(self.input, self.key_processor.empty_queue())

                raise Return(result)
Exemple #7
0
    def run(self, stdin, callbacks):
        """
        The input 'event loop'.
        """
        assert isinstance(stdin, Input)
        assert isinstance(callbacks, EventLoopCallbacks)
        assert not self._running

        if self.closed:
            raise Exception('Event loop already closed.')

        self._running = True
        self._callbacks = callbacks

        inputstream = InputStream(callbacks.feed_key)
        current_timeout = [INPUT_TIMEOUT]  # Nonlocal

        # Create reader class.
        stdin_reader = PosixStdinReader(stdin.fileno())

        # Only attach SIGWINCH signal handler in main thread.
        # (It's not possible to attach signal handlers in other threads. In
        # that case we should rely on a the main thread to call this manually
        # instead.)
        if in_main_thread():
            ctx = call_on_sigwinch(self.received_winch)
        else:
            ctx = DummyContext()

        def read_from_stdin():
            " Read user input. "
            # Feed input text.
            data = stdin_reader.read()
            inputstream.feed(data)

            # Set timeout again.
            current_timeout[0] = INPUT_TIMEOUT

            # Quit when the input stream was closed.
            if stdin_reader.closed:
                self.stop()

        self.add_reader(stdin, read_from_stdin)
        self.add_reader(self._schedule_pipe[0], None)

        with ctx:
            while self._running:
                # Call inputhook.
                with TimeIt() as inputhook_timer:
                    if self._inputhook_context:

                        def ready(wait):
                            " True when there is input ready. The inputhook should return control. "
                            return self._ready_for_reading(
                                current_timeout[0] if wait else 0) != []

                        self._inputhook_context.call_inputhook(ready)

                # Calculate remaining timeout. (The inputhook consumed some of the time.)
                if current_timeout[0] is None:
                    remaining_timeout = None
                else:
                    remaining_timeout = max(
                        0, current_timeout[0] - inputhook_timer.duration)

                # Wait until input is ready.
                fds = self._ready_for_reading(remaining_timeout)

                # When any of the FDs are ready. Call the appropriate callback.
                if fds:
                    # Create lists of high/low priority tasks. The main reason
                    # for this is to allow painting the UI to happen as soon as
                    # possible, but when there are many events happening, we
                    # don't want to call the UI renderer 1000x per second. If
                    # the eventloop is completely saturated with many CPU
                    # intensive tasks (like processing input/output), we say
                    # that drawing the UI can be postponed a little, to make
                    # CPU available. This will be a low priority task in that
                    # case.
                    tasks = []
                    low_priority_tasks = []
                    now = _now()

                    for fd in fds:
                        # For the 'call_from_executor' fd, put each pending
                        # item on either the high or low priority queue.
                        if fd == self._schedule_pipe[0]:
                            for c, max_postpone_until in self._calls_from_executor:
                                if max_postpone_until is None or max_postpone_until < now:
                                    tasks.append(c)
                                else:
                                    low_priority_tasks.append(
                                        (c, max_postpone_until))
                            self._calls_from_executor = []

                            # Flush all the pipe content.
                            os.read(self._schedule_pipe[0], 1024)
                        else:
                            handler = self._read_fds.get(fd)
                            if handler:
                                tasks.append(handler)

                    # Handle everything in random order. (To avoid starvation.)
                    random.shuffle(tasks)
                    random.shuffle(low_priority_tasks)

                    # When there are high priority tasks, run all these.
                    # Schedule low priority tasks for the next iteration.
                    if tasks:
                        for t in tasks:
                            t()

                        # Postpone low priority tasks.
                        for t, max_postpone_until in low_priority_tasks:
                            self.call_from_executor(
                                t, _max_postpone_until=max_postpone_until)
                    else:
                        # Currently there are only low priority tasks -> run them right now.
                        for t, _ in low_priority_tasks:
                            t()

                else:
                    # Flush all pending keys on a timeout. (This is most
                    # important to flush the vt100 'Escape' key early when
                    # nothing else follows.)
                    inputstream.flush()

                    # Fire input timeout event.
                    callbacks.input_timeout()
                    current_timeout[0] = None

        self.remove_reader(stdin)
        self.remove_reader(self._schedule_pipe[0])

        self._callbacks = None
Exemple #8
0
    def __enter__(self) -> 'ProgressBar':
        # Create UI Application.
        title_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.title),
                   height=1,
                   style='class:progressbar,title'),
            filter=Condition(lambda: self.title is not None))

        bottom_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.bottom_toolbar,
                                        style='class:bottom-toolbar.text'),
                   style='class:bottom-toolbar',
                   height=1),
            filter=~is_done & renderer_height_is_known
            & Condition(lambda: self.bottom_toolbar is not None))

        def width_for_formatter(formatter: Formatter) -> AnyDimension:
            # Needs to be passed as callable (partial) to the 'width'
            # parameter, because we want to call it on every resize.
            return formatter.get_width(progress_bar=self)

        progress_controls = [
            Window(content=_ProgressControl(self, f),
                   width=functools.partial(width_for_formatter, f))
            for f in self.formatters
        ]

        self.app: Application[None] = Application(
            min_redraw_interval=.05,
            layout=Layout(
                HSplit([
                    title_toolbar,
                    VSplit(progress_controls,
                           height=lambda: D(preferred=len(self.counters),
                                            max=len(self.counters))),
                    Window(),
                    bottom_toolbar,
                ])),
            style=self.style,
            key_bindings=self.key_bindings,
            color_depth=self.color_depth,
            output=self.output,
            input=self.input)

        # Run application in different thread.
        def run() -> None:
            set_event_loop(self._app_loop)
            with _auto_refresh_context(self.app, .3):
                try:
                    self.app.run()
                except BaseException as e:
                    traceback.print_exc()
                    print(e)

        self._thread = threading.Thread(target=run)
        self._thread.start()

        # Attach WINCH signal handler in main thread.
        # (Interrupt that we receive during resize events.)
        self._has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread()
        if self._has_sigwinch:
            self._previous_winch_handler = signal.getsignal(signal.SIGWINCH)
            self._loop.add_signal_handler(signal.SIGWINCH, self.invalidate)

        return self
Exemple #9
0
        async def _run_async() -> _AppResult:
            " Coroutine. "
            loop = get_event_loop()
            f = loop.create_future()
            self.future = f  # XXX: make sure to set this before calling '_redraw'.
            self.loop = loop
            self.context = contextvars.copy_context()

            # Counter for cancelling 'flush' timeouts. Every time when a key is
            # pressed, we start a 'flush' timer for flushing our escape key. But
            # when any subsequent input is received, a new timer is started and
            # the current timer will be ignored.
            flush_task: Optional[asyncio.Task[None]] = None

            # Reset.
            self.reset()
            self._pre_run(pre_run)

            # Feed type ahead input first.
            self.key_processor.feed_multiple(get_typeahead(self.input))
            self.key_processor.process_keys()

            def read_from_input() -> None:
                nonlocal flush_task

                # Ignore when we aren't running anymore. This callback will
                # removed from the loop next time. (It could be that it was
                # still in the 'tasks' list of the loop.)
                # Except: if we need to process incoming CPRs.
                if not self._is_running and not self.renderer.waiting_for_cpr:
                    return

                # Get keys from the input object.
                keys = self.input.read_keys()

                # Feed to key processor.
                self.key_processor.feed_multiple(keys)
                self.key_processor.process_keys()

                # Quit when the input stream was closed.
                if self.input.closed:
                    f.set_exception(EOFError)
                else:
                    # Automatically flush keys.
                    if flush_task:
                        flush_task.cancel()
                    flush_task = self.create_background_task(
                        auto_flush_input())

            async def auto_flush_input() -> None:
                # Flush input after timeout.
                # (Used for flushing the enter key.)
                # This sleep can be cancelled, in that case we won't flush yet.
                await sleep(self.ttimeoutlen)
                flush_input()

            def flush_input() -> None:
                if not self.is_done:
                    # Get keys, and feed to key processor.
                    keys = self.input.flush_keys()
                    self.key_processor.feed_multiple(keys)
                    self.key_processor.process_keys()

                    if self.input.closed:
                        f.set_exception(EOFError)

            # Enter raw mode.
            with self.input.raw_mode():
                with self.input.attach(read_from_input):
                    # Draw UI.
                    self._request_absolute_cursor_position()
                    self._redraw()
                    self._start_auto_refresh_task()

                    has_sigwinch = hasattr(signal,
                                           "SIGWINCH") and in_main_thread()
                    if has_sigwinch:
                        previous_winch_handler = signal.getsignal(
                            signal.SIGWINCH)
                        loop.add_signal_handler(signal.SIGWINCH,
                                                self._on_resize)

                    # Wait for UI to finish.
                    try:
                        result = await f
                    finally:
                        # In any case, when the application finishes. (Successful,
                        # or because of an error.)
                        try:
                            self._redraw(render_as_done=True)
                        finally:
                            # _redraw has a good chance to fail if it calls widgets
                            # with bad code. Make sure to reset the renderer anyway.
                            self.renderer.reset()

                            # Unset `is_running`, this ensures that possibly
                            # scheduled draws won't paint during the following
                            # yield.
                            self._is_running = False

                            # Detach event handlers for invalidate events.
                            # (Important when a UIControl is embedded in
                            # multiple applications, like ptterm in pymux. An
                            # invalidate should not trigger a repaint in
                            # terminated applications.)
                            for ev in self._invalidate_events:
                                ev -= self._invalidate_handler
                            self._invalidate_events = []

                            # Wait for CPR responses.
                            if self.input.responds_to_cpr:
                                await self.renderer.wait_for_cpr_responses()

                            if has_sigwinch:
                                loop.remove_signal_handler(signal.SIGWINCH)
                                signal.signal(signal.SIGWINCH,
                                              previous_winch_handler)

                            # Wait for the run-in-terminals to terminate.
                            previous_run_in_terminal_f = self._running_in_terminal_f

                            if previous_run_in_terminal_f:
                                await previous_run_in_terminal_f

                            # Store unprocessed input as typeahead for next time.
                            store_typeahead(self.input,
                                            self.key_processor.empty_queue())

                return result
        async def _run_async() -> _AppResult:
            " Coroutine. "
            loop = get_event_loop()
            f = loop.create_future()
            self.future = f  # XXX: make sure to set this before calling '_redraw'.
            self.loop = loop
            self.context = contextvars.copy_context()

            # Counter for cancelling 'flush' timeouts. Every time when a key is
            # pressed, we start a 'flush' timer for flushing our escape key. But
            # when any subsequent input is received, a new timer is started and
            # the current timer will be ignored.
            flush_counter = 0

            # Reset.
            self.reset()
            self._pre_run(pre_run)

            # Feed type ahead input first.
            self.key_processor.feed_multiple(get_typeahead(self.input))
            self.key_processor.process_keys()

            def read_from_input() -> None:
                nonlocal flush_counter

                # Ignore when we aren't running anymore. This callback will
                # removed from the loop next time. (It could be that it was
                # still in the 'tasks' list of the loop.)
                # Except: if we need to process incoming CPRs.
                if not self._is_running and not self.renderer.waiting_for_cpr:
                    return

                # Get keys from the input object.
                keys = self.input.read_keys()

                # Feed to key processor.
                self.key_processor.feed_multiple(keys)
                self.key_processor.process_keys()

                # Quit when the input stream was closed.
                if self.input.closed:
                    f.set_exception(EOFError)
                else:
                    # Increase this flush counter.
                    flush_counter += 1
                    counter = flush_counter

                    # Automatically flush keys.
                    ensure_future(auto_flush_input(counter))

            async def auto_flush_input(counter: int) -> None:
                # Flush input after timeout.
                # (Used for flushing the enter key.)
                await sleep(self.ttimeoutlen)

                if flush_counter == counter:
                    flush_input()

            def flush_input() -> None:
                if not self.is_done:
                    # Get keys, and feed to key processor.
                    keys = self.input.flush_keys()
                    self.key_processor.feed_multiple(keys)
                    self.key_processor.process_keys()

                    if self.input.closed:
                        f.set_exception(EOFError)

            # Enter raw mode.
            with self.input.raw_mode():
                with self.input.attach(read_from_input):
                    # Draw UI.
                    self._request_absolute_cursor_position()
                    self._redraw()

                    has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread()
                    if has_sigwinch:
                        previous_winch_handler = signal.getsignal(signal.SIGWINCH)
                        loop.add_signal_handler(signal.SIGWINCH, self._on_resize)

                    # Wait for UI to finish.
                    try:
                        result = await f
                    finally:
                        # In any case, when the application finishes. (Successful,
                        # or because of an error.)
                        try:
                            self._redraw(render_as_done=True)
                        finally:
                            # _redraw has a good chance to fail if it calls widgets
                            # with bad code. Make sure to reset the renderer anyway.
                            self.renderer.reset()

                            # Unset `is_running`, this ensures that possibly
                            # scheduled draws won't paint during the following
                            # yield.
                            self._is_running = False

                            # Detach event handlers for invalidate events.
                            # (Important when a UIControl is embedded in
                            # multiple applications, like ptterm in pymux. An
                            # invalidate should not trigger a repaint in
                            # terminated applications.)
                            for ev in self._invalidate_events:
                                ev -= self._invalidate_handler
                            self._invalidate_events = []

                            # Wait for CPR responses.
                            if self.input.responds_to_cpr:
                                await self.renderer.wait_for_cpr_responses()

                            if has_sigwinch:
                                loop.remove_signal_handler(signal.SIGWINCH)
                                signal.signal(signal.SIGWINCH, previous_winch_handler)

                            # Wait for the run-in-terminals to terminate.
                            previous_run_in_terminal_f = self._running_in_terminal_f

                            if previous_run_in_terminal_f:
                                await previous_run_in_terminal_f

                            # Store unprocessed input as typeahead for next time.
                            store_typeahead(self.input, self.key_processor.empty_queue())

                return result
Exemple #11
0
    def run(self, stdin, callbacks):
        """
        The input 'event loop'.
        """
        assert isinstance(stdin, Input)
        assert isinstance(callbacks, EventLoopCallbacks)
        assert not self._running

        if self.closed:
            raise Exception('Event loop already closed.')

        self._running = True
        self._callbacks = callbacks

        inputstream = InputStream(callbacks.feed_key)
        current_timeout = [INPUT_TIMEOUT]  # Nonlocal

        # Create reader class.
        stdin_reader = PosixStdinReader(stdin.fileno())

        # Only attach SIGWINCH signal handler in main thread.
        # (It's not possible to attach signal handlers in other threads. In
        # that case we should rely on a the main thread to call this manually
        # instead.)
        if in_main_thread():
            ctx = call_on_sigwinch(self.received_winch)
        else:
            ctx = DummyContext()

        def read_from_stdin():
            " Read user input. "
            # Feed input text.
            data = stdin_reader.read()
            inputstream.feed(data)

            # Set timeout again.
            current_timeout[0] = INPUT_TIMEOUT

            # Quit when the input stream was closed.
            if stdin_reader.closed:
                self.stop()

        self.add_reader(stdin, read_from_stdin)
        self.add_reader(self._schedule_pipe[0], None)

        with ctx:
            while self._running:
                # Call inputhook.
                with TimeIt() as inputhook_timer:
                    if self._inputhook_context:
                        def ready(wait):
                            " True when there is input ready. The inputhook should return control. "
                            return self._ready_for_reading(current_timeout[0] if wait else 0) != []
                        self._inputhook_context.call_inputhook(ready)

                # Calculate remaining timeout. (The inputhook consumed some of the time.)
                if current_timeout[0] is None:
                    remaining_timeout = None
                else:
                    remaining_timeout = max(0, current_timeout[0] - inputhook_timer.duration)

                # Wait until input is ready.
                fds = self._ready_for_reading(remaining_timeout)

                # When any of the FDs are ready. Call the appropriate callback.
                if fds:
                    # Create lists of high/low priority tasks. The main reason
                    # for this is to allow painting the UI to happen as soon as
                    # possible, but when there are many events happening, we
                    # don't want to call the UI renderer 1000x per second. If
                    # the eventloop is completely saturated with many CPU
                    # intensive tasks (like processing input/output), we say
                    # that drawing the UI can be postponed a little, to make
                    # CPU available. This will be a low priority task in that
                    # case.
                    tasks = []
                    low_priority_tasks = []
                    now = _now()

                    for fd in fds:
                        # For the 'call_from_executor' fd, put each pending
                        # item on either the high or low priority queue.
                        if fd == self._schedule_pipe[0]:
                            for c, max_postpone_until in self._calls_from_executor:
                                if max_postpone_until is None or max_postpone_until < now:
                                    tasks.append(c)
                                else:
                                    low_priority_tasks.append((c, max_postpone_until))
                            self._calls_from_executor = []

                            # Flush all the pipe content.
                            os.read(self._schedule_pipe[0], 1024)
                        else:
                            handler = self._read_fds.get(fd)
                            if handler:
                                tasks.append(handler)

                    # Handle everything in random order. (To avoid starvation.)
                    random.shuffle(tasks)
                    random.shuffle(low_priority_tasks)

                    # When there are high priority tasks, run all these.
                    # Schedule low priority tasks for the next iteration.
                    if tasks:
                        for t in tasks:
                            t()

                        # Postpone low priority tasks.
                        for t, max_postpone_until in low_priority_tasks:
                            self.call_from_executor(t, _max_postpone_until=max_postpone_until)
                    else:
                        # Currently there are only low priority tasks -> run them right now.
                        for t, _ in low_priority_tasks:
                            t()

                else:
                    # Flush all pending keys on a timeout. (This is most
                    # important to flush the vt100 'Escape' key early when
                    # nothing else follows.)
                    inputstream.flush()

                    # Fire input timeout event.
                    callbacks.input_timeout()
                    current_timeout[0] = None

        self.remove_reader(stdin)
        self.remove_reader(self._schedule_pipe[0])

        self._callbacks = None
    def run(self, stdin, callbacks):
        """
        The input 'event loop'.
        """
        assert isinstance(callbacks, EventLoopCallbacks)
        assert not self._running

        if self.closed:
            raise Exception('Event loop already closed.')

        self._running = True
        self._callbacks = callbacks

        inputstream = InputStream(callbacks.feed_key)
        current_timeout = INPUT_TIMEOUT

        # Create reader class.
        stdin_reader = PosixStdinReader(stdin)

        # Only attach SIGWINCH signal handler in main thread.
        # (It's not possible to attach signal handlers in other threads. In
        # that case we should rely on a the main thread to call this manually
        # instead.)
        if in_main_thread():
            ctx = call_on_sigwinch(self.received_winch)
        else:
            ctx = DummyContext()

        with ctx:
            while self._running:
                # Call inputhook.
                with TimeIt() as inputhook_timer:
                    if self._inputhook_context:
                        def ready(wait):
                            " True when there is input ready. The inputhook should return control. "
                            return self._ready_for_reading(stdin, current_timeout if wait else 0) != []
                        self._inputhook_context.call_inputhook(ready)

                # Calculate remaining timeout. (The inputhook consumed some of the time.)
                if current_timeout is None:
                    remaining_timeout = None
                else:
                    remaining_timeout = max(0, current_timeout - inputhook_timer.duration)

                # Wait until input is ready.
                r = self._ready_for_reading(stdin, remaining_timeout)

                # If we got a character, feed it to the input stream. If we got
                # none, it means we got a repaint request.
                if stdin in r:
                    # Feed input text.
                    data = stdin_reader.read()
                    inputstream.feed(data)
                    callbacks.redraw()

                    # Set timeout again.
                    current_timeout = INPUT_TIMEOUT

                # If we receive something on our "call_from_executor" pipe, process
                # these callbacks in a thread safe way.
                elif self._schedule_pipe[0] in r:
                    # Flush all the pipe content.
                    os.read(self._schedule_pipe[0], 1024)

                    # Process calls from executor.
                    calls_from_executor, self._calls_from_executor = self._calls_from_executor, []
                    for c in calls_from_executor:
                        c()
                else:
                    # Flush all pending keys on a timeout and redraw. (This is
                    # most important to flush the vt100 escape key early when
                    # nothing else follows.)
                    inputstream.flush()
                    callbacks.redraw()

                    # Fire input timeout event.
                    callbacks.input_timeout()
                    current_timeout = None

        self._callbacks = None
    def run(self, stdin, callbacks):
        """
        The input 'event loop'.
        """
        assert isinstance(stdin, Input)
        assert isinstance(callbacks, EventLoopCallbacks)
        assert not self._running

        if self.closed:
            raise Exception('Event loop already closed.')

        self._running = True
        self._callbacks = callbacks

        inputstream = InputStream(callbacks.feed_key)
        current_timeout = INPUT_TIMEOUT

        # Create reader class.
        stdin_reader = PosixStdinReader(stdin.fileno())

        # Only attach SIGWINCH signal handler in main thread.
        # (It's not possible to attach signal handlers in other threads. In
        # that case we should rely on a the main thread to call this manually
        # instead.)
        if in_main_thread():
            ctx = call_on_sigwinch(self.received_winch)
        else:
            ctx = DummyContext()

        with ctx:
            while self._running:
                # Call inputhook.
                with TimeIt() as inputhook_timer:
                    if self._inputhook_context:

                        def ready(wait):
                            " True when there is input ready. The inputhook should return control. "
                            return self._ready_for_reading(
                                stdin, current_timeout if wait else 0) != []

                        self._inputhook_context.call_inputhook(ready)

                # Calculate remaining timeout. (The inputhook consumed some of the time.)
                if current_timeout is None:
                    remaining_timeout = None
                else:
                    remaining_timeout = max(
                        0, current_timeout - inputhook_timer.duration)

                # Wait until input is ready.
                r = self._ready_for_reading(stdin, remaining_timeout)

                # If we got a character, feed it to the input stream. If we got
                # none, it means we got a repaint request.
                if stdin in r:
                    # Feed input text.
                    data = stdin_reader.read()
                    inputstream.feed(data)

                    # Set timeout again.
                    current_timeout = INPUT_TIMEOUT

                # When any of the registered read pipes are ready. Call the
                # appropriate callback.
                elif set(r) & set(self._read_fds):
                    for fd in r:
                        handler = self._read_fds.get(fd)
                        if handler:
                            handler()

                # If we receive something on our "call_from_executor" pipe, process
                # these callbacks in a thread safe way.
                elif self._schedule_pipe[0] in r:
                    # Flush all the pipe content.
                    os.read(self._schedule_pipe[0], 1024)

                    # Process calls from executor.
                    calls_from_executor, self._calls_from_executor = self._calls_from_executor, []
                    for c in calls_from_executor:
                        c()

                else:
                    # Flush all pending keys on a timeout. (This is most
                    # important to flush the vt100 'Escape' key early when
                    # nothing else follows.)
                    inputstream.flush()

                    # Fire input timeout event.
                    callbacks.input_timeout()
                    current_timeout = None

        self._callbacks = None
    def __enter__(self):
        # Create UI Application.
        title_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.title), height=1, style='class:progressbar,title'),
            filter=Condition(lambda: self.title is not None))

        bottom_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.bottom_toolbar,
                                        style='class:bottom-toolbar.text'),
                   style='class:bottom-toolbar',
                   height=1),
            filter=~is_done & renderer_height_is_known &
                Condition(lambda: self.bottom_toolbar is not None))

        def width_for_formatter(formatter):
            # Needs to be passed as callable (partial) to the 'width'
            # parameter, because we want to call it on every resize.
            return formatter.get_width(progress_bar=self)

        progress_controls = [
            Window(
                content=_ProgressControl(self, f),
                width=functools.partial(width_for_formatter, f))
            for f in self.formatters
        ]

        self.app = Application(
            min_redraw_interval=.05,
            layout=Layout(HSplit([
                title_toolbar,
                VSplit(progress_controls,
                       height=lambda: D(
                           preferred=len(self.counters),
                           max=len(self.counters))),
                Window(),
                bottom_toolbar,
            ])),
            style=self.style,
            key_bindings=self.key_bindings,
            color_depth=self.color_depth,
            output=self.output,
            input=self.input)

        # Run application in different thread.
        def run():
            with _auto_refresh_context(self.app, .3):
                try:
                    self.app.run()
                except BaseException as e:
                    traceback.print_exc()
                    print(e)

        self._thread = threading.Thread(target=run)
        self._thread.start()

        # Attach WINCH signal handler in main thread.
        # (Interrupt that we receive during resize events.)
        self._has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread()
        if self._has_sigwinch:
            self._previous_winch_handler = self._loop.add_signal_handler(
                signal.SIGWINCH, self.app.invalidate)

        return self