Skip to content

Gate kitty image output on terminal support - #95

Merged
llimllib merged 4 commits into
mainfrom
better-kitty-images-91-92
Aug 30, 2026
Merged

llimllib merged 4 commits into
mainfrom
better-kitty-images-91-92

Conversation

@llimllib

Copy link
Copy Markdown
Owner

Three related fixes to when and how kitty graphics escapes get emitted, from reading how pi handles terminal capabilities.

Set q=2 on kitty transmissions (#91)

Per the graphics protocol spec, q=1 suppresses OK responses and q=2 also suppresses failure responses.

Worth being precise about the scope here, because the issue as I originally filed it overstated the problem: we never set an image id (i=/I=), and kitty ties acknowledgements to those keys, so successful transmissions were already silent. This is hardening against the failure response, which would otherwise be written to a tty nobody reads and end up in the shell's input buffer. Cheap, so unconditional.

Disable images under tmux and screen (#92)

Multiplexers interpret the stream themselves and mangle graphics sequences rather than forwarding them, so --images kitty inside tmux drew garbage rather than an image. Now falls back to alt text, with the reason logged under --verbose.

Don't emit image escapes when stdout isn't a terminal (#93)

image_protocol was only gated behind use_color, so mdriver --color=always --images kitty README.md > out.md wrote megabytes of base64 into the file. Colors are still meaningful when piped; graphics escapes never are.

Note: this adds an environment variable

MDRIVER_FORCE_IMAGES=1 bypasses both new checks. This wasn't in any of the issues, so flagging it explicitly for review.

It's load-bearing for the tests: four existing tests in tests/logging.rs run --images kitty in a subprocess to exercise image-failure diagnostics, and once images require a tty there's no way to reach that code without a pty. It also gives users an out when the heuristic is wrong — tmux with graphics passthrough configured, say. The alternative is a pty-based test harness and no override, which I'm happy to switch to.

The per-condition logic is a pure function (mdriver::image_disable_reason) taking its inputs rather than reading the environment, so every case is unit tested directly; tests/images.rs covers the end-to-end piped behavior.

Verification

Beyond the test suite, checked against the real binary:

  • forced: emits \x1b_Gf=100,a=T,q=2,c=23,m=0;...
  • piped, default: zero escapes
  • TMUX set: images: disabled because tmux does not pass kitty graphics escapes through to the terminal

Commits are unsigned

Signing failed from the sandbox (Couldn't load public key ~/.ssh/id_ed25519.pub: No such file or directory), as CLAUDE.md anticipates. Needs re-signing before merge:

git rebase --exec 'git commit --amend -S --no-edit' main

Session transcript

Session transcript

Per the graphics protocol spec, q=1 suppresses OK responses and q=2 also
suppresses failure responses. mdriver sets no image id, so successful
transmissions already draw no acknowledgement, but a failed one replies on
the tty where we would never read it and the shell would echo it after we
exit. Cheap to make unconditional.

Refs #91
Multiplexers interpret the stream themselves and mangle kitty graphics
escapes rather than forwarding them, so --images kitty inside tmux drew
garbage on the screen instead of an image. Fall back to alt text and say
why under --verbose.

Fixes #92
image_protocol was only gated behind use_color, so --color=always
--images kitty wrote megabytes of base64 kitty escapes into whatever file
or pipe stdout pointed at.

Adds MDRIVER_FORCE_IMAGES=1 as an escape hatch, since the check is
necessarily a heuristic and there is otherwise no way to exercise image
diagnostics from a subprocess.

Fixes #93
@llimllib
llimllib merged commit 427c5da into main Aug 30, 2026
3 checks passed
@llimllib
llimllib deleted the better-kitty-images-91-92 branch August 30, 2026 10:46
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.

1 participant