revenant-probes(7)
REVENANT-PROBES(7)Revenant ManualREVENANT-PROBES(7)

Name

revenant-probes – interactive verification programs

Interactive terminal probes

Use just probe for new feature testing. It builds the standalone Go runner and opens TDN's feature browser; append a feature slug and case ID to run a case directly, for example just probe osc-8-hyperlinks links-demo. The probe guide is the source for navigation, F2 text selection, options, evidence and cleanup. Keep the legacy scripts below for comparison until the maintainer completes the TDN migration checklist.

Legacy comparison tools

Run the same probe inside Revenant, xterm, and Ghostty when comparing behavior. Record the emulator version, font configuration, locale, and exact invocation with any result.

Probe Purpose
tools/probe-color.sh SGR attributes, underline styles, 16/256 colors, and semicolon/colon truecolor ramps.
tools/probe-colors.py OSC 4 palette queries and resets, named palettes, and an -xrm spawn harness alongside expanded 16/256/truecolor displays.
tools/probe-reverse-video.sh Enter-gated comparison of SGR 7, DECSCNM repaint/restoration, explicit backgrounds, and widget reverse video.
tools/probe-osc8.sh Explicit OSC 8 labels and a detected plain URL for Shift-hover and safe HTTP(S)-only Shift-click activation.
tools/probe-emoji.py CPR-measured legacy-versus-mode-2027 widths for emoji presentation, modifiers, ZWJ sequences, flags, cluster boundaries, right-margin wrapping, and over-capacity clusters.
tools/probe-fonts.py CPR-measured widths plus visual diagnostics for combining marks, conjuncts, enclosing and spanning marks, and Zalgo-style stacking.
tools/probe-keymodes.py Cooked input, raw bytes, fixterms drift, Kitty flags, associated text, event types, and flag-stack restoration.
tools/probe-sync.py Slow redraw comparison with DEC mode 2026 off/on, with an optional mid-frame hold for timeout and resize checks.
tools/probe-clipboard.py OSC 52 selection set, clear, invalid-payload, and query with decoded replies, for comparing allowWindowOps policy across emulators.
tools/probe-titles.py Title/icon reports (XTWINOPS 20/21), nested push/pop (22/23), and visible title restoration.
tools/probe-dynamic-colors.py OSC 10/11/12 sets and queries, OSC 110/111/112 resets, comparison of reported RGB values with visible default colors, and the CSI ? 996 n light/dark query.
tools/probe-features.py Named dispatch fixtures for remaining features, plus live Mouse/Tcap Ops checks; use --list.

Examples:

tools/probe-color.sh
python3 tools/probe-colors.py --query
python3 tools/probe-colors.py --spawn --query --program ./build/revenant
tools/probe-reverse-video.sh
tools/probe-osc8.sh
python3 tools/probe-emoji.py --no-pause
python3 tools/probe-emoji.py --regime legacy
python3 tools/probe-emoji.py --regime cluster
python3 tools/probe-fonts.py --no-pause
python3 tools/probe-keymodes.py --kitty-only
python3 tools/probe-sync.py
python3 tools/probe-sync.py --mode off
python3 tools/probe-sync.py --mode on
python3 tools/probe-sync.py --mode on --frames 3 --hold-ms 1500
python3 tools/probe-clipboard.py
python3 tools/probe-clipboard.py --target p --set "from OSC 52"
python3 tools/probe-clipboard.py --query --st
python3 tools/probe-titles.py
python3 tools/probe-titles.py --query
python3 tools/probe-titles.py --target title --delay 2
python3 tools/probe-dynamic-colors.py
python3 tools/probe-dynamic-colors.py --query
python3 tools/probe-dynamic-colors.py --scheme
python3 tools/probe-dynamic-colors.py --background '#142850' --foreground '#ffe080'
python3 tools/probe-dynamic-colors.py --reset all

The equivalent just recipes are probe-color, probe-colors, probe-reverse-video, probe-osc8, probe-emoji, probe-fonts, probe-keymodes, probe-sync, probe-clipboard, probe-titles, probe-dynamic-colors, and probe-features.

The synchronized-output probe draws alternating colored frames one row at a time. By default it runs eight frames with synchronization off, then eight with it on. Off should show a moving boundary between old and new rows; on should show complete frames swapping together. The label reports the requested mode, not detected support: terminals that ignore mode 2026 may show the sweep in both phases. Run directly in the terminal when comparing emulators; multiplexers can affect the result.

