OdyTTY

A Linux-first GPU terminal emulator, written from scratch in Rust.

release v0.16.1 platforms Linux · Windows · macOS theme odyssey-classic visual ambient search

$ odytty --capabilities

Features and compatibility

Start with the shipped surface. Expand the implementation and compatibility sections only when you need them; unsupported shaping and protocol cases stay explicit.

$ Read-only panes

Right-click the terminal and choose Make Pane Read-Only or Make Pane Writable. The command palette also offers Toggle Read-Only Pane; toggle-read-only has no default chord. The mode starts off and belongs to one pane.

A persistent READ-ONLY label, or RO in a narrow pane, marks blocked input. Keys, IME, paste, dropped paths, pointer edits, and mouse reports do not reach its program. Copy, selection, search, scrollback, focus reports, and resize keep working. Blocked input is discarded. Workspace restore and named layouts keep the flag; new tabs and splits open writable.

Linux Wayland and X11, macOS, and Windows share one implementation.

Read-only pane reference

$ Guarded broadcast input

Right-click a terminal and choose Broadcast to This Pane or Remove Pane from Broadcast. The command palette offers the same toggle. Add receivers one at a time, including panes in other windows of the same process. Splits, new tabs, restored workspaces, and opened layouts never join automatically. Receiver sets are never saved.

Keys, IME commits, and paste reach the focused pane once and every writable receiver. A read-only receiver is skipped; a read-only focused pane sends nothing. BROADCAST n on the focused pane and RECV on receivers disclose the set, including hidden and remote counts. A paste containing any line break asks for confirmation and counts receivers, hidden panes, and remote panes. Cancel sends nothing anywhere.

Ctrl+Shift+X stops broadcast, cancels a pending broadcast paste, and is never sent to a shell. Stop Broadcast is also in the terminal menu and palette. Escape remains ordinary broadcast input.

Keys use the focused pane's keyboard encoding; each receiver frames paste for its own bracketed-paste mode. Mouse and focus reports, resize, click-to-position, and dropped paths stay with the focused pane. Local automation cannot send terminal text. Linux Wayland and X11, macOS, and Windows share this behavior; the stop chord stays on Ctrl on macOS.

Broadcast input reference

$ Secure keyboard input - macOS only

Turn on the Secure keyboard input settings row, or use Toggle Secure Keyboard Input in the command palette. This process-wide preference is off by default, has no default chord, and never detects password prompts. A later window adopts the preference. The macOS primitive is held only while the preference is on and an OdyTTY window has keyboard focus.

Each holding window shows SECURE INPUT; in split, stacked, or floating tabs it appears on the focused pane beside its other labels. Keyboard-intercept tools that use event taps stop receiving keystrokes while it is held. That also disables shortcuts supplied by hotkey and key-remapping apps using those taps. OdyTTY's own menu shortcuts keep working, and other apps still receive keys. Typing still reaches the focused pane and any broadcast receivers.

A crash while secure input is enabled can leave it reported as active, and keyboard-intercept tools blocked, until logout. Turning it off, moving focus to another application, closing the last focused window, or quitting normally releases it.

Windows and Linux do not offer the settings row or palette action and do not simulate secure keyboard input. A secure_keyboard_input line in a shared config is kept and not applied there.

Secure keyboard input reference

$ Move tabs and panes between windows

Right-click a tab to move that tab, or right-click the terminal to move the focused pane. The command palette offers the same actions. No shell is restarted: its PTY or attach handle, scrollback, images, profile, and --hold state move with it.

Right-click actionDestination
Move Tab to New WindowA new window, when something stays behind
Move Tab to Window...Another window chosen through the numbered merge picker, added to its current workspace
Move Pane to New WindowA new window, from a multi-pane tab
Move Pane to Window...Another window chosen through the merge picker

A moved tab keeps its panes and layout. A moved pane becomes a new tab, except when the destination's active tab is floating: it joins that floating tab. The empty tab-strip menu offers Merge This Window Into... and Pull Window Into This One... when another ordinary window exists. Merge and pull transfer complete source workspaces into the destination rail.

An emptied source window closes and hands over the saved-layout role. Only the primary window writes the workspace snapshot; moving a tab to a secondary window does not make all windows restore together. If a new window cannot open, the tab or pane stays at its source and a notice reports the failure.

Dragging a tab or pane out of a window is not supported on any platform. Right-click menus, command palette actions, and the merge picker are the route everywhere. On Wayland, the compositor chooses where a new window opens. The quick terminal never sends or receives tabs or panes.

Window movement reference

$ Stacked and floating pane layouts

In a tab with two or more panes, right-click the terminal for Stack Panes, Float Panes, or Tile Panes. The menu hides the layout already in use. The command palette offers the same actions. Tiled remains the default, and older layouts open tiled. Switching arrangements keeps shells running and preserves the split tree for returning to tiled geometry.

Stacked

The focused pane fills the content area while the others keep running. Ctrl+b o, Focus Next Pane, or the palette's Focus Pane k of n reaches every pane in stable order.

Floating

Overlapping whole-cell rectangles have a frame and z-order. Focus raises a pane. The minimum is 8 columns by 2 rows; shrinking the window clamps rectangles and never deletes a pane. Mode, rectangles, and stacking order are saved with the workspace layout.

Arrange Floating Pane, in the terminal menu and palette, enters keyboard arrangement: arrows move one cell, Shift+arrows resize one cell, Tab focuses the next pane, and Escape or Enter ends the mode. Other keys are swallowed while arranging. Pointer dragging of a floating frame is not implemented.

Inline images in a floating pane stay hidden while another floating pane covers any part of its drawable area and return when uncovered. These layouts live inside one window, with one implementation on Linux Wayland (including Hyprland) and X11, macOS, and Windows.

Pane layout reference

$ Export scrollback as text or HTML

Right-click the terminal for Export Scrollback As Text... or Export Scrollback As HTML..., also offered in the command palette. Each captures the focused pane's scrollback and current screen and opens a native save dialog. Linux uses the XDG portal on Wayland and X11; macOS and Windows use their native dialogs.

