Configure Webcam for detail control
Find a file
Jonatas Oliveira 7bdab35b23
Some checks failed
CI / audit (push) Has been cancelled
CI / clippy (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / test (push) Has been cancelled
chore: add gitignore
2026-06-08 10:13:33 +02:00
.github/workflows feat: add controls for camera 2026-06-08 10:05:42 +02:00
src feat: add controls for camera 2026-06-08 10:05:42 +02:00
tests feat: add controls for camera 2026-06-08 10:05:42 +02:00
.gitignore chore: add gitignore 2026-06-08 10:13:33 +02:00
.woodpecker.yml feat: add controls for camera 2026-06-08 10:05:42 +02:00
Cargo.lock feat: add controls for camera 2026-06-08 10:05:42 +02:00
Cargo.toml feat: add controls for camera 2026-06-08 10:05:42 +02:00
README.md feat: add controls for camera 2026-06-08 10:05:42 +02:00

CamConfig

CI License: MIT Rust

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

  • Real-time control — Adjust any V4L2 control while the camera is in use. Changes apply instantly.
  • Busy-device fallback — Automatically switches to v4l2-ctl subprocess 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 g to 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 (NoneImageColorExposureLensOther)
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
az, 09, _, - 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 g to filter.
  • 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.
  • 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 literal
  • Booleantrue / false
  • Menu → 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:

  1. CamConfig tries the native backend.
  2. If the device is busy (EBUSY / "resource temporarily unavailable"), it silently switches to v4l2-ctl.
  3. 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-dev and v4l-utils
  • cargo-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:

  1. Output of camconfig --list
  2. Output of v4l2-ctl --list-ctrls --device /dev/videoX
  3. The backend shown in the status bar (native or v4l2-ctl (fallback))
  4. 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.20 output format. Very old distributions may show garbled menu names in v4l2-ctl fallback 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 R after launching.
  • No mouse support — the event loop ignores mouse events by design.

License

MIT © Feanor