Example #1
0
    def __init__(self,
                 title='',
                 text='',
                 ok_text='Ok',
                 width=None,
                 wrap_lines=True,
                 scrollbar=False):
        self.future = Future()

        def accept_text(buf):
            get_app().layout.focus(ok_button)
            buf.complete_state = None
            return True

        def accept():
            self.future.set_result(self.text_area.text)

        def cancel():
            self.future.set_result(None)

        text_width = len(max(text.split('\n'), key=len)) + 2

        self.text_area = TextArea(completer=completer,
                                  multiline=False,
                                  width=D(preferred=text_width),
                                  accept_handler=accept_text)

        ok_button = Button(text='OK', handler=accept)
        cancel_button = Button(text='Cancel', handler=cancel)

        self.dialog = Dialog(title=title,
                             body=HSplit([Label(text=text), self.text_area]),
                             buttons=[ok_button, cancel_button],
                             width=width,
                             modal=True)
Example #2
0
    def wait_for_cpr_responses(self, timeout=1):
        """
        Wait for a CPR response.
        """
        cpr_futures = list(self._waiting_for_cpr_futures)  # Make copy.

        # When there are no CPRs in the queue. Don't do anything.
        if not cpr_futures or self.cpr_support == CPR_Support.NOT_SUPPORTED:
            return Future.succeed(None)

        f = Future()

        # When a CPR has been received, set the result.
        def wait_for_responses():
            for response_f in cpr_futures:
                yield From(response_f)
            if not f.done():
                f.set_result(None)

        ensure_future(wait_for_responses())

        # Timeout.
        def wait_for_timeout():
            time.sleep(timeout)

            # Got timeout.
            if not f.done():
                self._waiting_for_cpr_futures = deque()
                f.set_result(None)

        t = threading.Thread(target=wait_for_timeout)
        t.daemon = True
        t.start()

        return f
    def __init__(self, title='', label_text='', completer=None):
        self.future = Future()

        def accept_text():
            get_app().layout.focus(ok_button)
            self.text_area.buffer.complete_state = None

        def accept():
            self.future.set_result(self.text_area.text)

        def cancel():
            self.future.set_result(None)

        self.text_area = TextArea(
            completer=completer,
            multiline=False,
            width=D(preferred=40),
            accept_handler=accept_text)

        ok_button = Button(text='OK', handler=accept)
        cancel_button = Button(text='Cancel', handler=cancel)

        self.dialog = Dialog(
            title=title,
            body=HSplit([
                Label(text=label_text),
                self.text_area
            ]),
            buttons=[ok_button, cancel_button],
            width=D(preferred=80),
            modal=True)
Example #4
0
    def __init__(self,
                 title='',
                 text='',
                 ok_text='Ok',
                 lexer=None,
                 width=None,
                 wrap_lines=True,
                 scrollbar=False):
        self.future = Future()
        self.text = text

        def set_done():
            self.future.set_result(None)

        def get_text_width():
            if width is None:
                text_fragments = to_formatted_text(self.text)
                text = fragment_list_to_text(text_fragments)
                if text:
                    longest_line = max(
                        get_cwidth(line) for line in text.splitlines())
                else:
                    return D(preferred=0)
                return D(preferred=longest_line)
            else:
                return width

        # text_width = len(max(self.text.split('\n'), key=len)) + 2
        # text_height = len(text.split('\n'))

        # TODO: Add dynamic_h_scrollbar to TextArea and this Dialog
        # def dynamic_horizontal_scrollbar():
        #     max_text_width = get_app().renderer.output.get_size().columns - 2

        def dynamic_virtical_scrollbar():
            text_fragments = to_formatted_text(self.text)
            text = fragment_list_to_text(text_fragments)
            if text:
                text_height = len(self.text.splitlines())
                max_text_height = get_app().renderer.output.get_size().rows - 6
                if text_height > max_text_height:
                    return True

        self.text_area = TextArea(text=text,
                                  lexer=lexer,
                                  read_only=True,
                                  focusable=False,
                                  width=get_text_width(),
                                  wrap_lines=wrap_lines,
                                  scrollbar=dynamic_virtical_scrollbar())

        ok_button = Button(text='OK', handler=(lambda: set_done()))

        self.dialog = Dialog(title=title,
                             body=self.text_area,
                             buttons=[ok_button],
                             width=width,
                             modal=True)
Example #5
0
    def __init__(self, source_file=None, startup_command=None):
        self._client_states = {}  # connection -> client_state

        # Options
        self.enable_mouse_support = True
        self.enable_status = True
        self.enable_pane_status = True  #False
        self.enable_bell = True
        self.remain_on_exit = False
        self.status_keys_vi_mode = False
        self.mode_keys_vi_mode = False
        self.history_limit = 2000
        self.status_interval = 4
        self.default_terminal = 'xterm-256color'
        self.status_left = '[#S] '
        self.status_left_length = 20
        self.status_right = ' %H:%M %d-%b-%y '
        self.status_right_length = 20
        self.window_status_current_format = '#I:#W#F'
        self.window_status_format = '#I:#W#F'
        self.session_name = '0'
        self.status_justify = Justify.LEFT
        self.default_shell = get_default_shell()
        self.swap_dark_and_light = False

        self.options = ALL_OPTIONS
        self.window_options = ALL_WINDOW_OPTIONS

        # When no panes are available.
        self.original_cwd = os.getcwd()

        self.display_pane_numbers = False

        #: List of clients.
        self._runs_standalone = False
        self.connections = []
        self.done_f = Future()

        self._startup_done = False
        self.source_file = source_file
        self.startup_command = startup_command

        # Keep track of all the panes, by ID. (For quick lookup.)
        self.panes_by_id = weakref.WeakValueDictionary()

        # Socket information.
        self.socket = None
        self.socket_name = None

        # Key bindings manager.
        self.key_bindings_manager = PymuxKeyBindings(self)

        self.arrangement = Arrangement()

        self.style = ui_style
Example #6
0
File: posix.py Project: riag/ptterm
    def __init__(self, exec_func):
        self.exec_func = exec_func

        # Create pseudo terminal for this pane.
        self.master, self.slave = os.openpty()

        # Master side -> attached to terminal emulator.
        self._reader = PosixStdinReader(self.master, errors='replace')
        self._reader_connected = False
        self._input_ready_callbacks = []

        self.ready_f = Future()
        self.loop = get_event_loop()
        self.pid = None
Example #7
0
def _async_generator():
    " Simple asynchronous generator. "

    # await.
    result = yield From(Future.succeed(1))

    # yield
    yield AsyncGeneratorItem(result + 1)

    # await.
    result = yield From(Future.succeed(10))

    # yield
    yield AsyncGeneratorItem(result + 1)