--frame-ms controls the total row-drawing delay (default 400 ms), and --pause-ms controls the pause between frames (default 150 ms). Keep --frame-ms plus --hold-ms comfortably below Revenant's one-second timeout for the normal comparison. With --mode on --hold-ms 1500, the probe pauses halfway through each frame: the timeout should reveal a partial frame before the remaining rows arrive. Resize during a hold to observe the geometry repaint exception; the next frame adapts to the new size. Ctrl+C exits, resets mode 2026, restores cursor visibility, and leaves the alternate screen.

The TDN probe's version, just probe dec-mode-2026-synchronized-output rendering-sync, adds a third pass after off and on, also selectable alone with --mode boundaries. It labels three scenarios on screen with what must be visible while each holds: output followed by a hold in the same write; a release, a completed frame and a new hold in the same write; and the same transitions as consecutive writes. It is a visual check and says so: a PTY write is not one parser batch, and two writes may arrive as one, so a correct look is not proof that a terminal handles batch boundaries, and a wrong look under a multiplexer may be the multiplexer's. Revenant's deterministic check is the painted-frame helper in tests/xvfb-sync-output.sh, which feeds the parser one exact batch at a time and reads the window back. The probe releases mode 2026 on exit as before.

The TDN probe's clipboard cases (just probe osc-52-read clipboard-query, just probe osc-52-write clipboard-set --text 'café 🛠', and the clear and invalid cases) report replies exactly: the target a reply names, the decoded bytes in hex, and whether they are valid UTF-8. Every case except the query replaces the selection for all applications without saving it, so it says so and waits for consent first. After a set, the probe reads the selection back and compares bytes; that round trip shows only that the terminal returns what it was given. How a terminal converts for other X clients — STRING as Latin-1, COMPOUND_TEXT, an owner declaring one encoding while sending bytes that look like another — needs an external owner and reader, which Revenant's xvfb-clipboard suite provides; no in-terminal probe can show it.

just probe dec-mode-1006-sgr-mouse input-mouse captures raw mouse bytes by default. --scenario counts and --scenario handoff (or all) run labelled steps instead: turn the wheel as each step asks, press Space, and the probe prints the wheel presses, wheel releases, other reports and cursor keys it received. The handoff steps switch tracking off and on between notches, then repeat on the alternate screen with mode 1007, and restore every mode they changed. Only you know how many notches you turned, so comparing the counts is a human assessment. Revenant's exact-byte evidence is tests/xvfb-mouse-scroll.sh, which injects real wheel and drag input under Xvfb and checks the application's bytes and the PRIMARY text.

just probe selection-drag-scroll selection-scroll prints line 0001 to line 0300 and asks for drags across wheel scrolling, autoscroll and both directions. Paste each selection back (middle-click). Only the first and last line of a paste may be partial. The probe rejects any malformed or empty line between them, checks that the complete lines are consecutive, and names the first gap. It reports insufficient evidence when fewer than two complete lines arrive. Whether the ends are where the drag began and ended, and whether the highlight followed the pointer, is your judgement.

just probe policy-window-ops window-reports sends CSI 14, 16 and 18 t one at a time. It names the settings that decide each report, including xterm's gating of CSI 16 t by GetScreenSizeChars, and states each reply's units: text area in pixels, cell in pixels, text area in characters. Each report is labelled reply, silence (only the status request was answered), unexpected bytes, reply without the status reply, or timeout. The replies are compared with each other and with the kernel's window size (rows, columns and, when set, pixels). The case then queries again after you toggle Allow Window Ops twice in the menu. The probe itself sends only queries, but the menu changes persist after it ends. It asks you to note the entry's state first and to restore it when the case finishes, and reminds you again on q/Esc. The same case answers csi-14-t-pixel-size-report, csi-16-t-report-cell-size-in-pixels and csi-18-t-text-size. Registration is not support evidence: exact bytes and geometry against the X window are checked by tests/xvfb-window-ops.sh.

just probe startup-login-shell login-shell --program TERMINAL is an external launcher, run from an ordinary shell rather than inside the terminal under test:

build-probe/probe startup-login-shell login-shell --program build-u18-gcc/revenant

