ExTermbox (ExTermbox v2.0.4)

View Source

Elixir bindings for the termbox2 library, providing a way to control the terminal and draw simple UIs using Erlang NIFs.

This module provides the main user-facing API. It interacts with the termbox2 NIF functions via the ExTermbox.Server GenServer, which must be running.

Architecture

ExTermbox relies on a GenServer, typically registered as ExTermbox.Server, to manage the lifecycle of the termbox2 NIF library and handle asynchronous events. Most functions in this module are thin wrappers that send messages (calls or casts) to this server process.

See ExTermbox.Server for implementation details.

Usage

  1. Initialization: Call ExTermbox.init/1 to start the ExTermbox.Server. This calls tb_init() via the NIF and registers the calling process as the 'owner' to receive events.
  2. API Calls: Use functions like change_cell/5, clear/0, print/5, etc. These functions communicate with the running ExTermbox.Server.
  3. Display: Call ExTermbox.present/0 to synchronize the internal back buffer with the terminal screen.
  4. Events: The ExTermbox.Server automatically polls for terminal events (keyboard, mouse, resize) using tb_peek_event() via the NIF. Events are parsed into %ExTermbox.Event{} structs and sent as messages in the format {:termbox_event, event} to the owner process (the one that called init/1). You do not need to manually poll for events.
  5. Shutdown: Call ExTermbox.shutdown/0 when finished. This stops the ExTermbox.Server gracefully, which in turn calls tb_shutdown() via the NIF.

Event Handling

Events are delivered automatically as messages in the format {:termbox_event, %ExTermbox.Event{}} to the process that called init/1. You should handle these messages in your process's handle_info/2 callback (if it's a GenServer or similar OTP process).

Example (handle_info in the owner process):

def handle_info({:termbox_event, %ExTermbox.Event{type: :key, key: :q}}, state) do
  IO.puts("Quit key pressed!")
  # Initiate shutdown sequence
  ExTermbox.shutdown()
  {:stop, :normal, state}
end

def handle_info({:termbox_event, event}, state) do
  IO.inspect(event, label: "Received Termbox Event")
  # Handle other events (resize, mouse, other keys)
  {:noreply, state}
end

Summary

Functions

Changes the character, foreground, and background attributes of a specific cell in the internal back buffer by sending a request to the ExTermbox.Server.

Clears the internal back buffer by sending a request to the ExTermbox.Server.

[Debug] Causes the C helper process to exit immediately. FOR TESTING ONLY.

Retrieves the character, foreground, and background attributes of a specific cell by querying the ExTermbox.Server.

Returns the height of the terminal by querying the ExTermbox.Server.

Initializes the termbox library by starting the ExTermbox.Server GenServer.

Synchronizes the internal back buffer with the terminal screen by sending a request to the ExTermbox.Server.

A convenience function to print a string at a given position with specified attributes.

Selects the input mode by sending a request to the ExTermbox.Server.

Sets the clear attributes (foreground and background) used by clear/1 by sending a request to the ExTermbox.Server.

Sets the output mode by sending a request to the ExTermbox.Server.

Shuts down the termbox library by stopping the ExTermbox.Server GenServer.

Returns the width of the terminal by querying the ExTermbox.Server.

Types

pid_or_name()

@type pid_or_name() :: pid() | atom()

Functions

change_cell(x, y, char, fg, bg, server \\ ExTermbox.Server)

@spec change_cell(
  integer(),
  integer(),
  char() | String.t(),
  integer(),
  integer(),
  atom() | pid()
) ::
  :ok | {:error, any()}

Changes the character, foreground, and background attributes of a specific cell in the internal back buffer by sending a request to the ExTermbox.Server.

The server calls the termbox2 NIF function tb_set_cell(). This does not immediately affect the visible terminal; present/1 must be called to synchronize.

