Bitácora
← Blog

Arreglar el preview de imágenes en ranger: kitty (PNG/JPG) y SVG

Arreglar el preview de imágenes en ranger: kitty (PNG/JPG) y SVG

En ranger el panel de previsualización se quedó en blanco al seleccionar imágenes. Al investigarlo resultaron ser dos problemas independientes que conviene separar:

  1. Las imágenes raster (PNG, JPG, WebP…) no se veían por una incompatibilidad entre ranger 1.9.4 y kitty ≥ 0.40.
  2. Los SVG tampoco se veían, pero por otra razón: scope.sh los trae con la rama de conversión comentada y acaban en manos de PIL, que no sabe leer SVG.

El caso original está reportado en ranger/ranger#3193.

Nota: el preview de texto y el de directorios seguían funcionando. El fallo afecta solo a imágenes.

Parte 1 — PNG/JPG: EINVAL: temporary file not named correctly

El síntoma

Al pasar por encima de un PNG/JPG, ranger mostraba el error:

ranger.ext.img_display.ImgDisplayUnsupportedException:
kitty replied "b'\x1b_Gi=...;EINVAL: temporary file not named correctly\x1b\\'"

La causa

No es mala configuración de ranger ni de kitty. Es una incompatibilidad fija entre la versión 1.9.4 de ranger y kitty moderno.

ranger 1.9.4 envía la imagen ya escalada como archivo temporal (t=t) creado así:

NamedTemporaryFile(prefix='ranger_thumb_', suffix='.png')

Es decir, su ruta es algo como $TMPDIR/ranger_thumb_XXXX.png.

Desde kitty 0.40, el protocolo gráfico solo acepta t=t (y t=f) cuando la ruta completa contiene la cadena tty-graphics-protocol y está en un directorio temporal conocido. Si no, kitty responde EINVAL: temporary file not named correctly y no dibuja nada.

Se puede comprobar interceptando lo que manda kitten icat, que sí pasa el parámetro correcto:

a=q,f=24,t=t,s=1,v=1,S=47,i=2  payload=/dev/shm/kitty-tty-graphics-protocol-<pid>

ranger 1.9.4, en cambio, manda:

a=T,i=1,t=t,f=100  payload=/tmp/ranger_thumb_XXXX.png     # ← sin tty-graphics-protocol

Solución 1 (recomendada): ranger master

Ya está corregido en master: ranger/ext/img_display.py crea y usa un directorio con el nombre exigido:

self.temp_file_dir = os.path.join(gettempdir(), "tty-graphics-protocol")
os.mkdir(self.temp_file_dir)
# ...
NamedTemporaryFile(prefix='ranger_thumb_', suffix='.png',
                   dir=self.temp_file_dir, delete=False)

En Arch/CachyOS basta con el paquete git:

$ paru -S ranger-git

O desde el propio repositorio con pipx:

$ pipx install --force 'git+https://github.com/ranger/ranger.git'

Esto además trae el código nuevo de kitty (consulta DA1, passthrough de tmux y soporte de Ghostty).

Solución 2: wrapper, sin tocar ranger

Si quieres mantener el paquete 1.9.4, apunta TMPDIR a un directorio que contenga tty-graphics-protocol, solo durante la ejecución de ranger. No necesita root y no modifica nada del paquete.

bash/zsh (~/.bashrc o ~/.zshrc):

ranger() {
    local d="${XDG_RUNTIME_DIR:-/tmp}/tty-graphics-protocol"
    mkdir -p "$d"
    TMPDIR="$d" command ranger "$@"
}

fish (~/.config/fish/functions/ranger.fish):

function ranger
    set -l base $XDG_RUNTIME_DIR
    test -z "$base" -o ! -d "$base"; and set base /tmp
    set -l d $base/tty-graphics-protocol
    mkdir -p -- $d
    TMPDIR=$d command ranger $argv
end

Con esto ranger pasa a mandar:

a=T,i=1,t=t,f=100  payload=$TMPDIR/tty-graphics-protocol/ranger_thumb_XXXX.png

que kitty acepta.

Caso especial: EBADF (ssh / sin filesystem compartido)

Si en lugar de EINVAL kitty responde EBADF, ranger ya cae en el modo streaming (t=d, píxeles en línea), así que ese supuesto no necesita nada.

Parte 2 — SVG: la rama de scope.sh está comentada

El síntoma

Los .svg tampoco se veían, aunque aquí el error es distinto: silencio y panel vacío, sin mensaje de kitty.

La causa

El scope.sh de serie trae la conversión de SVG comentada:

## SVG
# image/svg+xml|image/svg)
#     convert -- "${FILE_PATH}" "${IMAGE_CACHE_PATH}" && exit 6
#     exit 1;;

Sin esa rama, un SVG cae en la regla image/* y sale con exit 7 (imagen directa). Ranger le entrega el .svg a PIL, y PIL no soporta SVG:

PIL.UnidentifiedImageError: cannot identify image file '.../test.svg'

La solución

Activa la rama en ~/.config/ranger/scope.sh y usa rsvg-convert como primera opción (tienes librsvg; ImageMagick queda de respaldo):

## SVG
image/svg+xml|image/svg)
    rsvg-convert -o "${IMAGE_CACHE_PATH}" -- "${FILE_PATH}" && exit 6
    magick -- "${FILE_PATH}" "${IMAGE_CACHE_PATH}" && exit 6
    convert -- "${FILE_PATH}" "${IMAGE_CACHE_PATH}" && exit 6
    exit 1;;

exit 6 significa «imagen convertida en $IMAGE_CACHE_PATH», que es justo lo que se necesita: se rasteriza el SVG a PNG y ranger lo pinta con el mismo mecanismo que un PNG normal.

Comprobación

$ ./scope.sh /tmp/test.svg 80 40 /tmp/cache_svg.png True ; echo "exit=$?"
exit=6
$ file /tmp/cache_svg.png
PNG image data, 100 x 80, 8-bit/color RGB, non-interlaced

Resumen

Tipo de archivo Antes Ahora
Texto ✅ exit 5 ✅ exit 5
PNG / JPG ❌ kitty rechaza el temporal ✅ con ranger-git o el wrapper TMPDIR
SVG ❌ PIL no sabe leerlo ✅ rsvg-convert → PNG (exit 6)
Directorios ✅ ✅

Las dos correcciones son complementarias: el arreglo del TMPDIR (o ranger-git) hace que kitty acepte las imágenes raster (incluido el PNG que resulta de convertir un SVG), y la rama de scope.sh hace que los SVG se conviertan antes de llegar a kitty.

Referencias