It starts TERMINAL six times with SHELL set to the probe binary: by default, with -ls, with +ls, with the loginShell resource, with +ls over the resource, and with -ls -e. The copy each terminal starts writes its own /proc/self/exe and argv to a file named in its environment, then exits. Each report must name the probe binary itself and give exactly xterm-411's argv: the shell's basename, dashed for a login shell, and a -e command as given. A missing, malformed or different report is a failure and the probe exits 1; it exits 0 only when all six match. The shell's appearance is not evidence; these argv reports are.

Launches are isolated from the caller's resources. HOME is a fresh directory, and XENVIRONMENT, XFILESEARCHPATH, XUSERFILESEARCHPATH and XAPPLRESDIR are /dev/null. Every launch starts with -xrm '*loginShell: false', which also overrides the server's resource database, before its own options. --xrm adds a deliberate override after that baseline, and the probe prints each one, since expectations assume it leaves loginShell alone. --seconds bounds each wait.

Each launch runs in its own process group, and its environment carries a marker. After each scenario, on a timeout, or on Ctrl-C or SIGTERM, the probe signals the group and every marked process (TERM, then KILL, each within a second), so children that started their own session are stopped too. A process still alive afterwards is a failure; an interruption exits 130. tests/xvfb-login-shell.sh checks the same rules through /proc.

just probe startup-hold-after-exit session-hold is a child fixture, not a case to run from a shell: it prints a final PROBE-HOLD-END status=N marker without a newline and exits with --status N (0 to 255). Launch the built probe as the terminal's own -e command, with no shell in between:

revenant -hold -e build-probe/probe startup-hold-after-exit session-hold --status 3

Without -hold the window closes when the probe exits; with it the window stays, showing the marker, until you close it. Whether the window remained is judged from outside the terminal; the fixture only supplies the output and the exit. tests/xvfb-hold.sh checks the same behaviour automatically.

just probe policy-color-ops-send-events colors-dynamic-policy names the startup resources that decide Color Ops (allowColorOps, allowSendEvents, disallowedColorOps). It then queries the default foreground and palette color 1 at startup, after a write, and after you turn Allow Color Ops off and on again. Each query is followed by a status request, and the probe reports what came back: - a valid reply; - silence, when only the status request was answered; - unexpected bytes, such as a reply for another color or a malformed one, which are shown rather than counted as silence; - a reply without the status reply; - a timeout, when nothing came back.

Before changing the foreground it explains the cleanup limit. On exit, q/Esc included, it requests the original foreground back if the startup query read it; otherwise it can only request a reset to the configured default (OSC 110). Both are refused while SetColor is denied, so run it in a disposable terminal and close that terminal afterwards. Exact bytes and pixels are checked by tests/xvfb-color-ops.sh.

just probe action-allow-title-ops titles-policy saves the labels, then asks you to turn Title Ops off and on, with the allow-title-ops action or the menu, between titled steps. Before any change it names the startup settings that decide labels (allowTitleOps, allowSendEvents) and those that decide reports and the stack (Window Ops). After each step it reports the window label, including an empty one. It then says only what it observed: the reported label matches or differs from the requested one, or no report arrived. A missing report is not explained, because the query may be refused, unsupported or slow. just probe policy-title-ops-send-events titles-policy runs the same case for the allowSendEvents scenarios. With allowSendEvents true, labels never change, the menu entry is greyed, and the action still changes the configured value. just probe resource-same-name titles-same-name sends repeated and changed labels in groups and shows the xprop -spy command to watch them from outside. Both are human-guided.

Both cases request a push of the labels at the start and a pop on exit, including q/Esc, and say so before changing any label. Neither is guaranteed: both are Window Ops stack operations. The pop restores the labels only if those operations are permitted and Title Ops is effective, meaning allowTitleOps is on and allowSendEvents is false. Turning Allow Title Ops on cannot restore anything while allowSendEvents is true. Run the cases in a disposable terminal. Exact property-notification counts and the action's argument handling are checked by tests/xvfb-title-ops.sh, which counts PropertyNotify events with an external observer.

