core: Add V4L2-backed FrameSource and camera enumeration
First hardware-touching FrameSource. Wraps the `v4l` crate to expose Linux
V4L2 capture through the same trait the mocks use. The two public entry
points cover the only operations the daemon needs:
- `enumerate()` walks /dev/video* and returns the cameras it can see.
- `V4l2FrameSource::open()` configures a specific device for capture.
# Exclusivity
V4L2 capture devices are exclusive while buffers are allocated: any other
process trying to open the same /dev/video* node gets EBUSY. The daemon's
intended pattern is therefore open-on-demand (per PAM auth request) and
drop-immediately-after so the camera stays available to other applications.
Drop cleanup must actually run — the v4l crate's `Stream<'a>` is
conservatively lifetime-bound to its `&Device`, so we use `self_cell` to
own both together and drop them in the right order (Stream first, then
Device).
# Feature shape
V4L2 is required for Pareidolia to actually run on a real system, so the
daemon's default feature set includes `v4l2` and the binary picks it up
out of the box. `pareidolia-core` keeps `v4l2` opt-in (default = []) so
consumers that only need the pipeline (mocks, format conversions) don't pay
the libv4l / bindgen build cost. CI passes `--no-default-features` to
disable v4l2 everywhere, which avoids needing libv4l in its Nixery image.
The workspace.dependencies entries set default-features = false on all the
internal crates so daemon's v4l2 doesn't transitively re-enable itself for
unrelated consumers (integration tests, the PAM module) through the dep
graph — a quirk of `cargo --workspace` feature unification.
A heuristic guesses IR vs RGB by checking whether the device advertises any
colour FourCCs (YUYV / RGB3 / MJPG). It's intentionally crude; the daemon's
config can override per device.