Example #8
0
    def __init__(self, title, text):
        self.future = Future()

        def set_done():
            self.future.set_result(None)

        ok_button = Button(text='OK', handler=(lambda: set_done()))

        self.dialog = Dialog(title=title,
                             body=HSplit([
                                 Label(text=text),
                             ]),
                             buttons=[ok_button],
                             width=D(preferred=80),
                             modal=True)
Example #9
0
        def run():
            with context() as ctx_id:
                self._context_id = ctx_id

                # Set input/output for all application running in this context.
                set_default_input(self.vt100_input)
                set_default_output(self.vt100_output)

                # Add reader.
                loop = get_event_loop()
                loop.add_reader(self.conn, handle_incoming_data)

                try:
                    obj = self.interact(self)
                    if _is_coroutine(obj):
                        # Got an asyncio coroutine.
                        import asyncio
                        f = asyncio.ensure_future(obj)
                        yield From(Future.from_asyncio_future(f))
                    else:
                        # Got a prompt_toolkit coroutine.
                        yield From(obj)
                except Exception as e:
                    print('Got %s' % type(e).__name__, e)
                    import traceback
                    traceback.print_exc()
                    raise
                finally:
                    self.close()
    def wait_for_cpr_responses(self, timeout=1):
        """
        Wait for a CPR response.
        """
        cpr_futures = list(self._waiting_for_cpr_futures)  # Make copy.

        # When there are no CPRs in the queue. Don't do anything.
        if not cpr_futures or self.cpr_support == CPR_Support.NOT_SUPPORTED:
            return Future.succeed(None)

        f = Future()

        # When a CPR has been received, set the result.
        def wait_for_responses():
            for response_f in cpr_futures:
                yield From(response_f)
            if not f.done():
                f.set_result(None)
        ensure_future(wait_for_responses())

        # Timeout.
        def wait_for_timeout():
            time.sleep(timeout)

            # Got timeout.
            if not f.done():
                self._waiting_for_cpr_futures = deque()
                f.set_result(None)

        t = threading.Thread(target=wait_for_timeout)
        t.daemon = True
        t.start()

        return f
Example #11
0
    def __init__(self, title='', text='', label='', values=[], padding=4, completer=None):
        self.future = Future()

        self.radios = RadioList(values=values)
        # radios.current_value will contain the first component of the selected tuple 
        # title = "Delete"
        # values =[
        #     (0, 'this instance'),
        #     (1, 'this and all subsequent instances'),
        #     (2, 'this and all previous instances'),
        #     (3, 'all instances - the item itself'),
        # ]

        def accept():
            self.future.set_result(self.radios.current_value)

        def cancel():
            self.future.set_result(None)


        ok_button = Button(text='OK', handler=accept)
        cancel_button = Button(text='Cancel', handler=cancel)

        self.dialog = Dialog(
            title=title,
            body=HSplit([
                Label(text=text),
                Frame(title=label, body=self.radios)
            ]),
            # body= Frame(title=label, body=self.radios),
            buttons=[ok_button, cancel_button],
            width=D(preferred=shutil.get_terminal_size()[0]-10),
            modal=True)
Example #12
0
def _read_chunk_from_socket(socket):
    """
    (coroutine)
    Turn socket reading into coroutine.
    """
    fd = socket.fileno()
    f = Future()

    def read_callback():
        get_event_loop().remove_reader(fd)

        # Read next chunk.
        try:
            data = socket.recv(1024)
        except OSError as e:
            # On OSX, when we try to create a new window by typing "pymux
            # new-window" in a centain pane, very often we get the following
            # error: "OSError: [Errno 9] Bad file descriptor."
            # This doesn't seem very harmful, and we can just try again.
            logger.warning(
                'Got OSError while reading data from client: %s. '
                'Trying again.', e)
            f.set_result('')
            return

        if data:
            f.set_result(data)
        else:
            f.set_exception(BrokenPipeError)

    get_event_loop().add_reader(fd, read_callback)

    return f
    def __init__(self, title='', label_text='', completer=None):
        self.future = Future()

        def accept_text(buf):
            get_app().layout.focus(ok_button)
            buf.complete_state = None
            return True

        def accept():
            self.future.set_result(self.text_area.text)

        def cancel():
            self.future.set_result(None)

        self.text_area = TextArea(
            completer=completer,
            multiline=False,
            width=D(preferred=40),
            accept_handler=accept_text)

        ok_button = Button(text='OK', handler=accept)
        cancel_button = Button(text='Cancel', handler=cancel)

        self.dialog = Dialog(
            title=title,
            body=HSplit([
                Label(text=label_text),
                self.text_area
            ]),
            buttons=[ok_button, cancel_button],
            width=D(preferred=80),
            modal=True)
Example #14
0
class InteractiveInputDialog(object):
    def __init__(self, title='', help_text='', evaluator=None, padding=10, completer=None):
        self.future = Future()

        def cancel():
            self.future.set_result(None)

        self.output_field = TextArea(
                text='',
                focusable=False,
                )
        self.input_field = TextArea(
            height=1, prompt='>>> ', multiline=False,
            focusable=True,
            wrap_lines=False)

        def accept(buff):
            # Evaluate "calculator" expression.
            try:
                output = 'In:  {}\nOut: {}\n\n'.format(
                    self.input_field.text,
                    evaluator(self.input_field.text))  
            except BaseException as e:
                output = '\n\n{}'.format(e)
            new_text = self.output_field.text + output

            # Add text to output buffer.
            self.output_field.buffer.text = new_text

        self.input_field.accept_handler = accept

        cancel_button = Button(text='Cancel', handler=cancel)

        self.dialog = Dialog(
            title=title,
            body=HSplit([
                Label(text=help_text),
                self.output_field,
                HorizontalLine(),
                self.input_field,
            ]),
            buttons=[cancel_button],
            width=D(preferred=shutil.get_terminal_size()[0]-padding),
            modal=True)

    def __pt_container__(self):
        return self.dialog
Example #15
0
class YesNoDialog(object):
    def __init__(self,
                 title='',
                 text='',
                 yes_text='Yes',
                 no_text='No',
                 width=None,
                 wrap_lines=True,
                 scrollbar=False):
        self.future = Future()

        def yes_handler():
            self.future.set_result(True)

        def no_handler():
            self.future.set_result(False)

        text_width = len(max(text.split('\n'), key=len)) + 2

        self.text_area = TextArea(
            text=text,
            read_only=True,
            # focus_on_click = True,
            focusable=False,
            width=D(preferred=text_width),
            wrap_lines=wrap_lines,
            scrollbar=scrollbar)

        self.dialog = Dialog(title=title,
                             body=self.text_area,
                             buttons=[
                                 Button(text=yes_text,
                                        width=1,
                                        handler=yes_handler),
                                 Button(text=no_text,
                                        width=1,
                                        handler=no_handler)
                             ],
                             with_background=True,
                             modal=True)

    def __pt_container__(self):
        return self.dialog