just probe resource-utf8-title titles-utf8 (also launchable as just probe x11-utf8-title-properties titles-utf8) supplies an X11 property inspection corpus. It pauses for initial properties, then for UTF-8 Titles as found, on, off and on again, and for ASCII, UTF-8 and empty labels sent through OSC 2, 1 and 0 separately. Missing selectors are recorded separately from property encoding; this does not add OSC 1 support to a terminal. --no-pause only dumps the corpus and cannot establish the intermediate properties.

Use a disposable terminal. The menu entry is named utf8-title; the action is set-utf8-title. In a UTF-8 locale the menu can be insensitive, so launch the terminal with explicit action bindings, for example:

revenant -xrm 'XTerm*VT100.translations: #override <Key>F10: set-utf8-title(on)\n<Key>F11: set-utf8-title(off)'

F10 requests on and F11 requests off. Record the startup utf8Title resource; xterm's menu check follows UTF-8 mode, not necessarily the current title setting. In another terminal, use xwininfo to select the test terminal's top-level window, then set id to that ID. At each pause run:

xprop -id "$id" -f WM_NAME 8x -f WM_ICON_NAME 8x \
  -f _NET_WM_NAME 8x -f _NET_WM_ICON_NAME 8x \
  WM_NAME WM_ICON_NAME _NET_WM_NAME _NET_WM_ICON_NAME

This external observer prints property types and raw hexadecimal bytes. Keep snapshots with their stage/sample names, distinguishing absent properties from present empty ones. Capture immediately after toggles too, before a new label: that separates toggle behavior from the next update's deletion or recreation. The probe prints the exact requested UTF-8 payload bytes, but makes no automatic encoding or support verdict. Compare with xterm-411 using the same locale and resources; repeat in separately launched C and UTF-8 locales with utf8Title false/true, recording allowC1Printable, sameName, Ops policy and terminal version. A CSI title report is not evidence of an X property's type or bytes.

UTF-8 Titles menu changes persist. Note and restore the initial setting manually, including after q/Esc. Label cleanup requests the same conditional stack pop as the title-policy cases above; it cannot guarantee restoration. Results remain unassessed without an assessment. No xterm comparison or terminal support record is implied by running the probe's own tests.

just probe text-shaping text-contrast prints the corpus of the linear-light study: ordinary text, combining marks, the italic face and a fitted text-presentation 🛠, first dark on light and then light on dark. Run it at several -fs sizes, including fractional ones, and compare stroke weight between the two blocks, mark placement and whether 🛠 stays inside its cell. The comparison is a human visual assessment, and the probe records no verdict. The study's pixel measurements come from tools/text-contrast-study.py.

just probe csi-decrqm mode-queries checks DECRQM in both its ANSI and DEC private forms. Each reply is graded separately for the private marker, the echoed mode number and the status, so a terminal that answers only the private form, drops the marker, or truncates a 16-bit mode number to 15 bits is shown as such; unanswered requests are reported as timeouts, never as a status. IRM and DECTCEM are set and reset around their queries and restored afterwards. The deterministic check for Revenant's own replies is the mode queries case of -self-test; the probe is for comparing terminals, and one exact answer says nothing about modes it did not ask about.

A terminal started with fd 0, 1 or 2 closed cannot be tested from inside it: by the time a probe runs, startup is over. tests/pty-closed-stdio.py is an external launcher that closes the descriptors named by a bit mask (1 = stdin, 2 = stdout, 4 = stderr) and execs the terminal, reporting its own failures on fd 9. The same script, run as the terminal's command, checks the child's stdio, controlling terminal and PTY round trip, and reads the terminal's own fds 0–2 from /proc:

python3 tests/pty-closed-stdio.py launch 4 "$work" /usr/bin/xterm -fn no-such-font \
    -e /bin/sh -c '"$0" "$1" child "$2"; echo "$?" >"$2/status"' \
    python3 tests/pty-closed-stdio.py "$work"

The report lands in $work/child, and the child's exit status (3) in $work/status. meson test xvfb-pty-closed-stdio runs Revenant through all eight masks, and also checks a failed exec, a missing display and, through a preload that faults /dev/null, that a failed reservation stops startup before X or the PTY.

For a sequence that differs between terminals, render it directly from the suspected font with hb-shape and hb-view; the diagnostics guide explains how this separates font artwork and shaping from terminal routing and clipping.

The reverse-video probe pauses before each state change so the original cells can be inspected after DECSCNM repaints them. Run it once normally and once in a terminal started with revenant -rv when comparing the startup resource; use tools/probe-reverse-video.sh --no-pause only for a quick replay.

