A graphical interface to remote-control a Canon EOS 450D (and probably other gphoto2-compatible EOS bodies) from Linux, similar in spirit to Canon's EOS Utility (Windows), built on top of gphoto2 and libgphoto2.
Context: this is a "vibe coded" project for a very niche need (tethering an old Canon 450D on Linux, with no simple EOS Utility equivalent). It was built with Claude Code (AI assistance), with a real human driving the actual hardware testing, architecture decisions, and code review — no pretense of polished, professional software. If you land here searching for a solution to a similar problem, great: that is exactly why this is public.
The interface is available in French, English, German, and Russian (switchable from a language selector in the top bar of the app).
- Automatic camera connection / detection
- Live view
- ISO / shutter speed / aperture settings, plus an advanced settings section (white balance, exposure compensation, picture style, metering mode, drive mode, color space — whatever the connected body actually exposes)
- Manual capture with autofocus triggering
- Custom shot sequences (different settings per shot), with the ability to save/reload named sequences
- Intervalometer / timelapse (shot count or total duration), resilient to a single failed capture (logs the error and keeps going instead of aborting the whole run)
- Bulb exposure support with a configurable duration timer, cancellable mid-exposure (the shortened image is kept) — see the Bulb mode notes below
- Image viewer: browse a folder of CR2/JPEG images with a thumbnail filmstrip, 100 % zoom, automatic display of each capture, and two display modes — instant embedded JPEG preview (default) or real full-resolution RAW rendering to judge focus
- Persistent configuration (save folders, saved sequences)
- Backend: Rust + Tauri 2 + the
gphoto2crate (reallibgphoto2bindings, no shelling out to thegphoto2CLI)- A dedicated thread owns the camera object and serializes all access (gphoto2 is not thread-safe)
- Frontend: static HTML/CSS/JS, no framework, no build step (no Node.js/npm required)
pacman -S rust webkit2gtk-4.1 gtk3 libgphoto2
cargo install tauri-cli --locked # once
cd src-tauri
cargo tauri dev
A PKGBUILD is provided at the repository root:
makepkg -f
sudo pacman -U canon-remote-*.pkg.tar.zst
(--nodeps may be needed if Rust comes from rustup rather than pacman's
rust package.)
A ready-to-install package is available in release/:
sudo pacman -U release/canon-remote-0.3.0-1-x86_64.pkg.tar.zst
Verify its integrity before installing:
sha256sum release/canon-remote-0.3.0-1-x86_64.pkg.tar.zst
Expected checksum:
caff0621fdbee70aab92b3d74c3fe1c2d386c977ef6b2ce0a7ceae6fa5fb6678
- Only tested on a Canon EOS 450D. Other EOS bodies should work (same gphoto2/PTP mechanism) but the exact names of advanced settings may vary by model/firmware.
- Live view autofocus on the 450D is limited by the hardware itself (first Canon body with live view, very slow contrast-detect AF) — independent of this software.
- Normal (non-bulb) captures need a memory card inserted to release the shutter, even in "Internal RAM" capture-target mode (a Canon firmware limitation, not a software one). Bulb captures are the exception: they work without any card (see below).
Bulb went through a full debugging campaign on real hardware and is now reliable. What was fixed and how it works:
- Put the lens in MF (manual focus), or focus first. The PTP bulb command is a full shutter press: like the physical button, the camera silently refuses to fire while autofocus hasn't confirmed focus. This was the root cause of nearly every "bulb randomly doesn't start" symptom during testing (an AF lens pointed at a low-contrast scene). The app now detects the refusal within seconds via the camera's exposure-progress events and reports this exact cause, instead of timing out cryptically.
- Images go straight to the computer. Bulb captures target the camera's internal RAM (no memory card needed — useful here, since the 450D rejects SDXC cards entirely) and are downloaded through gphoto2's event system within a single PTP session. The previous directory-diffing implementation is gone.
- A patched libgphoto2 camera driver is bundled (
src-tauri/camlibs/ ptp2.so, loaded via theCAMLIBSenvironment variable — the system install is untouched). Stock libgphoto2 2.5.34 has an uninitialized read induplicate_DevicePropDescthat crashes event polling on this body (the reason bulb previously avoidedwait_event). The one-line fix and a reproducible build script live inpackaging/. - Cancelling a pose closes the shutter immediately and still downloads the shortened image. Shutter close is retried on transient PTP errors so the shutter is never left open, and the file-wait timeout is based on the actual (possibly shortened) exposure time.
- Connection is self-healing. If the camera enumerates on USB but no longer answers PTP (auto power-off mid-session, rapid reconnects), the app performs a targeted USB reset (Canon still-imaging devices only) and retries automatically.
- Sequences resync between shots. After each bulb exposure the app waits until the body answers again before triggering the next shot (the camera stays busy for a few seconds after long exposures), which fixed sequences aborting with "Unspecified error".
MIT — do whatever you want with it; if it saves you an evening of fighting with camera tethering on Linux, it did its job.