Example #16
0
def async_6():
    " Create a `Future` and call `set_exception` later on. "
    f = Future()

    def in_executor():
        time.sleep(.2)
        f.set_exception(Exception('Failure from async_6'))

    run_in_executor(in_executor)
    return f
Example #17
0
def async_5():
    " Create a `Future` and call `set_result` later on. "
    f = Future()

    def in_executor():
        time.sleep(.2)
        f.set_result('Hello from async_5')

    run_in_executor(in_executor)
    return f
Example #18
0
    def __init__(self, source_file=None, startup_command=None):
        self._client_states = {}  # connection -> client_state

        # Options
        self.enable_mouse_support = True
        self.enable_status = True
        self.enable_pane_status = True#False
        self.enable_bell = True
        self.remain_on_exit = False
        self.status_keys_vi_mode = False
        self.mode_keys_vi_mode = False
        self.history_limit = 2000
        self.status_interval = 4
        self.default_terminal = 'xterm-256color'
        self.status_left = '[#S] '
        self.status_left_length = 20
        self.status_right = ' %H:%M %d-%b-%y '
        self.status_right_length = 20
        self.window_status_current_format = '#I:#W#F'
        self.window_status_format = '#I:#W#F'
        self.session_name = '0'
        self.status_justify = Justify.LEFT
        self.default_shell = get_default_shell()
        self.swap_dark_and_light = False

        self.options = ALL_OPTIONS
        self.window_options = ALL_WINDOW_OPTIONS

        # When no panes are available.
        self.original_cwd = os.getcwd()

        self.display_pane_numbers = False

        #: List of clients.
        self._runs_standalone = False
        self.connections = []
        self.done_f = Future()

        self._startup_done = False
        self.source_file = source_file
        self.startup_command = startup_command

        # Keep track of all the panes, by ID. (For quick lookup.)
        self.panes_by_id = weakref.WeakValueDictionary()

        # Socket information.
        self.socket = None
        self.socket_name = None

        # Key bindings manager.
        self.key_bindings_manager = PymuxKeyBindings(self)

        self.arrangement = Arrangement()

        self.style = ui_style
Example #19
0
class MessageDialog(object):
    def __init__(self,
                 title='',
                 text='',
                 ok_text='Ok',
                 shadow=False,
                 width=None,
                 wrap_lines=True,
                 scrollbar=False):
        self.future = Future()

        def set_done():
            self.future.set_result(None)

        text_width = len(max(text.split('\n'), key=len)) + 2
        text_height = len(text.split('\n'))
        app_height = get_app(
        ).ctui.layout.output_field.window.render_info.window_height - 2

        def dynamic_scrollbar():
            if text_height > app_height:
                return True

        self.text_area = TextArea(
            text=text,
            read_only=True,
            # focus_on_click = True,
            focusable=False,
            width=D(preferred=text_width),
            wrap_lines=wrap_lines,
            scrollbar=dynamic_scrollbar())

        ok_button = Button(text='OK', handler=(lambda: set_done()))

        self.dialog = Dialog(title=title,
                             body=self.text_area,
                             buttons=[ok_button],
                             width=width,
                             modal=True)

    def __pt_container__(self):
        return self.dialog
class MessageDialog(object):
    def __init__(self, title, text):
        self.future = Future()

        def set_done():
            self.future.set_result(None)

        ok_button = Button(text='OK', handler=(lambda: set_done()))

        self.dialog = Dialog(
            title=title,
            body=HSplit([
                Label(text=text),
            ]),
            buttons=[ok_button],
            width=D(preferred=80),
            modal=True)

    def __pt_container__(self):
        return self.dialog
Example #21
0
 def _run_in_terminal(self, func):
     # Make sure that when an application was active for this connection,
     # that we print the text above the application.
     with context(self._context_id):
         try:
             app = get_app(raise_exception=True)
         except NoRunningApplicationError:
             func()
             return Future.succeed(None)
         else:
             return app.run_in_terminal(func)
Example #22
0
    def write(self, message):
        """
        Coroutine that writes the next packet.
        """
        try:
            self.socket.send(message.encode('utf-8') + b'\0')
        except socket.error:
            if not self._closed:
                raise BrokenPipeError

        return Future.succeed(None)
Example #23
0
    def write(self, message):
        """
        Coroutine that writes the next packet.
        """
        try:
            self.socket.send(message.encode('utf-8') + b'\0')
        except socket.error:
            if not self._closed:
                raise BrokenPipeError

        return Future.succeed(None)
Example #24
0
def wait_for_event(event):
    """
    Wraps a win32 event into a `Future` and wait for it.
    """
    f = Future()

    def ready():
        get_event_loop().remove_win32_handle(event)
        f.set_result(None)

    get_event_loop().add_win32_handle(event, ready)
    return f
Example #25
0
class TextInputDialog(object):
    def __init__(self, title='', label_text='', default='', padding=10, completer=None):
        self.future = Future()

        def accept_text(buf):
            get_app().layout.focus(ok_button)
            buf.complete_state = None
            return True

        def accept():
            self.future.set_result(self.text_area.text)

        def cancel():
            self.future.set_result(None)

        self.text_area = TextArea(
            completer=completer,
            text=default,
            multiline=False,
            width=D(preferred=shutil.get_terminal_size()[0]-padding),
            accept_handler=accept_text)

        ok_button = Button(text='OK', handler=accept)
        cancel_button = Button(text='Cancel', handler=cancel)

        self.dialog = Dialog(
            title=title,
            body=HSplit([
                Label(text=label_text),
                self.text_area
            ]),
            buttons=[ok_button, cancel_button],
            # buttons=[ok_button],
            width=D(preferred=shutil.get_terminal_size()[0]-10),
            modal=True)

    def __pt_container__(self):
        return self.dialog
Example #26
0
class ConfirmDialog(object):
    def __init__(self, title="", text="", padding=10):
        self.future = Future()

        def set_yes():
            self.future.set_result(True)
        def set_no():
            self.future.set_result(False)

        yes_button = Button(text='Yes', handler=(lambda: set_yes()))
        no_button = Button(text='No', handler=(lambda: set_no()))

        self.dialog = Dialog(
            title=title,
            body=HSplit([
                Label(text=text),
            ]),
            buttons=[yes_button, no_button],
            width=D(preferred=shutil.get_terminal_size()[0]-padding),
            modal=True)

    def __pt_container__(self):
        return self.dialog
Example #27
0
    def __init__(self, title="", text="", padding=10):
        self.future = Future()

        def set_done():
            self.future.set_result(None)

        ok_button = Button(text='OK', handler=(lambda: set_done()))

        self.dialog = Dialog(
            title=title,
            body=HSplit([
                Label(text=text),
            ]),
            buttons=[ok_button],
            width=D(preferred=shutil.get_terminal_size()[0]-padding),
            modal=True)