Text is UTF-8, joining soft wraps. HTML is one self-contained file preserving text colors and styles, with a Content-Security-Policy that blocks scripts and network fetches. Only allowed http and https links become anchors. Images become a single [image] line.

Neither format adds application metadata such as working directory, host, user, profile, window title, or environment. Terminal text is exported as it appears, including sensitive text already on screen or in scrollback. Allowed HTTP and HTTPS link targets are kept as written in HTML.

The shared command-output writer refuses files over 32 MiB whole, writes privately and atomically, and refuses destination symlinks. Cancellation writes nothing. Capturing very long scrollback pauses the window; the cap limits file size, not capture time.

Scrollback export reference

$ Ambiguous character width

ambiguous_width controls East Asian Ambiguous characters: narrow is the default, one column; wide uses two. Each pane follows its launch profile's appearance.ambiguous_width, falling back to the global setting.

Changing it reflows that pane's grid and scrollback. The PTY is not resized and the shell is not told, so an existing wrapped prompt waits for the shell to redraw. An alternate screen is left for its application to repaint. Overlay chrome and the shared glyph atlas stay on the narrow table.

Ambiguous width reference

$ Quick terminal - a drop-down summoned by a shortcut

The quick terminal never sends or receives tabs or panes.

quick_terminal is off by default. When enabled, OdyTTY keeps one dedicated drop-down terminal with its own identity and settings, summoned by a global shortcut (quick_terminal_shortcut, default F12). Hiding keeps the same session; repeated summons never create a second quick window, and registration happens only after an ordinary frame.

Per-platform registration

Shortcut registration is platform-specific: Linux X11 grabs in-process, Wayland uses the org.freedesktop.portal.GlobalShortcuts portal, and macOS and Windows use their native global-shortcut paths. OdyTTY never reports success without platform confirmation. A denial, shortcut conflict, or unavailable backend raises an actionable in-app notice and records the same guidance in the log.

Compositor-controlled placement

On tiling Wayland compositors the edge, absolute position, monitor, and stacking are decided by the compositor, not OdyTTY, and slide motion resolves to instant. Where the portal is absent, the quick terminal can be toggled through the local automation command instead. OdyTTY reports the limitation in the quick window rather than claiming placement succeeded.

Quick-terminal geometry, shortcuts, and limitations →

$ Local automation - an opt-in odytty control endpoint

automation_endpoint is opt-in and off by default; the ordinary startup path creates no automation socket. When enabled, it binds after the first presented frame: Linux and macOS use owner-private sockets, and Windows uses \\.\pipe\odytty-control-<pid> with an owner-only DACL, remote-client rejection, and mutual process-token SID verification.

Structural only

The endpoint and the odytty control CLI list and focus windows, workspaces, tabs, and panes, open profiles, create tabs, splits, and workspaces, rename supported objects, and report non-sensitive status. When quick_terminal is enabled it can also queue a visibility toggle.

What it never does

It cannot send terminal input or read terminal contents, and it opens no network listener. Access is owner-scoped: cross-account and cross-machine access is refused by the socket or pipe ownership itself.

Automation endpoint, CLI, and ownership model →

$ External file drop - confirm-first path insertion

Drag files onto an eligible local pane and OdyTTY inserts their shell-quoted paths behind a confirm-first preview through the shipped paste-safety policy. A path is never executed and no Enter is appended, and Paste as One Line is not offered for file-drop batches. Remote and attached panes refuse local paths.

Native Wayland delivery

Because winit emits no Wayland drop event, a companion non-owning wl_data_device supplies delivery. OdyTTY requests the Copy action and only Copy and performs no file operation itself - it inserts quoted path text after confirmation and nothing else. On Hyprland, admission follows the compositor's drag behavior; a Move-only source is refused. A drop of more than 128 files or 256 KiB of path bytes refuses the whole gesture.

Refused on Windows

Windows refuses file-drop insertion with an explicit notice, because ConPTY exposes no foreground-process-group authority. The integrated-SSH image-upload path stays separate and confirm-first, and local paths are never interpreted as remote paths.

File-drop eligibility, Wayland delivery, and limits →

$ Keyboard window merge - fold one window into another

With two or more ordinary windows open, the command palette and the Session Navigator offer Merge This Window Into… and Pull Window Into This One…. Candidate windows paint temporary numerals inside their own surfaces, so targets are identifiable on decoration-less tiling compositors as well as X11, macOS, and Windows, without any compositor cooperation.

The transfer is atomic and same-process: tabs, panes, PTYs, profiles, and attach handles move together, and the source window closes only after a successful transfer. Esc cancels, and the quick terminal is never a merge origin or candidate.

Window merge and pull, targets, and cancellation →

$ Named profiles - a setup for each workflow

Keep development, operations, and focused command sessions ready to launch. A named profile combines a shell or command, starting directory, environment overrides, appearance, cursor, effects, a saved-layout reference, and a connection reference. Optional platform applicability limits a profile to Linux, macOS, Windows, or a combination.

Manage reusable setups

Open Settings → Profiles → Open Profile Manager to create, duplicate, rename, edit, validate, import, export, or delete a profile. Forms expose launch and appearance options with inline validation; deletion requires confirmation.

Profiles hold launch configuration and connection references. They are not credential storage. Keep passwords, tokens, and private keys out of profile files and exports.

Choose when you need to

Ordinary New Tab and New Workspace remain immediate. The adjacent ▾ chooser opens a searchable profile list for explicit selection.

Use New Tab with Profile… or New Workspace with Profile… in context menus, or New Tab: Profile … and Bind Workspace to Profile … in the command palette. The CLI accepts odytty --profile NAME; connections, saved layouts, and restoration can also select or reapply a profile.

Defaults, in plain language

Use Set as Default in Profile Manager for an explicit global default. A workspace can also have its own optional default.

LaunchProfile used
New tabThe workspace default when configured; otherwise the global default.
New window or unbound workspaceThe global default.
Missing or invalid saved defaultThe built-in System Default, with a brief notice.