Arguments:

  • x: The zero-based column index.
  • y: The zero-based row index.
  • char: The character to place in the cell. Can be:
    • An integer codepoint (e.g., ?a).
    • A single-character string (e.g., "a").
    • A single-codepoint UTF-8 string (e.g., "€").
  • fg: The foreground attribute (an integer constant from ExTermbox.Constants). Combine colors (e.g., Constants.color(:red)) with attributes (e.g., Constants.attribute(:bold)) using bitwise OR (Bitwise.bor/2).
  • bg: The background attribute (an integer constant from ExTermbox.Constants).
  • server: The registered name or PID of the server (defaults to #{inspect(@server_name)}).

Returns :ok if the arguments are valid and the request is sent. Returns {:error, :invalid_char} if the char argument is not a valid single character representation.

This function uses GenServer.cast, so errors during the NIF call itself will not be reported back directly but may be logged by the server.

clear(server \\ ExTermbox.Server)

@spec clear(atom() | pid()) :: :ok | {:error, any()}

Clears the internal back buffer by sending a request to the ExTermbox.Server.

The server calls the termbox2 NIF function tb_clear(). This does not immediately affect the visible terminal; present/1 must be called to synchronize.

Arguments:

  • server: The registered name or PID of the server (defaults to #{inspect(@server_name)}).

Returns :ok on success or {:error, reason} if the GenServer call fails.

debug_crash(pid_or_name)

@spec debug_crash(pid() | atom()) :: :ok | {:error, any()}

[Debug] Causes the C helper process to exit immediately. FOR TESTING ONLY.

Requires the PID or registered name of the PortHandler process.

get_cell(x, y, server \\ ExTermbox.Server)

@spec get_cell(integer(), integer(), atom() | pid()) ::
  {:ok, {integer(), integer(), integer()}} | {:error, any()}

Retrieves the character, foreground, and background attributes of a specific cell by querying the ExTermbox.Server.

Note: This function is not currently implemented in v2.0.0. The underlying tb_get_cell/2 function is not available in the current termbox2_nif version. Calls will return {:error, {:not_implemented, "..."}}.

This feature may be added in a future version when the NIF library is updated.

Returns {:ok, {char_codepoint, fg_attribute, bg_attribute}} on success, or {:error, reason} on failure.

Arguments:

  • x: The zero-based column index.
  • y: The zero-based row index.
  • server: The registered name or PID of the server (defaults to #{inspect(@server_name)}).

height(server \\ ExTermbox.Server)

@spec height(atom() | pid()) :: {:ok, non_neg_integer()} | {:error, any()}

Returns the height of the terminal by querying the ExTermbox.Server.

The server retrieves this information via the termbox2 NIF function tb_height().

Arguments:

init(opts \\ [])

@spec init(keyword()) :: {:ok, atom()} | {:error, any()}

Initializes the termbox library by starting the ExTermbox.Server GenServer.

This function attempts to start and link an ExTermbox.Server process. The server process, upon its own initialization, will call the underlying termbox2 NIF function tb_init().

The calling process is registered as the "owner" of the termbox session and will receive {:termbox_event, %ExTermbox.Event{}} messages.

Returns {:ok, server_name} on success, where server_name is the atom used to register the GenServer (defaults to ExTermbox.Server). Returns {:error, reason} if the server cannot be started or if tb_init() fails within the server.

If the server is already running under the specified name, it logs a warning and returns {:ok, server_name} without attempting to start a new one.

Options:

  • :name (atom): The registered name for the ExTermbox.Server GenServer. Defaults to ExTermbox.Server.
  • :owner (pid): The PID to receive termbox events. Defaults to self(). This is typically not overridden directly, as init/1 sets it.
  • :poll_interval_ms (pos_integer): The interval in milliseconds for polling terminal events via tb_peek_event. Defaults to 10.

All options are passed down to ExTermbox.Server.start_link/1.

present(server \\ ExTermbox.Server)

@spec present(atom() | pid()) :: :ok | {:error, any()}

Synchronizes the internal back buffer with the terminal screen by sending a request to the ExTermbox.Server.

The server calls the termbox2 NIF function tb_present().

Arguments:

  • server: The registered name or PID of the server (defaults to #{inspect(@server_name)}).

Returns :ok on success or {:error, reason} if the GenServer call fails.

print(x, y, fg, bg, str, server \\ ExTermbox.Server)

@spec print(integer(), integer(), integer(), integer(), String.t(), atom() | pid()) ::
  :ok

A convenience function to print a string at a given position with specified attributes.

This function iterates through the string's characters and calls change_cell/6 for each one, sending multiple requests to the ExTermbox.Server.

Note: This function assumes a left-to-right character display and does not handle line wrapping or terminal boundaries explicitly. Characters printed beyond the terminal width might be ignored by the underlying termbox2 library.

Arguments:

  • x: The starting zero-based column index.
  • y: The zero-based row index.
  • fg: The foreground attribute (integer constant from ExTermbox.Constants).
  • bg: The background attribute (integer constant from ExTermbox.Constants).
  • str: The string to print.
  • server: The registered name or PID of the server (defaults to #{inspect(@server_name)}).

Returns :ok. Like change_cell/6, this uses GenServer.cast, so errors during individual NIF calls are not reported directly.

select_input_mode(mode, server \\ ExTermbox.Server)

@spec select_input_mode(atom(), atom() | pid()) ::
  :ok | {:error, :invalid_input_mode | any()}

Selects the input mode by sending a request to the ExTermbox.Server.

The server validates the mode atom against ExTermbox.Constants.input_mode/1 and then calls the termbox2 NIF function tb_set_input_mode().

Arguments:

  • mode: An input mode atom defined in ExTermbox.Constants (e.g., :esc, :alt, :mouse).
  • server: The registered name or PID of the server (defaults to #{inspect(@server_name)}).

Returns :ok on success, {:error, :invalid_input_mode} if the mode atom is unrecognized, or {:error, reason} for other GenServer call errors.

set_clear_attributes(fg, bg, server \\ ExTermbox.Server)

@spec set_clear_attributes(integer(), integer(), atom() | pid()) ::
  :ok | {:error, any()}

Sets the clear attributes (foreground and background) used by clear/1 by sending a request to the ExTermbox.Server.

The server validates the attributes and calls the termbox2 NIF function tb_set_clear_attrs().

Arguments:

  • fg: The foreground attribute (an integer constant from ExTermbox.Constants).
  • bg: The background attribute (an integer constant from ExTermbox.Constants).
  • server: The registered name or PID of the server (defaults to #{inspect(@server_name)}).

Returns :ok on success, or {:error, reason} if the GenServer call fails.

set_cursor(x \\ Constants.hide_cursor(), y \\ Constants.hide_cursor(), server \\ ExTermbox.Server)

@spec set_cursor(integer(), integer(), atom() | pid()) :: :ok

Sets the cursor position by sending a request to the ExTermbox.Server.

The server calls the termbox2 NIF function tb_set_cursor().

Use x = -1 and y = -1 (or the default arguments) to hide the cursor. See ExTermbox.Constants.hide_cursor/0.

Arguments:

  • x: The zero-based column index (-1 to hide).
  • y: The zero-based row index (-1 to hide).
  • server: The registered name or PID of the server (defaults to #{inspect(@server_name)}).

Returns :ok. This function uses GenServer.cast, so errors during the NIF call will not be reported back directly but may be logged by the server.

set_output_mode(mode, server \\ ExTermbox.Server)

@spec set_output_mode(atom(), atom() | pid()) ::
  :ok | {:error, :invalid_output_mode | any()}

Sets the output mode by sending a request to the ExTermbox.Server.

The server validates the mode atom against ExTermbox.Constants.output_mode/1 and then calls the termbox2 NIF function tb_set_output_mode().

Arguments:

  • mode: An output mode atom defined in ExTermbox.Constants (e.g., :normal, :grayscale, :xterm256).
  • server: The registered name or PID of the server (defaults to #{inspect(@server_name)}).

Returns :ok on success, {:error, :invalid_output_mode} if the mode atom is unrecognized, or {:error, reason} for other GenServer call errors.

shutdown(server_name \\ ExTermbox.Server)

@spec shutdown(atom()) :: :ok | {:error, :unexpected_registry_value}

Shuts down the termbox library by stopping the ExTermbox.Server GenServer.

This function finds the ExTermbox.Server process (using the provided or default name) and requests it to stop gracefully (using :shutdown).

The server's terminate/2 callback is responsible for calling the termbox2 NIF function tb_shutdown() to restore the terminal state.

Returns :ok if the stop request was sent or if the server was not running. Returns {:error, reason} if finding the server process fails unexpectedly.

Arguments:

  • server_name: The registered name of the server to stop. Defaults to ExTermbox.Server.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

width(server \\ ExTermbox.Server)

@spec width(atom() | pid()) :: {:ok, non_neg_integer()} | {:error, any()}

Returns the width of the terminal by querying the ExTermbox.Server.

The server retrieves this information via the termbox2 NIF function tb_width().

Arguments: