bittty

bittty: A fast, pure Python terminal emulator library.

bittty (bitplane-tty) is a high-performance terminal emulator engine that provides comprehensive ANSI sequence parsing and terminal state management.

Vocabulary: the Board is the machine (devices, registers, video memory, the child process). A terminal (bittty.terminals) is the chrome a human looks at, plugged into the board’s display port. Terminal is deliberately not exported here — import it from bittty.terminals.

bittty.terminals.probe

Capability probing for the stdio terminal.

Ask the real outer terminal what it can do, then hand a TerminalCaps up to the backend. Uses a DA1-terminated handshake: fire all the queries plus a Primary DA request, then read until the (universally answered) DA reply arrives, so optional queries that go unanswered never make us hang. Non-tty or timeout ⇒ env/config caps only.

Nothing here reads capabilities the backend then trusts blindly — TerminalCaps is purely physical facts (colour depth, pixel geometry, background). Graphics-mode reconciliation is deferred with the graphics families.

color_depth_from_env

def color_depth_from_env(env) -> str

Best-effort colour depth from COLORTERM / TERM (no query exists for this).

parse_probe_replies

def parse_probe_replies(buf: str, env) -> TerminalCaps

Build TerminalCaps from a probe-reply buffer, unioned with env (probe wins).

probe_caps

def probe_caps(stdin_fd,
               write,
               env=None,
               timeout: float = 0.5) -> TerminalCaps

Query the real terminal and return TerminalCaps; env-only on a non-tty/timeout.

write is a callable that writes a str to the outer terminal (and flushes).

bittty.terminals.base

The Terminal abstract base: the chrome a human looks at.

A concrete terminal is-a Terminal and has-a Board (composition). It plugs itself into the board’s display port, pushes TerminalCaps up, and receives discrete side-effects through present(), which dispatches each PresentEvent to a typed hook. Every hook defaults to a no-op, so adding a new event type can never break an existing terminal — it just grows the surface with another optional override.

The board never imports this module: the boundary only ever runs terminal -> board, never the reverse.

Terminal Objects

class Terminal()

Abstract base for terminals (chrome). Compose a Board; override the hooks you need.

attach

def attach() -> None

Plug this terminal into its board’s display port.

detach

def detach() -> None

Unplug from the board’s display port.

set_caps

def set_caps(caps: TerminalCaps) -> None

Push the real terminal’s capabilities down to the board.

present

def present(event: PresentEvent) -> None

Route a present event to its typed hook (unknown types are ignored).

bittty.terminals.stdio

StdioTerminal: the reference terminal, whose venue is this process’s stdio.

Composes a Board (never subclasses it) and drives the real outer terminal: raw-mode stdin, ANSI rendering to stdout, resize handling, and mouse mirroring. Discrete side-effects arrive through the Terminal hooks (on_bell/on_title/ on_mouse_mode).

HostInputSink Objects

class HostInputSink()

Sink for the input-direction parser: host bytes in, board events out.

Every operation carries its raw bytes, so anything we don’t intercept is forwarded to the child verbatim and in order — arrow keys, control chars, pastes, whole unknown sequences. Interception is by raw prefix, not operation name: on the input direction CSI I is a focus report, never CHT.

StdioTerminal Objects

class StdioTerminal(Terminal)

Render a bittty Board to the real terminal this program is running in.

get_default_shell

def get_default_shell() -> str

Get the default shell command for the current platform.

on_bell

def on_bell() -> None

Ring the outer terminal’s bell.

on_title

def on_title(title: str, icon_title: str) -> None

Mirror the window title onto the outer terminal.

on_mouse_mode

def on_mouse_mode(mode: str, sgr: bool) -> None

Mirror the child’s requested mouse-tracking mode onto the outer terminal.

disable_host_mouse

def disable_host_mouse() -> None

Turn off host-terminal mouse reporting.

setup_terminal

def setup_terminal() -> None

Put the host terminal into raw mode, clear it, and ask for focus events.

restore_terminal

def restore_terminal() -> None

Restore the host terminal to its original state.

probe_capabilities

def probe_capabilities() -> None

Ask the outer terminal what it can do and push TerminalCaps to the backend.

render_screen

def render_screen() -> None

Render the current board state to stdout, then place the host’s hardware cursor.

The cursor is the host terminal’s own: position it and show it when the child wants it visible (DECTCEM). The host hollows it on unfocus by itself, exactly like a real terminal.

handle_pty_data

def handle_pty_data(data: str) -> None

Feed child output into the emulator and mark the screen dirty.

Rendering happens on the run loop’s tick, not per PTY chunk — a repaint per chunk backpressures a flooding child (it blocks writing to the PTY while we paint), turning a 66ms find into a 750ms one.

handle_sgr_mouse_sequence

def handle_sgr_mouse_sequence(sequence: str) -> bool

Parse a host SGR mouse report and re-inject it through bittty.

handle_focus

def handle_focus(focused: bool) -> None

A host focus event: the backend owns the state; we just repaint.

handle_input

def handle_input(data: str) -> None

Feed host input through the input-direction parser.

The parser reassembles sequences split across reads; HostInputSink intercepts SGR mouse reports and focus events and forwards everything else to the child verbatim.

flush_pending_input

def flush_pending_input() -> None

Release a held incomplete sequence that never completed.

A lone ESC keypress looks like a sequence prefix, so the parser holds it; when no follow-up arrives within an input-loop tick it was a real ESC and must reach the child.

handle_resize

def handle_resize() -> None

Re-read the host size and resize the emulator (called from a SIGWINCH handler).

input_loop

async def input_loop() -> None

Read host input and forward it.

run

async def run() -> None

Main loop: start the shell, pump input, render until it exits.

cleanup

def cleanup() -> None

Tear down the child and restore the host terminal.

bittty.terminals

Terminals: the chrome a human looks at, named by venue.

Kept in its own package so the board never imports terminal code — the “board is never subclassed by a terminal” rule, enforced structurally.

bittty.video

Video memory: the 2D cell grid the blitter writes and terminals render.

A Board has two pages of it (primary and alternate).

Video Objects

class Video()

A 2D grid that stores terminal content.

__init__

def __init__(width: int, height: int) -> None

Initialize buffer with given dimensions.

observe

def observe() -> int

Snapshot for dirty tracking: close the current epoch, open a new one.

Returns the new epoch; rows stamped at or after it are dirty relative to this observation. Each reader keeps its own returned value.

dirty_rows

def dirty_rows(seen: int) -> List[int]

Rows changed since seen (a value returned by observe()).

set_line_attribute

def set_line_attribute(y: int, attribute: str) -> None

Set a line’s DECDHL/DECDWL/DECSWL attribute.

get_line_attribute

def get_line_attribute(y: int) -> str

Return a line’s width/height attribute (single by default).

reset_line_attributes

def reset_line_attributes() -> None

Return every line to single-width, single-height (RIS).

get_content