System Default is the built-in launch profile. Odyssey Default (odyssey-default) is the default visual theme. These names describe different settings.

Optional context-aware appearance

Host/directory-aware switching is off by default. Enable profile_auto_switch to evaluate match rules as the focused pane's directory changes. A match applies appearance and shows a brief disclosure; it does not restart the running shell or rewrite your saved defaults. Remote matches use the trusted saved host identity. Terminal output cannot select, create, or rewrite profiles.

Profiles guide: selection, precedence, schema, import, and recovery →

$ External palette following - colors from one chosen source

Keep OdyTTY's colors aligned with a palette file you maintain locally. This is an explicit opt-in on Linux, macOS, and Windows: select a source format and local path, then enable follow_external_palette in Settings or a profile's appearance options.

Complete, valid changes apply live. Missing files, partial writes, malformed updates, and transient replacements retain the last known-good palette. The read-only External palette status row reports watching, applied, retained, or error states.

Compatible file formats

SourceCompatibility
colors_tomlCurrent named-color and legacy indexed Omarchy-compatible colors.toml.
colors_jsonpywal-compatible colors.json.
odyttyExplicit OdyTTY/Base16/ANSI palette files: a complete OdyTTY color payload including color0–color15, or a complete Base16 base00–base0F map.

Third-party format compatibility is independent. It implies no endorsement, partnership, official integration, or required modifications to another project.

OdyTTY follows only the selected local file; it does not discover and adopt arbitrary desktop palettes. Existing theme = system behavior selects configured dark/light themes from the OS appearance. External palette following is separate and takes precedence when enabled with a valid retained palette.

External palette settings, validation, and precedence →

$ Unified Session Navigator - find your place

Ctrl+Shift+A opens one searchable place for workspaces, tabs, panes, and supported local, integrated SSH, detached, and remote-persistent sessions. It is also available from the command palette and Manage Sessions context-menu item.

Search and focus

Type to filter (with an empty query, r, d, m, x, and o invoke actions), use ↑/↓ to move through results, and press Enter to focus a live tab or pane or enter the supported session-attach flow. Home/End jump through results; Esc closes the overlay. Opening the navigator does not attach, wake, or mutate sessions.

Act on the selected row

Row context menus offer the applicable focus/attach, rename, duplicate, move, and close actions. Availability depends on row type and platform. Destructive actions ask for confirmation. Closing a pane closes that pane; closing a tab closes all its panes.

Reopen uses the last closed tab or workspace's recorded directory and profile to start a fresh shell. It does not recover a terminated process; the bounded close history lasts only for the current app process.

Bounded previews, only when enabled

navigator_preview is off by default. When enabled, it shows at most eight frozen rows of recent visible output from a live pane, with redaction for sensitive assignments, long token-shaped values, and remote identities. Redaction cannot guarantee detection of every secret. Detached rows report preview unavailable; previews never attach, wake, or write to a session.

Platform boundary: Linux and macOS can list and attach supported Unix detached sessions. Windows shows live local and integrated SSH panes; detached/persistent Windows host sessions are not supported in v0.16.1. Remote persistence remains subject to the existing SSH/tmux support.

Session Navigator behavior and preview boundaries →

$ odytty --features - highlights

A few things most terminals do not do all at once, especially on top of an owned parser, terminal model, and renderer.

OdyTTY terminal pipeline

Not a skin over another terminal. PTY allocation, escape parsing, terminal state, render geometry, graphics placement, settings, and shaders are OdyTTY code - the crate-by-crate breakdown is in architecture.

PTYclean-room parserterminal modelrender geometrytext / emoji / image atlaseswgpu GPUpost-process

Workspaces, layouts & sessions

Group tabs into workspaces, then save the whole window as a named layout and reopen it with Replace or Add. restore_workspaces reopens the previous window shape at launch; Unix-only detachable sessions keep shells alive. Details in workflow.

Remote terminals

SSH that feels native. Quick-connect saved hosts via the system ssh (OdyTTY never stores passwords or private-key contents), with opt-in remote shell integration, connection reuse on Unix clients, tmux persistence, and reconnect-on-drop - long form in workflow.

Inline media on the GPU

Color emoji render on all three platforms - bitmap strikes (Noto Color Emoji, Apple Color Emoji), COLR/CPAL v0 layers, and COLR v1 Paint graphs, including stock Windows Segoe UI Emoji: flags, ZWJ families, skin tones, keycaps. And icat a photo and it just appears: the Kitty surface with animation and Unicode placeholders, a complete Sixel data language, and iTerm2 inline images. Details in graphics and rendering.

$ odytty --list-themes - theme gallery

OdyTTY ships 145 built-in themes, each a full appearance profile: default fg/bg, the window clear color, the 16-color ANSI palette, and semantic roles - with OS dark/light following and correct precedence over dynamic OSC color overrides. Hover to try one on, click to wear it - the whole page reskins live, including the CRT field's hue. That is the same data the terminal loads; the gallery is generated from the real .theme files.

Electric Blue (odyssey-electric-blue) is the newest preset, added in v0.15.0 as the 145th built-in theme: a blue-black background, lavender text, and an electric cyan cursor. It changes colors only; the default theme and every effect setting are unchanged.

The .theme file format

A theme is a plain text file of key = value lines. Drop one in ~/.config/odytty/themes/ and select it with theme = myname or point ODYTTY_THEME at a path. A bad or missing theme never prevents startup - OdyTTY falls back to odyssey-default and logs a warning. The legacy blue-black odyssey palette is now named odyssey-classic, with odyssey retained as a compatibility alias; system follows OS appearance rather than pinning a fixed palette. Red Planet and Red Planet Dark are built-in themes, and Theme Builder supports direct click-to-edit hexadecimal values.

~/.config/odytty/themes/example.theme
name = example
appearance = dark

foreground = #d6def4
background = #0c1224
clear      = #070b18

cursor    = #86c1ff
selection = #243352
search    = #4a4018

color0  = #12182a   # ANSI black
color1  = #e06b74   # red
# … color2 – color15 …

$ odytty --workflow - application workflows

