Popup-menu feasibility study¶
Every entry from xterm patch-411's mainMenu (Ctrl-Button1), vtMenu
(Ctrl-Button2) and fontMenu (Ctrl-Button3): what xterm does behind it,
how much of that lives in libghostty-vt, and how approachable it is for
Revenant. Reviewed against the then-selected libghostty headers on 2026-08-25.
Status key: DONE implemented; PARTIAL usable subset with documented gaps; READY can be wired now with no new subsystem; ROADMAP waits on a planned roadmap slice; BLOCKED needs a libghostty change; SKIP recommend not planning (record in the xterm differences ledger).
The tables are maintained per entry; grouped entries do not imply a current aggregate count.
The big pattern¶
About a third of the VT and main menus are DEC private modes. libghostty
exposes all of them through ghostty_terminal_set(GHOSTTY_TERMINAL_OPT_MODE)
and ghostty_terminal_get(GHOSTTY_TERMINAL_DATA_MODE), and the key encoder
re-reads them via ghostty_key_encoder_setopt_from_terminal. The generic
XtpTerminalSetMode/XtpTerminalGetMode pair in src/terminal.h now backs
autowrap, reversewrap, autolinefeed, appcursor, appkeypad, backarrow key,
num-lock and alt-esc, with checkmark state read back from DATA_MODE.
Meta-esc still needs a distinct X Meta/Alt policy and allow132 needs grid-size
polling after terminal input. Application cursor blinking, its presentation
timer, the four-value cursorBlink resource policy, and cursorBlinkXOR are
implemented; the menu toggle still needs defined transitions between
configured and forced policy.
mainMenu¶
| Item | What xterm does | libghostty involvement | Approachability | Status |
|---|---|---|---|---|
| toolbar | Xaw toolbar (OPT_TOOLBAR, off in most builds) |
None | Not worth reproducing | SKIP |
| fullscreen | Toggle _NET_WM_STATE_FULLSCREEN |
None | One EWMH client message | READY |
| securekbd | XGrabKeyboard+XGrabServer, reverse-video flash |
None | Pure Xlib, low value | READY |
| allowsends | Accept send_event X events |
None | Check xany.send_event in handlers |
READY |
| redraw | Full repaint | — | — | DONE |
| logging | Tee PTY output to logFile, honour logInhibit |
None (before vt_write) |
One coherent PTY tee with -/+l and -lf; tracked as TODO P1 |
ROADMAP |
| print-immediate | Dump screen text (+SGR) to printerCommand |
Screen read: ghostty_row_get/cell_get or select_all+selection_format_alloc |
Needs a "walk screen as styled text" helper | ROADMAP |
| print-on-error | Same, from the X I/O error handler | Same | Same walker | ROADMAP |
| Buffered screen dump | Same | Same walker | ROADMAP | |
| print-redir | Printer controller mode (CSI 5 i/4 i) |
Ghostty has no media copy; OPT_UNKNOWN_SEQUENCE is APC-only |
Needs MC callback or CSI passthrough | BLOCKED |
| dump-html | Screen with colours as HTML | Screen read + DATA_COLOR_PALETTE |
Reuse render walker | ROADMAP |
| dump-svg | Same as SVG | Same | Same | ROADMAP |
| 8-bit control | S8C1T single-byte C1 output | No encoder/response option | Ask for DRIFT entry instead | BLOCKED |
| backarrow key | DECBKM | GHOSTTY_MODE_BACKARROW_KEY_MODE (?67) |
Mode flip | DONE |
| num-lock | Ignore keypad app mode with NumLock | GHOSTTY_MODE_NUMLOCK_KEYPAD (?1035) |
Mode flip | DONE |
| alt-esc | altSendsEscape |
GHOSTTY_MODE_ALT_SENDS_ESC (?1039) |
Mode flip | DONE |
| meta-esc | metaSendsEscape |
GHOSTTY_MODE_ALT_ESC_PREFIX (?1036); Ghostty has one "alt" |
Mode flip + decide X Meta/Alt mapping (DRIFT note) | READY |
| delete-is-del | Delete sends 0x7F |
None | Special-case XTP_KEY_DELETE before encoder |
READY |
| oldFunctionKeys | Legacy F-key table | Encoder has one table | Post-mapping table; low value | SKIP |
| tcapFunctionKeys | Keys from terminfo | None | Needs terminfo layer | SKIP |
| hpFunctionKeys | HP table | None | Static table | SKIP |
| scoFunctionKeys | SCO table | None | Static table | SKIP |
| sunFunctionKeys | Sun table | None | Static table | SKIP |
| sunKeyboard | VT220 keypad mapping | None | Same family | SKIP |
| suspend/continue/interrupt/hangup/terminate/kill | kill(-pgrp, SIG*) |
None | Six one-liners in pty.c |
READY |
| quit | Exit | — | — | DONE |
vtMenu¶
| Item | What xterm does | libghostty involvement | Approachability | Status |
|---|---|---|---|---|
| scrollbar | Show/hide Xaw scrollbar | DATA_SCROLLBAR, scroll_viewport (already wrapped) |
Xaw widget, layout, thumb and navigation | DONE |
| jumpscroll | Batch lines per repaint | None | Render-scheduling knob; subtle drift | READY |
| reversevideo | Swap fg/bg | — | — | DONE |
| autowrap | DECAWM | GHOSTTY_MODE_WRAPAROUND (?7) |
Mode flip | DONE |
| reversewrap | Reverse wrap | GHOSTTY_MODE_REVERSE_WRAP (?45) |
Mode flip | DONE |
| autolinefeed | LNM | GHOSTTY_MODE_LINEFEED (20) |
Mode flip | DONE |
| appcursor | DECCKM | GHOSTTY_MODE_DECCKM (?1) |
Mode flip | DONE |
| appkeypad | DECKPAM | GHOSTTY_MODE_KEYPAD_KEYS (?66) |
Mode flip | DONE |
| scrollkey | Keypress snaps to bottom | scroll_viewport |
Resource, CLI, and menu policy | DONE |
| scrollttyoutput | Output snaps to bottom | Same | Resource, CLI, and menu policy | DONE |
| allow132 | Allow DECCOLM resize | GHOSTTY_MODE_ENABLE_MODE_3 (?40); no mode-change callback, poll DATA_COLS after feed |
Mode flip + small poll | READY |
| keepSelection | Keep highlight after loss | None | Roadmap #4 | ROADMAP |
| keepClipboard | Don't clear CLIPBOARD | None | Roadmap #4 | ROADMAP |
| selectToClipboard | Selection owns CLIPBOARD | Xt named-selection ownership | Resource, set-select, and menu policy |
DONE |
| visualbell | Flash | Bell callback exists | Invert + timer | READY |
| bellIsUrgent | WM urgency hint | Bell callback | XUrgencyHint |
READY |
| poponbell | Raise window | Bell callback | XRaiseWindow |
READY |
| cursorblink | Blink cursor | OPT_DEFAULT_CURSOR_BLINK, DATA_CURSOR_STYLE |
Toggle false/true as xterm does; RIS and DECSTR restore the startup four-value policy |
READY |
| titeInhibit | Ignore alt-screen switches | No way to disable a mode handler | Needs "permanently reset mode" option | BLOCKED |
| activeicon | Terminal in icon window | None | Modern WMs ignore | SKIP |
| copy_area | Toggle xterm's XCopyArea scroll optimization |
None | Revenant does not use xterm's scroll-copy path; see the drift ledger | SKIP |
| softreset | DECSTR | Pinned Ghostty does not handle CSI ! p |
Needs DECSTR support or a soft-reset API | BLOCKED |
| hardreset | RIS, keep saved lines | ghostty_terminal_reset() clears scrollback |
Needs a reset-preserving-history API or a complete local RIS implementation | BLOCKED |
| clearsavedlines | Reset + drop scrollback | ghostty_terminal_reset() performs RIS and clears scrollback |
Add the action and Meta+Button-2 binding; test reset state and DATA_SCROLLBACK_ROWS against xterm |
READY |
| tekshow/tekmode/vthide | Tek 4014 window | None, ever | See Tek section | SKIP |
| altscreen | Show alternate screen | DATA_ACTIVE_SCREEN; OPT_MODE ?1047 should switch — verify the set path runs the full handler |
Probably one call | ROADMAP |
| sixelScrolling | DECSDM | No sixel in Ghostty | Blocked upstream | BLOCKED |
| privateColorRegisters | Sixel colour registers | Sixel-only | Blocked | BLOCKED |
fontMenu¶
| Item | What xterm does | libghostty involvement | Approachability | Status |
|---|---|---|---|---|
| fontdefault, font1–font7 | Font slot | — | — | DONE |
| fontescape | Font from OSC 50 | OSC 50 ignored, not forwarded | Needs unknown-OSC passthrough | BLOCKED |
| fontsel | Font from PRIMARY | None | Small after Roadmap #4 | ROADMAP |
| allow-bold-fonts | Real bold faces | None; bold flag in XtpRenderCell |
Renderer toggle | READY |
| font-linedrawing | Internal box glyphs | None | Procedural Glyphs label; toggles forceBoxChars for every supported procedural range |
DONE |
| font-packed | Min glyph width for bitmap fonts | None | Cell-metric choice | ROADMAP |
| font-doublesize | DECDHL/DECDWL | Not in Ghostty | Blocked upstream | BLOCKED |
| font-loadable | DECDLD soft fonts | Not in Ghostty | Blocked, unused | BLOCKED |
| render-font | Xft vs bitmap | — | — | DONE |
| utf8-mode | UTF-8 vs Latin-1 | Ghostty is UTF-8 only | iconv possible; recommend permanently on (DRIFT) | BLOCKED |
| utf8-fonts | utf8Fonts slot set |
None | Only meaningful with utf8-mode | ROADMAP |
| utf8-title | UTF-8 _NET_WM_NAME |
Title callback and pop path | utf8Title, set-utf8-title(); EWMH labels written or deleted; entry state follows the locale as in xterm |
DONE |
| allow-color-ops | Gate dynamic-color OSC 10–19 and 110–119 | Selector observer enforces the live backend permission | Sets, queries, resets, deny-list exceptions and palette queries gated; remaining differences in drift ledger | PARTIAL |
| allow-font-ops | Gate OSC 50 | No public OSC 50 callback | Policy/resources and menu identity prepared; entry stays insensitive until fontescape | BLOCKED |
| allow-mouse-ops | Gate mouse and focus reports | Encoder master gate | Live toggle; requested modes preserved; named deny-list exceptions pending | PARTIAL |
| allow-tcap-ops | Gate XTGETTCAP | Complete generated replies gated at PTY effect | Live toggle and GetTcap deny-list exceptions; TN integration and XTSETTCAP pending | PARTIAL |
| allow-title-ops | Gate displayed title changes and applying saved labels | Title callback and pop path | Live toggle; OSC 1/icon changes still lack a backend callback; reports use Window Ops | PARTIAL |
| allow-window-ops | Toggle window-operation permissions | OSC 52 callbacks and title observer | Live selection and title stack/report policy; full Window Ops still pending | PARTIAL |
Tektronix 4014¶
In xterm the Tek items drive a second top-level widget (TekWidget,
Tekproc.c ≈ 2,100 lines plus TekPrsTbl.c state tables). Nothing about it
is a mode of the VT100 engine:
- Separate parser. In Tek mode the PTY byte stream bypasses the VT parser.
Switching is by
CSI ? 38 h(DECTEK),ESC ^X,ESC ETX, or the menu. libghostty would treat ?38 as unknown and keep consuming bytes as text. - Vector renderer: four text sizes, vector/point/incremental plot modes, 1024×780 virtual coordinates, persistent display list for redraw.
- GIN mode: crosshair cursor; a keypress sends key plus encoded coordinates.
- Copy: writes the display list as raw Tek escapes to
COPYfiles.
Revenant would have to intercept bytes before XtpTerminalFeed, own a Tek
state machine and display list, and add a widget with its own tekMenu.
libghostty contributes nothing. xterm's own --disable-tek4014 builds leave
the three items insensitive, which is what Revenant does today. Recommendation:
record "Tek 4014 not planned" in the xterm differences ledger.
Suggested order¶
- Slice A: the generic mode helper and eight mode-backed items are complete. Continue with signals ×6; allowsends; visualbell/bellIsUrgent/ poponbell; delete-is-del; and logging. (utf8-title is done.)
- Slice B, ride the roadmap: scrollbar family with #2; selection policies and fontsel with #4; named Mouse Ops exceptions; allow-bold-fonts and font-packed with #5.
- Slice C, screen-dump family: one styled-text walker unlocks print, print-immediate, print-on-error, dump-html, dump-svg.
- Upstream asks, by value: unknown OSC/CSI passthrough (fontescape, print-redir, full XTWINOPS); permanently-reset mode (titeInhibit); effective mouse tracking and OSC 50 hooks. Not worth asking: DECDHL, sixel, DECDLD, S8C1T.
- Drift-ledger entries: Tek 4014, toolbar, activeicon, copy_area, legacy keyboard tables/sunKeyboard, UTF-8-only, 8-bit controls, soft fonts.