def get_content() -> List[List[Cell]]

Get buffer content as a 2D grid.

get_cell

def get_cell(x: int, y: int) -> Cell

Get cell at position.

set_cell

def set_cell(x: int, y: int, char: str, style_or_ansi=None) -> None

Set a single cell at position.

Arguments:

x, y: Position

set

def set(x: int, y: int, text: str, style_or_ansi=None) -> None

Set text at position, overwriting existing content.

insert

def insert(x: int, y: int, text: str, style_or_ansi=None) -> None

Insert text at position, shifting existing content right.

delete

def delete(x: int, y: int, count: int = 1) -> None

Delete characters at position.

clear_region

def clear_region(x1: int,
                 y1: int,
                 x2: int,
                 y2: int,
                 style_or_ansi=None) -> None

Clear a rectangular region.

clear_line

def clear_line(y: int,
               mode: int = constants.ERASE_FROM_CURSOR_TO_END,
               cursor_x: int = 0,
               style_or_ansi=None) -> None

Clear line content.

scroll_up

def scroll_up(count: int) -> None

Scroll content up, removing top lines and adding blank lines at bottom.

scroll_down

def scroll_down(count: int) -> None

Scroll content down, removing bottom lines and adding blank lines at top.

scroll_region_up

def scroll_region_up(top: int, bottom: int, count: int) -> None

Scroll a specific region up by count lines. BLAZING FAST bulk operation!

scroll_region_down

def scroll_region_down(top: int, bottom: int, count: int) -> None

Scroll a specific region down by count lines. BLAZING FAST bulk operation!

resize

def resize(width: int, height: int) -> None

Resize buffer to new dimensions.

def link_extent(x: int, y: int) -> tuple | None

The contiguous same-link run containing (x, y) on its row.

Returns (uri, link_id, x0, x1) with an inclusive column span, or None if the cell isn’t a link. Segments split across rows share a link_id; grouping those into one hover is the chrome’s job.

get_line_text

def get_line_text(y: int) -> str

Get plain text content of a line (for debugging/testing).

get_line

def get_line(y: int, width: int = None) -> str

Get full ANSI sequence for a line — a pure read of video memory.

No cursor or pointer is composited in; those are chrome concerns, rendered by the terminal from the board’s registers.

get_line_tuple

def get_line_tuple(y: int, width: int = None) -> tuple

Get line as a hashable tuple for caching — a pure read of video memory.

bittty.connections

Connections and the board-side ports they plug into.

A port is a jack on the board, and both are full-duplex. The host port carries bytes both ways (a serial line: PTY, pipe, socket). The display port carries typed events both ways: present events down to the terminal (chrome), input events up from it.

Connection Objects

@runtime_checkable
class Connection(Protocol)

A cable implementation (PTY, pipe, socket) that accepts terminal input/reply data.

write

def write(data: str)

Write data to the connected host.

Presentable Objects

@runtime_checkable
class Presentable(Protocol)

A terminal (chrome) that receives discrete present events from the board.

present

def present(event: "PresentEvent") -> None

Handle one present event.

HostPort Objects

class HostPort()

The board’s jack toward the child program; a Connection (PTY, pipe) plugs in.

Full duplex: write() is the transmit pin (replies and encoded input toward the child); connect() starts the receive pump, feeding the child’s output into a sink — the board wires it to its parser.

attach

def attach(connection: Connection) -> None

Attach a connection to this host port (transmit side only).

detach

def detach() -> None

Detach the current connection.

connect

def connect(connection: Connection,
            on_data: Callable[[str], None],
            on_idle: Optional[Callable[[], bool]] = None,
            on_closed: Optional[Callable[[], None]] = None) -> None

Plug in a duplex connection and start pumping its receive side.

on_data receives each decoded chunk. on_idle fires when a read returns nothing — return True to stop the pump (the board reaps its dead child there). on_closed fires when the connection errors out.

disconnect

def disconnect() -> None

Stop the receive pump and unplug the connection.

connected

@property
def connected() -> bool

Whether a connection is attached.

write

def write(data: str, flush: bool = False)

Write data to the attached connection.

DisplayPort Objects

class DisplayPort()

The board’s jack toward the terminal (chrome); mirrors HostPort.

The name is the video-connector pun, kept on purpose: the one place “display” survives in board vocabulary. Full duplex: the board pushes discrete present events down; the terminal sends input events, focus changes, and capability reports up. When no terminal is attached present() is a no-op, so the board runs headless exactly as before.

attach

def attach(terminal: Presentable) -> None

Attach a terminal (chrome) to receive present events.

detach

def detach() -> None

Detach the current terminal.

connected

@property
def connected() -> bool

Whether a terminal (chrome) is attached.

present

def present(event: "PresentEvent") -> None

Forward a present event to the attached terminal, if any.

input

def input(data: str) -> None

Keystrokes from the terminal, translated per keyboard modes.

input_key

def input_key(char: str, modifier: int = constants.KEY_MOD_NONE) -> None

A key + modifier from the terminal.

input_fkey

def input_fkey(num: int, modifier: int = constants.KEY_MOD_NONE) -> None

A function key + modifier from the terminal.

input_numpad_key

def input_numpad_key(key: str) -> None

A numpad key from the terminal.

input_paste

def input_paste(text: str) -> None

Pasted text from the terminal, bracketed per mode 2004.

input_mouse

def input_mouse(x: int, y: int, button: int, event_type: str,
                modifiers: set[str]) -> None

A mouse event from the terminal.

focus_in

def focus_in() -> None

The box gained focus.

focus_out

def focus_out() -> None

The box lost focus.

set_caps

def set_caps(caps: "TerminalCaps") -> None

The terminal reports what its venue can do.

bittty.parser.dcs

DCS (Device Control String) operation parser.

parse_dcs_operation

def parse_dcs_operation(string_buffer: str, raw: str = "") -> Operation

Return an operation for a DCS sequence.

bittty.parser.csi

CSI (Control Sequence Introducer) operation parser.

param

def param(params, index=0, default=None)

Return params[index] when present and not None, else default.

parse_csi_params

@lru_cache(maxsize=1000)
def parse_csi_params(data)

Parse CSI parameters when actually needed.

Arguments:

Returns:

parse_csi_operation

@lru_cache(maxsize=4096)
def parse_csi_operation(raw_csi_data: str) -> Operation | None

Return a semantic operation for CSI sequences migrated to the operation layer.

Cached: real terminal traffic repeats a small CSI vocabulary (a full-screen TUI session uses a few hundred distinct sequences), and Operation is frozen with immutable args, so one instance per distinct sequence is safe to share.

bittty.parser.osc

OSC (Operating System Command) operation parser.

parse_osc_operation

def parse_osc_operation(string_buffer: str, raw: str = "") -> Operation | None

Return a semantic operation for an OSC sequence.

bittty.parser.core

Core Parser class with state machine and sequence dispatching (fast state-specific scanners).