Example #28
0
    def request_absolute_cursor_position(self):
        """
        Get current cursor position.
        For vt100: Do CPR request. (answer will arrive later.)
        For win32: Do API call. (Answer comes immediately.)
        """
        # Only do this request when the cursor is at the top row. (after a
        # clear or reset). We will rely on that in `report_absolute_cursor_row`.
        assert self._cursor_pos.y == 0

        # For Win32, we have an API call to get the number of rows below the
        # cursor.
        if is_windows():
            self._min_available_height = self.output.get_rows_below_cursor_position(
            )
        else:
            if self.full_screen:
                self._min_available_height = self.output.get_size().rows
            elif self.cpr_support == CPR_Support.NOT_SUPPORTED:
                return
            else:
                # Asks for a cursor position report (CPR).
                self._waiting_for_cpr_futures.append(Future())
                self.output.ask_for_cpr()

                # If we don't know whether CPR is supported, test using timer.
                if self.cpr_support == CPR_Support.UNKNOWN:

                    def timer():
                        time.sleep(self.CPR_TIMEOUT)

                        # Not set in the meantime -> not supported.
                        if self.cpr_support == CPR_Support.UNKNOWN:
                            self.cpr_support = CPR_Support.NOT_SUPPORTED

                            if self.cpr_not_supported_callback:
                                # Make sure to call this callback in the main thread.
                                get_event_loop().call_from_executor(
                                    self.cpr_not_supported_callback)

                    t = threading.Thread(target=timer)
                    t.daemon = True
                    t.start()
Example #29
0
    def __init__(self, mic, recognizer):
        self.future = future = Future()

        def listen():
            with mic as source:
                recognizer.adjust_for_ambient_noise(source, 0.5)
                audio = recognizer.listen(source)
            future.set_result(audio)

        run_in_executor(listen)

        self.dialog = Dialog(title='LISTENING',
                             body=HSplit([
                                 TextArea(text='',
                                          multiline=False,
                                          read_only=True),
                             ]),
                             width=D(preferred=80),
                             modal=True)
Example #30
0
    def __init__(self, choices, title=''):
        self.future = future = Future()

        text = 'Did you mean one of the following?'
        buttons = []

        for no, cmd in enumerate(choices):
            text += '\n{no}. {cmd}'.format(no=no, cmd=cmd)
            buttons.append(
                Button(text=str(no),
                       handler=lambda i=no: future.set_result(i)))

        buttons.append(
            Button(text='none', handler=lambda: future.set_result(None)))

        self.dialog = Dialog(title=title,
                             body=HSplit([
                                 Label(text=text),
                             ]),
                             buttons=buttons,
                             width=D(preferred=80),
                             modal=True)
Example #31
0
class Win32PipeConnection(PipeConnection):
    """
    A single active Win32 pipe connection on the server side.
    """
    def __init__(self, pipe_instance):
        assert isinstance(pipe_instance, PipeInstance)
        self.pipe_instance = pipe_instance
        self.done_f = Future()

    def read(self):
        """
        (coroutine)
        Read a single message from the pipe. (Return as text.)
        """
        if self.done_f.done():
            raise BrokenPipeError

        try:
            result = yield From(
                read_message_from_pipe(self.pipe_instance.pipe_handle))
            raise Return(result)
        except BrokenPipeError:
            self.done_f.set_result(None)
            raise

    def write(self, message):
        """
        (coroutine)
        Write a single message into the pipe.
        """
        if self.done_f.done():
            raise BrokenPipeError

        try:
            yield From(
                write_message_to_pipe(self.pipe_instance.pipe_handle, message))
        except BrokenPipeError:
            self.done_f.set_result(None)
            raise

    def close(self):
        pass
Example #32
0
class Win32PipeConnection(PipeConnection):
    """
    A single active Win32 pipe connection on the server side.
    """
    def __init__(self, pipe_instance):
        assert isinstance(pipe_instance, PipeInstance)
        self.pipe_instance = pipe_instance
        self.done_f = Future()

    def read(self):
        """
        (coroutine)
        Read a single message from the pipe. (Return as text.)
        """
        if self.done_f.done():
            raise BrokenPipeError

        try:
            result = yield From(read_message_from_pipe(self.pipe_instance.pipe_handle))
            raise Return(result)
        except BrokenPipeError:
            self.done_f.set_result(None)
            raise

    def write(self, message):
        """
        (coroutine)
        Write a single message into the pipe.
        """
        if self.done_f.done():
            raise BrokenPipeError

        try:
            yield From(write_message_to_pipe(self.pipe_instance.pipe_handle, message))
        except BrokenPipeError:
            self.done_f.set_result(None)
            raise

    def close(self):
        pass
Example #33
0
 def __init__(self, pipe_instance):
     assert isinstance(pipe_instance, PipeInstance)
     self.pipe_instance = pipe_instance
     self.done_f = Future()
def async_3():
    " Succeed immediately. "
    return Future.succeed('Hello from async_3')
Example #35
0
 def do_cpr():
     # Asks for a cursor position report (CPR).
     self._waiting_for_cpr_futures.append(Future())
     self.output.ask_for_cpr()
Example #36
0
 def __init__(self, pipe_instance):
     assert isinstance(pipe_instance, PipeInstance)
     self.pipe_instance = pipe_instance
     self.done_f = Future()
Example #37
0
def other_coroutine():
    value = yield From(Future.succeed(True))
    value = yield From(Future.succeed(True))
    raise Return('Result from coroutine.')
def async_4():
    " Fail immediately. "
    return Future.fail(Exception('Failure from async_4'))
Example #39
0
 def async_func():
     result = func()
     return Future.succeed(result)
Example #40
0
def async_3():
    " Succeed immediately. "
    return Future.succeed('Hello from async_3')
def other_coroutine():
    value = yield From(Future.succeed(True))
    value = yield From(Future.succeed(True))
    raise Return('Result from coroutine.')
Example #42
0
def async_4():
    " Fail immediately. "
    return Future.fail(Exception('Failure from async_4'))