A terminal you can configure and operate from inside its own window - the config file underneath, not the only UI.

OdyTTY split into two panes: a colorized git graph on the left, green cargo test results on the right.
real capture - Ctrl+Shift+E split: git graph one pane, cargo test the other

Tabs, Panes & Workspaces

Ctrl+Shift chords open, close, and split tabs and panes - the full set is in keys - with a tmux-style Ctrl-b prefix on top. A new tab or window inherits the active pane's working directory when one is tracked (OSC 7), falling back to the configured default.

The tab bar and a pinned workspace rail meet as one continuous, pixel-snapped chrome surface with a single intentional resize seam and no exposed gutter; the active tab is marked by a selection-role fill and a bright, bold label. The bar appears once a second tab exists, and always_show_tab_bar pins it (a single tab you have renamed always shows the bar; custom names last for the session).

Workspaces group whole tab strips, cycled from the keyboard, with a rail listing them once a second exists (workspace_rail: auto default or always, with workspace_rail_side picking left / right; the rail can also auto-hide and be width-dragged). An optional inactive_pane_dim (default 0.0; bypassed on render_quality = plain) dims background panes. Single-pane, single-workspace views stay byte-identical to a plain terminal.

Configured padding applies at divider-facing edges; collapsed panes keep a valid one-cell PTY backing model while clipping drawing and input, and completed window/divider resizes settle affected panes to whole-cell geometry with one final backend resize.

Background bell activity appears as a static dot on the tab, rolled up to its workspace rail row, and clears when that tab or workspace is viewed. Interactive window resize reports the settled columns × rows geometry in a centered transient readout.

Saved Layouts & Restore

Save All Workspaces as Layout captures the whole application - every workspace, tab, split, working directory, and host binding - while Save Workspace as Layout… saves just one workspace on its own; both are reachable from the rail's right-click menu, the terminal content menu, and the palette. Open a saved layout later with Replace (tear down the current workspaces and install the saved set) or Add (append it beside the current ones).

With restore_workspaces on (off by default), launching odytty reopens the previous window shape automatically. The first window owns restore and autosave; secondary windows run independently and neither restore nor overwrite that primary saved state. Merging the owning window into another window passes autosave to the surviving window, which then saves the merged layout.

A companion shell_exit_closes setting picks what happens when a shell exit would empty a workspace - close that workspace (the default, matching the cascade) or quit OdyTTY - so pairing "quit closes the app" with "next launch restores everything" is a two-knob configuration. Snapshots record structure only: fresh shells at their directories, never replayed output.

Remote Terminals

Ctrl+Shift+S opens the connection manager: quick-connect saved hosts via the system ssh (keys stay with your agent), bind a workspace to a host, or edit OdyTTY-owned entries in hosts.conf. Opt-in OpenSSH ~/.ssh/config host-name import is read-only and name-only (imported rows can be connected to but not edited). Opt-in knobs carry shell integration onto the remote, reuse one authenticated connection across tabs on Unix clients (Windows authenticates each connection independently), persist it in tmux, hold a dropped tab open with a reconnect prompt, and confirm-first paste an image up to the host.

Command Palette

Ctrl+Shift+P opens a fuzzy command palette over actions, settings, shell history, recent directories, workspace and layout commands, and saved hosts. A presentation-only overlay - it never blocks or intercepts the PTY.

In-app settings

Ctrl+Shift+, opens Settings. Rows live-apply, / filters, and Ctrl+S writes changed keys back to odytty.conf while preserving comments, blank lines, unknown keys, and ordering. Invalid values are refused with diagnostics rather than silently replacing the active resolved setting.

Theme, font, and keybinding tools

The theme picker, font picker, keybinding editor, and theme builder are native overlays. The theme builder can clone, tweak, live-preview, and save user themes through the same dependency-free .theme format as the built-ins.

Create Theme From Current Colors closes the gap where a look existed only as live terminal state: it snapshots the focused pane's effective colors - the OSC 4 palette overrides and OSC 10/11/12 foreground/background/cursor a prompt, vim colorscheme, or theme script applied, falling back to the theme-seeded value where nothing overrides - into a draft, derives the five roles the protocol cannot express (clear, selection, search, border, inactive) with documented luminance heuristics, and opens the theme builder on it. Every derived role is editable before saving, and capturing changes nothing on its own: the applied theme and the pane's live colors are untouched until you save.

Prompt-Aware Actions

OSC 133 prompt marks power command navigation, clear editable input, context-menu actions, and a per-command command-status gutter that colors each command's left rail by its exit status in single, split, and nested pane layouts. Verified command ranges now drive output-only and prompt-inclusive select and copy, command-scoped search, explicit failed-command navigation, and bounded plain-text export. Missing, stale, evicted, reset, alternate-screen, or unverified boundaries disable these actions instead of guessing from displayed shell syntax. Command-output copying respects soft-wrapped logical lines. Shell integration and the gutter are on by default.

Clicking in the typed command line moves the shell cursor there (rune-precise, on by default), and deleting a selection inside the prompt edits the real command line - exact with the bundled zsh integration, conservatively clamped elsewhere. On graphical Unix launches without SHELL, OdyTTY resolves the effective user's login shell through passwd/NSS before its final /bin/sh fallback, so the appropriate integration still loads. OdyTTY never sends edit bytes it cannot justify.

OdyTTY injects integration for Bash, Zsh, Fish, and Windows PowerShell/pwsh; cmd.exe has no supported OSC 133 hook surface, while Nushell users configure its native OSC 133/7/2 and Kitty-protocol support in Nushell itself.

Safer Paste And Completion Monitors

With warn_on_risky_paste = on (the default), risky non-bracketed multiline or control-bearing paste is held behind a bounded escaped preview with explicit Paste, reversible Paste as One Line when available, or Cancel. Single-line paste and child-enabled bracketed paste retain their existing byte paths. OdyTTY does not classify shell commands as safe or dangerous. Bounded OSC 9/777 notifications and OSC 9;4 progress, one-shot “Notify When This Command Finishes,” and per-pane activity, silence, bell, process-finish, and command-failure monitors use OdyTTY-authored chrome text; terminal-authored payloads stay untrusted, BEL semantics remain separate, and notifications never steal keyboard focus. See docs/features.md#paste-safety and docs/notifications.md.

