From b4e12512a0db4ce8823e2265faf020bb222875a7 Mon Sep 17 00:00:00 2001 From: cashmere Date: Sun, 26 Apr 2026 21:14:50 +0200 Subject: feat(tmux): unlock Sixel backend inside tmux >= 3.4 Sixel detection used to hard-disable the moment `$TMUX' was set. That guard predates tmux 3.4 (released 2024-02-13), which ships native Sixel rendering, so we were rejecting a working configuration. Drop the hard guard, gate on a parsed `tmux -V' >= 3.4, and let users opt out via a new custom var. - `kitty-gfx-tmux-allow-sixel' (default t). Set to nil if your tmux build lacks `--enable-sixel' or if you want to avoid the upstream scroll artifact (tmux's cell buffer is not pixel-aware, so images may persist after scrolling). - `kitty-gfx--tmux-version' parses `tmux -V' once per session and caches the result in `kitty-gfx--tmux-version-cache' (with an `unset' sentinel so we can distinguish "not probed" from "no tmux"). - `kitty-gfx--tmux-sixel-supported-p' centralises the (in-tmux + opt-in + version >= 3.4) check. - `kitty-gfx--sixel-detect' now uses the helper instead of `(not in-tmux)'. Debug log gained `tmux-ver=' and `tmux-ok='. - AGENTS.md / README.org / CHANGELOG.org: limitation reworded from "no tmux support" to a partial entry that explains exactly what works and what the remaining caveats are (Kitty APC passthrough is still unimplemented; scroll artifacts are upstream). - justfile: new `test-sixel-tmux' recipe wraps `test-sixel-image' in `tmux new-session' for one-command interactive verification, and the sibling `test-sixel-image' recipe was switched from a fragile shell conditional to an elisp `when' so passing an encoder argument no longer breaks the parse. Refs #2. --- AGENTS.md | 6 +++++- CHANGELOG.org | 6 ++++-- README.org | 2 +- justfile | 12 ++++++++++- kitty-graphics.el | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 77 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ccd185f..b18f76c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -244,7 +244,11 @@ Key design differences from images: ## Known Issues & Planned Work - **Fixed**: Overlays remain visible when org headings are collapsed (#1) -- **Limitation**: Does not work inside tmux (neither Kitty passthrough nor Sixel) (#2) +- **Partial**: tmux support (#2) -- Sixel works inside tmux >= 3.4 (native + rendering, foot/Konsole/etc as outer terminal); guarded by + `kitty-gfx-tmux-allow-sixel'. Known caveat: images may persist after + scrolling because tmux's cell buffer is not pixel-aware (upstream limit). + Kitty graphics passthrough is still unimplemented -- tracked separately. - **Limitation**: Each mode needs explicit `:around` advice integration - **Limitation**: GIF files are not supported (multi-frame conversion issues, no animation support in terminal) diff --git a/CHANGELOG.org b/CHANGELOG.org index 837968e..82e8c5b 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -51,9 +51,11 @@ I will keep on improving this feature. I think it is actually great eyecandy, an **** tmux support -tmux support is next. It already supports sixel, so the sixel backend will work there out of the box. +The Sixel side is now unlocked: starting in =0.5.0=, =kitty-graphics= no longer hard-disables the Sixel backend when =$TMUX= is set. tmux 3.4 (released 2024-02-13) ships native Sixel rendering, so running Emacs inside =tmux new-session= under foot/Konsole/mintty/mlterm/WezTerm just works. The detector probes =tmux -V= once per session and only engages on tmux >= 3.4. A new option =kitty-gfx-tmux-allow-sixel= (default =t=) lets you opt out if your tmux build lacks =--enable-sixel=. -There is also an in-progress branch adding [[https://github.com/tmux/tmux/tree/ta/kitty-img][kitty image protocol support to tmux]]. If you already use Kitty directly, though, I would stick with it. The kitty protocol gives you stateful image management, truecolor rendering, and a smoother overall experience. +Caveat: tmux's cell buffer is not pixel-aware, so images may stick to their old position after scrolling until the affected cells are overwritten. That is an upstream tmux limitation, not something this package can fix. + +The Kitty graphics protocol still does not work inside tmux because we do not yet wrap APC sequences with the =\ePtmux;...\e\\= passthrough envelope. There is also an in-progress branch adding [[https://github.com/tmux/tmux/tree/ta/kitty-img][native Kitty image protocol support to tmux]]. Until either lands, if you already use Kitty directly I would stick with it -- the Kitty protocol gives you stateful image management, truecolor rendering, and a smoother overall experience. **** Package renaming? diff --git a/README.org b/README.org index eb0ab62..6b95ac4 100644 --- a/README.org +++ b/README.org @@ -143,7 +143,7 @@ TIFF, BMP) are converted to PNG via ImageMagick before transmission. * Known Limitations -- No tmux support (neither Kitty passthrough nor Sixel) +- Partial tmux support: Sixel works inside tmux >= 3.4 (native sixel rendering, foot/Konsole/mintty/mlterm/WezTerm as outer terminal); set ~kitty-gfx-tmux-allow-sixel~ to ~nil~ to opt out. Images may stick to old positions after scrolling because tmux's cell buffer is not pixel-aware (upstream tmux limitation). Kitty graphics passthrough inside tmux is still unimplemented - Animated GIFs display only the first frame - Sixel is limited to 256 colors (vs truecolor on Kitty) and is stateless --- images are re-emitted on every scroll, which may be slower on large images - Without ImageMagick, only PNG files work on the Kitty backend; the Sixel backend requires ImageMagick unconditionally diff --git a/justfile b/justfile index 22fe756..008f4ad 100644 --- a/justfile +++ b/justfile @@ -141,10 +141,20 @@ test-sixel-image encoder="": @echo ">> Run inside foot, Konsole, mintty, mlterm, or WezTerm." TERM={{TERM_}} {{EMACS}} -nw -Q -l {{SRC}} \ --eval "(setq kitty-gfx-debug t kitty-gfx-preferred-protocol 'sixel)" \ - $(if [ -n "{{encoder}}" ]; then echo "--eval (setq\\ kitty-gfx-sixel-encoder-program\\ \"{{encoder}}\")"; fi) \ + --eval '(when (> (length "{{encoder}}") 0) (setq kitty-gfx-sixel-encoder-program "{{encoder}}"))' \ --eval "(kitty-graphics-mode 1)" \ tests/test-image.png +# Open test-image.png inside `tmux new-session', sixel backend forced +# (run on a sixel-capable outer terminal, requires tmux >= 3.4) +test-sixel-tmux encoder="": + @echo ">> Outer terminal must be sixel-capable (foot, Konsole, mintty, ...)." + @echo ">> tmux >= 3.4 with --enable-sixel required." + tmux new-session -As kgfx-sixel-test "TERM={{TERM_}} {{EMACS}} -nw -Q -l {{SRC}} \ + --eval '(setq kitty-gfx-debug t kitty-gfx-preferred-protocol (quote sixel))' \ + --eval '(when (> (length \"{{encoder}}\") 0) (setq kitty-gfx-sixel-encoder-program \"{{encoder}}\"))' \ + --eval '(kitty-graphics-mode 1)' tests/test-image.png" + # --- Logs ------------------------------------------------------------------- # Tail the kitty-gfx debug log (set kitty-gfx-debug to t to populate) diff --git a/kitty-graphics.el b/kitty-graphics.el index 640454b..c5a1e9d 100644 --- a/kitty-graphics.el +++ b/kitty-graphics.el @@ -207,6 +207,17 @@ will otherwise block Emacs." :type '(choice (const :tag "No timeout" nil) number) :group 'kitty-graphics) +(defcustom kitty-gfx-tmux-allow-sixel t + "When non-nil, allow the Sixel backend to engage inside tmux >= 3.4. +tmux 3.4 (released 2024-02-13) ships native Sixel rendering, so this +package no longer hard-disables Sixel under tmux. Set to nil if your +tmux is built without `--enable-sixel' or if you prefer to avoid the +upstream scroll artifact: tmux's cell buffer is not pixel-aware, so +images may persist after scrolling until the affected cells are +overwritten." + :type 'boolean + :group 'kitty-graphics) + (defcustom kitty-gfx-heading-scales '((1 . 2.0) (2 . 1.5) (3 . 1.2)) "Alist mapping org heading level to visual scale factor. Headings at levels not listed use scale 1.0 (normal size). @@ -588,19 +599,60 @@ OV is the overlay (unused by Kitty backend but part of the interface)." (defvar kitty-gfx--sixel-cache (make-hash-table :test 'equal) "Maps (file . dims-string) to temp sixel file paths.") +(defvar kitty-gfx--tmux-version-cache 'unset + "Cached `kitty-gfx--tmux-version' result. +Sentinel `unset' means the probe has not run yet; nil means tmux was +absent or its version could not be parsed; otherwise a list (MAJOR MINOR).") + +(defun kitty-gfx--tmux-version () + "Return tmux version as (MAJOR MINOR) integers, or nil when unavailable. +Memoised in `kitty-gfx--tmux-version-cache'." + (when (eq kitty-gfx--tmux-version-cache 'unset) + (setq kitty-gfx--tmux-version-cache + (when (executable-find "tmux") + (with-temp-buffer + (when (zerop (ignore-errors + (call-process "tmux" nil t nil "-V"))) + (goto-char (point-min)) + (when (re-search-forward + "tmux\\(?:[[:space:]]+next-\\)?[[:space:]]+\\([0-9]+\\)\\.\\([0-9]+\\)" + nil t) + (list (string-to-number (match-string 1)) + (string-to-number (match-string 2))))))))) + kitty-gfx--tmux-version-cache) + +(defun kitty-gfx--tmux-sixel-supported-p () + "Return non-nil when running under tmux >= 3.4 with Sixel allowed. +Returns nil outside tmux, nil when `kitty-gfx-tmux-allow-sixel' is off, +nil when tmux's version cannot be determined, and nil for tmux < 3.4." + (and (getenv "TMUX") + kitty-gfx-tmux-allow-sixel + (let ((ver (kitty-gfx--tmux-version))) + (and ver + (or (> (car ver) 3) + (and (= (car ver) 3) (>= (cadr ver) 4))))))) + (defun kitty-gfx--sixel-detect () - "Return non-nil if the terminal likely supports Sixel protocol." + "Return non-nil if the terminal likely supports Sixel protocol. +Inside tmux, requires tmux >= 3.4 (native Sixel rendering, 2024-02-13) +and `kitty-gfx-tmux-allow-sixel'. Older tmux versions still disable +Sixel because they drop the DCS payload." (let* ((term (getenv "TERM")) (term-prog (getenv "TERM_PROGRAM")) (in-tmux (getenv "TMUX")) + (tmux-ver (and in-tmux (kitty-gfx--tmux-version))) + (tmux-ok (kitty-gfx--tmux-sixel-supported-p)) (supported (and term - (not in-tmux) ; Sixel doesn't work in tmux + (or (not in-tmux) tmux-ok) (or (string-match-p "xterm\\|vt[0-9]\\|foot\\|contour" term) (member term-prog '("foot" "Konsole" "mintty" "mlterm" "contour" "WezTerm"))) t))) - (kitty-gfx--log "sixel-detect: %s (TERM=%s TERM_PROGRAM=%s TMUX=%s)" - supported term term-prog (if in-tmux "yes" "no")) + (kitty-gfx--log + "sixel-detect: %s (TERM=%s TERM_PROGRAM=%s TMUX=%s tmux-ver=%s tmux-ok=%s)" + supported term term-prog (if in-tmux "yes" "no") + (if tmux-ver (format "%d.%d" (car tmux-ver) (cadr tmux-ver)) "n/a") + (if tmux-ok "yes" "no")) supported)) (defun kitty-gfx--sixel-cache-path (file cols rows) -- cgit v1.3.1 From 15970781b5e3eafaac2fd7ed3730079fd4cfb451 Mon Sep 17 00:00:00 2001 From: cashmere Date: Sun, 26 Apr 2026 21:20:59 +0200 Subject: fix(justfile): test-sixel-tmux skips nesting when already inside tmux Running `just test-sixel-tmux' from a tmux pane previously printed `sessions should be nested with care, unset $TMUX to force' and exited with code 1. Switch the recipe to a shebang body that branches at runtime: when `$TMUX' is already set, run emacs directly in the current pane (the test condition is already satisfied -- we're inside tmux); otherwise spawn a fresh `tmux new-session' wrapper as before. --- justfile | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/justfile b/justfile index 008f4ad..306290e 100644 --- a/justfile +++ b/justfile @@ -145,15 +145,28 @@ test-sixel-image encoder="": --eval "(kitty-graphics-mode 1)" \ tests/test-image.png -# Open test-image.png inside `tmux new-session', sixel backend forced -# (run on a sixel-capable outer terminal, requires tmux >= 3.4) +# Open test-image.png inside tmux with sixel backend forced. +# Outer terminal must be sixel-capable (foot, Konsole, mintty, mlterm, WezTerm). +# Requires tmux >= 3.4 built with --enable-sixel. +# When already inside tmux, runs emacs directly (no nesting). test-sixel-tmux encoder="": - @echo ">> Outer terminal must be sixel-capable (foot, Konsole, mintty, ...)." - @echo ">> tmux >= 3.4 with --enable-sixel required." - tmux new-session -As kgfx-sixel-test "TERM={{TERM_}} {{EMACS}} -nw -Q -l {{SRC}} \ - --eval '(setq kitty-gfx-debug t kitty-gfx-preferred-protocol (quote sixel))' \ - --eval '(when (> (length \"{{encoder}}\") 0) (setq kitty-gfx-sixel-encoder-program \"{{encoder}}\"))' \ - --eval '(kitty-graphics-mode 1)' tests/test-image.png" + #!/usr/bin/env bash + set -eu + if [ -n "${TMUX:-}" ]; then + echo ">> Already inside tmux -- running emacs directly in this pane." + exec env TERM={{TERM_}} {{EMACS}} -nw -Q -l {{SRC}} \ + --eval '(setq kitty-gfx-debug t kitty-gfx-preferred-protocol (quote sixel))' \ + --eval '(when (> (length "{{encoder}}") 0) (setq kitty-gfx-sixel-encoder-program "{{encoder}}"))' \ + --eval '(kitty-graphics-mode 1)' \ + tests/test-image.png + else + echo ">> Outer terminal must be sixel-capable; spawning fresh tmux session." + exec tmux new-session -As kgfx-sixel-test \ + "TERM={{TERM_}} {{EMACS}} -nw -Q -l {{SRC}} \ + --eval '(setq kitty-gfx-debug t kitty-gfx-preferred-protocol (quote sixel))' \ + --eval '(when (> (length \"{{encoder}}\") 0) (setq kitty-gfx-sixel-encoder-program \"{{encoder}}\"))' \ + --eval '(kitty-graphics-mode 1)' tests/test-image.png" + fi # --- Logs ------------------------------------------------------------------- -- cgit v1.3.1 From 8d96906eda19ac94613323deac3c039f88d466e0 Mon Sep 17 00:00:00 2001 From: cashmere Date: Sun, 26 Apr 2026 21:33:26 +0200 Subject: docs(sixel): hard-state img2sixel as the recommended encoder + warn loudly when none is on PATH Users hit a silent-failure mode where `kitty-gfx--sixel-detect' returned true (terminal supports Sixel) but no encoder was on `PATH', so every `sixel-place' logged `no encoder found' and produced no image. The echo-area `message' from `sixel-encode' was easy to miss inside tmux, which made the issue look like a refresh / tmux passthrough bug. - README.org: Requirements section now explicitly lists a Sixel encoder as required and recommends `img2sixel' (libsixel) over ImageMagick, with the concrete numbers from PR #10's bench (~2.2x smaller payload, ~10x faster) and a note that this matters most inside tmux because every refresh re-emits the full DCS payload. Known-Limitations reworded: missing encoder = silent failure surfaced via the debug log. - AGENTS.md: Dependencies block reorganised the same way; adds a pointer to `just sixel-encoder' as the first step when diagnosing Sixel rendering bugs. - kitty-graphics.el: when the active backend is Sixel and `kitty-gfx--sixel-resolve-encoder' returns nil at mode-enable time, fire a `display-warning' (`*Warnings*' buffer + visible message) pointing at img2sixel/ImageMagick, so the failure is no longer buried in /tmp/kitty-gfx.log. --- AGENTS.md | 18 ++++++++++++++---- README.org | 9 +++++++-- kitty-graphics.el | 13 +++++++++++++ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b18f76c..bfd618b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -269,10 +269,20 @@ Key design differences from images: - **Required**: Emacs >= 27.1, a supported terminal (Kitty/WezTerm/Ghostty for Kitty backend; foot/Konsole/xterm/mlterm/mintty for Sixel) -- **Required for Sixel**: a Sixel encoder on PATH -- `img2sixel` - (libsixel, recommended) or ImageMagick (`magick`/`convert`). - Override via `kitty-gfx-sixel-encoder-program'; bound by - `kitty-gfx-sixel-encoder-timeout' (default 5s). +- **Required for Sixel**: a Sixel encoder on `PATH`. + - **Strongly recommended**: `img2sixel` from libsixel (~2.2x smaller + payload, ~10x faster than ImageMagick on the same input). Critical + inside tmux, where every refresh re-emits the full DCS payload -- + using ImageMagick there leads to visible flicker. + - **Fallback**: ImageMagick 7 (`magick`) or 6 (`convert`). Works, + just slower. + - Auto-detect order: `img2sixel` > `magick` > `convert`. Override + via `kitty-gfx-sixel-encoder-program' (nil = auto, string = pin). + - With **no** encoder on `PATH`, the Sixel backend silently produces + no output and logs `no encoder found` (set `kitty-gfx-debug' to + surface this). Always verify with `just sixel-encoder' before + diagnosing rendering bugs. + - Bounded by `kitty-gfx-sixel-encoder-timeout' (default 5s). - **Optional for Kitty**: ImageMagick for non-PNG formats and pixel-accurate sizing - **Optional for typst preview**: `typst` CLI on PATH (used by `kitty-gfx-typst-preview`) diff --git a/README.org b/README.org index 6b95ac4..258dde0 100644 --- a/README.org +++ b/README.org @@ -47,7 +47,11 @@ Integrations are provided for: - A supported terminal: - *Kitty backend*: [[https://sw.kovidgoyal.net/kitty/][Kitty]] >= 0.20.0, WezTerm, or Ghostty - *Sixel backend*: foot, Konsole, xterm (with ~+sixel~), mlterm, mintty, VS Code Terminal, and others with Sixel support -- ImageMagick (~magick~ / ~convert~ / ~identify~) --- required for Sixel, and for non-PNG formats on Kitty +- A Sixel encoder on ~PATH~ --- /required/ for the Sixel backend. + - *Strongly recommended*: ~img2sixel~ from [[https://github.com/saitoha/libsixel][libsixel]] (Nix: ~nixpkgs#libsixel~). Roughly =2.2x= smaller payloads and =~10x= faster than ImageMagick, which matters a lot in tmux where every refresh re-emits the full DCS payload. + - *Fallback*: ImageMagick 7 (~magick~) or 6 (~convert~). Works, but slower; on long sessions and in tmux you may notice flicker on redraw. + - Override the auto-detect order via ~kitty-gfx-sixel-encoder-program~. If neither is on ~PATH~ the Sixel backend logs ~no encoder found~ and silently produces no image, so make sure one is installed before launching Emacs. +- ImageMagick (~magick~ / ~identify~) --- still needed for non-PNG formats on the Kitty backend (JPEG/WebP/SVG/TIFF/BMP -> PNG conversion) and for the dimension probe - For LaTeX preview: a TeX distribution with ~dvipng~ (e.g. ~texlive~) - For doc-view: ~ghostscript~ (for PDF), ~dvipng~ (for DVI) --- same tools as GUI Emacs - Launch Emacs with ~TERM=xterm-256color~ (Emacs often can't find the ~xterm-kitty~ terminfo) @@ -146,7 +150,8 @@ TIFF, BMP) are converted to PNG via ImageMagick before transmission. - Partial tmux support: Sixel works inside tmux >= 3.4 (native sixel rendering, foot/Konsole/mintty/mlterm/WezTerm as outer terminal); set ~kitty-gfx-tmux-allow-sixel~ to ~nil~ to opt out. Images may stick to old positions after scrolling because tmux's cell buffer is not pixel-aware (upstream tmux limitation). Kitty graphics passthrough inside tmux is still unimplemented - Animated GIFs display only the first frame - Sixel is limited to 256 colors (vs truecolor on Kitty) and is stateless --- images are re-emitted on every scroll, which may be slower on large images -- Without ImageMagick, only PNG files work on the Kitty backend; the Sixel backend requires ImageMagick unconditionally +- The Sixel backend needs a Sixel encoder on ~PATH~. ~img2sixel~ (libsixel) is strongly recommended; ~magick~ / ~convert~ work as fallback but are noticeably slower. With no encoder on ~PATH~, the backend silently produces no output and only logs ~no encoder found~ to ~/tmp/kitty-gfx.log~ when ~kitty-gfx-debug~ is non-nil +- Without ImageMagick, only PNG files work on the Kitty backend - Doc-view page may briefly flash at wrong position before centering on initial load * License diff --git a/kitty-graphics.el b/kitty-graphics.el index c5a1e9d..adfe6b7 100644 --- a/kitty-graphics.el +++ b/kitty-graphics.el @@ -2166,6 +2166,19 @@ The buffer should be writable (caller handles `inhibit-read-only')." (kitty-gfx--query-text-sizing-support)) (kitty-gfx--install-hooks) (kitty-gfx--install-integrations) + ;; Sixel backend silently drops images when no encoder is on + ;; PATH. Warn loudly so users notice before they wonder why + ;; nothing renders. + (when (and (eq kitty-gfx--active-backend 'sixel) + (not (kitty-gfx--sixel-resolve-encoder))) + (kitty-gfx--log "mode: WARNING no Sixel encoder on PATH") + (display-warning + 'kitty-graphics + "Sixel backend active but no encoder on PATH. +Install `img2sixel' (libsixel; strongly recommended) or +ImageMagick (`magick'/`convert'). Without an encoder, no images +will render even though detection reports Sixel as supported." + :warning)) (kitty-gfx--log "mode: enabled (backend=%s cell=%dx%d text-sizing=%s)" kitty-gfx--active-backend kitty-gfx--cell-pixel-width kitty-gfx--cell-pixel-height -- cgit v1.3.1