|
|
||
|---|---|---|
| config | ||
| prototypes | ||
| src | ||
| .gitignore | ||
| BLOG.md | ||
| Cargo.toml | ||
| DECISION.md | ||
| LICENSE | ||
| QUICKSTART.md | ||
| README.md | ||
README.md
marten
A modern, accuracy-first image viewer for Linux. Version 0.4.0 · GPL-2.0-or-later · Built with Rust, iced, wgpu, and winit.
Marten is a desktop image viewer for Linux, written from scratch in Rust. Named after the marten (genus Martes) — a small agile mustelid native to forests across the Northern Hemisphere. Like its cousin the ferret (marten's sibling app for video playback), the marten is quick, curious, and nimble. Fitting energy for a photo viewer designed to move fast through large libraries.
What marten does
Marten is a single-window image viewer focused on fast folder-based browsing. It is not a photo manager, not an editor, not a library tool. It opens a folder, shows you the images in it, and gets out of your way.
Core features (v0.4.0)
- Ristretto-style scroll-wheel navigation — scroll the wheel anywhere in the image area to shuffle to the next/previous photo in the folder. Natural-scroll direction (scroll up = previous, scroll down = next).
- gpicview-style bottom toolbar — all controls live in a thin bottom bar, leaving the top free for the thumbnail strip. SVG icons (Lucide) throughout.
- Top thumbnail strip — lazy-loaded horizontal strip at the top of the
window. Current image highlighted with an accent border. Click any
thumbnail to jump to it. Thumbnails decode on a tokio
spawn_blockingthread pool at 72px and are cached in an LRU window of ±15 around the current image. - Tier 2 format support — JPEG XL (
.jxl), TIFF (.tif/.tiff), SVG (.svg/.svgz), and OpenEXR (.exr) join the Tier 1 roster via dedicated codec modules. Thumbnails and EXIF parsing apply to every supported format. - Step-aware random navigation —
zjumps to a random image in the current folder;Shift+Zwalks the parent folder tree recursively, collects every supported image across all subfolders, and picks one at random. If the chosen image lives in a different subfolder, marten switches to that folder automatically. Both actions are step-aware: subsequent arrow/scroll navigation continues sequentially from the new position rather than from where you left off. - Timeline-style thumbnail auto-scroll — the top thumbnail strip auto-scrolls to keep the current image centered, like a video editor's playhead on a timeline. Triggered on every navigation event (scroll, arrow keys, random jump, thumbnail click).
- Folder-as-video export — right-click → Export folder as video…
opens a modal that turns the current folder into a
.webmslideshow with a user-selected audio track. ffmpeg runs in a background thread viatokio::task::spawn_blocking; VP9 (libvpx-vp9) and AV1 (libaom-av1) codecs are selectable. Requires ffmpeg installed. - Togglable folder tree sidebar — press
Tabto show a 240px left panel listing sibling folders (children of the current parent that contain images), each with an image count. Click a folder to switch to it. The sidebar hides in fullscreen. - Slideshow mode — press
sto auto-advance to the next photo every three seconds via aniced::time::everysubscription. Presssagain orEscapeto stop. A toast confirms the state change. - EXIF properties panel — press
ior right-click → Properties to open a modal showing filename, path, dimensions, format, file size, and EXIF metadata (camera make/model, lens, ISO, aperture, shutter speed, focal length, timestamp, GPS coordinates, orientation) parsed viakamadak-exif. - Fit-to-window rendering — iced's native
ContentFit::Containhandles centering and scaling. No manual offset math, no overflow, no scrollbars in fit mode. Image always perfectly centered in the viewport. - Zoom modes —
0for fit-to-window,1for 100%,+/-orCtrl+scrollfor custom zoom. Each zoom step multiplies the factor by 1.1 (or divides by 1.1) for smooth, graceful changes. In zoom modes the image is the direct child of a bidirectionalscrollablewithContentFit::ContainandLength::Fixed(dw/dh)— the previousLength::Fillwrapper collapsed insidescrollableand hid the image. Pan by dragging the scrollbars or scrolling. - Visual rotation —
Rrotates 90° clockwise,Shift+Rcounter- clockwise. Rotation is applied by pre-rotating the RGBA pixel buffer (iced 0.13 lacks native image rotation; the buffer-rotation approach is the chosen implementation). The original image is preserved so rotation is non-destructive and reversible. - gpicview-style right-click context menu — 12 actions, all functional:
- Open With… (xdg-open)
- Copy Path (to clipboard)
- Copy Image (pixel data to clipboard)
- Copy to Pictures (Shift+Home — copies file to
~/Pictures/) - Rotate 90° CW / CCW
- Set as Wallpaper (gsettings, with feh as alternate backend)
- Move to Trash (trash crate)
- Delete Permanently (Shift+Delete —
std::fs::remove_file) - Properties (opens the EXIF properties panel)
- Export folder as video… (opens the video export dialog)
- About marten (shows the About dialog)
- Anti-list error modal — opening a file on the project's anti-list (HEIC, CR3, NEF, ARW, PSD, DNG, Apple Live Photos, etc.) shows a full-screen modal explaining the project's stance and suggesting open alternatives. The viewer does NOT silently skip these files; it tells you why it refuses to open them.
- Configurable keymap — every keybinding is overridable via
~/.config/marten/keymap.toml. Ship-the-defaults works out of the box; power users can remap everything. - Fullscreen mode —
F11orShift+Fcollapses all chrome; image fills the window. A small floating hint at the top-center of the screen ("Press F11 to exit fullscreen") with a semi-transparent dark background reminds you how to leave. Press again to exit. - About dialog — press
aor click the info button in the toolbar. Matches the ferret app's About style: orange accent border, dark card, author/website/license rows, build info, copyright.
Format support
Marten takes a deliberate stance on format support. There are three tiers plus an explicit anti-list.
Tier 1 — supported (via the image crate)
| Format | Extensions | Notes |
|---|---|---|
| PNG | .png .apng |
Including animated PNG |
| JPEG | .jpg .jpeg .jfif |
Baseline + progressive |
| GIF | .gif |
Including animation |
| WebP | .webp |
Lossy + lossless + animated |
| AVIF | .avif |
HDR, animation, alpha — our modern baseline |
| BMP | .bmp |
Legacy compatibility |
| ICO / CUR | .ico .cur |
Windows icon / cursor |
Tier 2 — supported (dedicated codec modules)
| Format | Extensions | Decoder crate |
|---|---|---|
| JPEG XL | .jxl |
jxl-oxide |
| TIFF | .tif .tiff |
tiff |
| SVG | .svg .svgz |
resvg (with usvg + tiny-skia) |
| OpenEXR | .exr |
exr |
Tier 3 — niche
QOI, JPEG 2000, JPEG XS.
🚫 Anti-list — will NEVER be supported
Marten explicitly rejects proprietary or patent-encumbered formats:
- HEIF / HEIC — HEVC patent-licensing baggage. AVIF covers the same use case royalty-free.
- Canon CR3, Nikon NEF, Sony ARW — proprietary camera RAW specs.
- Adobe PSD — proprietary Photoshop format.
- Adobe DNG — "partially open"; Adobe-controlled.
- Apple Live Photos — proprietary paired image+video container.
If you open one of these files, marten shows a modal explaining the stance
and suggesting an open alternative. See DECISION.md (decisions D002 and
D007) for the full rationale.
Project layout
The codebase is a single Cargo binary crate with internal modules. Total source size: 6,008 lines of Rust across 25 files, with 35 unit tests.
marten/
├── Cargo.toml # package metadata, deps, release profile
├── Cargo.lock # pinned dependency versions
├── LICENSE # GPL-2.0 full text
├── README.md # this file
├── QUICKSTART.md # 5-minute getting-started guide
├── BLOG.md # v0.4.0 release announcement
├── DECISION.md # architectural decision records (D001–D016)
├── config/
│ ├── keymap.toml # default keymap, user-overridable
│ └── settings.toml # default settings, user-overridable
├── bin/
│ └── marten # prebuilt release binary (x86-64 Linux, 24.5 MB)
├── src/
│ ├── main.rs # 26 lines — entry point, iced bootstrap
│ ├── app.rs # 1,317 lines — Viewer state, Message dispatch, subscriptions
│ ├── config.rs # 367 lines — keymap.toml parser + loader
│ ├── settings.rs # 187 lines — settings.toml parser + loader
│ ├── codec/
│ │ ├── mod.rs # 339 lines — FormatRegistry, Codec trait, rotate_rgba()
│ │ ├── anti_list.rs # 126 lines — 8 rejected formats with reasons
│ │ ├── image_crate.rs # 110 lines — Tier 1 decoder (image crate)
│ │ ├── jxl.rs # 141 lines — JPEG XL decoder (jxl-oxide)
│ │ ├── tiff.rs # 189 lines — TIFF decoder (tiff crate)
│ │ ├── svg.rs # 123 lines — SVG decoder (resvg + tiny-skia)
│ │ └── exr.rs # 120 lines — OpenEXR decoder (exr crate)
│ ├── nav/
│ │ ├── mod.rs # 210 lines — Navigator (index, wrap-around, random)
│ │ └── folder.rs # 134 lines — scan_folder() + walk_folder_tree()
│ └── ui/
│ ├── mod.rs # 43 lines — refined dark palette
│ ├── icons.rs # 79 lines — 20 Lucide SVG icons
│ ├── image_view.rs # 244 lines — fit/zoom/pan/rotation rendering
│ ├── toolbar.rs # 167 lines — bottom toolbar with shuffle button
│ ├── status_bar.rs # 81 lines — filename · n/total · dims · zoom%
│ ├── thumbnail_bar.rs # 200 lines — lazy-loading top strip, scrollable::Id
│ ├── context_menu.rs # 208 lines — gpicview-style right-click menu
│ ├── error_modal.rs # 207 lines — anti-list / decode error overlay
│ ├── exif_panel.rs # 338 lines — EXIF properties modal
│ ├── sidebar.rs # 228 lines — togglable folder tree panel
│ ├── about_dialog.rs # 225 lines — ferret-style About dialog
│ └── export_dialog.rs # 555 lines — folder-as-video export modal
└── prototypes/ # original iced/egui spikes (archived)
├── SPIKE_COMPARISON.md
├── iced-viewer/
└── egui-viewer-archived/
Module responsibilities
main.rs— bootsenv_logger, callsiced::application()with theViewer::update/Viewer::viewpair, sets dark theme and initial window size (1200×800).app.rs— the centralViewerstruct owns all sub-state (codec registry wrapped inArc<FormatRegistry>for sharing across async boundaries, navigator, image view, toolbar, status bar, thumbnail bar, sidebar, EXIF panel, context menu, error modal, about dialog, export dialog, slideshow timer). TheMessageenum is the union of all sub-component messages.update()dispatches;view()composes the layout (thumbnail bar top, optional sidebar left, image middle, status- toolbar bottom) and layers overlays via
iced::widget::stack;subscription()drives the slideshow tick.
- toolbar bottom) and layers overlays via
config.rs—Keymapstruct with serde,parse_binding()for"Ctrl+Shift+R"style strings,load_keymap()that merges user toml over defaults. Unknown keys warn but do not crash.settings.rs—Settingsstruct with serde,load()that merges usersettings.tomlover defaults. Controls slideshow interval, thumbnail size, default zoom mode, thumbnail cache window, smooth-scroll toggle, and fullscreen hint toggle.codec/—FormatRegistryowns the codec list and the anti-list.decode(path)checks the anti-list first (fail fast, no I/O), then dispatches to the matching codec.rotate_rgba()rotates pixel buffers by 90/180/270°. The Tier 2 decoders (jxl.rs,tiff.rs,svg.rs,exr.rs) live alongsideimage_crate.rs.nav/—Navigatormanages the sorted image list + current index with wrap-around.scan_folder()walks a directory, filters by supported extensions, skips anti-listed and hidden files.walk_folder_tree()recursively collects all supported images in a folder tree for random tree-wide navigation.random_same_folder()andrandom_from_tree()useSystemTimenanos as the entropy source.ui/— one module per UI piece. Each module exports its state struct, message enum, andview()function. Thethememodule owns the palette constants.
Build & install
Prerequisites (Arch Linux)
sudo pacman -S --needed rust gtk3 wayland-protocols libx11 libxcb fontconfig
Build from source
git clone http://git.dcos.net/dcosnet/marten.git
cd marten
cargo build --release
# binary is at target/release/marten
Use the prebuilt binary
The bin/marten file in this distribution is a prebuilt release binary
(x86-64 Linux ELF, dynamically linked, stripped, 24.5 MB). Copy it anywhere
in your $PATH:
cp bin/marten ~/.local/bin/
marten
Run without installing
cd marten
cargo run --release
Configuration
Marten loads two configuration files from $XDG_CONFIG_HOME/marten/ (falling
back to ~/.config/marten/ if XDG is not set):
Keymap — keymap.toml
Defines which key triggers which action. All fields optional; missing fields
use defaults. See config/keymap.toml for the full schema.
Settings — settings.toml
Defines scalar runtime preferences (slideshow interval, thumbnail size,
default zoom mode, thumbnail cache window, smooth-scroll toggle, fullscreen
hint toggle). All fields optional; missing fields use defaults. See
config/settings.toml for the full schema.
~/.config/marten/
├── keymap.toml # key bindings
└── settings.toml # runtime preferences
If either file does not exist, marten uses built-in defaults. If a file exists but is malformed, marten falls back to defaults and logs a warning. Partial overrides merge over defaults.
Inspiration (no code reuse)
Marten draws inspiration from four existing Linux image viewers. No code was reused from any of them — the entire codebase is fresh Rust, written from scratch.
- Ristretto (XFCE) — the scroll-wheel-to-shuffle-photos paradigm, the thin-toolbar-plus-thumbnail-strip layout (which we inverted in v0.2).
- gPhoto (GNOME) — the togglable sidebar that lets you hop between sibling folders without reopening the file picker (added in v0.3).
- Viewnior — the minimal, distraction-free fullscreen mode with the exit hint.
- gpicview (LXDE) — the right-click context menu structure, which we cloned almost one-to-one (with three additions: Copy to Pictures, Export folder as video…, and About marten).
License
marten is licensed under the GNU General Public License v2.0 or later.
See LICENSE for the full text.
marten — a modern, accuracy-first image viewer for Linux.
Copyright (C) 2026 Jeremy Anderson
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
The full text of the GPL-2.0 is in LICENSE. The "or later" clause means
you may also choose to apply GPL-3.0+ terms if you prefer.
Author
Jeremy Anderson — http://git.dcos.net/dcosnet/marten
Marten is a sibling project to ferret, a modern accuracy-first video player for Linux. Both apps share the same design language: dark themes, orange accent, ferret-style About dialog, GPL-2.0-or-later license, and a small-mustelid naming convention.