Example #43
0
class Pymux(object):
    """
    The main Pymux application class.

    Usage:

        p = Pymux()
        p.listen_on_socket()
        p.run_server()

    Or:

        p = Pymux()
        p.run_standalone()
    """
    def __init__(self, source_file=None, startup_command=None):
        self._client_states = {}  # connection -> client_state

        # Options
        self.enable_mouse_support = True
        self.enable_status = True
        self.enable_pane_status = True#False
        self.enable_bell = True
        self.remain_on_exit = False
        self.status_keys_vi_mode = False
        self.mode_keys_vi_mode = False
        self.history_limit = 2000
        self.status_interval = 4
        self.default_terminal = 'xterm-256color'
        self.status_left = '[#S] '
        self.status_left_length = 20
        self.status_right = ' %H:%M %d-%b-%y '
        self.status_right_length = 20
        self.window_status_current_format = '#I:#W#F'
        self.window_status_format = '#I:#W#F'
        self.session_name = '0'
        self.status_justify = Justify.LEFT
        self.default_shell = get_default_shell()
        self.swap_dark_and_light = False

        self.options = ALL_OPTIONS
        self.window_options = ALL_WINDOW_OPTIONS

        # When no panes are available.
        self.original_cwd = os.getcwd()

        self.display_pane_numbers = False

        #: List of clients.
        self._runs_standalone = False
        self.connections = []
        self.done_f = Future()

        self._startup_done = False
        self.source_file = source_file
        self.startup_command = startup_command

        # Keep track of all the panes, by ID. (For quick lookup.)
        self.panes_by_id = weakref.WeakValueDictionary()

        # Socket information.
        self.socket = None
        self.socket_name = None

        # Key bindings manager.
        self.key_bindings_manager = PymuxKeyBindings(self)

        self.arrangement = Arrangement()

        self.style = ui_style

    def _start_auto_refresh_thread(self):
        """
        Start the background thread that auto refreshes all clients according to
        `self.status_interval`.
        """
        def run():
            while True:
                time.sleep(self.status_interval)
                self.invalidate()

        t = threading.Thread(target=run)
        t.daemon = True
        t.start()

    @property
    def apps(self):
        return [c.app for c in self._client_states.values()]

    def get_client_state(self):
        " Return the active ClientState instance. "
        app = get_app()
        for client_state in self._client_states.values():
            if client_state.app == app:
                return client_state

        raise ValueError('Client state for app %r not found' % (app, ))

    def get_connection(self):
        " Return the active Connection instance. "
        app = get_app()
        for connection, client_state in self._client_states.items():
            if client_state.app == app:
                return connection

        raise ValueError('Connection for app %r not found' % (app, ))

    def startup(self):
        # Handle start-up comands.
        # (Does initial key bindings.)
        if not self._startup_done:
            self._startup_done = True

            # Execute default config.
            for cmd in STARTUP_COMMANDS.splitlines():
                self.handle_command(cmd)

            # Source the given file.
            if self.source_file:
                call_command_handler('source-file', self, [self.source_file])

            # Make sure that there is one window created.
            self.create_window(command=self.startup_command)

    def get_title(self):
        """
        The title to be displayed in the titlebar of the terminal.
        """
        w = self.arrangement.get_active_window()

        if w and w.active_process:
            title = w.active_process.screen.title
        else:
            title = ''

        if title:
            return '%s - Pymux' % (title, )
        else:
            return 'Pymux'

    def get_window_size(self):
        """
        Get the size to be used for the DynamicBody.
        This will be the smallest size of all clients.
        """
        def active_window_for_app(app):
            with set_app(app):
                return self.arrangement.get_active_window()

        active_window = self.arrangement.get_active_window()

        # Get sizes for connections watching the same window.
        apps = [client_state.app for client_state in self._client_states.values()
                if active_window_for_app(client_state.app) == active_window]
        sizes = [app.output.get_size() for app in apps]

        rows = [s.rows for s in sizes]
        columns = [s.columns for s in sizes]

        if rows and columns:
            return Size(rows=min(rows) - (1 if self.enable_status else 0),
                        columns=min(columns))
        else:
            return Size(rows=20, columns=80)

    def _create_pane(self, window=None, command=None, start_directory=None):
        """
        Create a new :class:`pymux.arrangement.Pane` instance. (Don't put it in
        a window yet.)

        :param window: If a window is given, take the CWD of the current
            process of that window as the start path for this pane.
        :param command: If given, run this command instead of `self.default_shell`.
        :param start_directory: If given, use this as the CWD.
        """
        assert window is None or isinstance(window, Window)
        assert command is None or isinstance(command, six.text_type)
        assert start_directory is None or isinstance(start_directory, six.text_type)

        def done_callback():
            " When the process finishes. "
            if not self.remain_on_exit:
                # Remove pane from layout.
                self.arrangement.remove_pane(pane)

                # No panes left? -> Quit.
                if not self.arrangement.has_panes:
                    self.stop()

                # Make sure the right pane is focused for each client.
                for client_state in self._client_states.values():
                    client_state.sync_focus()

            self.invalidate()

        def bell():
            " Sound bell on all clients. "
            if self.enable_bell:
                for c in self.apps:
                    c.output.bell()

        # Start directory.
        if start_directory:
            path = start_directory
        elif window and window.active_process:
            # When the path of the active process is known,
            # start the new process at the same location.
            path = window.active_process.get_cwd()
        else:
            path = None

        def before_exec():
            " Called in the process fork (in the child process). "
            # Go to this directory.
            try:
                os.chdir(path or self.original_cwd)
            except OSError:
                pass  # No such file or directory.

            # Set terminal variable. (We emulate xterm.)
            os.environ['TERM'] = self.default_terminal

            # Make sure to set the PYMUX environment variable.
            if self.socket_name:
                os.environ['PYMUX'] = '%s,%i' % (
                    self.socket_name, pane.pane_id)

        if command:
            command = command.split()
        else:
            command = [self.default_shell]

        # Create new pane and terminal.
        terminal = Terminal(done_callback=done_callback, bell_func=bell,
                            before_exec_func=before_exec)
        pane = Pane(terminal)

        # Keep track of panes. This is a WeakKeyDictionary, we only add, but
        # don't remove.
        self.panes_by_id[pane.pane_id] = pane

        logger.info('Created process %r.', command)

        return pane

    def invalidate(self):
        " Invalidate the UI for all clients. "
        logger.info('Invalidating %s applications', len(self.apps))

        for app in self.apps:
            app.invalidate()

    def stop(self):
        for app in self.apps:
            app.exit()
        self.done_f.set_result(None)

    def create_window(self, command=None, start_directory=None, name=None):
        """
        Create a new :class:`pymux.arrangement.Window` in the arrangement.
        """
        assert command is None or isinstance(command, six.text_type)
        assert start_directory is None or isinstance(start_directory, six.text_type)

        pane = self._create_pane(None, command, start_directory=start_directory)

        self.arrangement.create_window(pane, name=name)
        pane.focus()
        self.invalidate()

    def add_process(self, command=None, vsplit=False, start_directory=None):
        """
        Add a new process to the current window. (vsplit/hsplit).
        """
        assert command is None or isinstance(command, six.text_type)
        assert start_directory is None or isinstance(start_directory, six.text_type)

        window = self.arrangement.get_active_window()

        pane = self._create_pane(window, command, start_directory=start_directory)
        window.add_pane(pane, vsplit=vsplit)
        pane.focus()
        self.invalidate()

    def kill_pane(self, pane):
        """
        Kill the given pane, and remove it from the arrangement.
        """
        assert isinstance(pane, Pane)

        # Send kill signal.
        if not pane.process.is_terminated:
            pane.process.kill()

        # Remove from layout.
        self.arrangement.remove_pane(pane)

    def leave_command_mode(self, append_to_history=False):
        """
        Leave the command/prompt mode.
        """
        client_state = self.get_client_state()

        client_state.command_buffer.reset(append_to_history=append_to_history)
        client_state.prompt_buffer.reset(append_to_history=True)

        client_state.prompt_command = ''
        client_state.confirm_command = ''

        client_state.app.layout.focus_previous()

    def handle_command(self, command):
        """
        Handle command from the command line.
        """
        handle_command(self, command)

    def show_message(self, message):
        """
        Set a warning message. This will be shown at the bottom until a key has
        been pressed.

        :param message: String.
        """
        self.get_client_state().message = message

    def detach_client(self, app):
        """
        Detach the client that belongs to this CLI.
        """
        connection = self.get_connection()
        if connection:
            connection.detach_and_close()

        # Redraw all clients -> Maybe their size has to change.
        self.invalidate()

    def listen_on_socket(self, socket_name=None):
        """
        Listen for clients on a Unix socket.
        Returns the socket name.
        """
        def connection_cb(pipe_connection):
            # We have to create a new `context`, because this will be the scope for
            # a new prompt_toolkit.Application to become active.
            with context():
                connection = ServerConnection(self, pipe_connection)

            self.connections.append(connection)

        self.socket_name = bind_and_listen_on_socket(socket_name, connection_cb)

        # Set session_name according to socket name.