Tab And Workspace Menus

Right-click a tab for:

Right-click a rail slot for new, duplicate, rename, close, Move Up / Move Down, bind/unbind host, layout-save, and Settings actions. Bindable actions can be remapped in the keybinding editor; layout, host-binding, and reorder actions remain palette- or menu-driven.

$ odytty --rendering - text, emoji, motion

OdyTTY's visual identity sits on a text-quality foundation: reliable default fonts, stable glyph geometry, explicit fallbacks, and a hard direct-render profile when effects are the wrong trade-off.

Text Pipeline

Bundled Victor Mono ships as the default at 20 logical pixels, with JetBrains Mono also bundled and selectable. Users can select system font families, direct font files, weight variants, line height, synthetic styles, text gamma, stem darkening (on by default at 0.7), and optional RGB/BGR subpixel AA with grayscale fallback. Color composes in linear light before glyph coverage is applied.

Programming ligatures are on by default: eligible runs render through OpenType shaping with calt and liga together, covering ASCII graphics plus a curated allowlist of common non-ASCII operators and arrows, with optional off-by-default ss01/ss02 stylistic sets. Shaping is a presentation overlay on a fixed cell grid: runs group grapheme clusters and anchor every shaped glyph back to its source cells, so a length-changing substitution never moves a terminal column and copy, selection, search, and cursor placement are unchanged.

Arabic joining letters get their initial/medial/final/isolated forms in logical left-to-right cell order - contextual joining, not bidi reordering. Fonts without coverage, and content the shaper declines, fall back to the plain per-cell renderer, as does ligatures = off / ODYTTY_LIGATURES=off. Full complex-script reordering, bidi, and open-ended ssXX sets remain deferred.

The independent 2026-08-16 ucs-detect run covered 85 languages and recorded an 81.2% aggregate check pass rate; failures appeared in 22 cases, all Brahmic or derived Southeast Asian Brahmic scripts. That measures one corpus, not a percentage of languages supported. Full Unicode bidirectional layout and complex Indic/Brahmic shaping need architectural logical-to-visual and cluster-ownership work, not a configuration change. Sequence-aware grapheme width and further joining-script coverage without visual reordering are tractable follow-up work. See the shaping roadmap.

How Text Is Drawn

Ordinary terminal text follows the same path on Linux, macOS, and Windows. Choose a font: the bundled Victor Mono, JetBrains Mono, and Nerd Font symbol faces are built into the binary, and system families and fallback faces come from the platform font directories. On Linux, a character missing from every loaded face is looked up through Fontconfig (fc-match and fc-list) and checked for real coverage before use; macOS and Windows use a fixed list of system symbol faces. Read the font: skrifa, part of the maintained Fontations stack, supplies names, metrics, character coverage, and glyph outlines behind a small OdyTTY-owned font handle, and rejects malformed files without panicking. Rasterize: ab_glyph_rasterizer converts outlines to grayscale coverage, with synthetic bold or italic and stem darkening applied, cached once per glyph and size in a GPU atlas. Compose: the GPU applies the text_gamma coverage weight and blends coverage with the text color in linear light, optionally with subpixel antialiasing.

Box drawing, blocks, Braille, Powerline separators, and legacy-computing cells skip the font reading and rasterizing steps and use OdyTTY's procedural cell coverage. Ligatures are shaped with swash, and color emoji use swash, Fontations, and a separate color atlas. The window title bar is separate from terminal text: macOS and Windows draw it natively, and X11 window managers and most Wayland compositors draw their own. When a Wayland compositor asks OdyTTY to draw its own title bar, the title text goes through the system FreeType and Fontconfig libraries (crossfont), so Linux builds link those two libraries.

v0.15.5 moved font reading from ttf-parser and ab_glyph to skrifa while keeping the same rasterizer. On the sampled bundled faces, sizes, and positions, the new path produced identical glyph pixels, and controlled before/after window captures were byte-identical. See How Text Is Drawn in the feature reference.

Glyph And Symbol Handling

The dynamic atlas uses bearing-aware glyph quads, HiDPI-aware rebuilds, wide-cell handling, procedural box/block/shade, Braille, Powerline, and sextant/octant rendering, box-thickness tuning, bundled Nerd Font v3/v2 symbol fallback, host fallback, and per-range symbol_map overrides. Decomposed clusters - a base letter plus a zero-width combining accent - draw the accent over the base cell and keep it when copied from scrollback and live selection alike (up to four marks per cell, where the font provides them). Runtime fallback shares parsed faces by filesystem identity and face index; collection files are reconstructed one selected face at a time rather than retained whole. CPU atlas bitmaps remain resident because live glyph insertion and atlas growth still write into them.

Color Emoji

Color emoji use swash, Fontations, and a dedicated premultiplied-RGBA atlas. Sources are tried in order: bitmap strikes first (Noto Color Emoji CBDT/CBLC on Linux and Windows, Apple Color Emoji sbix on macOS), then static COLR/CPAL v0 layers, then COLR v1 Paint graphs - solid fills, linear/radial/sweep gradients, affine transforms, clipping, nested glyphs, and the standard composite modes. Directory discovery recognizes stock Windows Segoe UI Emoji, so a clean Windows install renders emoji in color.

Flags, keycaps, skin tones, variation selectors, and common ZWJ clusters are supported; coverage is still bounded by the host font, and because stock Segoe ships no regional-indicator glyphs, flag clusters there show the visible letter fallback native Windows applications show. OdyTTY does not bundle Noto Color Emoji on Windows. Text-default symbols stay on the monochrome path, an SVG-only glyph falls back there too (SVG-in-OpenType is deferred), and emoji pixels are not SGR-tinted.

Cursor, Motion & Effects

