Commit Graph

8 Commits (bdecba0cf38e5943c14f0c4de8942a7c95e47501)

Author SHA1 Message Date
Amir Alexander Abdelbaki c2fa8d3e75 feat(presence-detect): back off while another app holds the camera
Before each tick, check whether another process already has the real camera
open (fuser, which sees same-user holders without root — every camera app runs
as us). If so, skip the read entirely for that tick: attempting would be
pointless (we'd get EBUSY) and a simultaneous open could make the other app
fail. A camera in use is proof the user is present, so mark presence and wait
it out, touching the camera zero times for the whole duration another app holds
it — instead of poking it every tick and racing the app for access.

V4L2 has no way to be notified that another app wants the device, so this
can't preempt an in-progress read; it just guarantees we never contend while
another app is already streaming. Falls through to a normal read (and the
Python helper's exit-3 busy path) if fuser isn't installed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 04:11:16 +02:00
Amir Alexander Abdelbaki 83687764d8 feat(presence-detect): skip the camera entirely during a manual caffeine session
When idle is inhibited manually (a caffeine toggle, not this daemon), the lock
is held regardless of presence, so polling the camera is pointless. Guard the
loop: if the shared idle lock is alive but we don't own it (same test as
caffeine-manual-status.sh), skip the read and leave the camera free for other
apps for the whole manual session. PRESENCE_FLAG is cleared while skipping,
since we're no longer watching — so the widget's presence input reads false
during a manual session instead of showing a stale "camera sees you".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 04:01:16 +02:00
Amir Alexander Abdelbaki bc544898c1 fix(presence-detect): read camera directly instead of v4l2loopback mirror
The loopback feeder (defa69f) held /dev/video0 open 24/7, so real apps —
video calls, howdy — could never open the camera, and the single loopback
device only ever served one reader at a time (a second consumer got
VIDIOC_REQBUFS -EBUSY). Net effect: the camera was permanently blocked and
nothing could access it.

Drop the ffmpeg mirror and read the physical camera directly, only for the
~0.5s per tick it takes to grab frames, then release it — so the camera is
free for other apps the rest of the time. When another app is already holding
the camera, the Python detector now distinguishes "busy/in use" (device node
present but unreadable → exit 3) from "unavailable" (node gone → exit 2), and
the daemon treats busy as presence: a camera in use is itself proof the user
is here, so a video call keeps the session awake.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 03:51:44 +02:00
Amir Alexander Abdelbaki 1fa5ebee70 feat(presence-detect): 4-state caffeine widget + steadier, faster detection
Rework the Eww caffeine widget into two independent inputs — manual inhibit
and presence detected — yielding four states across all three bars:
  neither            → 󰒲 sleep,  purple
  manual only        →  coffee, purple
  presence only      → 󰈈 eye,    pink
  manual + presence  →  coffee, pink
Colour tracks presence; the coffee cup shows only while manually inhibiting.

To make the two inputs independent, the daemon now writes a standalone
/tmp/presence-detected flag each tick (decoupled from lock ownership, so it's
true even during a manual session). presence-status.sh reports that flag
(presence input); new caffeine-manual-status.sh reports lock-held-and-not-
presence-owned (manual input).

Make detection less finnicky: space the detector's frame grabs (0.06s) so slow
movement registers and loopback reads never diff a stale duplicate frame, and
lower the thresholds (delta 25→18, area 2%→0.8%). Add a 90s grace window so
brief stillness never flickers presence off; any motion resets the clock.

Add a third poll tier — INTERVAL_FAST=2s when CPU-or-RAM usage is below 30%
(LOAD_LIGHT) — alongside the existing 20s normal and 120s busy tiers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 03:07:22 +02:00
Amir Alexander Abdelbaki defa69faab feat(presence-detect): mirror camera via v4l2loopback so the daemon never blocks it
presence-detect.sh now feeds the real webcam into a v4l2loopback virtual
device (/dev/video42) via a self-healing ffmpeg background process, and reads
motion off that mirror instead of opening the physical camera directly. This
means the daemon's 20s-interval checks never contend with other apps (video
calls, howdy) for exclusive camera access. Falls back to direct camera access
if the loopback isn't set up yet, so the feature degrades gracefully rather
than breaking.

New etc-v4l2loopback/ ships the modules-load.d/modprobe.d config (fixed
video_nr=42, exclusive_caps=1) following the repo's existing etc-greetd/
etc-lightdm/ convention. Deliberately no runtime sudo/modprobe calls inside
the daemon itself — the module loads declaratively at boot instead, matching
this repo's existing caution around unattended sudo.

hyprlua.sh and niri.sh installers now pull in linux-headers,
v4l2loopback-dkms, and v4l2loopback-utils and deploy the module config
immediately. sysupdate.sh gained _ensure_v4l2loopback(), wired into
sync_configs(), so existing machines pick up the new packages and /etc
config on their next --config or --both run without needing a fresh install.

enroll-biometrics.sh's camera picker now excludes the loopback device itself
(by card_label) so configuring the real camera can't accidentally target the
mirror it feeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 16:39:11 +02:00
Amir Alexander Abdelbaki f90dbdae8b feat(presence-detect): switch to motion detection, adaptive interval, shared caffeine lock
Replace the Haar-cascade face detector with lightweight frame-differencing
motion detection so the daemon can poll every 20s (backing off to 120s under
heavy CPU/RAM load) without adding noticeable overhead.

Presence detection now shares caffeine's systemd-inhibit lock instead of
holding its own, so the caffeine status widget reflects either source. A new
ownership flag ensures the daemon only ever releases a lock it acquired
itself, so a manually-started caffeine session is never interrupted by the
user stepping away. Also re-enables the daemon in hyprlua's autostart (was
previously only wired up for niri).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 16:23:49 +02:00
Amir Alexander Abdelbaki cb464c830f chore: add inline comments across all modules and configs 2026-06-25 13:07:06 +02:00
Amir Alexander Abdelbaki 9638fd337a feat(hyprlua/niri): webcam presence detection + systemd-inhibit idle/caffeine
- Add presence-detect.sh daemon: checks webcam every 2 min via OpenCV haar
  cascade (presence_detect.py); holds systemd-inhibit --what=idle lock while a
  face is detected so hypridle never fires during an active session
- Add enroll-biometrics.sh: Cyberqueer dialog TUI for camera configuration/test
  and howdy face auth enrollment (add/list/remove/test models)
- Rewrite caffeine.sh (hyprlua + niri): replace kill/restart of idle daemon with
  systemd-inhibit --what=idle:sleep + PID file; idle daemon stays running
- Fix hypridle.conf: correct ;; → ; in after_sleep_cmd, add
  ignore_dbus_inhibit=false, bump lock timeout 120s→180s to account for the
  2-min presence detection cycle
- Wire Super+Shift+B keybind for enrollment TUI in both hyprlua and niri
- Fix niri/scripts/python: was real dir with inner symlink causing cp -rL to
  create a nested python/python/ hierarchy; replaced with direct symlink
- Add python-opencv and v4l-utils to hyprlua + niri installers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 14:50:36 +02:00