#        if '.' in self.socket_name:
#            self.session_name = self.socket_name.rpartition('.')[-1]

        logger.info('Listening on %r.' % self.socket_name)
        return self.socket_name

    def run_server(self):
        # Ignore keyboard. (When people run "pymux server" and press Ctrl-C.)
        # Pymux has to be terminated by termining all the processes running in
        # its panes.
        def handle_sigint(*a):
            print('Ignoring keyboard interrupt.')

        signal.signal(signal.SIGINT, handle_sigint)

        # Start background threads.
        self._start_auto_refresh_thread()

        # Run eventloop.
        try:
            get_event_loop().run_until_complete(self.done_f)
        except:
            # When something bad happens, always dump the traceback.
            # (Otherwise, when running as a daemon, and stdout/stderr are not
            # available, it's hard to see what went wrong.)
            fd, path = tempfile.mkstemp(prefix='pymux.crash-')
            logger.fatal(
                'Pymux has crashed, dumping traceback to {0}'.format(path))
            os.write(fd, traceback.format_exc().encode('utf-8'))
            os.close(fd)
            raise

        finally:
            # Clean up socket.
            os.remove(self.socket_name)

    def run_standalone(self, color_depth):
        """
        Run pymux standalone, rather than using a client/server architecture.
        This is mainly useful for debugging.
        """
        self._runs_standalone = True
        self._start_auto_refresh_thread()

        client_state = self.add_client(
            input=create_input(),
            output=create_output(stdout=sys.stdout),
            color_depth=color_depth,
            connection=None)

        client_state.app.run()

    def add_client(self, output, input, color_depth, connection):
        client_state = ClientState(self,
            connection=None,
            input=input,
            output=output,
            color_depth=color_depth)

        self._client_states[connection] = client_state

        return client_state

    def remove_client(self, connection):
        if connection in self._client_states:
            del self._client_states[connection]
 def async_func():
     result = func()
     return Future.succeed(result)