parse_string_sequence

@lru_cache(maxsize=300)
def parse_string_sequence(data: str, sequence_type: str) -> str

Strip prefix and terminator for OSC/DCS/APC/PM/SOS.

Parser Objects

class Parser()

State machine: GROUND → (CSI | STRING[osc|dcs|apc|pm|sos]) → GROUND Uses small, state-specific scanners for speed.

flush_trailing

def flush_trailing() -> None

Emit any held incomplete sequence as plain text and reset to ground.

For an input-direction parser: a lone ESC (or a dangling introducer) that never completed within the caller’s patience was a keypress, not a sequence prefix, and must reach the sink as-is. Never called on the output direction, where waiting for the rest of the sequence is right.

bittty.parser.escape

Simple escape sequence operation parser.

parse_escape_operation

def parse_escape_operation(data: str) -> Operation | None

Return a semantic operation for a simple ESC sequence.

parse_hash_operation

def parse_hash_operation(data: str) -> Operation | None

Return a semantic operation for an ESC # n line-size / alignment sequence.

parse_charset_operation

def parse_charset_operation(data: str) -> Operation | None

Return a semantic operation for a charset designation sequence.

bittty.parser

Parser module for terminal escape sequence processing.

This module provides a modular parser system that processes terminal escape sequences through specialized handlers:

The main Parser class coordinates all these handlers and maintains the state machine.

bittty.operations

Parser operation model.

Operation Objects

@dataclass(frozen=True)
class Operation()

A parsed terminal operation.

OperationSink Objects

class OperationSink(Protocol)

Receives operations emitted by the parser.

handle_operation

def handle_operation(operation: Operation) -> None

Handle one parsed operation.

control_name

def control_name(ch: str) -> str

Return a stable operation name for a C0 control character.

bittty.keymap

Keyboard encoding as model data.

Function keys are where real terminals diverge most: a VT100 has only PF1-PF4 and no keyboard-modifier encoding, while xterm sends F1-F12 and folds shift/alt/ ctrl into a ;mod parameter. A KeyMap captures that difference as data.

KeyMap Objects

@dataclass(frozen=True)
class KeyMap()

How a terminal encodes its keys (and whether it encodes modifiers).

cursor_keys

“up”/”down”/”left”/”right” -> CSI/SS3 final byte

“home”/”end”/… -> CSI body

modifiers

whether shift/alt/ctrl are folded into the sequence

apply_modifier

def apply_modifier(sequence: str, modifier: int) -> str

Fold an xterm-style modifier into a function-key sequence.