The cursor is OdyTTY's v0.9 signature, and its motion ships on by default. Fresh profiles open a blinking Block cursor that glides between adjacent cells and snaps instantly on large jumps, resizes, and scrollback navigation. A jump beyond the short glide range sends one cursor-shaped follower that stretches toward the target and settles, under a subtle / balanced / expressive profile (cursor_trail_strength, balanced by default) with a brief fading trail.

Blinking yields to typing - keyboard and IME activity hold it visibly on, blinking resumes after a short quiet period, and it parks solid when idle so nothing keeps redrawing. A restrained shape-aware glow traces the actual Block, Bar, or Underline geometry in the resolved cursor color; its strength is adjustable on an independent cursor_glow_intensity scale (0.0–1.0, default 0.5), separate from the whole-scene bloom. An unfocused Block cursor becomes a hollow outline, and in a split every part of this runs only in the focused pane and never wakes an idle one.

All of it is presentation-only: the logical cursor, selection, copy, and terminal input reach their destination immediately. reduced_motion = on is one explicit switch that snaps the cursor slide, trail, glow, blink fade, and new-output fade to static without overwriting their saved settings - a config setting, not an OS preference, and it leaves cursor blinking, smooth scrolling, and ligatures untouched.

Bloom, CRT, retro, curvature, padding, borders, and backgrounds are settings-backed too. The retro preset keeps its scanlines, bloom, and vignette but does not force curvature: crt_curvature = 0.0 is the flat default and is exposed only through config or ODYTTY_CRT_CURVATURE, not the Settings panel.

Scrollback uses an animated glide between wheel notches (default on, six rows per notch) with a pixel-precise continuous lane for touchpads and high-resolution wheels; the glide follows split panes at sub-cell precision, and copy across the full selection works cleanly across the scrollback.

A bundled "Dark Waves" background image ships behind the grid by default - set background_treatment = color to turn it off. Window transparency (window_transparency + window_opacity, translucent down to 20% opacity) makes the background translucent while text, the cursor, selection, and every overlay stay fully opaque (window_transparency = on and window_opacity = 80 by default; needs a compositing window manager, and a configured background image composes over the desktop too). render_quality = plain bypasses post-processing and visual treatments. v0.12.0 resamples background images to the drawable surface with bounded resize headroom, and releases post-process textures whenever the effect stack becomes inactive instead of retaining or resizing unused targets.

When wheel_zoom is enabled and the running application has not claimed mouse reporting, effective Ctrl+wheel steps show the current font size in the same static, bounded readout used for terminal dimensions. Repeated steps replace the value; the readout clears without an animation phase or idle wakeup.

$ odytty --readability - effect controls

OdyTTY's visual layer is allowed to be expressive because it is bounded. Text legibility, input correctness, and stable rendering stay above atmosphere.

Readability Controls

  • Minimum WCAG contrast-ratio floor, default 17.0 (range 1.0–21.0)
  • OKLab/OKLCH perceptual color helpers for dimming, mixing, and contrast lift
  • Theme roles for cursor, selection, search, border, and inactive UI
  • Color-vision adaptation with cvd_mode and cvd_strength
  • Focus dimming applies before the contrast floor so text can be re-lifted
  • Low-opacity legibility: colored_bg_opacity = 0.9 preserves colored cells and text_brightness = 1.0 is the identity baseline
  • selection_opacity = 1.0 is fully opaque; 0.0..=1.5 adds bounded color emphasis above 1 without invalid GPU alpha

Effect Safety

  • Bloom adds light; scanlines and vignette are capped with a brightness floor
  • Background images use opacity, optional blur, and auto scrim to preserve contrast
  • Chrome uses tab_panel_strength = 0.8 with tab_seam = off; themed overlays stay opaque in single- and multi-pane layouts
  • new_output_fade = on fades new rows over new_output_fade_ms = 250; reduced motion makes it static without overwriting the preference
  • Adapters without filterable Rgba16Float support fall back to the direct plain path
  • Every atmospheric effect has an off switch or is bypassed by render_quality = plain

$ odytty --graphics - inline media

Kitty graphics, Sixel, and iTerm2 inline images all land on OdyTTY-owned APC/DCS/OSC parser plumbing and share the GPU image layer.

Kitty Graphics & Sixel

The Kitty APC still-image surface (chunking, image and placement ids, crop, scaling, pixel offsets) accepts bounded zlib-compressed payloads through o=z. A complete Sixel data language keeps hard caps of 10,000 × 10,000 pixels or 40 million total pixels. DA1 replies with CSI ? 62 ; 4 ; 6 ; 22 ; 28 c; attribute 4 lets clients discover Sixel. XTSMGRAPHICS is not implemented and receives no reply. The exact action / transport / placement matrix is in protocols.

Unicode Placeholders & Animation

Virtual placements (a=T,U=1 / a=p,U=1) store an image and its cell extent without drawing anything; the image then renders wherever the client prints U+10EEEE placeholder cells carrying row/column diacritics. Because position lives in the text, the image scrolls, pages into scrollback, and is overwritten or erased exactly as text is - the placement mode TUI toolkits rely on.

Animation covers frame transmission (a=f), playback control (a=a), rectangle composition (a=c), and single-frame deletion (d=f/d=F). Frames share the image store's byte budget with a 64-frame per-image cap, only visible placements advance, and a session with no animated image schedules no timer and does no per-frame work.

Transmission and animation commands accept image-number addressing through I=, resolving a number to its newest image; display and delete commands still require i=. Placeholder tiles split an image uniformly instead of letterboxing.

iTerm2 Inline Images

OSC 1337 ; File= hands the terminal a whole container file rather than pixels. OdyTTY content-sniffs PNG, JPEG, and WebP (never trusting a declared type), supports inline, size, width/height in cell, px, %, and auto units, and preserveAspectRatio, then advances the cursor to column 0 below the image. The payload rides the OSC accumulator, so one command is bounded at 128 KiB (roughly 96 KiB of encoded file bytes) and an over-cap command is rejected whole rather than decoded from a truncated prefix. inline=0 download requests are never honored - no escape sequence writes a file. The chunked MultipartFile form is unhandled, and unknown argument keys degrade to “ignored”, not “rejected”.