Example #45
0
File: main.py Project: ashang/pymux
class Pymux(object):
    """
    The main Pymux application class.

    Usage:

        p = Pymux()
        p.listen_on_socket()
        p.run_server()

    Or:

        p = Pymux()
        p.run_standalone()
    """
    def __init__(self, source_file=None, startup_command=None):
        self._client_states = {}  # connection -> client_state

        # Options
        self.enable_mouse_support = True
        self.enable_status = True
        self.enable_pane_status = True  #False
        self.enable_bell = True
        self.remain_on_exit = False
        self.status_keys_vi_mode = False
        self.mode_keys_vi_mode = False
        self.history_limit = 2000
        self.status_interval = 4
        self.default_terminal = 'xterm-256color'
        self.status_left = '[#S] '
        self.status_left_length = 20
        self.status_right = ' %H:%M %d-%b-%y '
        self.status_right_length = 20
        self.window_status_current_format = '#I:#W#F'
        self.window_status_format = '#I:#W#F'
        self.session_name = '0'
        self.status_justify = Justify.LEFT
        self.default_shell = get_default_shell()

        self.options = ALL_OPTIONS
        self.window_options = ALL_WINDOW_OPTIONS

        # When no panes are available.
        self.original_cwd = os.getcwd()

        self.display_pane_numbers = False

        #: List of clients.
        self._runs_standalone = False
        self.connections = []
        self.done_f = Future()

        self._startup_done = False
        self.source_file = source_file
        self.startup_command = startup_command

        # Keep track of all the panes, by ID. (For quick lookup.)
        self.panes_by_id = weakref.WeakValueDictionary()

        # Socket information.
        self.socket = None
        self.socket_name = None

        # Key bindings manager.
        self.key_bindings_manager = PymuxKeyBindings(self)

        self.arrangement = Arrangement()

        self.style = ui_style

    def _start_auto_refresh_thread(self):
        """
        Start the background thread that auto refreshes all clients according to
        `self.status_interval`.
        """
        def run():
            while True:
                time.sleep(self.status_interval)
                self.invalidate()

        t = threading.Thread(target=run)
        t.daemon = True
        t.start()

    @property
    def apps(self):
        return [c.app for c in self._client_states.values()]

    def get_client_state(self):
        " Return the active ClientState instance. "
        app = get_app()
        for client_state in self._client_states.values():
            if client_state.app == app:
                return client_state

        raise ValueError('Client state for app %r not found' % (app, ))

    def get_connection(self):
        " Return the active Connection instance. "
        app = get_app()
        for connection, client_state in self._client_states.items():
            if client_state.app == app:
                return connection

        raise ValueError('Connection for app %r not found' % (app, ))

    def startup(self):
        # Handle start-up comands.
        # (Does initial key bindings.)
        if not self._startup_done:
            self._startup_done = True

            # Execute default config.
            for cmd in STARTUP_COMMANDS.splitlines():
                self.handle_command(cmd)

            # Source the given file.
            if self.source_file:
                call_command_handler('source-file', self, [self.source_file])

            # Make sure that there is one window created.
            self.create_window(command=self.startup_command)

    def get_title(self):
        """
        The title to be displayed in the titlebar of the terminal.
        """
        w = self.arrangement.get_active_window()

        if w and w.active_process:
            title = w.active_process.screen.title
        else:
            title = ''

        if title:
            return '%s - Pymux' % (title, )
        else:
            return 'Pymux'

    def get_window_size(self):
        """
        Get the size to be used for the DynamicBody.
        This will be the smallest size of all clients.
        """
        def active_window_for_app(app):
            with set_app(app):
                return self.arrangement.get_active_window()

        active_window = self.arrangement.get_active_window()

        # Get sizes for connections watching the same window.
        apps = [
            client_state.app for client_state in self._client_states.values()
            if active_window_for_app(client_state.app) == active_window
        ]
        sizes = [app.output.get_size() for app in apps]

        rows = [s.rows for s in sizes]
        columns = [s.columns for s in sizes]

        if rows and columns:
            return Size(rows=min(rows) - (1 if self.enable_status else 0),
                        columns=min(columns))
        else:
            return Size(rows=20, columns=80)

    def _create_pane(self, window=None, command=None, start_directory=None):
        """
        Create a new :class:`pymux.arrangement.Pane` instance. (Don't put it in
        a window yet.)

        :param window: If a window is given, take the CWD of the current
            process of that window as the start path for this pane.
        :param command: If given, run this command instead of `self.default_shell`.
        :param start_directory: If given, use this as the CWD.
        """
        assert window is None or isinstance(window, Window)
        assert command is None or isinstance(command, six.text_type)
        assert start_directory is None or isinstance(start_directory,
                                                     six.text_type)

        def done_callback():
            " When the process finishes. "
            if not self.remain_on_exit:
                # Remove pane from layout.
                self.arrangement.remove_pane(pane)

                # No panes left? -> Quit.
                if not self.arrangement.has_panes:
                    self.stop()

                # Make sure the right pane is focused for each client.
                for client_state in self._client_states.values():
                    client_state.sync_focus()

            self.invalidate()

        def bell():
            " Sound bell on all clients. "
            if self.enable_bell:
                for c in self.apps:
                    c.output.bell()

        # Start directory.
        if start_directory:
            path = start_directory
        elif window and window.active_process:
            # When the path of the active process is known,
            # start the new process at the same location.
            path = window.active_process.get_cwd()
        else:
            path = None

        def before_exec():
            " Called in the process fork (in the child process). "
            # Go to this directory.
            try:
                os.chdir(path or self.original_cwd)
            except OSError:
                pass  # No such file or directory.

            # Set terminal variable. (We emulate xterm.)
            os.environ['TERM'] = self.default_terminal

            # Make sure to set the PYMUX environment variable.
            if self.socket_name:
                os.environ['PYMUX'] = '%s,%i' % (self.socket_name,
                                                 pane.pane_id)

        if command:
            command = command.split()
        else:
            command = [self.default_shell]

        # Create new pane and terminal.
        terminal = Terminal(done_callback=done_callback,
                            bell_func=bell,
                            before_exec_func=before_exec)
        pane = Pane(terminal)

        # Keep track of panes. This is a WeakKeyDictionary, we only add, but
        # don't remove.
        self.panes_by_id[pane.pane_id] = pane

        logger.info('Created process %r.', command)

        return pane

    def invalidate(self):
        " Invalidate the UI for all clients. "
        logger.info('Invalidating %s applications', len(self.apps))

        for app in self.apps:
            app.invalidate()

    def stop(self):
        for app in self.apps:
            app.exit()
        self.done_f.set_result(None)

    def create_window(self, command=None, start_directory=None, name=None):
        """
        Create a new :class:`pymux.arrangement.Window` in the arrangement.
        """
        assert command is None or isinstance(command, six.text_type)
        assert start_directory is None or isinstance(start_directory,
                                                     six.text_type)

        pane = self._create_pane(None,
                                 command,
                                 start_directory=start_directory)

        self.arrangement.create_window(pane, name=name)
        pane.focus()
        self.invalidate()

    def add_process(self, command=None, vsplit=False, start_directory=None):
        """
        Add a new process to the current window. (vsplit/hsplit).
        """
        assert command is None or isinstance(command, six.text_type)
        assert start_directory is None or isinstance(start_directory,
                                                     six.text_type)

        window = self.arrangement.get_active_window()

        pane = self._create_pane(window,
                                 command,
                                 start_directory=start_directory)
        window.add_pane(pane, vsplit=vsplit)
        pane.focus()
        self.invalidate()

    def kill_pane(self, pane):
        """
        Kill the given pane, and remove it from the arrangement.
        """
        assert isinstance(pane, Pane)

        # Send kill signal.
        if not pane.process.is_terminated:
            pane.process.kill()

        # Remove from layout.
        self.arrangement.remove_pane(pane)

    def leave_command_mode(self, append_to_history=False):
        """
        Leave the command/prompt mode.
        """
        client_state = self.get_client_state()

        client_state.command_buffer.reset(append_to_history=append_to_history)
        client_state.prompt_buffer.reset(append_to_history=True)

        client_state.prompt_command = ''
        client_state.confirm_command = ''

        client_state.app.layout.focus_previous()

    def handle_command(self, command):
        """
        Handle command from the command line.
        """
        handle_command(self, command)

    def show_message(self, message):
        """
        Set a warning message. This will be shown at the bottom until a key has
        been pressed.

        :param message: String.
        """
        self.get_client_state().message = message

    def detach_client(self, app):
        """
        Detach the client that belongs to this CLI.
        """
        connection = self.get_connection()
        if connection:
            connection.detach_and_close()

        # Redraw all clients -> Maybe their size has to change.
        self.invalidate()

    def listen_on_socket(self, socket_name=None):
        """
        Listen for clients on a Unix socket.
        Returns the socket name.
        """
        def connection_cb(pipe_connection):
            # We have to create a new `context`, because this will be the scope for
            # a new prompt_toolkit.Application to become active.
            with context():
                connection = ServerConnection(self, pipe_connection)

            self.connections.append(connection)

        self.socket_name = bind_and_listen_on_socket(socket_name,
                                                     connection_cb)

        # Set session_name according to socket name.
        #        if '.' in self.socket_name:
        #            self.session_name = self.socket_name.rpartition('.')[-1]

        logger.info('Listening on %r.' % self.socket_name)
        return self.socket_name

    def run_server(self):
        # Ignore keyboard. (When people run "pymux server" and press Ctrl-C.)
        # Pymux has to be terminated by termining all the processes running in
        # its panes.
        def handle_sigint(*a):
            print('Ignoring keyboard interrupt.')

        signal.signal(signal.SIGINT, handle_sigint)

        # Start background threads.
        self._start_auto_refresh_thread()

        # Run eventloop.
        try:
            get_event_loop().run_until_complete(self.done_f)
        except:
            # When something bad happens, always dump the traceback.
            # (Otherwise, when running as a daemon, and stdout/stderr are not
            # available, it's hard to see what went wrong.)
            fd, path = tempfile.mkstemp(prefix='pymux.crash-')
            logger.fatal(
                'Pymux has crashed, dumping traceback to {0}'.format(path))
            os.write(fd, traceback.format_exc().encode('utf-8'))
            os.close(fd)
            raise

        finally:
            # Clean up socket.
            os.remove(self.socket_name)

    def run_standalone(self, color_depth):
        """
        Run pymux standalone, rather than using a client/server architecture.
        This is mainly useful for debugging.
        """
        self._runs_standalone = True
        self._start_auto_refresh_thread()

        client_state = self.add_client(input=create_input(),
                                       output=create_output(stdout=sys.stdout),
                                       color_depth=color_depth,
                                       connection=None)

        client_state.app.run()

    def add_client(self, output, input, color_depth, connection):
        client_state = ClientState(self,
                                   connection=None,
                                   input=input,
                                   output=output,
                                   color_depth=color_depth)

        self._client_states[connection] = client_state

        return client_state

    def remove_client(self, connection):
        if connection in self._client_states:
            del self._client_states[connection]