The probes deliberately do not decide pass or fail from screenshots or visual output. Stable machine assertions belong in the automated test suite. The keyboard probe is the exception for protocol parsing: its noninteractive --self-test validates the decoder used to explain captured bytes.

The emoji and font probes share the same protocol-aware measurement engine. On a tty they use cursor-position reports to measure advance mechanically, first request mode 2027 reset and then set, query DECRQM once after each request, and grade against the contract the terminal reports as active. A terminal that does not recognize 2027 remains a correct legacy terminal when it preserves legacy widths; lack of cluster support is reported as a capability gap. Both probes report the startup state and restore it when DECRQM recognizes the mode, because terminals may configure either initial state. Appearance and font artwork are displayed but never graded.

The emoji probe's legacy accept sets use contemporary glibc-style, per-codepoint wcwidth arithmetic. Regional-indicator cases explicitly accept the common table variants. A normal sample that crosses a row boundary is reported as wrapped, rather than producing a misleading negative width; the dedicated right-margin case separately verifies that a two-cell cluster wraps atomically.

Machine acceptance for keyboard delivery lives separately in tests/xvfb-keyboard.sh and tests/xvfb-kitty-keyboard.sh. The latter checks exact press, repeat, release, alternate-key, associated-text, and modifier bytes through a real X server and PTY; it is not a replacement for testing a human keyboard layout or input method with probe-keymodes.py.

The clipboard probe writes, clears, or queries one OSC 52 target. Revenant and xterm refuse both directions by default. Enable Allow Window Ops in the Ctrl+right-click menu, or start the terminal with -xrm 'XTerm*allowWindowOps: true', to compare the permitted behavior; a query that receives no reply within the timeout reports the denial. --invalid sends a payload that is not base64 to show whether the emulator clears the selection, as xterm does, or ignores the request.

The title probe runs an Enter-paced original → A → B → A → original demo, using two nested pushes and matching pops. It requests distinct title and icon labels and prints decoded reports at each stage; --target title or --target icon isolates one label. --query only reads current labels. --delay 2 displays each stage for two seconds after its queries instead of waiting for Enter. Ctrl+C requests any outstanding pops and restores terminal input settings. Restoring the original labels depends on the terminal supporting and permitting the stack operations; requests are not proof of support. Shell prompt hooks may replace the title when the probe exits.

In xterm, compare --query with Allow Window Ops unchecked and checked: the default deny list blocks GetIconTitle and GetWinTitle, while permitting PushTitle and PopTitle. OSC 0/1/2 title setting uses the separate allowTitleOps permission, controlled by Allow Title Ops in the same Ctrl+right-click menu. Disable it before starting the demo to confirm that title changes and restoration are blocked while permitted reports still work. Older Revenant builds that apply Window Ops only to clipboard access do not enable title reports when the toggle is checked. The probe distinguishes an empty reported title from no reply, so it can compare those builds with implementations of title reporting. Keep permissions unchanged during a stack demo, then rerun to compare configurations. Run directly outside tmux/screen to observe the emulator's own behavior.

The dynamic-color probe queries the current foreground, background, and cursor colors, then demonstrates yellow text on a dark blue background with a pink cursor. Press Enter through each stage, or use --delay 2. It resets the three colors individually to their configured defaults. An explicit RGB sample provides a reference that should stay unchanged while default-colored text, including text already printed, changes. Query replies alone do not prove that the window repainted correctly.

Normal exit and Ctrl+C request restoration of the original queried colors. This restores RGB values by installing overrides; it cannot recover whether an original value was itself an override. If a query was refused or unsupported, cleanup requests the configured default for that target instead. Keep color permissions unchanged through cleanup, then rerun with another policy. --foreground, --background, and --cursor send persistent set requests; --reset foreground|background|cursor|all sends persistent reset requests. --query changes nothing and distinguishes a decoded RGB reply from silence. --bel tests BEL termination instead of the default ST.