Draw Order

Render order follows the Kitty model: cell backgrounds, negative-z images, glyphs and decorations, color emoji, then non-negative-z images. Primary and alternate screens maintain independent placement scenes.

File Transport Safety

Kitty direct and chunked-inline transfers are always available. The named file, temp-file, and POSIX shared-memory transports are off by default behind kitty_named_transports - a local host-I/O authority grant to enable only when the whole PTY session, including SSH output, is trusted; with the gate off they are rejected before any file or shared-memory I/O. When enabled on Unix they stay confined by a temp-directory allowlist, reject symlinks (O_NOFOLLOW) and non-regular objects, require the protocol marker with delete-before-decode for temp files, validate shared-memory objects before shm_unlink, and cap size before decode. Windows applies the corresponding final-component reparse-point rejection. Full rationale is in OdyTTY's docs/graphics.md.

In-App Image Viewer

Separate from the wire protocols: Ctrl/Cmd+click opens a file (path:line:col jumps an editor), right-click adds “Open With…” / Copy Path / Reveal, and Ctrl/Cmd+click a resolved png/jpg/jpeg/webp path (or pick “Open in OdyTTY”) and it opens in a presentation-only lightbox drawn above the CRT/bloom post-pass, so effects never touch the photo. Decode is bounded before it runs; Esc or a click outside dismisses. Opt-in, behind the interactive_paths gate; clicking bare URLs is governed by the separate interactive_urls knob (on by default).

try inline images
kitty +kitten icat /path/to/image.png
img2sixel --width=200 /path/to/image.png

$ odytty --keys - bindings

The default local keyboard surface. Most named local actions - tabs, workspaces, panes, sessions, and overlays - are rebindable through ODYTTY_KEYBINDS / the keybinds config key, and the in-app keybinding editor covers the full bindable set. The direct first-split chords and prompt-selection Delete/Backspace path are fixed. Most defaults are Ctrl+Shift chords a TUI can't receive; documented exceptions include tab/page navigation, workspace duplication, and the multi-pane prefix. The set below tracks OdyTTY's own docs/keybindings.md, kept in sync at each release. On macOS, Cmd+click is the open chord (the OS routes Ctrl+click as a secondary click).

shell_integration = on is the default; shell_key_enhancement = off is the correctness-preserving default. When explicitly enabled, Bash and Zsh use disambiguation only while their line editor owns the prompt; Bash 4.4+ removes it through PS0, while macOS Bash 3.2 uses a prompt-guarded first-command DEBUG boundary. Fish manages its keyboard protocol itself. PowerShell uses PSReadLine through app-requested ConPTY Win32 input records, preserving modifier state, key-up events, Ctrl+Backspace, and Shift+Enter. Linux normalizes compositor-dependent editing keys before dispatch, including Ctrl+Backspace on KDE/Wayland.

With the default osc52_write = ask, the clipboard prompt uses Ctrl+Shift+1 to allow once, Ctrl+Shift+S to allow for the current PTY session, Ctrl+Shift+D to deny for that session, and Esc to cancel. Default-on smart Ctrl+C copies and clears a live local selection; otherwise it remains the shell interrupt.

Tabs, panes, workspaces & sessions