Example #46
0
File: posix.py Project: riag/ptterm
class PosixTerminal(Terminal):
    def __init__(self, exec_func):
        self.exec_func = exec_func

        # Create pseudo terminal for this pane.
        self.master, self.slave = os.openpty()

        # Master side -> attached to terminal emulator.
        self._reader = PosixStdinReader(self.master, errors='replace')
        self._reader_connected = False
        self._input_ready_callbacks = []

        self.ready_f = Future()
        self.loop = get_event_loop()
        self.pid = None

    def add_input_ready_callback(self, callback):
        self._input_ready_callbacks.append(callback)

    @classmethod
    def from_command(cls, command, before_exec_func=None):
        """
        Create Process from command,
        e.g. command=['python', '-c', 'print("test")']

        :param before_exec_func: Function that is called before `exec` in the
            process fork.
        """
        assert isinstance(command, list)
        assert before_exec_func is None or callable(before_exec_func)

        def execv():
            if before_exec_func:
                before_exec_func()

            for p in os.environ['PATH'].split(':'):
                path = os.path.join(p, command[0])
                if os.path.exists(path) and os.access(path, os.X_OK):
                    os.execv(path, command)

        return cls(execv)

    def connect_reader(self):
        if self.master is not None and not self._reader_connected:

            def ready():
                for cb in self._input_ready_callbacks:
                    cb()

            self.loop.add_reader(self.master, ready)
            self._reader_connected = True

    @property
    def closed(self):
        return self._reader.closed

    def disconnect_reader(self):
        if self.master is not None and self._reader_connected:
            self.loop.remove_reader(self.master)
            self._reader_connected = False

    def read_text(self, amount=4096):
        return self._reader.read(amount)

    def write_text(self, text):
        self.write_bytes(text.encode('utf-8'))

    def write_bytes(self, data):
        while self.master is not None:
            try:
                os.write(self.master, data)
            except OSError as e:
                # This happens when the window resizes and a SIGWINCH was received.
                # We get 'Error: [Errno 4] Interrupted system call'
                if e.errno == 4:
                    continue
            return

    def set_size(self, width, height):
        """
        Set terminal size.
        """
        assert isinstance(width, int)
        assert isinstance(height, int)

        if self.master is not None:
            set_terminal_size(self.master, height, width)

    def start(self):
        """
        Create fork and start the child process.
        """
        pid = os.fork()

        if pid == 0:
            self._in_child()
        elif pid > 0:
            # We wait a very short while, to be sure the child had the time to
            # call _exec. (Otherwise, we are still sharing signal handlers and
            # FDs.) Resizing the pty, when the child is still in our Python
            # code and has the signal handler from prompt_toolkit, but closed
            # the 'fd' for 'call_from_executor', will cause OSError.
            time.sleep(0.1)

            self.pid = pid

            # Wait for the process to finish.
            self._waitpid()

    def kill(self):
        " Terminate process. "
        self.send_signal(signal.SIGKILL)

    def send_signal(self, signal):
        " Send signal to running process. "
        assert isinstance(signal, int), type(signal)

        if self.pid and not self.closed:
            try:
                os.kill(self.pid, signal)
            except OSError:
                pass  # [Errno 3] No such process.

    def _in_child(self):
        " Will be executed in the forked child. "
        os.close(self.master)

        # Remove signal handler for SIGWINCH as early as possible.
        # (We don't want this to be triggered when execv has not been called
        # yet.)
        signal.signal(signal.SIGWINCH, 0)

        pty_make_controlling_tty(self.slave)

        # In the fork, set the stdin/out/err to our slave pty.
        os.dup2(self.slave, 0)
        os.dup2(self.slave, 1)
        os.dup2(self.slave, 2)

        # Execute in child.
        try:
            self._close_file_descriptors()
            self.exec_func()
        except Exception:
            traceback.print_exc()
            time.sleep(5)

            os._exit(1)
        os._exit(0)

    def _close_file_descriptors(self):
        # Do not allow child to inherit open file descriptors from parent.
        # (In case that we keep running Python code. We shouldn't close them.
        # because the garbage collector is still active, and he will close them
        # eventually.)
        max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[-1]

        try:
            os.closerange(3, max_fd)
        except OverflowError:
            # On OS X, max_fd can return very big values, than closerange
            # doesn't understand, e.g. 9223372036854775807. In this case, just
            # use 4096. This is what Linux systems report, and should be
            # sufficient. (I hope...)
            os.closerange(3, 4096)

    def _waitpid(self):
        """
        Create an executor that waits and handles process termination.
        """
        def wait_for_finished():
            " Wait for PID in executor. "
            os.waitpid(self.pid, 0)
            self.loop.call_from_executor(done)

        def done():
            " PID received. Back in the main thread. "
            # Close pty and remove reader.

            self.disconnect_reader()
            os.close(self.master)
            os.close(self.slave)

            self.master = None

            # Callback.
            self.ready_f.set_result(None)

        self.loop.run_in_executor(wait_for_finished)

    def get_name(self):
        " Return the process name. "
        result = '<unknown>'

        # Apparently, on a Linux system (like my Fedora box), I have to call
        # `tcgetpgrp` on the `master` fd. However, on te Window subsystem for
        # Linux, we have to use the `slave` fd.

        if self.master is not None:
            result = get_name_for_fd(self.master)

        if not result and self.slave is not None:
            result = get_name_for_fd(self.slave)

        return result

    def get_cwd(self):
        if self.pid:
            return get_cwd_for_pid(self.pid)