From d055a1c256233afd955fa9c6415c3334c3bc697b Mon Sep 17 00:00:00 2001 From: Dylan Araps Date: Thu, 12 Mar 2026 11:44:11 +0200 Subject: dfm: Add support for images --- README.md | 15 +++++++++++++++ config.h.in | 9 +++++++++ config_key.h.in | 1 + dfm.c | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+) diff --git a/README.md b/README.md index 82aab58..a7438e2 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Initial Announcement: https://dylan.gr/1772192922 * Manually implemented interactive line editor * Efficient low-bandwidth partial rendering * UTF8 support (minus grapheme clusters and other unruly things) +* Inline image viewing (sixel, kitty) * Multiple view modes (name, size, permissions, mtime, ...) * Multiple sort modes (name, extension, size, mtime, reverse, ...) * Ranger-style bulk rename @@ -45,6 +46,7 @@ Initial Announcement: https://dylan.gr/1772192922 * [View Modes](#view-modes) * [Sort Modes](#sort-modes) * [Prompt](#prompt) + * [Images](#images) * [Searching](#searching) * [Marking](#marking) * [Commands](#commands) @@ -185,6 +187,8 @@ environment, default values are derived from the `config.h.in` file. - DFM_TRASH (Program to use when trashing files) - DFM_TRASH_DIR (Path to trash directory) + +- DFM_IMG_MODE (Image mode to use: 'chafa' (default), 'kitty') ``` ### CD On Exit @@ -299,6 +303,17 @@ comes time to commit the input it is simply joined together. Make not of this detail as it is necessary to know it when creating your own bound commands. +### Images + +Images can be viewed inside of `dfm` by pressing `i` by default. This will +display the image and wait for a keypress before returning to the directory +listing. Two backends are supported: `sixel` (via `chafa`) and `kitty`. + +The mode can be set in `config.h.in` or at runtime via an environment variable. + +![Image viewer](https://private-user-images.githubusercontent.com/6799467/562138699-23bcd982-ad50-47c3-bd40-7fc0f4deca83.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzMzMDg1NTksIm5iZiI6MTc3MzMwODI1OSwicGF0aCI6Ii82Nzk5NDY3LzU2MjEzODY5OS0yM2JjZDk4Mi1hZDUwLTQ3YzMtYmQ0MC03ZmMwZjRkZWNhODMucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMxMiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMTJUMDkzNzM5WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MzhmNDYzYjIyODdiMDhhMTdhMjFkYmRkMjY2Yjc5NjUyNzMwOTk5MTczMDI3MDY3ODk2Njg2NmEyNzhkN2NiOSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.-Qwa4zE0m7zcj9rFFNy7p95A8PoIypitokz8RJx5pd8) + + ### Searching There are two search modes: `startswith` (default `/`) and `substring` diff --git a/config.h.in b/config.h.in index d7be704..8eb7106 100644 --- a/config.h.in +++ b/config.h.in @@ -33,6 +33,15 @@ #define DFM_BOOKMARK_8 "" #define DFM_BOOKMARK_9 "" +// +// Default image viewer. +// The command is executed as follows: +// 'arg0' 'arg1' '{col}x{rol}' 'img' +// +#define DFM_IMG_MODE "chafa" // "kitty" +#define DFM_IMG_KITTY_CMD "kitten", "icat", "--align", "left", "--place" +#define DFM_IMG_CHAFA_CMD "chafa", "--format=sixel", "-s" + // // Default trash utility. // Can be set at runtime via environment: \$DFM_TRASH diff --git a/config_key.h.in b/config_key.h.in index d658928..0026853 100644 --- a/config_key.h.in +++ b/config_key.h.in @@ -89,6 +89,7 @@ static inline void (*fm_key(u32 cp))(struct fm *) case 'n': return cmd_mkdir; case 'r': return cmd_rename; case 'x': return act_stat; + case 'i': return act_view_image; case 'z': return act_alt_buffer; case 'p': return cmd_chmod; case 'P': return cmd_chown; diff --git a/dfm.c b/dfm.c index 64e3cb0..02eae64 100644 --- a/dfm.c +++ b/dfm.c @@ -195,6 +195,7 @@ struct fm { s64 tz; u8 nl; + u8 im; }; // Entry Virtual {{{ @@ -3014,6 +3015,41 @@ act_stat(struct fm *p) p->f |= FM_REDRAW; } +static inline void +act_view_image(struct fm *p) +{ + if (p->c == SIZE_MAX) return; + cut e = fm_ent(p, p->c); + u64 m = ent_load(p, p->c); + if (ENT_IS_DIR(ent_get(m, TYPE))) return; + p->f |= FM_REDRAW; + STR_PUSH(&p->io, VT_ED2 VT_CUP1); + fm_draw_flush(p); + p->io.l = 0; + str_push_u32(&p->io, p->col); + str_push_c(&p->io, 'x'); + str_push_u32(&p->io, p->row - 1); + int r = 0; + if (p->im == 'k') { + STR_PUSH(&p->io, "@0x0\0"); + const char *const a[] = { DFM_IMG_KITTY_CMD, p->io.m, e.d, NULL }; + r = fm_exec(p, -1, NULL, a, 0, 0); + } else { + str_terminate(&p->io); + const char *const a[] = { DFM_IMG_CHAFA_CMD, p->io.m, e.d, NULL }; + r = fm_exec(p, -1, NULL, a, 0, 0); + } + p->io.l = 0; + if (r < 0) return; + rl_clear(&p->r); + STR_PUSH(&p->r.cl, VT_DECTCEM_N " viewing: "); + str_push(&p->r.cl, e.d, e.l); + fm_draw_buf(p, CUT(DFM_COL_NAV)); + fm_draw_flush(p); + rl_clear(&p->r); + term_key_read(p->t.fd, &p->k); +} + static inline void act_open(struct fm *p) { @@ -3531,6 +3567,7 @@ fm_init(struct fm *p) if (fm_nest(p) == -1) return -1; p->opener = get_env("DFM_OPENER", DFM_OPENER); + p->im = get_env("DFM_IMG_MODE", DFM_IMG_MODE).d[0]; p->dfd = AT_FDCWD; p->ds = DFM_DEFAULT_SORT; p->dv = DFM_DEFAULT_VIEW; -- cgit v1.2.3