ChordAction
Ctrl+Shift+T / WNew tab / close active tab
Ctrl+Shift+DDuplicate active tab - a fresh shell in the active pane's directory
Ctrl+Shift+NNew window (also in the right-click menu; inherits the active pane's working directory when one is tracked, falling back to the default)
Ctrl+PageDown / PageUpNext tab / previous tab
Ctrl+Shift+' / Ctrl+Shift+;Next tab / previous tab - secondary physical-key shortcuts. Unavailable where these keys carry letters, such as German Ö/Ä layouts; use the command palette.
Ctrl+Shift+E / OSplit focused pane into columns / rows
Ctrl+b then % " ↑↓←→ o x z =tmux-style pane prefix: split, focus, cycle, close, zoom, equalize
Ctrl+Shift+EnterNew workspace (the rail's + slot does the same)
Ctrl+Shift+PageDown / PageUpNext workspace / previous workspace
Ctrl+Shift+Alt+DDuplicate active workspace - a fresh workspace in the active pane's directory
Ctrl+Shift+GWorkspace picker (rename, close, bind-to-host, and Save/Open Layout live in the right-click menus and palette)
Ctrl+Shift+ASession Navigator - workspaces, tabs, panes, and supported Unix detached sessions

Overlays & settings

ChordAction
Ctrl+Shift+PCommand palette (actions, settings, shell history, recent dirs)
Ctrl+Shift+SConnection manager (saved SSH hosts)
Ctrl+Shift+H / BTheme picker / theme builder
Ctrl+Shift+RSession replay overlay (opt-in recording)
Ctrl+Shift+,In-app settings panel (font, theme, cursor, all knobs)
Ctrl+S (in panel)Write changed rows back to odytty.conf (preservation-first)

Editing, navigation & mouse

ChordAction
Ctrl+Shift+FScrollback search (next/prev, match highlights)
Ctrl+Shift+C / VCopy selection / paste
Shift+PageUp / PageDownScroll local viewport
Ctrl+Shift+Up / DownJump to previous / next OSC 133 prompt mark
Ctrl+Shift+SpaceKeyboard copy mode (vim-style scrollback selection)
Ctrl+Shift+LKeyboard quick-select hints (URLs, paths, hashes)
Ctrl+Shift+KClear the shell input line (readline Ctrl+A then Ctrl+K; no shell integration required)
Double-clickSelect word · Triple-click selects line
Ctrl/Cmd+ClickOpen an OSC 8 hyperlink or interactive path (scheme allowlist, argv-safe)

$ odytty --protocols - support matrix

What OdyTTY understands on the wire, for reference. Everything ticked here is verified by the test suite.

Graphics & images

  • Supported: Kitty graphics protocol - still-image actions t/T/p/d/q plus animation f/a/c
  • Supported: Kitty Unicode placeholders (U=1 virtual placements from U+10EEEE cells)
  • Supported: RGB/RGBA (f=24/f=32) & PNG (f=100)
  • Supported: Bounded zlib payload compression (o=z) across formats, transports, chunks, queries, and animation frames
  • Supported: Image numbers (I=) on transmissions and animation f/a/c; newest matching image wins
  • Supported: Transports: direct and chunked-inline always on; named file / temp-file (all platforms) and POSIX shared memory (Unix) are default-off behind kitty_named_transports
  • Supported: Placements, z-index, source crop, cell scaling, offsets
  • Supported: Sixel (DCS q) - RGB/HLS, repeat, raster attrs, transparency, VT340 palette, DECSDM, and capability advertising through DA1
  • Supported: iTerm2 inline images (OSC 1337 ; File=) - PNG/JPEG/WebP, cell/px/%/auto sizing, aspect fit
  • Not supported: Kitty I= addressing on display/delete commands, including d=n/d=N
  • Not supported: Animated containers (APNG, GIF) - they decode as one still frame; animation comes from the protocol's frame commands
  • Not supported: The iTerm2 chunked MultipartFile form, and inline=0 downloads (no escape sequence writes files)

Input & mouse

  • Supported: Kitty keyboard protocol (progressive enhancement)
  • Supported: IME composition input for CJK and compose-key/dead-key accents
  • Supported: Mouse modes 9 / 1000 / 1002 / 1003
  • Supported: Encodings 1005 / 1006 / 1015 / 1016 (SGR-pixel)
  • Supported: Alternate scroll mode 1007 maps wheel motion to cursor keys in full-screen TUIs
  • Supported: Focus reporting (DECSET 1004)
  • Supported: Bracketed paste with sanitization, queued as one atomic marker + text + marker transaction (32 MiB limit)
  • Supported: Risky-paste confirmation for non-bracketed multiline or control-bearing text - bounded escaped preview with Paste, Paste as One Line when available, or Cancel
  • Supported: Program-defined clickable output buttons - master gate on by default, sticky lifetime off by default

OSC & dynamic color

  • Supported: OSC 0/2 window title
  • Supported: OSC 8 hyperlinks (hover underline, Ctrl+click on Linux/Windows or Cmd+click on macOS, allowlist)
  • Supported: OSC 7 working directory (advisory, localhost-only; shell integration percent-encodes paths safely)
  • Supported: OSC 52 clipboard write - per-session consent prompt (ask) by default; reads remain off
  • Supported: OSC 133 prompt marks for command-aware navigation, command-output select/copy/search/navigation/export, and command-status gutters
  • Supported: Bounded OSC 9/777 notifications and OSC 9;4 progress - pane-owned, rate-limited, and never stealing keyboard focus
  • Supported: OSC 4 / 10 / 11 / 12 + resets (104/110/111/112)

Rendering & text

  • Supported: SGR 256-color + truecolor (colon & semicolon)
  • Supported: DEC G0/G1 designation, SO/SI selection, and Special Graphics mapping for ncurses line drawing
  • Supported: Extended underlines (4:0–4:5) + underline color (58/59)
  • Supported: Legacy SGR 21 double underline alongside the modern underline styles
  • Supported: Mouse cursor shape: I-beam over grid, hand over hyperlinks, arrow over chrome
  • Supported: Subpixel AA (dual-source) with grayscale fallback
  • Supported: Synchronized output (DEC 2026, 150 ms timeout)
  • Supported: XTGETTCAP, DECRQSS, DECRQM/DECRPM capability queries
  • Supported: Color fonts: bitmap strikes, COLR/CPAL v0 layers, and COLR v1 Paint graphs (gradients, transforms, clips, composites)
  • Supported: Shaping: calt+liga with a curated non-ASCII operator allowlist, optional ss01/ss02, and Arabic joining forms in logical cell order
  • Not supported: SVG-in-OpenType color glyphs fall back to the monochrome path
  • Not supported: Bidi reordering and full complex-script shaping - RTL text is drawn in logical cell order

$ odytty --privacy - local data handling

OdyTTY runs on the local machine. The absence of network product plumbing is a design decision, not an omitted settings toggle. This application privacy statement is separate from optional Cloudflare site-level analytics on the project website.

Application data flows

  • No telemetry, analytics, crash-reporting service, account, cloud sync, or update ping
  • No cloud service; the Unix detached-session host communicates through an owner-private, per-user local socket
  • Settings and themes remain local; session replay is an opt-in, memory-only bounded ring and is never synced
  • Diagnostics stay local too: WARN+ by default in a bounded rotated log under the platform state/log directory; terminal content, PTY bytes, typed input, and window titles are excluded (an OS-authored error string can include a filesystem path)
  • On Unix, saved layouts, session state, and diagnostic logs live in owner-private 0700 directories and 0600 files validated through no-follow handles; a symlinked or foreign-owned path disables that file rather than being followed. macOS and Windows use their platform ACL behavior
  • Externally influenced session metadata, layouts, host records, fonts, clipboard images, and detached-session traffic have explicit size or queue boundaries; a PNG-encoded clipboard image stops at the 10 MiB upload ceiling, and oversized inputs fail without partially rewriting the affected state
  • The source is GPL-3.0-only, so the privacy stance is inspectable

User-initiated network activity

  • Ctrl+click on Linux/Windows or Cmd+click on macOS opens allowed links and paths through the platform opener
  • The connection manager launches the system ssh only when you choose a host; confirmed remote-image paste also transfers through ssh
  • OSC 52 clipboard writes default to ask: allow once, allow for the current PTY session, deny for that session, or cancel. Even on accepts writes only from the active session while the window has OS focus and fails closed otherwise; notices report the target and byte count, never content
  • OSC 52 clipboard read replies are off by default; enable only in trusted sessions
  • Kitty named file, temp-file, and POSIX shared-memory graphics transports are off by default (kitty_named_transports) and rejected before any host I/O, so remote or SSH output cannot read local files unless you grant that authority; direct and chunked-inline images still work

The screenshot gallery is kept separate from the feature reference.

Screenshot gallery →