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:
Backend::supportsColorCodes()controls whetherTerminalemits ANSI color sequences itself. If it returnsfalse, the terminal callsBackend::emitColor()instead.Backend::supportsCursorCodes()controls whether cursor movement and screen clearing use ANSI escape sequences or backend hooks such asBackend::moveCursor()andBackend::clearScreen().Backend::supportsCursorVisibilityCodes()decides whether cursor visibility uses ANSIESC[?25handESC[?25lorBackend::setCursorVisible().Backend::supportsAlternateScreenBufferCodes()decides whether alternate-screen switching uses ANSIESC[?1049handESC[?1049lin addition toBackend::setAlternateScreenBuffer().Backend::supportedCharAttributes()reports which character attributes the backend can represent at all.Backend::supportedCharAttributeCodes()tellsTerminalwhich of those attributes can be emitted directly as ANSI SGR codes. If a supported attribute is missing here, the terminal flushes pending text and callsBackend::emitCharAttributes()instead.
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:
Backend::detectScreenSize()reports the visible terminal size orstd::nulloptif the host cannot provide one.Backend::isInteractive()tellsTerminalwhether a real, interactive console is attached.Backend::inputMode(),Backend::setInputMode(),Backend::readKey(), andBackend::readLine()power the publicInputAPI.
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, theemitColor()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, themoveCursor()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 codesESC[?25landESC[?25hto control the cursor visibility. If it returnsfalse,TerminalcallssetCursorVisible()instead.
-
inline virtual bool supportsAlternateScreenBufferCodes() const noexcept
Test if the backend supports alternate screen buffer codes.
If this method returns
true, the ANSI codesESC[?1049handESC[?1049lare sent to the backend to switch to the alternate screen buffer. AdditionallysetAlternateScreenBuffer()is called to notify the backend about the change. If this method returnsfalse,Terminalonly callssetAlternateScreenBuffer()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:
trueif 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()andTerminal::testScreenSize()when size detection is enabled. Interactive applications may calltestScreenSize()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
Terminalautomatically.- Returns:
The detected screen size, or
std::nulloptif detection failed.
-
inline virtual void emitColor(Color color)
Change the current color.
Only called if
supportsColorCodes()returnsfalse.- 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()returnsfalse.- 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()returnsfalse.
-
inline virtual void setCursorVisible(bool visible)
Control if the cursor is visible on the terminal.
If
supportsCursorVisibilityCodes()returnsfalse, 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 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::ReadLinemode, the user has to enter a character and press enter.- Parameters:
timeout – Maximum wait time in
Mode::Key; ignored inMode::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::Keymode.- Returns:
The read text, without line breaks.
Public Static Functions
-
static BackendPtr createPlatformDefault(TerminalFlags terminalFlags)
Create the default backend for this platform.
-
virtual void initializePlatform() = 0