Skip to content

feat: support OSC envelope for RGP (fixes #67, unblocks RGP on Windows)#68

Open
ahostbr wants to merge 4 commits into
orhun:mainfrom
ahostbr:feat/osc-envelope
Open

feat: support OSC envelope for RGP (fixes #67, unblocks RGP on Windows)#68
ahostbr wants to merge 4 commits into
orhun:mainfrom
ahostbr:feat/osc-envelope

Conversation

@ahostbr
Copy link
Copy Markdown
Contributor

@ahostbr ahostbr commented May 18, 2026

Summary

Adds OSC envelope (ESC ] 8901 ; ratty ; g ; … ESC \) as an alternative to the existing APC envelope for the Ratty Graphics Protocol, and fixes a related scroll-tracking false-positive. Together these unblock RGP-based widgets — including the bundled big_rat, draw, and document examples — on Windows. Fixes #67.

Why

Windows ConPTY strips APC sequences (ESC _ … ESC \) before they reach the host process. Verified with a minimal portable-pty-backed probe: a child running printf '\x1b_PROBE;hello=world\x1b\' arrives at the PTY master with only the bash init CSIs (\x1b[?9001h\x1b[?1004h) — APC body and ST terminator gone. Equivalent OSC payloads at arbitrary numbers (1337, 9001, 99999, 52, 0) survive intact end-to-end.

This is structural in conhost, not a configuration issue:

  • PSEUDOCONSOLE_PASSTHROUGH_MODE isn't a real public flag. The value 0x8 in current winconpty.h is PSEUDOCONSOLE_GLYPH_WIDTH_GRAPHEMES (not passthrough). The #[allow(dead_code)] constant in portable-pty traces back to a 2019 proposal that never shipped.
  • Side-loading the latest microsoft/terminal v1.24 conpty.dll + OpenConsole.exe does not change the APC behavior (empirically tested). The 1.22 "passthrough" PR (#17510) was an internal conhost refactor that enables conhost to render unrecognised sequences (Sixel) in its own renderer — not a forwarding feature for third-party hosts.
  • OSC, by contrast, is preserved by conhost for any number it doesn't have a hardcoded handler for. Issue RGP examples don't render inline 3D on Windows — ConPTY strips APC sequences #67 has the full investigation.

Changes

src/rgp.rs — adds RGP_OSC_START = b"\x1b]8901;ratty;g;". consume_sequence is refactored to recognise either envelope and strip the right terminator (APC ends on ST/C1-ST; OSC also accepts BEL). The body grammar (<verb>;<key=value>;…) is unchanged, so APC and OSC paths share the same parser.

src/inline.rsconsume_pty_output's scan loop gains an EnvelopeKind discriminator; it picks the next APC (\x1b_) or the specific RGP OSC prefix and dispatches each through the right path. Unrelated OSC traffic (window titles, hyperlinks, prompt marks) flows straight through to vt100 as before.

src/inline.rs + src/systems.rs — fixes a pre-existing scroll-tracking false-positive that the OSC migration exposes. When a Place arrives in the same PTY chunk that clears and repaints the terminal, infer_upward_scroll can return a large shift (25 rows on a 32-row screen in this repro) and the old apply_scroll evicts the very anchor that chunk just set. Tracked via a new placed_this_chunk: HashSet<u32> on TerminalInlineObjects; pump_pty_output drains it after consume and scrolls only pre_anchor_ids \ placed_this_chunk. Public apply_scroll(rows) API is unchanged; new apply_scroll_to_existing(rows, exempt) is the underlying helper.

widget/src/lib.rsratatui-ratty's seven emit sites (register path, register payload init/chunk/empty, place, update, delete) switch from APC to OSC 8901. ST terminator emitted; parser also accepts BEL.

OSC 8901 — number choice

Picked as a 4-digit private number with no known assignment in any surveyed terminal codebase, registry, or shell-integration spec. Numbers explicitly avoided:

Number Used by
0,1,2,4,7,8,9,10,11,12,52,104,110-119 conhost hardcoded
99 kitty notifications
133 FinalTerm / Warp prompt marks
633 VS Code shell integration
777 urxvt
1337 iTerm2 image protocol
9001 Microsoft Terminal's WTAction in src/terminal/parser/OutputStateMachineEngine.hpp — dispatching is numeric-first, so the ;ratty; discriminator does not protect against a numeric collision

The ;ratty; discriminator after the number namespaces the payload for the protocol family, leaving room for g; (graphics) plus future verb-spaces if you want them.

Verification

Windows 11 Pro 26200.6457 / RTX 5090 / Vulkan backend. cargo fmt --check, cargo check, cargo clippy --all-targets --all-features -- -D warnings all green.

Wire-level probe (vendored portable-pty 0.8.1 shelling out to bash.exe):

=== APC (before) ===
Total bytes read: 16
0000: 1b 5b 3f 39 30 30 31 68 1b 5b 3f 31 30 30 34 68    .[?9001h.[?1004h
Contains \x1b_ (APC start)?  false
Contains 'PROBE;' marker?    false

=== OSC 8901 (after) ===
Total bytes read: 167
0000: 1b 5b 3f 39 30 30 31 68 1b 5b 3f 31 30 30 34 68 1b 5b 3f 32 35 6c …
0040: …  .]9001;r…[trimmed]
0080: …;animate=1;scale=1.0;depth=0.0;color=ffffff;brightness=1.0;…
Contains \x1b]8901;ratty;g;…?  true

End-to-end inside ratty.exeratty -e big_rat.exe:

INFO ratty::inline: registered RGP object 7 from assets/objects/SpinyMouse.glb

The full RGP register → place → spawn → render chain fires, and the SpinyMouse GLB renders inside Ratty's viewport for the first time on Windows. Screenshot below.

image

Compatibility

Existing APC senders keep working. The parser tries both envelopes per sequence, and ratatui-ratty's on-the-wire behavior is the only change for downstream widget consumers — they get a one-time API recompile but nothing breaks in the body grammar.

Open follow-ups (separate work)

  • ChatGPT deep research flagged that mid-line OSC emission can interact with a conhost ordering bug (microsoft/terminal#17313) on pre-1.23 conhost. RGP place is already self-describing (row, col, w, h carry absolute coordinates), so reordering does not silently mis-anchor the object, but a freshness key (seq= or frame=) would defeat stale-arrival cases. Worth a follow-up if it ever bites in practice.
  • Multiplexers (tmux, screen, zellij) intercept selected OSC numbers and may need a \ePtmux;…\e\ passthrough wrapper if a user runs RGP-emitting apps through them. Out of scope here.

ahostbr added 2 commits May 17, 2026 20:59
The Ratty Graphics Protocol previously rode an APC envelope
(`\x1b_ratty;g;…\x1b\`). Windows ConPTY strips APC sequences before
they reach the host process in every shipping conhost — verified by a
portable-pty-backed probe that sends `printf '\x1b_…\x1b\'` and reads
the PTY master. APC body never arrives; OSC payloads at arbitrary
numbers (1337, 9001, 99999, 52, 0) survive intact.

Changes to make RGP work over OSC end-to-end:

* `rgp.rs`: add `RGP_OSC_START = b"\x1b]8901;ratty;g;"`. Refactor
  `consume_sequence` to strip either envelope (APC ends on ST/C1-ST;
  OSC also accepts BEL) and dispatch the common body grammar.
* `inline.rs`: extend the PTY scan loop with an `EnvelopeKind`
  discriminator that picks the next APC *or* the specific RGP OSC
  prefix. Unrelated OSC traffic (window titles, hyperlinks, prompt
  marks) still flows straight through to vt100.
* `inline.rs` + `systems.rs`: fix a pre-existing scroll-tracking
  false-positive that the OSC migration exposes. When an `\x1b]…\x1b\`
  Place arrives mid-frame, `infer_upward_scroll` can return a large
  shift (e.g. 25 rows on a 32-row screen) because the chunk that
  carried the Place also cleared and repainted the terminal. The old
  `apply_scroll` then evicts the very anchor that chunk just set.
  Track which object ids had a Place inside the chunk
  (`placed_this_chunk`), drain it after consume, and scroll only the
  intersection of pre-chunk anchors minus the freshly-placed set. The
  public `apply_scroll` API is unchanged; new `apply_scroll_to_existing`
  takes the exemption set.

OSC 8901 choice: 4-digit private number with no known assignment.
Numbers explicitly avoided: 0,1,2,4,7,8,9,10,11,12,52,104,110-119
(recognised by conhost), 99 (kitty notifications), 133 (FinalTerm /
Warp), 633 (VS Code shell integration), 777 (urxvt), 1337 (iTerm2),
9001 (Microsoft Terminal's `WTAction` in
`src/terminal/parser/OutputStateMachineEngine.hpp` — dispatching is
numeric-first, so the `;ratty;` discriminator does not protect against
a numeric collision).

Existing APC senders keep working: the parser tries both envelopes per
sequence. The widget switches to OSC in a follow-up commit.

Verified on Windows 11 26200.6457 + RTX 5090: `big_rat.exe` running
inside `ratty.exe` registers, places, and renders the SpinyMouse GLB
end-to-end.
Updates `ratatui-ratty`'s seven emit sites (register path, register
payload init/chunk/empty, place, update, delete) from APC
(`\x1b_ratty;g;…\x1b\`) to OSC 8901 (`\x1b]8901;ratty;g;…\x1b\`).

The body grammar is unchanged: `<verb>;<key=value>;…`. Existing
parsers that handle the old APC form keep working unchanged because
the matching Ratty parser commit accepts both envelopes during the
migration.

Required on Windows because ConPTY strips APC mid-line. Verified by
the matching Ratty parser commit on Win11 26200.6457 — `big_rat` and
`document` examples register, place, and render their inline 3D
objects end-to-end inside `ratty.exe`.

ST (`\x1b\`) is emitted as the OSC terminator; the parser also
accepts BEL for compatibility with terminals that prefer the older
form.
@orhun
Copy link
Copy Markdown
Owner

orhun commented May 18, 2026

Does it require any updates in the protocol document?

Adds a Transport subsection covering the OSC compatibility envelope:
when to use it, the number selection rationale, and the collision
avoidance table. Both envelopes carry identical body grammar.

Agent-Name: Sentinel
Agent-ID: bf13c4e3-4681-4470-9d0e-663aa2f1d3c9
Agent-Tier: orchestrator
@ahostbr
Copy link
Copy Markdown
Contributor Author

ahostbr commented May 18, 2026

Good call — pushed a29fabf which adds a Transport subsection to protocols/graphics.md covering:

  • OSC 8901 as a compatibility envelope alongside the existing APC envelope
  • When to use it (Windows ConPTY strips APC silently)
  • The OSC number selection rationale with a collision-avoidance table (conhost, Kitty, iTerm2, VS Code, WTAction 9001, etc.)
  • That both envelopes carry identical body grammar — only framing differs
  • Support reply always uses APC regardless of query envelope (safe direction: Ratty → app)

Agent-Name: Sentinel
Agent-ID: bf13c4e3-4681-4470-9d0e-663aa2f1d3c9
Agent-Tier: orchestrator
@orhun
Copy link
Copy Markdown
Owner

orhun commented May 22, 2026

Thanks for clarifying. I probably need to test this on Windows before I go ahead and review it.
I currently don't have a Windows setup, do you know if it is possible to test it out on Linux somehow?

I also appreciate more eyes on this PR 🙏🏼 If anyone uses Windows and tries Ratty, feel free to chime in on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RGP examples don't render inline 3D on Windows — ConPTY strips APC sequences

2 participants