3 min read
My multiplexer lied about what it could do
kitty icat drew nothing when tuios ran in a browser client. The graphics passthrough said yes to file transmission without asking the host.
GGGaurav Gosain
Images displayed fine in my terminal. Fine in the multiplexer running inside
it. Fine in the browser client. Stack all three (the browser client hosting a
shell that runs the multiplexer) and icat produced nothing at all. No error,
no placeholder, no partial image. Just the prompt, sitting where the image
should have been.
What icat is actually doing
The kitty graphics protocol has a capability query. Before sending anything
real, icat asks the terminal whether it supports a given transmission medium,
and it asks about three of them:
_Ga=q,f=24,s=1,v=1,S=3,i=1 can you take pixels inline
_Ga=q,f=24,t=t,s=1,v=1,S=47,i=2 can you read a temp file
_Ga=q,f=24,t=s,s=1,v=1,S=18,i=3 can you read shared memoryIt uses the first medium the terminal answers OK to, which is sensible. A
file is much cheaper than inline base64, so if the terminal can read files,
everyone wins.
The lie
The multiplexer's passthrough answered OK to all three. Unconditionally.
response := vt.BuildKittyResponse(true, cmd.ImageID, "")
ptyInput(response)It never asked its own host anything. It just said yes.
So icat picked a file medium, wrote the image to /tmp, and sent a path. The
multiplexer forwarded that path out to the browser. The browser cannot read
/tmp on the server, so it dropped the transmission and drew nothing.
Captured at the browser, the failure looks like this:
a=t,t=f,i=1,f=100,s=64,v=64,q=2,c=8,r=4payload=132 "L3RtcC9jbGF1ZGUtMTAwMC8t"a=p,i=1,p=1,c=8,r=4,q=2 place image 1images:0 placements:0 canvases:0
Why each layer alone was fine
In a real terminal, the host genuinely can read the path, so the lie is true by accident.
In the browser client alone, nothing lies. The client answers ENOTSUPPORTED
to the file media, icat falls back to streaming the bytes inline, and
everything works.
To hit the bug you need a dishonest middle layer in front of a host that cannot do the thing, and that only happens in the three-layer stack. Each component was correct in the environment its author tested it in. That is why staring at any one of them got me nowhere.
Try the combinations yourself:
a=q,t=d inline pixels OKa=q,t=t temp file OKa=q,t=s shared memory OKguest sends a file pathhost receives the path, as sent
The fix
Probe the host for file transmission alongside the direct probe, record the answer, and pass it on:
if isFileMedium(cmd.Medium) && !kp.hostReadsFiles() {
ok = false
errMsg = "ENOTSUPPORTED:host terminal cannot read files from this machine"
}A guest that never asks is covered too. If a file transmission arrives and the host cannot read files, the multiplexer re-encodes it as direct data rather than forwarding a path that will be silently discarded. A file-capable host still gets the plain path with no extra copy, so the fast case stays fast.
There was a second bug hiding in the same function. Quiet mode has two levels,
q=1 to suppress success responses and q=2 to suppress everything, and the
code treated both as "say nothing". A guest that asked for q=1 and hit an
error got silence, waited out its timeout, and never tried the next medium. An
error has to reach a guest that only suppressed successes.
Ask, or say no
A proxy answering capability questions on behalf of something else has two honest options: ask the far end, or say no and let the guest fall back to the medium that always works. Saying yes because yes usually works gets you a failure that only appears when the layers are combined, only for people running unusual stacks, with nothing logged anywhere and every component pointing at its own passing tests.
What annoys me is that the multiplexer already had the machinery to handle this. A function that re-encodes file transmissions as inline data was sitting in the codebase, gated behind a build-specific flag instead of behind "can the host actually read files". I did not so much write the fix as move it.
It is the same protocol as the query bugs in an earlier post, with a new failure: there the answer went missing, and here it was made up. What tuios now tells programs about graphics, in a native terminal and in the browser, is on the architecture page and the web terminal page.