ESC O X becomes ESC [ 1 ; mod X; ESC [ n ~ becomes ESC [ n ; mod ~.

bittty.model

Terminal models: the emulation profile as data.

A model captures the constants that distinguish one real terminal from another — starting with the Device Attributes (DA) responses used to identify the terminal to the host. Over time this grows to carry charset repertoire, colour depth, and which capabilities a board assembles.

Model Objects

@dataclass(frozen=True)
class Model()

A terminal type expressed as data.

da1_response

Primary Device Attributes (answer to CSI c)

da2_response

Secondary DA (CSI > c); None if unsupported

da3_response

Tertiary DA (CSI = c); None if unsupported

get_model

def get_model(term_name: str | None, default: Model = DEFAULT) -> Model

Resolve a $TERM name to a model, falling back through shorter prefixes.

So “xterm-kitty” or “screen.xterm-256color” degrade gracefully to the nearest known family, and an unknown or empty TERM yields the default (xterm).

bittty.pty.base

Base PTY interface for terminal emulation.

This module provides a concrete base class that works with file-like objects, with platform-specific subclasses overriding only the byte-level I/O methods.

PTY Objects

class PTY()

A generic PTY that lacks OS integration.

Uses StringIO if no file handles are provided, and subprocess to handle its children.

If you use this then you’ll have to

__init__

def __init__(from_process: Optional[BinaryIO] = None,
             to_process: Optional[BinaryIO] = None,
             rows: int = constants.DEFAULT_TERMINAL_HEIGHT,
             cols: int = constants.DEFAULT_TERMINAL_WIDTH)

Initialize PTY with file-like input/output sources.

Arguments:

read_bytes

def read_bytes(size: int) -> bytes

Read raw bytes. Override in subclasses for platform-specific I/O.

write_bytes

def write_bytes(data: bytes) -> int

Write raw bytes. Override in subclasses for platform-specific I/O.

read

def read(size: int = constants.DEFAULT_PTY_BUFFER_SIZE) -> str

Read data using the C incremental UTF-8 decoder (buffers split code points).

write

def write(data: str) -> int

Write string as UTF-8 bytes.

resize

def resize(rows: int, cols: int) -> None

Resize the terminal (base implementation just updates dimensions).

close

def close() -> None

Close the PTY streams.

closed

@property
def closed() -> bool

Check if PTY is closed.

spawn_process

def spawn_process(command: str, env: dict[str, str] = ENV) -> subprocess.Popen

Spawn a process connected to PTY streams.

read_async

async def read_async(size: int = constants.DEFAULT_PTY_BUFFER_SIZE) -> str

Async read using thread pool executor.

Uses loop.run_in_executor() as a generic cross-platform approach. Unix PTY overrides this with more efficient file descriptor monitoring. Windows and other platforms use this thread pool implementation.

flush

def flush() -> None

Flush output.

bittty.pty.unix

Unix/Linux/macOS PTY implementation.

UnixPTY Objects

class UnixPTY(PTY)

Unix/Linux/macOS PTY implementation.

resize

def resize(rows: int, cols: int) -> None

Resize the terminal using TIOCSWINSZ ioctl.

close

def close() -> None

Close the PTY file descriptors.

spawn_process

def spawn_process(command: str,
                  env: dict[str, str] = UNIX_ENV) -> subprocess.Popen

Spawn a process attached to this PTY.

flush

def flush() -> None

PTY master file descriptors don’t support fsync - data flows immediately.

For Unix PTYs, data written to the master side appears immediately on the slave side, so no explicit flushing is needed.

read_async

async def read_async(size: int = constants.DEFAULT_PTY_BUFFER_SIZE) -> str

Async read from PTY using efficient file descriptor monitoring.

Uses loop.add_reader() with file descriptors for maximum efficiency on Unix. This is the most performant approach since Unix supports select/poll on PTY fds.

bittty.pty.windows

Windows PTY implementation using pywinpty.

WinptyFileWrapper Objects

class WinptyFileWrapper()

File-like wrapper for winpty.PTY to work with base PTY class.

read

def read(size: int = -1) -> str

Read data as strings.

write

def write(data: str) -> int

Write string data.

close

def close() -> None

Close the PTY.

closed

@property
def closed() -> bool

Check if closed.

flush

def flush() -> None

Flush - no-op for winpty.

WinptyProcessWrapper Objects

class WinptyProcessWrapper()

Wrapper to provide subprocess.Popen-like interface for winpty PTY.

poll

def poll()

Check if process is still running.

wait

def wait()

Wait for process to complete.

returncode

@property
def returncode()

Get the return code.

pid

@property
def pid()

Get the process ID.

WindowsPTY Objects

class WindowsPTY(PTY)

Windows PTY implementation using pywinpty.

Note: This PTY operates in text mode - winpty handles UTF-8 internally. The read/write methods work directly with strings for performance, with bytes conversion only when needed for compatibility.

read

def read(size: int = constants.DEFAULT_PTY_BUFFER_SIZE) -> str

Read data directly from winpty (text mode, no UTF-8 splitting needed).

write

def write(data: str) -> int

Write string data directly to winpty (text mode).

resize

def resize(rows: int, cols: int) -> None

Resize the terminal.

spawn_process

def spawn_process(command: str,
                  env: Optional[Dict[str, str]] = ENV) -> subprocess.Popen

Spawn a process attached to this PTY.

bittty.pty

PTY implementations for terminal emulation.

bittty.constants

Constants for the terminal parser and emulator.

This module contains constants used in the terminal parsing and emulation logic, following standards like VT100, VT220, and xterm.

ENQ

Enquiry (triggers answerback)

BEL

Bell

BS

Backspace

HT

Horizontal Tab

LF

Line Feed

VT

Vertical Tab

FF

Form Feed

CR

Carriage Return

SO

Shift Out (activate G1)

SI

Shift In (activate G0)

ESC

Escape

DEL

Delete

LINE_SINGLE

DECSWL — normal single-width, single-height

LINE_DOUBLE_WIDTH

DECDWL

LINE_DOUBLE_TOP

DECDHL top half

LINE_DOUBLE_BOTTOM

DECDHL bottom half

DECKBUM_KEYBOARD_USAGE

DECKBUM is mode 68; mode 69 is DECLRMM (left/right margins)

DA1_132_COLUMNS

132 column mode

DA1_PRINTER_PORT

Printer port

DA1_REGIS_GRAPHICS

ReGIS graphics

DA1_SIXEL_GRAPHICS

Sixel graphics

DA1_SELECTIVE_ERASE

Selective erase

DA1_USER_DEFINED_KEYS

User-defined keys (UDKs)

DA1_NATIONAL_REPLACEMENT_CHARSETS

National replacement character sets

DA1_TECH_CHARACTERS

Technical characters

DA1_LOCATOR_PORT

Locator port

DA1_TERMINAL_STATE_INTERROGATION

Terminal state interrogation

DA1_USER_WINDOWS

User windows

DA1_DUAL_SESSIONS

Dual sessions

DA1_HORIZONTAL_SCROLLING

Horizontal scrolling

DA1_ANSI_COLOR

ANSI color

DA1_GREEK_CHARSET

Greek character set

DA1_TURKISH_CHARSET

Turkish character set

DA1_ISO_LATIN2_CHARSET

ISO Latin-2 character set

DA1_PC_TERM

PC Term

DA1_SOFT_KEY_MAP

Soft key map

DA1_ASCII_EMULATION

ASCII emulation

EBADF

Bad file descriptor

EINVAL

Invalid argument

bittty.charsets

Character set mappings for terminal emulation.

get_charset

def get_charset(designator: str) -> dict

Get character set mapping for a designator.

bittty.devices.query

Query operation handler for the current board state.

QueryDevice Objects

class QueryDevice(Device)

Applies terminal query operations to the current board implementation.

handle_cwd

def handle_cwd(operation: Operation) -> None

OSC 7 — record the reported working directory.

handle_notify

def handle_notify(operation: Operation) -> None

OSC 9 / 777 / 99 — a desktop notification.

handle_shell_mark

def handle_shell_mark(operation: Operation) -> None

OSC 133 — a shell-integration prompt/command mark.

handle_pointer_shape

def handle_pointer_shape(operation: Operation) -> None

OSC 22 — the requested mouse-pointer shape.

handle_font

def handle_font(operation: Operation) -> None

OSC 50 — set the font, or answer a query (data == ‘?’) with the current one.

report_version

def report_version(operation: Operation) -> None
XTVERSION (CSI > q) — reply DCS > name version ST.

report_status_string

def report_status_string(operation: Operation) -> None

DECRQSS — answer a request for the current value of a setting.

handle_clipboard

def handle_clipboard(operation: Operation) -> None

OSC 52 — set the clipboard, or answer a query with its current contents.

handle_window_op

def handle_window_op(operation: Operation) -> None

XTWINOPS — window manipulation requests and reports; a terminal (chrome) actuates them.

set_conformance_level

def set_conformance_level(operation: Operation) -> None

DECSCL — record the requested conformance level (behaviourally a no-op).

handle_setterm

def handle_setterm(operation: Operation) -> None

linux setterm CSI…] — update the board’s hardware registers.

request_checksum

def request_checksum(operation: Operation) -> None

DECRQCRA — reply DCS Pid ! ~ HHHH ST with a 16-bit checksum of a rectangle.

This is the DEC character-value form: the negated sum of the codepoints in the area, masked to 16 bits. (xterm can fold SGR attributes in too; that is a model detail we can add when a terminal needs it.)

request_termcap

def request_termcap(operation: Operation) -> None

XTGETTCAP — answer hex-encoded termcap/terminfo capability requests.

bittty.devices.board

The board: the whole terminal emulator machine.

Hosts the devices and registers, owns the child process and its PTY, and routes parser operations to device handlers. A terminal (chrome) (bittty.terminals) plugs into the display port; the child program is wired to the host port via a PTY.

Board Objects

class Board()

The terminal emulator: devices, registers, and process/PTY lifecycle.

get_pty_handler

@staticmethod
def get_pty_handler(rows: int = constants.DEFAULT_TERMINAL_HEIGHT,
                    cols: int = constants.DEFAULT_TERMINAL_WIDTH,
                    stdin=None,
                    stdout=None)

Create a platform-appropriate PTY handler.

def print_text(text: str) -> None

Write printable text (the parser’s fast path — no Operation wrapper).

resize

def resize(width: int, height: int) -> None

Resize the terminal, including buffers and the attached PTY.

bell

def bell() -> None

Ring the terminal bell: pushed to the terminal (chrome) as a present event.

present

def present(event: PresentEvent) -> None

Push a discrete side-effect to the attached terminal (no-op if none).

set_caps

def set_caps(caps: TerminalCaps) -> None

Record what the real terminal can do (a terminal (chrome) pushes this after probing).

set_focus

def set_focus(focused: bool) -> None

Record the box’s focus state and report it to the child (DECSET 1004).

reset

def reset(hard: bool = True) -> None

Reset the terminal. hard is RIS (full power-on); soft is DECSTR.

get_device

def get_device(name: str)

Return a plugged-in device by slot name.

get_content

def get_content()

Get current screen content as raw buffer data.

capture_pane

def capture_pane() -> str

Capture screen content: a pure pull of video memory as ANSI lines.

No cursor or pointer is composited in — the chrome renders those from the board’s registers (cursor.x/y, modes.cursor_visible, mouse.x/y).

def link_at(x: int, y: int) -> tuple | None

The hyperlink under a cell: (uri, link_id) or None.

The chrome pulls this for hover and click arbitration — link data is model state; the hover effect and the click-through are chrome.

attach_display

def attach_display(display) -> None

Attach a terminal (chrome) to receive present events (mirrors the host/PTY cable).

detach_display

def detach_display() -> None

Detach the current terminal.

input_key

def input_key(char: str, modifier: int = constants.KEY_MOD_NONE) -> None

Convert key + modifier to standard control codes, then send to the host.

input_fkey

def input_fkey(num: int, modifier: int = constants.KEY_MOD_NONE) -> None

Convert function key + modifier to standard control codes, then send to the host.

input_numpad_key

def input_numpad_key(key: str) -> None

Convert numpad key to appropriate sequence based on DECNKM mode.

input

def input(data: str) -> None

Translate control codes based on terminal modes and send to the host.

input_paste

def input_paste(text: str) -> None

Pasted text from the terminal: bracketed when mode 2004 is on, else raw.

Bypasses keyboard translation — a paste is data, not keystrokes.

input_mouse

def input_mouse(x: int, y: int, button: int, event_type: str,
                modifiers: set[str]) -> None

Handle mouse input, cache position, and send appropriate sequence to the host.

Arguments:

focus_in

def focus_in() -> None

The box gained focus: record it and report to the child if DECSET 1004 is on.

focus_out

def focus_out() -> None

The box lost focus: record it and report to the child if DECSET 1004 is on.

pty

@property
def pty() -> Optional[Any]

Attached PTY connection.

set_pty_data_callback

def set_pty_data_callback(callback: Callable[[str], None]) -> None

Swap the host port’s receive sink (a terminal uses this to add render throttling).

start_process

async def start_process() -> None

Start the child process with PTY.

stop_process

def stop_process() -> None

Stop the child process and clean up.

bittty.devices.charset

Charset operation handler for the current board state.

CharsetDevice Objects

class CharsetDevice(Device)

Owns charset state and applies charset operations.

designate

def designate(index: int, designator: str) -> None

Apply an SCS G-set designation, ignoring charsets the terminal lacks.

translate

def translate(text: str) -> str

Translate text through the invoked G-sets: GL for 0x20-0x7F, GR for 0xA0-0xFF.

set_g0_charset

def set_g0_charset(charset: str) -> None

Set the G0 character set.

set_g1_charset

def set_g1_charset(charset: str) -> None

Set the G1 character set.

set_g2_charset

def set_g2_charset(charset: str) -> None

Set the G2 character set.

set_g3_charset

def set_g3_charset(charset: str) -> None

Set the G3 character set.

shift_in

def shift_in() -> None

Shift In (SI / LS0) - invoke G0 into GL.

shift_out

def shift_out() -> None

Shift Out (SO / LS1) - invoke G1 into GL.

locking_shift

def locking_shift(gset: int, right: bool = False) -> None

Persistently invoke a G-set: LS2/LS3 into GL, LS1R/LS2R/LS3R into GR.

single_shift_2

def single_shift_2() -> None

Single Shift 2 (SS2) - use G2 for next character only.

single_shift_3

def single_shift_3() -> None

Single Shift 3 (SS3) - use G3 for next character only.

reset

def reset() -> None

Reset charset selections to US ASCII.

bittty.devices.style

Style operation handler for the current board state.

StyleDevice Objects

class StyleDevice(Device)

Owns current style state and applies style operations.

pop_sgr

def pop_sgr() -> None

XTPOPSGR — restore the SGR attributes saved by the matching XTPUSHSGR.

current_ansi_code

@property
def current_ansi_code() -> str

The active style as an ANSI SGR string (boundary/compat accessor).

apply_sgr

def apply_sgr(style: Style | None, reset: bool = False) -> None

Apply an SGR style update (API/testing surface over the hot handler).

set_default

def set_default() -> None

ESC [ 8 ] — make the current attributes the default (the SGR 0 target).

def set_hyperlink(uri: str, link_id: str = "") -> None

OSC 8 — start (non-empty URI) or end (empty) the active hyperlink.

The id= param groups link segments a layout has split (wrapped lines, columns); the chrome uses it to hover them as one link.

set_protected

def set_protected(mode: int) -> None

DECSCA — 1 protects subsequent characters from selective erase; 0/2 clears it.

reset

def reset() -> None

Reset to the default style (and clear the ESC[8] default register and SGR stack).

background_ansi

def background_ansi() -> str

Return the active background style as ANSI.

bittty.devices.base

Base class shared by board devices.

Device Objects

class Device()

A board device: dispatches an operation to its name -> handler table.

bittty.devices.blitter

The blitter: writes video memory. Screen and editing operation handlers.

Blitter Objects

class Blitter(Device)

Owns the video pages and applies screen/editing operations.

set_line_attribute

def set_line_attribute(attribute: str) -> None

DECDHL/DECDWL/DECSWL — set the cursor line’s width/height attribute.

write_text

def write_text(text: str, ansi_code: str = "") -> None

Write printable text at the cursor position, wrapping runs longer than the line.

A single PRINT run can be arbitrarily long (a shell line wider than the screen arrives as one chunk), so write it line-sized piece by line-sized piece; prepare_for_text_write supplies the wrap (or the clip, with autowrap off) between pieces.

repeat_last_character

def repeat_last_character(count: int) -> None

Repeat the last printed character count times.

resize

def resize(width: int, height: int) -> None

Resize terminal dimensions and screen buffers.

clear_screen

def clear_screen(mode: int = constants.ERASE_FROM_CURSOR_TO_END) -> None

Clear screen.

clear_line

def clear_line(mode: int = constants.ERASE_FROM_CURSOR_TO_END) -> None

Clear line.

clear_rect

def clear_rect(x1: int,
               y1: int,
               x2: int,
               y2: int,
               ansi_code: str = "") -> None

Clear a rectangular region.

selective_erase_display

def selective_erase_display(mode: int) -> None

DECSED — erase in display, leaving DECSCA-protected characters.

selective_erase_line

def selective_erase_line(mode: int) -> None

DECSEL — erase in line, leaving DECSCA-protected characters.

fill_rectangle

def fill_rectangle(params) -> None

DECFRA — fill a rectangle with a character (Pch;Pt;Pl;Pb;Pr).

erase_rectangle

def erase_rectangle(params) -> None

DECERA — erase a rectangle (Pt;Pl;Pb;Pr).

selective_erase_rectangle

def selective_erase_rectangle(params) -> None

DECSERA — erase a rectangle, leaving DECSCA-protected characters.

copy_rectangle

def copy_rectangle(params) -> None

DECCRA — copy a rectangle to another origin (Pts;Pls;Pbs;Prs;Pps;Ptd;Pld;Ppd).

set_attr_change_extent

def set_attr_change_extent(ps: int) -> None

DECSACE — 1 = stream (wrapping run), else rectangle (default).

change_attributes_rectangle

def change_attributes_rectangle(params) -> None

DECCARA — merge SGR attributes into every cell of the area (rectangle or stream).

reverse_attributes_rectangle

def reverse_attributes_rectangle(params) -> None

DECRARA — toggle the given attributes (1/4/5/7) across the area (rectangle or stream).

switch_screen

def switch_screen(alt: bool) -> None

Switch between primary and alternate screen.

alignment_test

def alignment_test() -> None

Fill the screen with ‘E’ characters for alignment testing.

set_scroll_region

def set_scroll_region(top: int, bottom: int) -> None

Set scroll region.

set_top_and_bottom_margins

def set_top_and_bottom_margins(top: int, bottom: int | None) -> None

DECSTBM — set the scroll region and home the cursor (origin-aware).

insert_lines

def insert_lines(count: int) -> None

Insert blank lines at cursor position.

delete_lines

def delete_lines(count: int) -> None

Delete lines at cursor position.

insert_characters

def insert_characters(count: int, ansi_code: str = "") -> None

Insert blank characters at cursor position.

delete_characters

def delete_characters(count: int) -> None

Delete characters at cursor position.

scroll

def scroll(lines: int) -> None

Scroll content within the active scroll region.

pan

def pan(columns: int) -> None

SL/SR — pan the scroll-region rows horizontally within the left/right margins.

shift_columns

def shift_columns(count: int) -> None

DECIC (count > 0) / DECDC (count < 0) — insert/delete columns at the cursor.

Confined to the left/right margin box; a cursor outside it is a no-op.

set_left_right_margins

def set_left_right_margins(left: int | None, right: int | None) -> None

DECSLRM — set the left/right margins (1-based; None/0 = extremes) and home the cursor.

reset_left_right_margins

def reset_left_right_margins() -> None

Restore the margins to the full screen width.

apply_left_right_margins

def apply_left_right_margins(operation: Operation) -> None

CSI Pl ; Pr s — DECSLRM when margin mode is on, else SCOSC (save cursor).

scroll_up

def scroll_up(count: int) -> None

Scroll content up within scroll region.

scroll_down

def scroll_down(count: int) -> None

Scroll content down within scroll region.

reset

def reset(hard: bool = True) -> None

Restore the full scroll region; a hard reset also clears both buffers to primary.

set_column_mode

def set_column_mode(columns: int) -> None

DECCOLM — switch 80/132 columns; always clears the screen and homes the cursor.

erase_characters

def erase_characters(count: int) -> None

Erase count characters from the cursor with the current style; the cursor stays (ECH).

bittty.devices.control

Control operation handler for the current board state.

ControlDevice Objects

class ControlDevice(Device)

Applies C0 and simple control operations to the current board implementation.

carriage_return_line_feed

def carriage_return_line_feed() -> None

CR+LF as one fused token (the parser batches the pair).

next_line

def next_line() -> None

NEL — carriage return followed by line feed.

answerback

def answerback() -> None

ENQ — transmit the programmed answerback string, if any is set.

bittty.devices.cursor

Cursor operation handler for the current board state.

CursorDevice Objects

class CursorDevice(Device)

Owns cursor state and applies cursor operations.

set_position

def set_position(x: int | None, y: int | None) -> None

Move cursor to an absolute, clamped terminal position.

move_to

def move_to(x: int | None, y: int | None) -> None

Apply a CUP/HVP/VPA move, honouring origin mode (DECOM).

Under origin mode the row is relative to the scroll region’s top and the column to the left margin, each clamped within its margins.

clamp_to_terminal

def clamp_to_terminal() -> None

Clamp the current position after terminal dimensions change.

carriage_return

def carriage_return() -> None

Move cursor to the beginning of the current line.

line_feed

def line_feed(is_wrapped: bool = False) -> None

Move down one line, scrolling the active scroll region if needed.

forward_index

def forward_index() -> None

DECFI — move right; at the right margin, pan the margin box one column left.

back_index

def back_index() -> None

DECBI — move left; at the left margin, pan the margin box one column right.

reverse_index

def reverse_index() -> None

Move up one line, scrolling down at the top of the scroll region.

backspace

def backspace() -> None

Move cursor back one position, wrapping to the previous line if needed.

set_tab_stop

def set_tab_stop(x: int | None = None) -> None

Set a horizontal tab stop at the given column.

next_tab_stop

def next_tab_stop() -> int

Return the next horizontal tab stop, clamped to the last column.

horizontal_tab

def horizontal_tab() -> None

Advance to the next horizontal tab stop.

previous_tab_stop

def previous_tab_stop() -> int

Return the nearest tab stop left of the cursor, or column 0.

forward_tab

def forward_tab(count: int) -> None

CHT — advance count tab stops.

backward_tab

def backward_tab(count: int) -> None

CBT — retreat count tab stops.

clear_tab_stop

def clear_tab_stop(mode: int) -> None

TBC — clear the tab stop at the cursor (0) or all tab stops (3).

tab_control

def tab_control(mode: int) -> None

CTC — set (0) or clear (2) a tab stop at the cursor, or clear all (5).

reset_tab_stops

def reset_tab_stops() -> None

DECST8C — reset to a tab stop every 8 columns.

next_line

def next_line(count: int) -> None

CNL — move to the first column, count lines down (no scroll).

previous_line

def previous_line(count: int) -> None

CPL — move to the first column, count lines up (no scroll).

set_cursor_style

def set_cursor_style(style: int) -> None

DECSCUSR — set cursor shape and blink from the style parameter.

prepare_for_text_write

def prepare_for_text_write() -> None

Apply wrapping or clipping before writing at the cursor.

advance_after_text_write

def advance_after_text_write(character_count: int) -> None

Advance after printable text, preserving existing autowrap behavior.

save

def save() -> None

Save cursor position and attributes.

restore

def restore() -> None

Restore cursor position and attributes.

reset

def reset(hard: bool = True) -> None

Home the cursor and clear saved state; a hard reset restores default tab stops.

bittty.devices.palette

Palette device: owns the live colour palette and the OSC colour surface.

PaletteDevice Objects

class PaletteDevice(Device)

Holds the current 256-colour table plus fg/bg/cursor, and applies OSC colour ops.

special_color

def special_color(operation: Operation) -> None

OSC 5 — set or (spec == ‘?’) query a special colour (0=bold, 1=underline, …).

special_color_enable

def special_color_enable(operation: Operation) -> None

OSC 6 — enable or disable a special colour.

reset

def reset() -> None

Restore the model’s default colours plus construction overrides (RIS).

dynamic_color

def dynamic_color(operation: Operation, slot: str, cmd: int,
                  fallback: str) -> None

OSC 13/14/17/19 — set, or (data == ‘?’) query a dynamic colour with a fg/bg fallback.

push_colors

def push_colors() -> None

XTPUSHCOLORS — save the whole palette (256 entries plus fg/bg/cursor).

pop_colors

def pop_colors() -> None

XTPOPCOLORS — restore the palette saved by the matching XTPUSHCOLORS.

resolve

def resolve(color) -> RGB | None

Resolve a Style colour to concrete RGB. None means “use the default fg/bg”.

set_palette

def set_palette(operation: Operation) -> None

OSC 4 ; n ; spec [; n ; spec …] — set or (spec == ‘?’) query palette entries.

set_or_query_special

def set_or_query_special(operation: Operation, slot: str, cmd: int) -> None

OSC 10/11/12 — set or (data == ‘?’) query the fg/bg/cursor colour.

reset_palette

def reset_palette(operation: Operation) -> None

OSC 104 — reset all palette entries, or just the listed indices.

reset_special

def reset_special(slot: str) -> None

OSC 110/111/112 — reset the fg/bg/cursor colour to the model default.

set_linux_palette

def set_linux_palette(operation: Operation) -> None

ESC ] P nrrggbb — the linux console’s own single-entry palette set.

bittty.devices.printer

Printer device: the terminal’s aux printer port (Media Copy / MC).

Historically a terminal had a printer hanging off its aux port; Media Copy routed screen data to it. Here the printer is a board device with an attachable sink — a callable taking a str, or any object with a write() method (a file, an io.StringIO, a real printer driver). Unattached, printed output is simply discarded, exactly like a terminal with no printer connected.

PrinterDevice Objects

class PrinterDevice(Device)

Owns printer state (controller/auto-print modes) and the output sink.

attach

def attach(sink) -> None

Attach a printer sink: a callable(str), or an object with a write(str) method.

emit

def emit(text: str) -> None

Send text to the attached sink, if any.

media_copy

def media_copy(operation: Operation) -> None

MC (CSI Ps i) — ANSI media copy.

dec_media_copy

def dec_media_copy(operation: Operation) -> None

DEC MC (CSI ? Ps i) — auto-print and DEC print variants.

def print_screen() -> None

Send the whole current screen to the printer, one line per row.

def print_line(y: int) -> None

Send a single row to the printer.

reset

def reset(hard: bool = True) -> None

Leave printer controller/auto-print modes; the attached sink is config, so it stays.

bittty.devices.mouse

Mouse input encoder: xterm mouse reports and the DEC locator protocol.

MouseDevice Objects

class MouseDevice(Device)

Owns mouse presentation state and emits mouse input reports.

enable_locator

def enable_locator(operation: Operation) -> None

DECELR — enable/disable locator reporting; ps2==1 selects pixel coordinates.

select_locator_events

def select_locator_events(operation: Operation) -> None

DECSLE — choose whether button presses/releases trigger reports.

set_filter_rectangle

def set_filter_rectangle(operation: Operation) -> None

DECEFR — report once the locator leaves this rectangle.

request_locator_position

def request_locator_position(operation: Operation) -> None

DECRQLP — report the locator position now (or that it is unavailable).

input_mouse

def input_mouse(x: int, y: int, button: int, event_type: str,
                modifiers: set[str]) -> None

Handle mouse input, cache position, and send appropriate sequence to the host.

Arguments:

bittty.devices.keyboard

Keyboard input encoder for terminal key events.

KeyboardDevice Objects

class KeyboardDevice(Device)

Encodes keyboard input into terminal control sequences.

set_user_keys

def set_user_keys(operation: Operation) -> None

DECUDK — install user-defined strings for function keys.

set_modify_keys

def set_modify_keys(operation: Operation) -> None

XTMODKEYS (CSI > Pp ; Pv m) — set a key-modifier resource; Pp 4 is modifyOtherKeys.

kitty_push

def kitty_push(operation: Operation) -> None

CSI > flags u — save the current flags and adopt new ones.

kitty_pop

def kitty_pop(operation: Operation) -> None

CSI < n u — pop n saved flag-states off the stack.

kitty_set

def kitty_set(operation: Operation) -> None

CSI = flags ; mode u — set (1), add (2) or remove (3) flag bits.

kitty_query

def kitty_query(operation: Operation) -> None

CSI ? u — report the current Kitty flags as CSI ? flags u.

report_focus

def report_focus(focused: bool) -> None

Focus reporting (DECSET 1004) — send CSI I on focus in, CSI O on focus out.

reset

def reset(hard: bool = True) -> None

RIS clears the modern-keyboard negotiation state.

input_key

def input_key(char: str, modifier: int = constants.KEY_MOD_NONE) -> None

Convert key + modifier to standard control codes, then send to input().

input_fkey

def input_fkey(num: int, modifier: int = constants.KEY_MOD_NONE) -> None

Encode a function key using any user-defined string, else the keymap.

input_numpad_key

def input_numpad_key(key: str) -> None

Convert numpad key to the sequence for the current keypad mode.

input

def input(data: str) -> None

Translate control codes based on terminal modes and send to the host.

translate_application_cursor_keys

def translate_application_cursor_keys(data: str) -> str

Translate embedded normal cursor-key CSI sequences to application mode.

bittty.devices.modes

Terminal modes as a declarative capability table.

Each mode is a small Mode capability that claims a number (ANSI or DEC private), knows how to apply itself and how to report its DECRQM status, and can be omitted by a model (so, e.g., a terminal that predates bracketed paste simply does not recognise mode 2004). The boolean flags themselves stay as attributes on the device: they are read widely across the emulator, and several modes legitimately share one flag (mode 7 and 1000 both drive mouse_tracking).

Mode Objects

@dataclass(frozen=True)
class Mode()

One terminal mode: which flag it backs and how it answers DECRQM.

attr

device flag this mode drives, if any

invert

“set” stores the negation (modes 12, 66)

queryable

DECRQM reports this mode’s state

apply_fn

side effect

status_fn

custom DECRQM status

peripheral

“mouse”/”cursor”/”sync”: emit a present event on change

status

def status(device: ModeDevice) -> int

DECRQM status: 1 = set, 2 = reset, 0 = not recognised.

ModeDevice Objects

class ModeDevice(Device)

Owns terminal mode state and applies mode operations via the mode table.

reset

def reset(hard: bool = True) -> None

Reset modes. hard restores every flag (RIS); soft is the DECSTR subset.

set_mode

def set_mode(mode: int, value: bool = True, private: bool = False) -> None

Set a single terminal mode.

clear_mode

def clear_mode(mode: int, private: bool = False) -> None

Clear a single terminal mode.

bittty.devices.title

Title operation handler for the current board state.

TitleDevice Objects

class TitleDevice(Device)

Owns terminal title state and applies title operations.

set_title

def set_title(title: str) -> None

Set terminal title.

set_icon_title

def set_icon_title(icon_title: str) -> None

Set terminal icon title.

set_both

def set_both(title: str) -> None

Set both the window title and the icon title.

push

def push() -> None

XTWINOPS 22 — save the current window and icon titles.

pop

def pop() -> None

XTWINOPS 23 — restore the most recently saved titles.

bittty.devices

Device adapters for applying parser operations.

bittty.style

CURSOR_CODE

Reverse video for cursor display

RESET_CODE

Reset all formatting

Style Objects

class Style()

Immutable text style. The keyword surface matches the old dataclass field per field (None = inherit); internally the tri-state attributes are packed so merge/eq/hash cost a couple of int ops instead of a 20-field walk.

merge

def merge(other: Style) -> Style

Merge another style into this one; the other’s set attributes win.

replace

def replace(**kwargs) -> Style

A new Style with the given fields replaced (None = back to inherit).

diff

def diff(other: "Style") -> str

Generate minimal ANSI sequence to transition to another style.

parse_sgr_with_reset

@lru_cache(maxsize=10000)
def parse_sgr_with_reset(ansi: str) -> Tuple[Optional[Style], bool]

Parse an SGR sequence into (style, reset): reset means “clear, then apply style”.

A reset token (0, 00, or an empty parameter) anywhere in the sequence discards everything before it, so ESC[0;31m is “reset, then red” — not a red merge into the current attributes. A pure reset returns (None, True) so the hot path can skip the merge without comparing 20 Style fields.

interpret

@lru_cache(maxsize=10000)
def interpret(tokens: Tuple[str, ...]) -> Style

Interpret SGR tokens into a Style, accumulating the packed masks directly.

get_background

@lru_cache(maxsize=10000)
def get_background(ansi: str) -> str

Extract just the background color as an ANSI sequence.

Arguments:

Returns:

ANSI sequence with just the background color, or empty string

merge_ansi_styles

@lru_cache(maxsize=10000)
def merge_ansi_styles(base: str, new: str) -> str

Merge two ANSI style sequences, returning a new ANSI sequence.

Arguments:

Returns:

Merged ANSI sequence

style_to_ansi

@lru_cache(maxsize=10000)
def style_to_ansi(style: Style) -> str

Convert a Style object back to an ANSI escape sequence.

Arguments:

Returns:

ANSI escape sequence string

bittty.caps

TerminalCaps: physical facts about the real terminal, pushed up by the chrome.

The backend answers the child’s physical-fact queries (window/cell pixel size, background colour) from these when a terminal (chrome) has supplied them. Every field defaults to “unknown” (None / “unknown”), meaning “change nothing” — so a board with no attached terminal behaves exactly as it does today.

Graphics-capability flags (sixel / kitty / iTerm images) are deliberately absent until graphics modes are on the table; there is nothing to reconcile without them.

TerminalCaps Objects

@dataclass(frozen=True)
class TerminalCaps()

What the real terminal can actually do (terminal -> board).

color_depth

“monochrome” / “16” / “256” / “truecolor” / “unknown”

cell_px

character cell size in pixels (CSI 16 t)

window_px

window size in pixels (CSI 14 t)

background

actual background colour (OSC 11)

unknown

@classmethod
def unknown(cls) -> "TerminalCaps"

Caps that assert nothing — the backend keeps its current behaviour.

bittty.palette

Colour palettes: the index -> RGB mapping a terminal presents.

bittty stores colours symbolically (a Style holds an indexed or rgb Color); the palette is the authoritative map an indexed colour resolves through. It is seeded from the model, mutated by OSC colour sequences, and queried by their ? forms. Rendering to real pixels stays a terminal (chrome) concern — the terminal calls PaletteDevice.resolve() when it wants RGB.

build_256

def build_256(base16: tuple[RGB, ...]) -> list[RGB]

Build the full 256-colour table: 16 base + 216 colour cube + 24 greys.

parse_color_spec

def parse_color_spec(spec: str) -> RGB | None

Parse an X11 colour spec: rgb:R/G/B or RGB/RRGGBB/… .

format_rgb

def format_rgb(rgb: RGB) -> str

Format an RGB triple as an X11 rgb:rrrr/gggg/bbbb reply string.

PaletteDefaults Objects

@dataclass(frozen=True)
class PaletteDefaults()

A terminal’s default colours: the 16 ANSI colours plus fg/bg/cursor.

bittty.present

Present events: discrete side-effects the board pushes to the attached terminal (chrome).

Screen content stays pull (a terminal (chrome) reads capture_pane()/get_line on its own cadence). Only these discrete events are pushed, through the board’s DisplayPort. Each is a plain frozen dataclass — pure data, no imports from board/devices — so board.py can depend on this module without a cycle.

Bell Objects

@dataclass(frozen=True)
class Bell()

The terminal bell rang (C0 BEL).

TitleChanged Objects

@dataclass(frozen=True)
class TitleChanged()

Window and/or icon title changed (OSC 0/1/2, XTWINOPS title stack).

Notification Objects

@dataclass(frozen=True)
class Notification()

A desktop notification was posted (OSC 9 / 777 / 99).

ClipboardChanged Objects

@dataclass(frozen=True)
class ClipboardChanged()

A clipboard selection was set (OSC 52 write).

PromptMark Objects

@dataclass(frozen=True)
class PromptMark()

A shell-integration prompt/command mark (OSC 133).

PointerShapeChanged Objects

@dataclass(frozen=True)
class PointerShapeChanged()

The mouse-pointer shape was requested (OSC 22).

FontChanged Objects

@dataclass(frozen=True)
class FontChanged()

The font was set (OSC 50).

CwdChanged Objects

@dataclass(frozen=True)
class CwdChanged()

The reported working directory changed (OSC 7).

WindowRequest Objects

@dataclass(frozen=True)
class WindowRequest()

A window action was requested: “raise” / “lower” / “refresh” (XTWINOPS 5/6/7).

WindowStateChanged Objects

@dataclass(frozen=True)
class WindowStateChanged()

Window iconify/maximize/fullscreen/position state changed (XTWINOPS).

ConsoleRequest Objects

@dataclass(frozen=True)
class ConsoleRequest()

A virtual-console switch was requested (linux setterm 12/15).

kind

“switch” / “previous”

MouseModeChanged Objects

@dataclass(frozen=True)
class MouseModeChanged()

The requested mouse-tracking mode changed (derived from modes 9/1000/1002/1003).

mode

“off” / “basic” / “button” / “any”

sgr

SGR (1006) encoding requested

CursorVisibilityChanged Objects

@dataclass(frozen=True)
class CursorVisibilityChanged()

Text-cursor visibility changed (DECTCEM, mode 25).

SyncOutputChanged Objects

@dataclass(frozen=True)
class SyncOutputChanged()

Synchronized-output mode toggled (mode 2026); a terminal (chrome) gates repaint on it.