Drafter.Terminal.TermiosNif (drafter v0.3.1)

Copy Markdown View Source

Terminal and pseudoterminal control through the termios_nif native library.

Covers what cannot be done from the BEAM: putting the controlling terminal into raw mode, reading and setting window size by ioctl, discarding the operating system's input queue, and allocating pseudoterminal pairs.

{:ok, {cols, rows, _xpixel, _ypixel}} = Drafter.Terminal.TermiosNif.get_winsize()

The library is loaded from priv/termios_nif on module load. A failed load is not an error: the Elixir bodies stay in place, so disable_flow_control/0, enable_flow_control/0, enter_raw_mode/0, exit_raw_mode/0, set_tui_active/0, set_tui_inactive/0 and flush_stdin/0 return :nif_not_loaded, while every other function raises ErlangError with :nif_not_loaded. Callers must handle whichever applies to the function they use.

Raw mode is a process-independent property of the terminal. Enter it once and leave it once; Drafter.Terminal.Driver owns that lifecycle for the local terminal.

Summary

Types

What a function with an Elixir fallback returns.

Functions

Close a descriptor returned by open_pty/2.

Clear IXON/IXOFF on the controlling terminal, so Ctrl+S and Ctrl+Q reach the application.

Restore IXON/IXOFF software flow control on the controlling terminal.

Put the controlling terminal into raw mode, saving the previous settings.

Restore the terminal settings saved by enter_raw_mode/0.

Discard the bytes the operating system has queued on standard input but not delivered.

The controlling terminal's size via TIOCGWINSZ, as {cols, rows, xpixel, ypixel}.

The size of the terminal behind fd, as {cols, rows, xpixel, ypixel}.

Load the native library, leaving the Elixir fallbacks in place if it is missing.

Allocate a pseudo-terminal pair sized cols by rows.

Mark the terminal as held in TUI mode and install a SIGINT handler.

Clear the TUI-mode mark set by set_tui_active/0 and restore the default SIGINT handling.

Set the window size of the pty behind fd via TIOCSWINSZ.

Types

termios_result()

@type termios_result() :: :ok | :error | :nif_not_loaded

What a function with an Elixir fallback returns.

:ok on success, :error when the underlying tcgetattr/tcsetattr failed, and :nif_not_loaded when the native library is absent.

Functions

close_fd(fd)

@spec close_fd(integer()) :: :ok | {:error, binary()}

Close a descriptor returned by open_pty/2.

On failure the error term carries the strerror text for errno as a binary, so closing the same descriptor twice gives {:error, "Bad file descriptor"}.

disable_flow_control()

@spec disable_flow_control() :: termios_result()

Clear IXON/IXOFF on the controlling terminal, so Ctrl+S and Ctrl+Q reach the application.

The settings in force at the first call are saved, if enter_raw_mode/0 has not already saved them, and are what exit_raw_mode/0 restores.

enable_flow_control()

@spec enable_flow_control() :: termios_result()

Restore IXON/IXOFF software flow control on the controlling terminal.

Sets both bits regardless of whether they were set before, and saves nothing.

enter_raw_mode()

@spec enter_raw_mode() :: termios_result()

Put the controlling terminal into raw mode, saving the previous settings.

Input is delivered unbuffered and unechoed, and control characters are passed through instead of generating signals. exit_raw_mode/0 restores what was saved. The settings are saved only on the first call that saves them, so entering raw mode twice does not lose the original ones.

exit_raw_mode()

@spec exit_raw_mode() :: termios_result()

Restore the terminal settings saved by enter_raw_mode/0.

Returns :ok when nothing was ever saved, having changed nothing.

flush_stdin()

@spec flush_stdin() :: :ok | :nif_not_loaded

Discard the bytes the operating system has queued on standard input but not delivered.

Bytes already read into the emulator are unaffected.

get_winsize()

@spec get_winsize() ::
  {:ok,
   {non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer()}}
  | {:error, :not_a_tty}

The controlling terminal's size via TIOCGWINSZ, as {cols, rows, xpixel, ypixel}.

Standard output is asked first, standard input second. {:error, :not_a_tty} means neither is a terminal. The pixel dimensions are zero on terminals that do not report them.

get_winsize(fd)

@spec get_winsize(integer()) ::
  {:ok,
   {non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer()}}
  | {:error, binary()}

The size of the terminal behind fd, as {cols, rows, xpixel, ypixel}.

On failure the error term carries the strerror text for errno as a binary.

load_nif()

@spec load_nif() :: :ok

Load the native library, leaving the Elixir fallbacks in place if it is missing.

Runs on module load. Always returns :ok, so a missing library never stops the module from loading.

open_pty(cols, rows)

@spec open_pty(non_neg_integer(), non_neg_integer()) ::
  {:ok, {integer(), integer(), binary()}} | {:error, binary()}

Allocate a pseudo-terminal pair sized cols by rows.

Returns {master_fd, slave_fd, slave_path}. The master is the side this process reads and writes — hand it to :erlang.open_port({:fd, master, master}, [:binary]) to get ordinary Erlang message-passing I/O. The slave is what a child process uses as its controlling terminal, either by inheriting the descriptor or by opening slave_path.

Does no forking or exec'ing. Both descriptors must be released with close_fd/1. On failure the error term carries the strerror text for errno as a binary.

set_tui_active()

@spec set_tui_active() :: :ok | :nif_not_loaded

Mark the terminal as held in TUI mode and install a SIGINT handler.

While the mark is set, a SIGINT or a normal exit of the emulator restores the saved terminal settings and writes the sequences that leave the alternate screen, show the cursor and turn mouse reporting off, so a crash does not leave the terminal unusable.

set_tui_inactive()

@spec set_tui_inactive() :: :ok | :nif_not_loaded

Clear the TUI-mode mark set by set_tui_active/0 and restore the default SIGINT handling.

set_winsize(fd, cols, rows, xpixel, ypixel)

@spec set_winsize(
  integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer()
) :: :ok | {:error, :ioctl_failed}

Set the window size of the pty behind fd via TIOCSWINSZ.

The kernel then reports the new dimensions to processes on the other side of the pty and signals SIGWINCH to its foreground process group. Each dimension is truncated to 16 bits. A failed ioctl gives {:error, :ioctl_failed}.