Backend

The backend classes connect Terminal to the actual console, host environment, or test harness that emits text and receives input.

Most applications should use the built-in platform backend. Reach for Backend only when you need to embed the library into another console abstraction, capture terminal output for tests, or adapt terminal behavior to a non-standard runtime.

Usage

Choosing When to Use a Custom Backend

The default backend already handles normal terminal work on POSIX and Windows systems. A custom backend is usually only worth the extra code in three situations:

  • You want to integrate the library into an existing UI or console host.

  • You need a deterministic recording backend for tests or demos.

  • You are targeting a platform where terminal access is provided through a different abstraction layer.

The simplest way to install a backend is to pass it to the terminal constructor before calling Terminal::initializeScreen().

auto backend = std::make_shared<MyBackend>();
auto terminal = Terminal{backend, Size{80, 25}};

terminal.initializeScreen();
// ...
terminal.restoreScreen();

You can also swap the backend later with Terminal::setBackend(), but configuring the terminal once up front is usually easier to follow.

As a general application-level rule, keep exactly one Terminal instance alive for the process lifetime. The built-in platform backends maintain shared process-wide state, so a single terminal instance is the safest default design.

How Terminal Uses Backend Capabilities

The backend API is capability-based. Terminal checks what the backend supports and then chooses between direct ANSI output and backend callbacks.

The most important capability methods are:

Two details are easy to overlook:

  • Backend::emitText() always receives UTF-8 text. When ANSI is enabled, escape sequences are passed through as text as well, and each call contains complete UTF-8 and ANSI sequences.

  • Line buffering is only available when color codes, cursor codes, and character-attribute codes can all stay in sync through ANSI. If a backend requires callback-based attribute updates, the terminal disables that optimization automatically.

Implementing a Recording Backend

This example shows a small backend that records emitted text and tracks the cursor through backend callbacks instead of ANSI cursor codes.

class RecordingBackend final : public Backend {
public:
    void initializePlatform() override {}
    void restorePlatform() override {}

    [[nodiscard]] auto supportsColorCodes() const noexcept -> bool override { return false; }
    [[nodiscard]] auto supportsCursorCodes() const noexcept -> bool override { return false; }
    [[nodiscard]] auto isInteractive() const noexcept -> bool override { return true; }
    [[nodiscard]] auto detectScreenSize() -> std::optional<Size> override { return Size{80, 25}; }

    void emitColor(Color color) override { _lastColor = color; }
    void moveCursor(Position posOrDelta, MoveMode mode) override {
        if (mode == MoveMode::Absolute) {
            _cursor = posOrDelta;
        } else {
            _cursor += posOrDelta;
        }
    }
    void clearScreen() override { _cursor = Position{0, 0}; }

    void emitText(std::string_view text) override { _log += text; }
    void emitFlush() override {}

    [[nodiscard]] auto inputMode() const noexcept -> Input::Mode override { return _inputMode; }
    void setInputMode(Input::Mode mode) override { _inputMode = mode; }
    [[nodiscard]] auto readKey(std::chrono::milliseconds) -> Key override { return {}; }
    [[nodiscard]] auto readLine() -> std::string override { return {}; }

    [[nodiscard]] auto log() const noexcept -> const std::string & { return _log; }

private:
    Position _cursor;
    Color _lastColor;
    Input::Mode _inputMode{Input::Mode::ReadLine};
    std::string _log;
};

This style of backend is especially useful for integration tests that need to inspect what the terminal would have emitted without depending on a real terminal session.

Screen Size and Input Responsibilities

Backends are also responsible for the low-level parts of interactivity:

The terminal applies its own one-column and one-row safe margin on top of the detected screen size. Backend implementations should therefore return the real visible terminal dimensions instead of subtracting that margin themselves.

Public API and Internal Implementations

This page describes the stable public contract. The built-in backend implementations are documented separately in the implementation notes:

These pages explain how the shipped backends satisfy this contract on their respective platforms, including size detection, raw key handling, and shutdown cleanup.

Interface

class Backend : public std::enable_shared_from_this<Backend>

The interface to the underlying platform.

This library expects that the platform implementation correctly handles UTF-8 encoding and VT100 ANSI control codes.

Public Functions

virtual void initializePlatform() = 0

Initialize the platform.

This method is called from Terminal::initializeScreen().

virtual void restorePlatform() = 0

Restore the platform.

This method is called from Termina::restoreScreen().

virtual bool supportsColorCodes() const noexcept = 0

Test if the platform supports color output.

This just tests for color support, not cursor movement. If you return false, no ANSI color codes are emitted and the line buffer is disabled. Instead, the emitColor() method is called on the backend.

Note

Before each call to emitColor(), the text from the line buffer is emitted.

