# Raxol Revived ExTermbox
[](https://hex.pm/packages/rrex_termbox)
[](https://hexdocs.pm/rrex_termbox)
An Elixir library for interacting with the terminal via the [termbox2](https://github.com/termbox/termbox2) C library, a maintained fork of the original termbox.
**Starting with version 2.0.0, this library uses Elixir Native Implemented Functions (NIFs) provided by the [`termbox2`](https://hex.pm/packages/termbox2) Hex package (and its associated NIF bindings).** This replaces the previous Port/Unix Domain Socket architecture, leveraging a maintained C library and simplifying the build process.
For high-level, declarative terminal UIs in Elixir, see [raxol](https://github.com/Hydepwns/raxol) or its predecessor [Ratatouille](https://github.com/ndreynolds/ratatouille), which build on top of this library.
For the API Reference, see the `ExTermbox` module: [https://hexdocs.pm/rrex_termbox/ExTermbox.html](https://hexdocs.pm/rrex_termbox/ExTermbox.html).
## Getting Started
### Architecture (NIF Based)
**Note:** If you previously used versions prior to 2.0.0, be aware that the underlying communication mechanism has changed significantly from a Port/UDS system back to NIFs, leveraging the `termbox2` dependency. See the [Changelog](./CHANGELOG.md) for details.
`ExTermbox` now interacts directly with the `termbox2` C library through NIFs provided by the `termbox2` Hex dependency.
1. **Initialization:** `ExTermbox.init/1` starts a `GenServer` (`ExTermbox.Server`) which calls the `tb_init()` NIF function. This server manages the termbox state and handles API calls.
2. **API Calls:** Public functions in the `ExTermbox` module (e.g., `ExTermbox.print/5`, `ExTermbox.clear/0`, `ExTermbox.present/0`) communicate with the `ExTermbox.Server` via `GenServer` calls/casts. The server then invokes the corresponding `termbox2` NIF function (e.g., `tb_print`, `tb_clear`, `tb_present`).
3. **Event Handling:** The `ExTermbox.Server` periodically polls for terminal events (like key presses, mouse events, or resizes) using the `tb_peek_event()` NIF. When an event occurs, it's translated into an `ExTermbox.Event` struct and sent as a standard Elixir message (`{:termbox_event, event}`) to the process that originally called `ExTermbox.init/1` (the "owner" process).
The public API is exposed primarily through the `ExTermbox` module.
### Hello World
Let's go through a simple example.
Create an Elixir script (e.g., `hello.exs`) in any Mix project that includes `rrex_termbox` in its dependencies (see Installation below).
```elixir
# hello.exs
defmodule HelloWorld do
use GenServer
alias ExTermbox
def start_link(_opts) do
# Start our process that will own the termbox session
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
@impl true
def init(:ok) do
# Initialize ExTermbox, registering this GenServer process as the owner
# The owner process will receive {:termbox_event, event} messages
case ExTermbox.init(self()) do
:ok ->
IO.puts("ExTermbox initialized successfully.")
# Send ourselves a message to trigger drawing the initial screen
send(self(), :draw)
{:ok, %{}} # Initial state for our GenServer
{:error, reason} ->
IO.inspect(reason, label: "Error initializing ExTermbox")
{:stop, :init_failed}
end
end
@impl true
def handle_info(:draw, state) do
# Clear the screen
:ok = ExTermbox.clear()
# Print "Hello, World!" at (0, 0) with default colors
:ok = ExTermbox.print(0, 0, :default, :default, "Hello, World!")
# Print "(Press <q> to quit)" at (0, 2)
:ok = ExTermbox.print(0, 2, :default, :default, "(Press <q> to quit)")
# Render the changes to the terminal
:ok = ExTermbox.present()
{:noreply, state}
end
# Handle events sent from ExTermbox.Server
@impl true
def handle_info({:termbox_event, %ExTermbox.Event{type: :key, key: :q}}, state) do
IO.puts("Quit event received.")
# Trigger shutdown before stopping
ExTermbox.shutdown()
{:stop, :normal, state}
end
@impl true
def handle_info({:termbox_event, event}, state) do
# Optional: Log other events
# IO.inspect(event, label: "Received event")
{:noreply, state}
end
# Handle other messages if needed
@impl true
def handle_info(msg, state) do
# IO.inspect(msg, label: "Received other message")
{:noreply, state}
end
@impl true
def terminate(reason, _state) do
IO.puts("HelloWorld GenServer terminating: #{inspect(reason)}")
# Ensure termbox is shut down if termination wasn't triggered by :q
# (This might be redundant if ExTermbox.Server links/monitors)
ExTermbox.shutdown()
:ok
end
# Helper to run the example
def run do
# Ensure the app is started if running as a script
{:ok, _} = Application.ensure_all_started(:rrex_termbox)
{:ok, pid} = start_link([])
# Keep the script alive until the GenServer terminates
Process.monitor(pid)
receive do
{:DOWN, _, :process, ^pid, reason} ->
IO.puts("HelloWorld process finished: #{inspect(reason)}")
end
end
end
HelloWorld.run()
```
In this example, we use a `GenServer` to manage the application's lifecycle and handle the asynchronous `{:termbox_event, ...}` messages.
Finally, run the example like this (assuming you have `rrex_termbox` added to a Mix project):
```bash
mix run hello.exs
```
You should see the text we rendered and be able to quit with 'q'.
## Installation
Add `rrex_termbox` as a dependency in your project's `mix.exs`.
**Important:** This library currently relies on a fork of the `termbox2` NIF wrapper to include necessary fixes and features. Point your dependency directly to the GitHub repository:
```elixir
def deps do
[
# {:rrex_termbox, "~> 2.0.0"}, # Use this once published to Hex
{:rrex_termbox, git: "https://github.com/Hydepwns/rrex_termbox.git", tag: "v2.0.0-alpha.2"} # Or branch: "main"
# The underlying NIF library (currently points to a fork)
# rrex_termbox depends on this, so it's usually fetched automatically,
# but explicitly listing it might be needed for overrides.
# {:termbox2, github: "Hydepwns/termbox2-nif", tag: "0.1.1-hydepwns-fix1", submodules: true}
]
end
```
*(Note: Once `rrex_termbox` v2.0.0 (or later) is published on Hex.pm and the underlying `termbox2` dependency issues are resolved upstream or the fork is published, the dependency specification can likely be simplified back to `{:rrex_termbox, "~> 2.0.0"}`.)*
You will need standard C build tools (like `gcc` or `clang`, often part of `build-essential` or Xcode Command Line Tools) installed on your system for the `termbox2` NIF dependency to compile.
Mix should handle fetching the dependency and compiling the NIFs automatically when you run `mix deps.get` and `mix compile`.
If you encounter build issues, ensure your build tools are installed and check the `termbox2` dependency's documentation or repository for any specific requirements.
## Distribution
Building standalone releases for applications using `rrex_termbox` (and its underlying NIF dependency) should work with standard Elixir **[Releases](https://hexdocs.pm/mix/Mix.Tasks.Release.html)**. The build process compiles the NIFs into a shared object file (`.so` or `.dylib`) located in the `priv/` directory of the dependency (`termbox2`). Releases are designed to package these `priv/` artifacts correctly.
Ensure your release configuration properly includes the `rrex_termbox` and `termbox2` applications. Consult the Elixir Releases documentation for details.