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.
Go to file
Jeremy Anderson e70bdb60f1 updated file loading 2026-08-19 23:33:25 -04:00
config 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. 2026-08-02 03:44:04 -04:00
prototypes 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. 2026-08-02 03:44:04 -04:00
src updated file loading 2026-08-19 23:33:25 -04:00
.gitignore 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. 2026-08-02 03:44:04 -04:00
BLOG.md 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. 2026-08-02 03:44:04 -04:00
Cargo.toml 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. 2026-08-02 03:44:04 -04:00
DECISION.md 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. 2026-08-02 03:44:04 -04:00
LICENSE 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. 2026-08-02 03:44:04 -04:00
QUICKSTART.md 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. 2026-08-02 03:44:04 -04:00
README.md 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. 2026-08-02 03:44:04 -04:00

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_blocking thread 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 navigationz jumps to a random image in the current folder; Shift+Z walks 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 .webm slideshow with a user-selected audio track. ffmpeg runs in a background thread via tokio::task::spawn_blocking; VP9 (libvpx-vp9) and AV1 (libaom-av1) codecs are selectable. Requires ffmpeg installed.
  • Togglable folder tree sidebar — press Tab to 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 s to auto-advance to the next photo every three seconds via an iced::time::every subscription. Press s again or Escape to stop. A toast confirms the state change.
  • EXIF properties panel — press i or 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 via kamadak-exif.
  • Fit-to-window rendering — iced's native ContentFit::Contain handles centering and scaling. No manual offset math, no overflow, no scrollbars in fit mode. Image always perfectly centered in the viewport.
  • Zoom modes0 for fit-to-window, 1 for 100%, +/- or Ctrl+scroll for 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 bidirectional scrollable with ContentFit::Contain and Length::Fixed(dw/dh) — the previous Length::Fill wrapper collapsed inside scrollable and hid the image. Pan by dragging the scrollbars or scrolling.
  • Visual rotationR rotates 90° clockwise, Shift+R counter- 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 modeF11 or Shift+F collapses 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 a or 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 (D001D016)
├── 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 — boots env_logger, calls iced::application() with the Viewer::update / Viewer::view pair, sets dark theme and initial window size (1200×800).
  • app.rs — the central Viewer struct owns all sub-state (codec registry wrapped in Arc<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). The Message enum 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.
  • config.rsKeymap struct 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.rsSettings struct with serde, load() that merges user settings.toml over defaults. Controls slideshow interval, thumbnail size, default zoom mode, thumbnail cache window, smooth-scroll toggle, and fullscreen hint toggle.
  • codec/FormatRegistry owns 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 alongside image_crate.rs.
  • nav/Navigator manages 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() and random_from_tree() use SystemTime nanos as the entropy source.
  • ui/ — one module per UI piece. Each module exports its state struct, message enum, and view() function. The theme module 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 Andersonhttp://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.