Use Ctrl+right-click → Allow Color Ops to compare the dynamic-color probe with permission checked and unchecked. It defaults to checked; no -xrm option is needed to change it. Start with python3 tools/probe-dynamic-colors.py --query, then run without arguments for the Enter-paced visual demo. Keep the permission unchanged during a demo so cleanup can restore colors; toggle it between runs. With permission off, queries should time out and sets/resets should leave dynamic colors unchanged. The current menu gates OSC 10–19 and 110–119; palette-query permissions and disallowedColorOps exceptions remain open. Changed RGB replies with unchanged painted colors expose the pending runtime-color rendering work.

Dispatch fixtures

tools/probe-features.py supplies one named fixture per remaining work area. Most of those features are still pending; a sent request or an unanswered query is not evidence of implementation. Start with:

python3 tools/probe-features.py --list
python3 tools/probe-features.py mouse --help
python3 tools/probe-features.py tcap --cap Co
python3 tools/probe-features.py mouse --seconds 60

Run in a newly built Revenant window. Ctrl+right-click opens the menu: switch Allow Tcap Ops off and rerun the tcap --cap Co query; it should become silent under the default restrictions. Switch it on and it should reply again. During mouse, uncheck Allow Mouse Ops to stop mouse/focus bytes and restore ordinary selection, then check it to resume. Both permissions start true. A configured GetTcap exception can keep queries allowed while the Tcap master override is unchecked:

revenant -xrm 'XTerm*allowTcapOps: false' \
  -xrm 'XTerm*disallowedTcapOps: *,~GetTcap'

Allow Font Ops remains disabled: its policy/resource plumbing is prepared, but OSC 50 has no connected callback. The font probe cannot make font changes work. TN may also be unanswered until the configured terminal-name task is implemented; use Co for the existing Tcap permission check.

Subcommand Manual check
answerback Send ENQ and show raw answerback bytes. A default empty answer is silent.
tcap Decode XTGETTCAP replies; repeat --cap NAME to select capabilities.
mouse Observe mouse/focus bytes; --mode 9/1000/1002/1003, --seconds N; q exits.
font Query OSC 50; --font NAME explicitly requests a persistent font change.
underline Compare colored underline styles, indexed/RGB colors, and SGR 59 reset.
cursor Inspect startup cursor before a DECSCUSR request; --styles also cycles application styles.
pointer Move over the grid to compare requested OSC 22 pointer shapes.
identity Show DA1, DA2 and XTVERSION reply bytes.
unknown Send an unsupported APC; Revenant logs it as unknown APC ignored under -debug and never replies.
cwd --cwd /tmp Report an OSC 7 directory for a future consumer/debug check.
prompts Emit synthetic OSC 133 prompts and print a pass checklist for Ctrl+Shift+Up/Down navigation and both boundaries.
pipe Emit three synthetic command outputs; Ctrl+Shift+G with pipeCommandOutput set should capture only COMMAND-3-BEGIN through COMMAND-3-END.
notify After three seconds to change focus, send OSC 9 and OSC 777 notifications; Revenant sets the WM_HINTS urgency flag while unfocused.
progress Cycle OSC 9;4 normal, error, indeterminate, paused and cleared states.
glyphs Inspect joined boxes/blocks, braille and Powerline at different fonts/sizes.
copy Select sample text and check a future copy-highlight flash and paste contents.
search Seed scrollback with repeated, Unicode, missing and wrapped search cases.
graphics Place red/green over blue/yellow using an inline Kitty RGBA image; inspect resize/scroll.

Visual stages wait for Enter; --delay SECONDS advances them automatically. --timeout SECONDS bounds each reply wait. These options follow the subcommand. just probe-features SUBCOMMAND ... invokes the same runner. Shell fixtures display commands and shell-looking text as data; they never execute those commands. The graphics fixture covers static inline placement only; later graphics tasks must add their own media/animation cases.

Ctrl+C restores termios and requests cleanup for temporary state. Mouse modes are queried before the probe and known settings restored; modes whose state cannot be queried are reset. Underline styling is reset; pointer shape returns to default; progress is cleared; only the probe's image is deleted. cursor --styles restores the configured default, not an earlier application cursor override. An explicit font --font request is persistent: restore it through the font menu. Shell fixtures restore the probe process's cwd URI and close synthetic command markers; they cannot reconstruct a shell's prior semantic state. Run in a disposable test window for comparisons requiring pristine state.

See the dispatch guide for the existing policy APIs, feature boundaries and test expectations.

RevenantSeptember 23, 2026REVENANT-PROBES(7)