XeFM has a built-in image viewer. Put the cursor on an image and press V (or Enter) to open it full-window, with zoom, pan, and navigation to the other images in the same directory — without leaving XeFM.
| Key | What it does |
|---|---|
V |
View the focused file — an image opens in the image viewer |
Enter |
Same, for a file with no other enter rule |
Recognized formats: PNG, JPEG, GIF, BMP, WebP, TIFF, ICO, TGA, and the netpbm family (PPM/PGM/PBM/PNM).
| Key | Action |
|---|---|
+ / = |
Zoom in |
- |
Zoom out |
0 |
Fit the whole image to the window |
Shift-↑ Shift-↓ Shift-← Shift-→ |
Pan (while zoomed in) |
| mouse drag | Pan |
| mouse scroll | Zoom in / out |
↓ |
Next image |
↑ |
Previous image |
Home / End |
First / last image |
? |
Key help |
q / Esc |
Close |
Zoom starts at fit — the whole image in the window — and each step magnifies by 25%, up to 40×. Panning past an edge stops at the border rather than dropping your zoom level, and zoom and pan reset when you move to another image.
The header shows the file name, its position in the list (2/7), pixel
dimensions, file size, and the current zoom while you are zoomed in.
↓ and ↑ walk the images in the pane you opened the viewer from, in the
order shown there — so your sort order and filters carry into the viewer, and
non-image files are skipped. The list wraps at both ends.
The pane’s cursor follows along: stepping to another image (including with
Home/End) moves the file list’s cursor onto that file, so closing the
viewer leaves you exactly on the image you were looking at.
The list is fixed when the viewer opens, so a background directory refresh cannot shift it under you. Close and reopen to pick up new files.
On a theme with a screen post-effect (glow, scanlines, bloom), the effect is suspended while an image is open so you see the real pixels rather than a filtered version of them. It returns automatically when you close the viewer.
Terminals do not all display images. XeFM uses whichever inline-image protocol your terminal speaks:
| Terminal | Protocol |
|---|---|
| kitty, Ghostty, WezTerm, Konsole | kitty graphics |
| iTerm2, mintty | iTerm2 inline images |
foot, contour, mlterm, xterm (-ti vt340) |
sixel |
In the desktop app images always render, whatever your terminal.
In a terminal without any of these protocols — Terminal.app and the VS Code
terminal, notably — the viewer shows a card with the format, dimensions and
file size instead of the picture. Navigation still works; zoom and pan are
hidden, since there is nothing to zoom. To view the picture itself, use a
terminal from the table above, run the desktop app, or press Cmd-Enter /
open_with_os to hand the file to your OS image viewer.
You can force or disable the protocol with the PUIKIT_TERM_GRAPHICS
environment variable (kitty, iterm2, sixel, or none) — useful when XeFM
guesses wrong, or inside tmux/screen, which intercept these sequences:
PUIKIT_TERM_GRAPHICS=none xefm # never try to draw images inline
PUIKIT_TERM_GRAPHICS=sixel xefm # force sixel
Decoding is done by Pillow, which ships with XeFM. If it is somehow missing, the viewer still opens and still navigates, but shows the metadata card instead of the picture.
Images on S3 or over SSH, and images inside an archive, open like any other — their bytes are fetched to a temporary file for the life of the viewer and removed when it closes.
By default V uses the built-in viewer and Cmd-Enter (open_with_os) hands
the file to your OS app. To send V to an external program too, point the
view entry at it in your config’s FILE_ASSOCIATIONS:
FILE_ASSOCIATIONS = [
{
'pattern': ['*.jpg', '*.jpeg', '*.png', '*.gif'],
'open|view': ['open', '-a', 'Preview'], # V leaves XeFM again
'edit': ['open', '-a', 'GIMP'],
},
]
Setting 'view': None (the default) selects the built-in viewer.
Upgrading: XeFM’s shipped default used to send
viewto Preview. Your existing config keeps whatever it has — change that entry as above ifVstill opens Preview and you would rather stay in XeFM.
The zoom, navigation, and pan keys are rebindable in your config’s
KEY_BINDINGS:
KEY_BINDINGS = {
'image_zoom_in': ['+', '='],
'image_zoom_out': ['-', '_'],
'image_zoom_reset': ['0'],
'image_next': ['DOWN'],
'image_prev': ['UP'],
'image_scroll_up': ['Shift-UP'],
'image_scroll_down': ['Shift-DOWN'],
'image_scroll_left': ['Shift-LEFT'],
'image_scroll_right': ['Shift-RIGHT'],
}
Home/End are viewer-local and not rebindable, matching the text viewer’s
scroll keys.
If your config predates these actions (they are missing from your
KEY_BINDINGS), the viewer keeps its historical keys: n / p step through
the images and the plain arrow keys pan. Add the entries above to switch to
the current defaults.
doc/dev/IMAGE_VIEWER_IMPLEMENTATION.md — how it works internallydoc/TEXT_VIEWER_FEATURE.md — the viewer for everything that is not an image