Harlock.Terminal.Termios (harlock v0.2.0)

Copy Markdown View Source

POSIX termios access for /dev/tty via a small NIF.

Most Harlock apps don't touch this directly — the runtime owns one control fd for the app's lifecycle (snapshot on init, restore on terminate) and input is delivered via arm_select/1 + read_nonblock/2. The module is documented so you can drive termios from your own code if you need raw mode outside the Harlock runtime.

See c_src/README.md for the design rationale — in particular, why tcgetattr / tcsetattr / ioctl(TIOCGWINSZ) go through a NIF instead of :os.cmd("stty ...") (the subshell loses the controlling terminal) and why reads use enif_select_read + non-blocking read(2) instead of :file.read/2 (the latter doesn't deliver bytes from a spawned process on macOS).

Summary

Types

Snapshot of termios attributes. Opaque to callers.

Opaque resource — a /dev/tty fd.

Functions

Register the fd with the BEAM IO poller for one read-ready notification. When the fd has data available, BEAM sends {:tty_ready, ref} to the process that called this function. Caller must re-arm after each read.

Close the fd. Idempotent; the fd is also GC'd via NIF resource.

Read current termios attributes. The returned binary is opaque.

Open /dev/tty for termios control. Returns {:error, :no_tty} in environments without a controlling terminal (CI, piped stdin) so callers can detect non-interactive contexts cleanly.

Non-blocking read(2) of up to max_bytes. The fd is O_NONBLOCK so this returns :wouldblock when no data is ready (caller should arm_select and wait for the next {:tty_ready, _}). :eof means the tty was closed (ssh disconnect, tmux kill, etc.) — surface this to the app for clean shutdown.

Restore termios attributes from a prior get/1 result.

Put the terminal in raw mode (cfmakeraw + VMIN=1, VTIME=0).

Current window size in cells, via TIOCGWINSZ.

Types

attrs()

@opaque attrs()

Snapshot of termios attributes. Opaque to callers.

ref()

@opaque ref()

Opaque resource — a /dev/tty fd.

Functions

arm_select(ref)

@spec arm_select(ref()) :: :ok | {:error, atom() | {atom(), term()}}

Register the fd with the BEAM IO poller for one read-ready notification. When the fd has data available, BEAM sends {:tty_ready, ref} to the process that called this function. Caller must re-arm after each read.

Only the process that called open/0 may arm/read — others get {:error, :not_owner}.

close(ref)

@spec close(ref()) :: :ok

Close the fd. Idempotent; the fd is also GC'd via NIF resource.

get(ref)

@spec get(ref()) :: {:ok, attrs()} | {:error, atom() | {atom(), term()}}

Read current termios attributes. The returned binary is opaque.

open()

@spec open() :: {:ok, ref()} | {:error, atom() | {atom(), term()}}

Open /dev/tty for termios control. Returns {:error, :no_tty} in environments without a controlling terminal (CI, piped stdin) so callers can detect non-interactive contexts cleanly.

read_nonblock(ref, max_bytes)

@spec read_nonblock(ref(), pos_integer()) ::
  {:ok, binary()} | :wouldblock | :eof | {:error, atom() | {atom(), term()}}

Non-blocking read(2) of up to max_bytes. The fd is O_NONBLOCK so this returns :wouldblock when no data is ready (caller should arm_select and wait for the next {:tty_ready, _}). :eof means the tty was closed (ssh disconnect, tmux kill, etc.) — surface this to the app for clean shutdown.

set(ref, attrs)

@spec set(ref(), attrs()) :: :ok | {:error, atom() | {atom(), term()}}

Restore termios attributes from a prior get/1 result.

set_raw(ref)

@spec set_raw(ref()) :: :ok | {:error, atom() | {atom(), term()}}

Put the terminal in raw mode (cfmakeraw + VMIN=1, VTIME=0).

winsize(ref)

@spec winsize(ref()) ::
  {:ok, pos_integer(), pos_integer()} | {:error, atom() | {atom(), term()}}

Current window size in cells, via TIOCGWINSZ.