Standards & protocols
Terminal emulators are layered specs. ECMA-48 from 1976 gave us cursor movement, SGR colors, and the basic CSI grammar; xterm added a handful of extensions through the 80s and 90s; DEC contributed Sixel; iTerm2 added inline images; Kitty rebuilt keyboard handling from scratch. Modern terminals are a sum of these, and modern shells, editors, and TUIs assume they’re all there.
This page catalogues every spec tmnl’s shell mode implements, with
the source-of-truth source file, the wire format, and any
tmnl-specific notes. Shell mode is the pty-hosting track that
shares wire compatibility with xterm, kitty, iTerm2, WezTerm, and
friends. Native mode has its own protocol —
tmnl-protocol — covered at the end of this page.
OSC sequences
Section titled “OSC sequences”OSC (“Operating System Command”) sequences carry side-channel commands that don’t produce visible output: set the title, change a color, ring a bell, mark a prompt boundary. The shape is always the same:
ESC ] <number> ; <payload> STST (string terminator) is either BEL (0x07) or ESC \. tmnl accepts
both forms everywhere.
OSC 7 — current working directory
Section titled “OSC 7 — current working directory”xterm sequences ·
source: src/osc7.rs
Shells emit ESC ] 7 ; file://<host>/<percent-encoded-path> ST after each
cd. tmnl extracts the path to drive per-pane cwd-aware UI — tab-label
fallback, “open in Finder” actions, and future remote-aware integrations.
Over SSH, OSC 7 is the only reliable way for tmnl to learn the remote
shell’s cwd.
The host portion is informational and dropped. Percent-encoded escapes
(%20 for space, etc.) are decoded; unknown escapes pass through literally
so we never lose user bytes.
OSC 8 — hyperlinks
Section titled “OSC 8 — hyperlinks”VTE hyperlink spec ·
source: src/osc8.rs
The de-facto “clickable text in the terminal” protocol:
ESC ] 8 ; <params> ; <uri> ST <visible text> ESC ] 8 ; ; STThe closing sequence has both params and URI empty. tmnl tags each cell
covered by an “open” segment with a link id in a parallel grid, so the
existing mouse-hover and cmd+click paths work uniformly. Params (commonly
id=<some-id>) are parsed but ignored — per-cell tagging gives the same
visual effect without tracking ids.
Common emitters tmnl handles in the wild: gh CLI, gitui, ls --hyperlink=auto, git log recent versions, GNU grep --color=always.
OSC 9 — ConEmu / iTerm2 extensions
Section titled “OSC 9 — ConEmu / iTerm2 extensions”ConEmu OSC 9 spec ·
iTerm2 proprietary escape codes ·
source: src/osc9.rs
Two forms share the OSC 9 prefix:
ESC ] 9 ; <message> ST— desktop notification (iTerm2 style).ESC ] 9 ; 4 ; <state> ; <percent> ST— progress report (ConEmu style).
Progress states: 0 clear, 1 normal, 2 error tint, 3 indeterminate
(spinning), 4 paused. tmnl auto-clears the progress chip after 15 s of
inactivity, mirroring Ghostty’s convention, so a crashed cargo build
doesn’t pin a stale bar forever.
Programs that emit these: cargo, npm/pnpm, make 4.4+, apt,
Windows installers, plus iTerm2 shell-integration on long-running command
completion. Unknown sub-IDs fall back to “treat as notification body” —
again matching Ghostty.
OSC 22 — mouse-cursor shape
Section titled “OSC 22 — mouse-cursor shape”VTE OSC 22 ·
source: src/osc22.rs
Programs ask the terminal to change the mouse-cursor shape:
ESC ] 22 ; <shape-name> ST<shape-name> is an X11 cursor-spec keyword — text / pointer / hand
/ crosshair / wait / progress / not-allowed / ew-resize /
ns-resize / move / help, plus aliases like xterm, default,
hand1. The App polls each tick and calls winit::Window::set_cursor when
the shape on the focused pane changes.
Empty body or ? is a query; tmnl doesn’t reply (matching Ghostty’s
behavior for unknown shapes).
OSC 52 — read/write system clipboard
Section titled “OSC 52 — read/write system clipboard”xterm OSC 52 ·
source: src/osc52.rs
The killer use case is SSH: yy in vim on a remote host can land in the
local Mac’s pasteboard without tmux clipboard forwarding, without
pbcopy over ssh, without any clipboard daemon.
ESC ] 52 ; <targets> ; <base64> STTargets are one or more of c (clipboard), p (primary), s (selection),
or 0–7 (xterm cut buffers). tmnl owns one OS pasteboard per platform,
so any non-empty target string maps to “clipboard.” Empty payload after the
targets is a query — tmnl silently ignores it (never echoes the pasteboard
back into the pty, for security).
Paste-protect: large multi-line payloads route through the same confirm
dialog as ⌘V, blocking the multi-line-clipboard-injection attack class
where a remote process tries to forge an “rm -rf …” that the user
“approves” by pressing Enter.
OSC 99 — Kitty notifications
Section titled “OSC 99 — Kitty notifications”Kitty notification protocol ·
source: src/osc99.rs
Kitty’s richer notification protocol — colon-separated key=value
metadata, then the body:
ESC ] 99 ; <metadata> ; <body> STtmnl v1 supports n=<title>, i=<id> (notification id; replaces prior
sends with the same id), and p=<priority> (0 low, 1 normal, 2
critical). Skipped for v1: e= (encoding alternatives), d= (multi-chunk
done marker), s= (sound), u=/a= (action buttons), o=
(sticky/close-on-click). The common path — gh notify /
cargo build done / notify-send-equivalents — only uses n= + body.
OSC 133 — semantic prompts
Section titled “OSC 133 — semantic prompts”VS Code shell integration overview ·
FinalTerm OSC 133 spec ·
source: src/osc133.rs
The four-mark “FinalTerm” semantic-prompt protocol — A (prompt about to
draw), B (command input begins), C (output begins), D[;exit]
(command finished). With the shell-integration snippet
installed, tmnl folds these into per-session lifecycle state that drives:
- The
✗Ntab-strip chip for non-zero exits. ⌘↑/⌘↓jump-to-prompt navigation through scrollback.- The command-line anchor that AI command completion (
⌘I/⌘K) reads to know where the editable input starts.
Without the snippet, no marks arrive and the state stays inert. Every consumer degrades gracefully.
OSC 1337 — iTerm2 proprietary escape codes
Section titled “OSC 1337 — iTerm2 proprietary escape codes”iTerm2 docs ·
sources: src/osc1337_file.rs, src/shell_osc1337.rs (attention scanner)
iTerm2’s catch-all prefix carries several sub-grammars; tmnl recognizes two:
ESC ] 1337 ; File = <keys> : <base64> ST — inline image display.
Decodes PNG via the image crate and JPEG via jpeg-decoder, normalizing
both into the same kitty_graphics::Image representation so the rest of
the renderer treats OSC 1337 images identically to Kitty graphics. Honored
keys: name, size, width/height (cells, Npx, N%, or auto),
preserveAspectRatio, inline. Skipped: file transfer (inline=0), GIF
(use the Kitty graphics path).
ESC ] 1337 ; RequestAttention=... ST — attention signal. Claude Code
uses this when a turn finishes and is waiting for input. tmnl doesn’t parse
the iTerm2 sub-grammar (Notify=…, RequestAttention=…, StealFocus) —
any OSC 1337 payload flips the pane’s attention flag, which surfaces
through the dock badge / chime / prompt-path attention chip. See
Attention & notifications. The cost of this coarse-grained
approach is a few harmless false-positives when vim or tmux emits other
1337 sequences (cursor color, etc.).
OSC 4 / 10 / 11 / 12 / 104 / 110 / 111 / 112 — palette ops
Section titled “OSC 4 / 10 / 11 / 12 / 104 / 110 / 111 / 112 — palette ops”xterm Operating System Commands ·
source: src/osc_palette.rs
xterm’s palette set/reset/query family:
| Sequence | Meaning |
| --- | --- |
| OSC 4 ; <idx> ; rgb:rrrr/gggg/bbbb ST | set palette color <idx> (0–255) |
| OSC 10 ; rgb:... ST | set default foreground |
| OSC 11 ; rgb:... ST | set default background |
| OSC 12 ; rgb:... ST | set cursor color |
| OSC 104 | reset all palette indices |
| OSC 104 ; <idx> ST | reset palette index |
| OSC 110 / 111 / 112 | reset default fg / bg / cursor |
Query form substitutes ? for the color spec. tmnl replies to query forms
(OSC 10 ; ? ST, etc.) so Neovim’s light-vs-dark background autodetection
(&background) works. Color spec accepts rgb:RRRR/GGGG/BBBB (xterm
canonical) or #RRGGBB / #RRRRGGGGBBBB (compat); each channel may be 1
to 4 hex digits and normalizes to 0..=255.
OSC 13 / 14 (mouse fg/bg), 15 / 16 / 18 (Tektronix), and a handful of less common slots are also parsed for completeness.
CSI extensions
Section titled “CSI extensions”CSI (“Control Sequence Introducer”) sequences start with ESC [ and are
the workhorse of terminal control — cursor movement, color, mode toggles,
queries. The sub-grammar is intricate; tmnl peels off the parts vt100 0.16
doesn’t handle.
REP — CSI Ps b
Section titled “REP — CSI Ps b”ECMA-48 §8.3.103 ·
source: src/csi_rep.rs
“Repeat the previous graphic character Ps times.” Originally an
ECMA-48 hold-over for line-drawing run-length compression; modern programs
use it as a bandwidth optimization for long runs (seq, ncurses horizontal
rules, some progress bars).
vt100 0.16 drops b-terminated CSI sequences. tmnl expands REP in the
reader thread before passing the chunk to vt100: scan for the marker,
look back for the last printable character, splice in (count - 1) extra
copies. Capped at 64 KiB total expansion to defend against
CSI 99999 b pathologies.
Colon-subparameter underline styles — CSI 4:N m
Section titled “Colon-subparameter underline styles — CSI 4:N m”Kitty colored-text underline styles ·
source: src/underline_sgr.rs
Modern underline styles via SGR colon-subparameter:
ESC [ 4:0 m offESC [ 4:1 m singleESC [ 4:2 m doubleESC [ 4:3 m curly (LSP diagnostics, spell-check)ESC [ 4:4 m dottedESC [ 4:5 m dashedvt100 0.16 only knows the legacy CSI 4 m / CSI 24 m forms. tmnl peels
the colon-subparameter form off the byte stream, emits one transition event
per change, and the reader thread carries a parallel underline_grid so
apply_to_grid can tag the right attribute bits. Legacy 4 m / 24 m
also flow through the same scanner so the parallel grid stays in sync when
a session mixes both forms.
Colored underlines — CSI 58 … / CSI 59 m
Section titled “Colored underlines — CSI 58 … / CSI 59 m”Kitty colored-text underline styles ·
source: src/csi_underline_color.rs
Mirrors the 38/48 form for foreground/background, but for the underline
color. Both the semicolon-canonical xterm form and the colon-subparameter
ITU canonical form are accepted:
CSI 58 ; 2 ; R ; G ; B m true-color (xterm-original)CSI 58 : 2 :: R : G : B m true-color, colon-subparam (ITU canon)CSI 58 ; 5 ; <idx> m 8-bit indexedCSI 59 m reset to default (= fg)Indexed form resolves the index against the per-pane PaletteState.indexed
override, so a program themed via OSC 4 gets its undercurl colors
following the theme.
DECSET / DECRST mode toggles
Section titled “DECSET / DECRST mode toggles”xterm DECSET ·
source: src/csi_modes.rs
Programs ask the terminal to enable / disable various features:
ESC [ ? <n> h enable mode <n>ESC [ ? <n> l disable mode <n>The modes tmnl tracks today:
| Mode | Meaning | tmnl note |
| --- | --- | --- |
| ?1000 | X10 mouse press-only | tracked; encoder honors press-only |
| ?1002 | mouse drag (button motion) | write_mouse_motion reports drag |
| ?1003 | mouse hover + drag (any motion) | write_mouse_motion reports all motion |
| ?1004 | focus reporting | terminal sends ESC [ I / ESC [ O on focus changes |
| ?1006 | SGR mouse encoding | extended-range coordinates; the canonical modern form |
| ?2004 | bracketed paste | cmd+v wraps payload in ESC [ 200~ / ESC [ 201~ |
| ?2026 | synchronized output | renderer keeps the prior frame until the unmatched ?2026 l arrives |
Sequences are still forwarded to vt100 normally; vt100 handles its own
internal mode tracking (cursor visibility, etc.) independently. tmnl’s
parallel state is what surfaces user-facing behavior — flipping ?2004 off
makes cmd+v send raw bytes; flipping ?1004 on makes window focus
generate input.
Title-stack window manipulation — CSI 22 t / CSI 23 t
Section titled “Title-stack window manipulation — CSI 22 t / CSI 23 t”xterm window manipulation ·
source: src/csi_title_stack.rs
Push and pop the current title onto a per-pane stack — vim, less, and fzf use this to set a transient title and restore the prior one on exit.
CSI 22 [;Ps] t push (0 = both, 1 = icon, 2 = window)CSI 23 [;Ps] t poptmnl carries one label per pane (the tab strip displays the window title);
icon-only push/pop (Ps=1) becomes a no-op at the TitleSink level while
still consuming the byte sequence. vt100 0.16 silently drops t-terminated
CSI sequences entirely, so this scanner is the only path that surfaces
them.
Device & status queries — CSI c, CSI n, CSI t
Section titled “Device & status queries — CSI c, CSI n, CSI t”xterm Device Attributes ·
source: src/csi_responses.rs
Without responses to these, Neovim assumes “ancient VT100” mode and disables modern features; htop picks a conservative color depth; image-rendering tools can’t compute scale.
| Query | Reply |
| --- | --- |
| CSI c (primary DA) | terminal class |
| CSI > c (secondary DA) | terminal version |
| CSI = c (tertiary DA) | DCS device-id sequence |
| CSI 5 n (DSR — “are you OK?”) | CSI 0 n (“OK”) |
| CSI 14 t | window pixel size |
| CSI 18 t | text-area cell size |
Each emits an Event the reader thread translates into a pre-built reply
pushed to pending_pty_writes.
Mode-state queries — DECRQM / DECRQSS
Section titled “Mode-state queries — DECRQM / DECRQSS”xterm DECRQM ·
source: src/mode_query.rs
Programs probe terminal capability via these. Neovim checks whether it
actually enabled ?2004 (bracketed paste) before emitting paste-wrapped
sequences; some shells check ?2026 before starting a sync transaction.
DECRQM CSI [?] Ps $ p — query mode state (set / reset / permanent)DECRQSS DCS $ q Pt ST — query setting (SGR, DECSCUSR, margins, conformance)DECRQM reply uses Pm values: 0 (unknown), 1 (set), 2 (reset), 3
(permanently set), 4 (permanently reset). tmnl tracks every mode the
csi_modes scanner surfaces, plus a handful of “definitely set” /
“definitely reset” replies for modes the terminal can’t toggle.
DECSCUSR — set cursor style — CSI Ps SP q
Section titled “DECSCUSR — set cursor style — CSI Ps SP q”VT520 DECSCUSR ·
source: src/decscusr.rs
Programs like vim, Neovim, zsh-vi-mode, and fish flip the cursor style between modes (block in normal, bar in insert, underline in replace):
| Ps | Shape | Blink | | --- | --- | --- | | 0, 1 | block | yes | | 2 | block | no | | 3 | underline | yes | | 4 | underline | no | | 5 | bar / I-beam | yes | | 6 | bar | no |
tmnl tracks the shape on the focused pane’s ShellSession and the App
reads it when applying the cursor attr to the cell at the vt100 cursor.
DECSTR — soft reset — CSI ! p
Section titled “DECSTR — soft reset — CSI ! p”VT510 DECSTR ·
source: src/decstr.rs
“Please reset to a sane default state.” tmux sends this on attach to recover from whatever state the previous attached process left things in; some shell programs send it on init for the same reason.
vt100 0.16 doesn’t handle DECSTR, so tmnl detects the sequence and resets
the parallel state tmnl tracks above vt100: bracketed paste, focus
reporting, sync depth, Kitty keyboard flags, cursor style, cursor blink,
palette overrides, underline and link grids, and the OSC 8 / OSC 4:N
continuation state on the reader side. Nothing vt100-owned gets reset — a
“true” DECSTR would reset cell colors too, but that mismatches what real
programs expect from our parallel state (a stuck sync_depth > 0 from a
crashed program being the biggest concrete win).
Graphics protocols
Section titled “Graphics protocols”Three competing inline-image specs coexist in the terminal world. tmnl
implements all three and normalizes them into the same internal Image +
Placement representation so the GPU pipeline doesn’t care which one drew
the image.
Kitty graphics protocol
Section titled “Kitty graphics protocol”Kitty graphics protocol ·
source: src/kitty_graphics.rs
The richest of the three. Wire shape:
ESC _ G <key1=val1,key2=val2,...> ; <base64-payload> ESC \tmnl v1 implements the “core path” that covers kitty +icat, imgcat,
and most gh / lazygit use cases:
a=T(transmit and display)f=100(PNG payload) andf=24/f=32(raw RGB / RGBA)t=d(direct / inline transmission)m=0(single chunk; multi-chunkm=1is deferred)c=<cols>/r=<rows>— placement size in cells (the missing axis is picked to preserve aspect ratio)s=<px>/v=<px>— image pixel dimensions (required for raw formats)i=<id>— image id (defaults to a per-pane sequential)
Deferred (parsed but ignored or rejected): t=f / t=t / t=s (file /
temp / shared-memory transmission), o=z (zlib), a=d (delete), a=q
(query), z= (z-index), x / y / w / h (source region offsets),
p= (placement id), Unicode placeholder positioning, animation frames.
Sixel — DEC pixel graphics
Section titled “Sixel — DEC pixel graphics”All About SIXELs ·
source: src/sixel.rs
The DEC original. Sixel encodes 6-pixel-tall vertical strips of an image
as printable ASCII characters (0x3F..0x7E mapped to a 6-bit bitmap). Common
emitters: img2sixel from libsixel, gnuplot / ipython / matplotlib with
the sixel backend, sixcat, chafa --format=sixel.
ESC P <p1>;<p2>;<p3> q [#color-map]* [sixel-data]* ESC \Output is an RGBA8 frame buffer feeding the same upload path Kitty graphics and OSC 1337 use. Decoded images get an auto-assigned id range distinct from Kitty’s, so the program’s chosen ids don’t collide.
iTerm2 inline images (OSC 1337 File=)
Section titled “iTerm2 inline images (OSC 1337 File=)”iTerm2 imgcat protocol ·
source: src/osc1337_file.rs — see OSC 1337 above
for the catalog entry.
Keyboard protocols
Section titled “Keyboard protocols”Kitty keyboard protocol
Section titled “Kitty keyboard protocol”Kitty keyboard protocol ·
source: src/kitty_kbd.rs
Modern programs (Neovim, Helix, modern shells, Claude Code) use the Kitty
keyboard protocol to disambiguate keystrokes that collide in the legacy
encoding (Ctrl+i vs Tab, Ctrl+m vs Enter, Ctrl+[ vs Esc) and
to surface modifier state on every key.
ESC [ > <flags> m push <flags> onto the stackESC [ = <flags> ; <mode> u set <flags> with mode 1=set, 2=add, 3=clearESC [ < <n> m pop the top <n> stack entries (default 1)ESC [ ? u query — terminal replies with current flagsFlag bits:
| Bit | Meaning |
| --- | --- |
| 0x1 | disambiguate escape codes (most-asked-for) |
| 0x2 | report event types (press / repeat / release) |
| 0x4 | report alternate keys |
| 0x8 | report all keys as escape codes |
| 0x10 | report associated text |
tmnl v1 tracks the flag stack and the encoder honors the disambiguate bit
(0x1). The other bits are parsed for completeness so adding them is a
one-spot change in encode_key_kitty. Mode-set form (CSI = flags ; mode u) parses and translates into a stack-replace event so consumers don’t
have to thread the difference through.
Input modes
Section titled “Input modes”Focus event reporting — DECSET ?1004
Section titled “Focus event reporting — DECSET ?1004”xterm focus event mode ·
source: src/csi_modes.rs + src/shell/reader.rs (DECSET callback) +
src/shell/accessors.rs (focus_reporting_enabled + write_focus_event)
When the program enables ?1004, tmnl sends ESC [ I on focus-in and
ESC [ O on focus-out. Used by vim to refresh after switching away, by
Claude Code / Codex to re-sync, by long-running watchers to know when to
spit cached output.
The ?1004 flag is per-shell-session (focus_reporting: Arc<AtomicBool>) so two panes in the same window track their own state
independently.
Mouse modes — ?1000 / ?1002 / ?1003 / ?1006
Section titled “Mouse modes — ?1000 / ?1002 / ?1003 / ?1006”xterm mouse tracking ·
source: src/shell_mouse.rs
xterm mouse tracking, parameterized by which events the program wants:
| Mode | Events |
| --- | --- |
| ?1000 | press and release only |
| ?1002 | press, release, drag (motion while a button is held) |
| ?1003 | press, release, drag, and hover (any motion) |
| ?1006 | SGR encoding (extended coordinates; modern canonical) |
?1006 is an encoding flag rather than a tracking mode — it can be
combined with any of the above. encode_mouse honors the parser’s active
encoding (SGR 1006 when set, X10 / default otherwise). Releases are encoded
as button-3 in X10 (the protocol doesn’t carry the released button) and as
a lowercase m terminator in SGR.
tmnl-protocol button order (LEFT=0, RIGHT=1, MIDDLE=2) gets remapped
to xterm button order (LEFT=0, MIDDLE=1, RIGHT=2) at encode time;
wheels (BUTTON_WHEEL_UP=4, BUTTON_WHEEL_DOWN=5) stay distinct. Modifier
bits remap too: tmnl’s shift/ctrl/alt (bits 0/1/2) become xterm’s button-
byte bits (2/4/3). The super modifier has no slot in xterm encoding and
is dropped.
Bracketed paste — DECSET ?2004
Section titled “Bracketed paste — DECSET ?2004”xterm bracketed paste ·
source: src/csi_modes.rs + src/shell/reader.rs (DECSET callback) +
src/shell/accessors.rs (bracketed_paste_enabled)
When the program enables ?2004, tmnl wraps cmd+v payloads in
ESC [ 200~ … ESC [ 201~ so the shell can distinguish typed input from a
paste. Modern interactive shells (zsh, bash 5.1+, fish) set the flag
on init.
Tracked per-session as bracketed_paste: Arc<AtomicBool>. The OSC 52
clipboard path runs through the same paste-wrap logic. When the program
hasn’t enabled ?2004, tmnl sends raw bytes — so cat > file.txt pasted
into a no-bracketing context still works.
IME composition — preedit / commit / cancel
Section titled “IME composition — preedit / commit / cancel”source: src/app_event_routes.rs (handler) · tmnl_protocol::ImeEvent
(wire types)
IME (“Input Method Editor”) composition — typing Chinese, Japanese, or
Korean text via a composition buffer. The winit-level Ime::Enabled /
Disabled / Preedit(text, cursor) / Commit(text) events arrive on the
window, and tmnl routes them differently per pane:
- Shell mode panes receive the committed text as raw bytes via
write_bytesonce composition finishes. Preedit text shows in the overlay while composing (theime_preeditfield) but doesn’t reach the shell — matching every other terminal’s behavior. - Native-mode panes whose client advertised
Caps::IME_EVENTSget the full composition state viaInputEvent::Ime(Preedit | Commit | Cancel). Backing apps can render their own preedit highlight, support in-line IME composition in mnml’s editor, etc. - Native-mode panes without the cap fall back to per-character
Key(Char)events on commit, so legacy clients still receive text (just not the preedit state).
The IME pipeline is always enabled on the window via set_ime_allowed(true)
at resume time; per-pane opt-in is via the protocol cap.
Native mode — tmnl-protocol
Section titled “Native mode — tmnl-protocol”Distinct from every spec above: when tmnl runs as a “native mode” host, the
pane’s backing app speaks binary tmnl-protocol over a Unix socket
instead of ANSI escape codes. mnml as a tab, mixr as a tab,
examples/hello_client.rs — all of these are tmnl-protocol clients.
The protocol is documented in detail at:
- SDK — building a backing app — the
Clienthelper, theEventenum, capability negotiation, end-to-end examples. - Native tabs — wire-level walkthrough.
Capability bitmask (Caps) flags currently defined on the Hello
handshake:
| Flag | Meaning |
| --- | --- |
| CLIENT_COMMANDS | client exposes a ListClientCommands query the host can call to populate the palette |
| FOCUS_EVENTS | client receives InputEvent::Focus(Entered | Left) |
| HOVER_EVENTS | client receives InputEvent::Hover(Entered | Moved | Left) |
| IME_EVENTS | client receives InputEvent::Ime(Preedit | Commit | Cancel) (see IME above) |
| INLINE_IMAGES | client may send Message::InlineImageCreate / Update / Destroy (protocol v7, see Inline images below) |
Wire-level back-compat: pre-v6 peers decode to Caps::empty(), so adding a
new bit doesn’t break old clients.
Pty-fd handoff (receiver) — SCM_RIGHTS
Section titled “Pty-fd handoff (receiver) — SCM_RIGHTS”source: src/transfer.rs
A separate single-message listener at
<TMPDIR>/tmnl-<pid>-transfer.sock (exported via TMNL_TRANSFER_SOCKET)
accepts Message::OpenPaneTransfer with an attached pty master fd via
tmnl_protocol::read_message_with_fd. The fd becomes a new adopted-shell
tab on the next tick.
This can’t ride the streaming tmnl-protocol connection — SCM_RIGHTS
ancillary data can’t be read through a BufReader — hence the separate
socket. The sender side lives in mnml as :tmnl.pop-pty.
Inline images (protocol v7)
Section titled “Inline images (protocol v7)”source: tmnl-protocol/src/lib.rs (wire types) · src/image_pipeline.rs
(decoder + GPU upload)
Backing apps overlay PNG images at cell coordinates inside the pane:
Message::InlineImageCreate { id: u32, cell_row: u16, cell_col: u16, cell_rows: u16, cell_cols: u16, image_bytes: Vec<u8>, // PNG}Message::InlineImageUpdate { id, ... }Message::InlineImageDestroy { id }tmnl decodes via the image crate, uploads to a wgpu texture, and renders
through the textured-quad pipeline. The image scrolls with the underlying
cells — cell-anchored, not pixel-anchored. SDK wrappers:
Client::send_inline_image_create / _update / _destroy. Gated by
Caps::INLINE_IMAGES. Demo: examples/inline_image_client.rs.
What tmnl does not implement (yet)
Section titled “What tmnl does not implement (yet)”In the interest of full disclosure — these are recognized specs that tmnl intentionally does not ship today, with the reasoning:
- Multi-chunk Kitty graphics (
m=1) — covered by single-chunk + Sixel + OSC 1337 in practice; deferred until a real use case arrives. - Kitty graphics animation frames — same reasoning.
- GIF in OSC 1337
File=— rare via this protocol; the Kitty graphics path is the canonical animated-image route. - Title manipulation beyond push/pop (
CSI 8 tresize,CSI 11 ticonify-state query, etc.) — the window-manipulation family is large and most slots are no-ops on macOS anyway. - OSC 99 action buttons / sound / multi-chunk encoding — the common
path (
n=+ body) coversgh notify,cargo build done, etc.
- OSC 133 shell integration — the four marks in depth
- Attention & notifications — what surfaces from unfocused tabs
- Native tabs — the tmnl-protocol wire format
- SDK — building a backing app —
Client, capability negotiation, examples - AI completion (offline) — what rides on top of the OSC 133 anchor