- Rust 100%
| .github/workflows | ||
| src | ||
| tests | ||
| .gitignore | ||
| .woodpecker.yml | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
CamConfig
Terminal UI for configuring Logitech StreamCam and other V4L2 webcams in real time on Linux.
CamConfig lets you tweak camera settings — brightness, contrast, saturation, white balance, exposure, focus, zoom, and more — while the camera is actively being used by Zoom, OBS, or any other application. It automatically falls back to v4l2-ctl when the device is locked, so you never have to stop your stream to adjust settings.
Table of Contents
- Features
- Quick Start
- Installation
- Usage
- Keyboard Reference
- Understanding the UI
- Presets
- Backends
- Architecture
- Library API
- Contributing
- Troubleshooting
- Known Limitations
- License
Features
- Real-time control — Adjust any V4L2 control while the camera is in use. Changes apply instantly.
- Busy-device fallback — Automatically switches to
v4l2-ctlsubprocess when the camera is locked by another application (Zoom, OBS, Firefox, etc.). - Auto-discovery — Detects all connected V4L2 devices on startup and prefers Logitech StreamCam when available.
- Grouped controls — Controls are organized into logical groups: Image, Color, Exposure, Lens, and Other.
- Group filtering — Press
gto cycle through groups and focus only on the controls you care about. - Presets — Save the entire camera state to a named preset and restore it instantly. Presets are stored as human-readable TOML files in your XDG config directory.
- Mock backend — Full test-driven development support with an in-memory fake camera. Run tests and develop without any hardware connected.
- Zero async — Simple threaded event loop with MPSC channels. No tokio, no runtime bloat.
- Small binary — Release builds are statically optimized (
lto = true,strip = true).
Quick Start
# 1. Install system dependencies (Debian/Ubuntu)
sudo apt-get install libv4l-dev v4l-utils
# 2. Clone and build
git clone https://github.com/feanor/camconfig.git
cd camconfig
cargo build --release
# 3. Run — auto-discovers your camera
./target/release/camconfig
# 4. Or try the mock backend without a camera
./target/release/camconfig --mock
Installation
From Source
Requirements:
- Rust 1.70+ with the 2024 edition
libv4l-dev(build-time headers)v4l-utils(runtime, required for fallback mode)
# Debian / Ubuntu
sudo apt-get install libv4l-dev v4l-utils
# Fedora
sudo dnf install libv4l-devel v4l-utils
# Arch
sudo pacman -S v4l-utils
# Build release binary
cargo build --release
# Install to ~/.cargo/bin
cargo install --path .
The compiled binary will be at target/release/camconfig.
Precompiled Binaries
Prebuilt binaries are not yet published. Track progress in the issue tracker.
Usage
Command-Line Options
$ camconfig --help
TUI for configuring V4L2 webcams in real time
Usage: camconfig [OPTIONS]
Options:
-d, --device <DEVICE> Path to the video device (e.g. /dev/video0)
-l, --list List available devices and exit
--mock Use mock backend for testing (no real camera required)
--ctl Force v4l2-ctl subprocess backend
-h, --help Print help
-V, --version Print version
Examples
# Auto-detect and open the best available camera
camconfig
# List all V4L2 devices
camconfig --list
# Open a specific device
camconfig --device /dev/video2
# Force the v4l2-ctl backend (useful when native fails for reasons other than EBUSY)
camconfig --ctl
# Run with mock camera (no hardware needed — great for testing UI behavior)
camconfig --mock
# Combine flags
camconfig --device /dev/video0 --ctl
Keyboard Reference
Normal Mode
| Key | Action |
|---|---|
↑ / ↓ |
Navigate up / down through controls |
k / j |
Navigate up / down (Vim-style) |
← / → |
Decrease / increase the selected control value |
h / l |
Decrease / increase (Vim-style) |
Enter |
Toggle a boolean control or open a menu control |
g |
Cycle group filter (None → Image → Color → Exposure → Lens → Other) |
Esc |
Clear group filter and show all controls |
s |
Quick-save current state to the default preset |
S |
Enter Save Preset mode (type a custom name and press Enter) |
r |
Quick-load the default preset |
R |
Enter Load Preset mode (select from list with ↑/↓ and press Enter) |
Ctrl + d |
Alternative shortcut to save default preset |
q |
Quit |
Save Preset Mode
| Key | Action |
|---|---|
a–z, 0–9, _, - |
Type characters into the preset name |
Backspace |
Delete last character |
Enter |
Confirm and save |
Esc |
Cancel and return to Normal mode |
Load Preset Mode
| Key | Action |
|---|---|
↑ / ↓ |
Navigate preset list |
Enter |
Load selected preset |
Esc |
Cancel and return to Normal mode |
Menu Editing Mode
| Key | Action |
|---|---|
↑ / ↓ |
Navigate menu items |
Enter |
Select highlighted item |
Esc |
Cancel and return to Normal mode |
Understanding the UI
The screen is divided into three vertical panels:
┌─────────────────────────────────────────────┐
│ CamConfig — /dev/video0 [Lens] │ ← Header
│ q quit ↑↓ nav ←→ adjust g group ... │
├─────────────────────────────────────────────┤
│ Controls │
│ > Zoom, Absolute 100 [0,500] │ ← Main list
│ Focus, Auto ON │
│ Brightness 128 [0,255] │
├─────────────────────────────────────────────┤
│ Status: Set Zoom updated [preset: studio] │ ← Footer
│ Presets: default, studio, lowlight │
└─────────────────────────────────────────────┘
Header
- Shows the active device path (e.g.,
/dev/video0). - Displays the current group filter in brackets (e.g.,
[Lens]). - Shows a one-line cheat sheet of the most common shortcuts.
Controls List
- Each row shows the control name, its current value, and the valid range.
- The selected control is highlighted with a
>cursor and a cyan background. - Read-only controls display
[RO]in dark gray. - Controls are grouped logically; use
gto filter.
Status Bar (Footer Left)
- Displays the result of the last action (
Set Zoom updated, preset loaded, errors, etc.). - Errors are shown in red and stay visible until a new action occurs.
- If a preset is active, its name appears in brackets.
Presets Bar (Footer Right)
- Lists all saved presets found in the config directory.
Presets
Presets allow you to save the complete state of all camera controls and restore them instantly. They are perfect for switching between lighting conditions (e.g., "daylight", "studio", "night").
Where Presets Are Stored
Presets are saved as TOML files in your XDG config directory:
$HOME/.config/camconfig/presets/
├── default.toml
├── studio.toml
└── lowlight.toml
Because they are plain TOML, you can edit them by hand, version-control them, or share them across machines.
Preset File Format
# ~/.config/camconfig/presets/studio.toml
"1" = 128 # Brightness
"2" = 32 # Contrast
"3" = 60 # Saturation
"4" = 4000 # White Balance Temperature
"5" = false # Auto Exposure
"6" = 156 # Exposure (Absolute)
"7" = 100 # Zoom
The keys are V4L2 control IDs (numeric strings), and the values match the control type:
Integer→ integer literalBoolean→true/falseMenu→ integer ID of the selected menu item
Loading Presets on Startup
There is no built-in "autoload on start" flag yet, but you can approximate it with a shell alias:
alias camconfig-studio='camconfig && echo "Load preset manually with R + studio"'
A CLI flag like --preset <name> is planned for a future release.
Backends
CamConfig uses a hybrid controller that tries the fastest backend first and falls back automatically when needed.
┌────────────────────────────────────────────┐
│ HybridCameraController │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Native │───▶│ v4l2-ctl │ │
│ │ (v4l crate) │fail│ (subprocess) │ │
│ └──────────────┘ └──────────────┘ │
└────────────────────────────────────────────┘
| Backend | Speed | Works when busy | Use case |
|---|---|---|---|
| native | Fast | No | Direct V4L2 API via the v4l crate. Preferred when the camera is free. |
| v4l2-ctl | Medium | Yes | Subprocess fallback using v4l2-ctl. Activated automatically on EBUSY or when --ctl is passed. |
| mock | N/A | N/A | In-memory fake camera for offline development and CI. |
When Does Fallback Happen?
When you open a device:
- CamConfig tries the native backend.
- If the device is busy (
EBUSY/ "resource temporarily unavailable"), it silently switches to v4l2-ctl. - The status bar shows
Backend: v4l2-ctl (fallback)so you know which path is active.
You never have to close Zoom or OBS to change settings.
Forcing a Backend
# Always use subprocess (useful for debugging or exotic drivers)
camconfig --ctl
# Use fake camera (no hardware at all)
camconfig --mock
Architecture
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Ratatui │────▶│ App/UI │────▶│ Event Loop │
│ (TUI) │◀────│ (state) │◀────│ (crossterm) │
└─────────────┘ └──────────────┘ └─────────────────┘
│
▼
┌──────────────────────────────┐
│ HybridCameraController │
│ (tries native, falls back) │
└──────────────────────────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ v4l crate │ │ v4l2-ctl │
│ (fast API) │ │ (subprocess) │
└──────────────┘ └──────────────┘
Module Overview
| File | Responsibility |
|---|---|
src/main.rs |
CLI parsing (clap), terminal setup, main event loop |
src/app.rs |
Application state machine (App), business logic, preset integration |
src/ui.rs |
Ratatui widgets — renders the three-panel layout and popups |
src/events.rs |
Crossterm event loop running in a dedicated thread, feeds AppMessage via MPSC |
src/controls.rs |
Data model: ControlMeta, ControlValue, ControlKind, ControlGroup, classification heuristics |
src/preset.rs |
Preset persistence (TOML) in XDG config dir, diff logic |
src/config.rs |
CLI argument definitions (clap derive) |
src/camera/mod.rs |
CameraController trait and DeviceInfo struct |
src/camera/v4l2.rs |
Native V4L2 implementation using the v4l crate |
src/camera/v4l2ctl.rs |
Subprocess implementation parsing v4l2-ctl output |
src/camera/hybrid.rs |
Auto-failover wrapper that switches backends on EBUSY |
src/camera/mock.rs |
Fake camera controller for unit tests and --mock mode |
src/lib.rs |
Public library exports (crate can be consumed as a library) |
Library API
CamConfig is published as both a binary and a library. You can embed the camera control logic in your own Rust applications:
use camconfig::camera::{hybrid::HybridCameraController, CameraController};
use camconfig::preset::{PresetManager, diff_snapshot};
fn main() -> anyhow::Result<()> {
// Create controller (native with automatic fallback)
let mut controller = HybridCameraController::new(false);
// List devices
for dev in controller.list_devices()? {
println!("{}", dev);
}
// Open a device
controller.open("/dev/video0")?;
// List controls
for ctrl in controller.list_controls()? {
println!("{} = {:?}", ctrl.name, controller.get_control(ctrl.id)?);
}
// Save a preset
let pm = PresetManager::new()?;
// ... build ControlSnapshot ...
// pm.save("my_preset", &snapshot)?;
Ok(())
}
See src/camera/mod.rs for the full CameraController trait and src/controls.rs for the data types.
Contributing
Prerequisites
- Rust 1.70+ (
rustup update stable) libv4l-devandv4l-utilscargo-audit(optional, for security audit)
Running Tests
# Unit and integration tests with mock backend (no camera needed)
cargo test
# Tests that require a real V4L2 device
cargo test -- --ignored
Code Quality
All PRs must pass the following checks (enforced in CI):
# Linting
cargo clippy --all-targets --all-features -- -D warnings
# Formatting
cargo fmt --all -- --check
# Security audit
cargo audit
# Build release to ensure no regressions
cargo build --release
Coding Standards
- Edition: Rust 2024
- No async runtime — keep the architecture simple and threaded.
- TDD preferred — use the mock backend to write tests before hardware integration.
- Minimal changes — prefer small, focused PRs.
- Follow existing style — match the formatting and naming conventions already present.
Reporting Issues
When opening a bug report, please include:
- Output of
camconfig --list - Output of
v4l2-ctl --list-ctrls --device /dev/videoX - The backend shown in the status bar (
nativeorv4l2-ctl (fallback)) - Steps to reproduce
Troubleshooting
"Device or resource busy" / camera is used by Zoom, OBS, or a browser
This is handled automatically. CamConfig detects the busy device and falls back to v4l2-ctl subprocess mode. You will see Backend: v4l2-ctl (fallback) in the status bar.
If the fallback also fails, ensure v4l-utils is installed:
sudo apt-get install v4l-utils
Permission denied on /dev/videoX
Add your user to the video group and log out/back in:
sudo usermod -aG video $USER
v4l2-ctl not found
Install the v4l-utils package:
sudo apt-get install v4l-utils
Zoom or focus controls do not appear
CamConfig only displays controls that your camera driver exposes through V4L2. Some cameras (including many Logitech models) do not expose zoom or focus via standard V4L2 controls. There is nothing CamConfig can do in that case — use the manufacturer's software or OBS plugins for digital zoom.
Also make sure you are not hiding them with a group filter. Press Esc to clear the filter.
"Permission denied" when loading or saving presets
If CamConfig was ever run as root, the preset directory may be owned by root:
sudo chown -R $USER:$USER ~/.config/camconfig
Controls not updating in real time
Some applications buffer camera settings. Changes made via CamConfig reflect immediately in most native apps (Zoom, OBS, Cheese). If not, try restarting the video stream in the target application.
Application crashes on startup with terminal errors
CamConfig uses raw terminal mode via crossterm. If it crashes, the terminal may be left in a broken state. Run:
reset
Or press Ctrl + C and then stty sane.
Native backend fails with an error that is NOT "busy"
Some drivers or kernel versions are incompatible with the v4l crate. Force the subprocess backend:
camconfig --ctl
Known Limitations
- String and compound control types are not yet supported (only integer, boolean, and menu).
- Menu item parsing depends on
v4l-utils >= 1.20output format. Very old distributions may show garbled menu names inv4l2-ctlfallback mode. - Linux only — V4L2 is a Linux kernel API. macOS and Windows are not supported.
- No autoload preset on startup — you must load presets manually with
Rafter launching. - No mouse support — the event loop ignores mouse events by design.
License
MIT © Feanor