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
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
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.
Only the process that called open/0 may arm/read — others get
{:error, :not_owner}.
@spec close(ref()) :: :ok
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.
@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.
Restore termios attributes from a prior get/1 result.
Put the terminal in raw mode (cfmakeraw + VMIN=1, VTIME=0).
@spec winsize(ref()) :: {:ok, pos_integer(), pos_integer()} | {:error, atom() | {atom(), term()}}
Current window size in cells, via TIOCGWINSZ.