virtual bool supportsCursorCodes() const noexcept = 0

Test if the platform supports cursor movement.

This includes cursor position, clearing the terminal and other cursor-related operations. If you return false, no cursor movement commands are emitted and the line buffer is disabled. Instead, the moveCursor() etc. methods are called on the backend.

Note

Before each call of the cursor methods, the text from the line buffer is emitted.

inline virtual bool supportsCursorVisibilityCodes() const noexcept

Test if the backend supports cursor visible/invisible ANSI sequences.

If this method returns true, the ANSI codes ESC[?25l and ESC[?25h to control the cursor visibility. If it returns false, Terminal calls setCursorVisible() instead.

inline virtual bool supportsAlternateScreenBufferCodes() const noexcept

Test if the backend supports alternate screen buffer codes.

If this method returns true, the ANSI codes ESC[?1049h and ESC[?1049l are sent to the backend to switch to the alternate screen buffer. Additionally setAlternateScreenBuffer() is called to notify the backend about the change. If this method returns false, Terminal only calls setAlternateScreenBuffer() instead.

virtual bool isInteractive() const noexcept = 0

Check if an interactive terminal is attached to the process.

This state is typically established during Terminal::initializeScreen().

Returns:

true if interactive terminal features such as resize and cursor control are available.

virtual std::optional<Size> detectScreenSize() = 0

Detect the current terminal screen size.

This method is invoked by Terminal::initializeScreen() and Terminal::testScreenSize() when size detection is enabled. Interactive applications may call testScreenSize() before every screen update, so implementations should be efficient or perform updates asynchronously in the background.

The returned size should represent the visible terminal area available for output. A safety margin of one column and one row is applied by Terminal automatically.

Returns:

The detected screen size, or std::nullopt if detection failed.

inline virtual void emitColor(Color color)

Change the current color.

Only called if supportsColorCodes() returns false.

Parameters:

color – The new color to set.

inline virtual CharAttributes supportedCharAttributes() const noexcept

Get the character attributes supported by this backend.

The returned value is a fully specified bit mask.

Returns:

The supported character attributes.

inline virtual CharAttributes supportedCharAttributeCodes() const noexcept

Get the character attributes that can be emitted directly via ANSI codes.

The returned value is a fully specified bit mask.

Returns:

The character attributes supported through ANSI codes.

inline virtual void emitCharAttributes(CharAttributes attributes)

Change the current character attributes.

Only called if one or more supported attributes cannot be emitted through ANSI codes. The backend receives the current supported attribute state and can diff it against its own local state.

Parameters:

attributes – The current supported attribute state.

inline virtual void moveCursor(Position posOrDelta, MoveMode mode)

Move the cursor.

Only called if supportsCursorCodes() returns false.

Parameters:
  • posOrDelta – The absolute or relative movement for the cursor. (0,0) = top-left corner.

  • mode – The move mode, either absolute or relative.

inline virtual void clearScreen()

Clear the screen and move the cursor to (0,0).

Only called if supportsCursorCodes() returns false.

inline virtual void setCursorVisible(bool visible)

Control if the cursor is visible on the terminal.

If supportsCursorVisibilityCodes() returns false, this method is called to control the cursor visibility.

inline virtual void setAlternateScreenBuffer(bool enabled)

Enables/disables the alternate screen buffer or notifies the backend about the change.

virtual void emitText(std::string_view text) = 0

Emit the given UTF-8 encoded text.

If the backend does not support UTF-8, you are responsible to convert the text to the expected encoding. Line breaks are usually just NL and not CRLF. UTF-8 sequences, and ANSI sequences are always complete in one call of this method.

Parameters:

text – The UTF-8 encoded text to emit.

virtual void emitFlush() = 0

Flush the output buffer.

After a call of this method, the backend must flush all previously emitted text and control sequences to the terminal.

virtual Input::Mode inputMode() const noexcept = 0

Get the current input mode.

virtual void setInputMode(Input::Mode mode) = 0

Set the current input mode.

Parameters:

mode – The new input mode.

virtual Key readKey(std::chrono::milliseconds timeout) = 0

Read one key event.

In Input::Mode::ReadLine mode, the user has to enter a character and press enter.

Parameters:

timeout – Maximum wait time in Mode::Key; ignored in Mode::ReadLine.

Returns:

The parsed key event, or an invalid key if no supported input was read.

virtual std::string readLine() = 0

Read text input in the terminal.

Can be ignored in Input::Mode::Key mode.

Returns:

The read text, without line breaks.

Public Static Functions

static BackendPtr createPlatformDefault(TerminalFlags terminalFlags)

Create the default backend for this platform.

using erbsland::cterm::BackendPtr = std::shared_ptr<Backend>