# fluxWebCam

A Window Maker style webcam snapshot dockapp for the Fluxbox slit. Watches a
snapshot file written by a capture script and repaints whenever it changes,
with a sunken border, a camera label, and a staleness tint when the frame
goes overdue. Left-click opens the full-resolution snapshot in a viewer,
right-click opens the live stream in a video player.

Part of the [Flux Dock Apps Suite](https://www.chrisspackman.com/software/flux-dock-apps/).

## Design: producer / consumer split on a file

fluxWebCam never fetches frames itself. The entire interface between capture
and display is **one file on disk, watched by mtime**:

```
 capture script            snapshot file              fluxWebCam
 (cron / fluxData)  ──►  name-latest.jpg   ──►  watch mtime, repaint,
 yt-dlp + ffmpeg                                 dim + badge if stale
```

Because they share nothing but the file, either side can be killed, resized,
or rewritten at any time without missing a captured frame. Run **one process
per camera**, each with its own config (`fluxWebCam --camera NAME`); the slit
owns position and ordering.

## Screenshot

```
┌──────────────┐      ┌──────────────┐
│              │      │  STALE 12m   │  ← red badge when the snapshot
│   [frame]    │      │  [dimmed]    │     is older than stale_after
│  Kenroku-en  │      │  Kenroku-en  │
└──────────────┘      └──────────────┘
```

## Installation

Requires Rust and a working X11 environment.

```sh
cargo build --release
./install.sh
```

The release build is stripped and LTO-optimized. No runtime dependencies
beyond libc and libX11.

## The capture side

fluxWebCam expects a companion script to keep a stable `name-latest.jpg`
pointer fresh. A minimal grabber resolves the stream and pulls one frame:

```sh
stream_url="$(yt-dlp -f 'bv[height<=1080]/b' -g "$URL")"
ffmpeg -hide_banner -loglevel warning -y -nostdin \
  -rw_timeout 20000000 -i "$stream_url" -frames:v 1 -an -f image2 "$tmp"
mv "$tmp" "$out/name-$(date +%F__%H-%M-%S).jpg"
ln -sfn "$out/name-...jpg" "$out/.name-latest.tmp"
mv -Tf "$out/.name-latest.tmp" "$out/name-latest.jpg"
```

`-nostdin` is essential: without it `ffmpeg` under `timeout` in a script gets
SIGTTIN-suspended and hangs. The atomic `mv` keeps consumers from ever seeing
a partial frame. Schedule the script with cron or fluxData — one capture
implementation, any scheduler in front.

## Usage

```
fluxWebCam --camera kenrokuen
```

Add one line per camera to your Fluxbox startup to embed them in the slit.

### Options

| Flag | Description |
|---|---|
| `--camera NAME` | Load `~/.config/fluxWebCam/NAME.config`; NAME is the default label |
| `--image PATH` | Snapshot file to watch |
| `--url URL` | Live stream URL (right-click) |
| `--label TEXT` | Label drawn along the bottom |
| `--stale-after SECS` | Mark stale after this many seconds (default 600) |
| `--interval SECS` | Snapshot re-check interval (default 3) |
| `--size SIZE` | Window size in pixels, 64 to 1024 (default 96; 64, 96 and 128 are the classic dockapp sizes) |
| `--font PATH` | Font file (default: fontconfig monospace) |
| `--viewer CMD` | Image viewer for left-click |
| `--player CMD` | Video player for right-click |
| `--theme THEME` | Color theme (e.g. zenburn) |
| `--background`, `--bg` | Run in background |
| `--write-config [PATH]` | Write annotated config file and exit |
| `--configure` | Interactive configuration wizard |
| `--config PATH` | Load config from PATH |
| `--dock N` | Open in slit N under NeoFlux (ignored elsewhere) |
| `--help` | Show help message |

### Click behavior

| Button | Action |
|---|---|
| Left click | Open the full-res snapshot in a viewer (or run `left_click`) |
| Middle click | Reload config (or run `middle_click`) |
| Right click | Open the live stream in a video player (or run `right_click`) |

### Staleness

The snapshot's mtime is the entire signal. When `now - mtime` exceeds
`stale_after`, the frame is dimmed and a red `STALE <age>` badge is overlaid
(`12s`, `8m`, `3h`, `2d`). A missing snapshot file shows `NO SIGNAL`. Set
`stale_after` a little above your capture interval. The display poll
(`interval`) and the capture cadence are independent; they meet only at
`stale_after`.

## Configuration

Config file: `~/.config/fluxWebCam/config`, or `~/.config/fluxWebCam/NAME.config`
with `--camera NAME`.

Generate an annotated config:

```sh
fluxWebCam --camera kenrokuen --write-config
```

Or use the interactive wizard:

```sh
fluxWebCam --configure
```

### Config options

```ini
# Snapshot image to watch (required)
image = ~/.local/share/fluxWebCam/kenrokuen-latest.jpg

# Live stream URL, opened on right-click
url = https://www.youtube.com/watch?v=...

# Display label
label = Kenroku-en

# Tint stale after this many seconds
stale_after = 600

# Snapshot re-check interval in seconds
interval = 3

# Window size in pixels, 64 to 1024 (64, 96 and 128 are the classic sizes)
size = 96

# Image viewer (left-click) and video player (right-click), auto-detected
viewer = feh
player = mpv
```

## License

[CC0 1.0](LICENSE) — Public Domain
