Call Detection
emeet-pixyd detects video calls by monitoring which processes hold the camera device open. This works with any application — no per-app integration needed.
Mechanism
Section titled “Mechanism”Every EMEET_PIXYD_POLL_INTERVAL (default 2s), the daemon:
- Identifies the current video device path (e.g.,
/dev/video0) - Scans
/proc/*/fd/*for any file descriptor pointing to that device - Filters out itself and its own descendant processes
- If any remaining process holds the device open, the camera is “in use”
Process Tree Exclusion
Section titled “Process Tree Exclusion”The daemon excludes itself and its descendants to avoid false positives from its own MJPEG streaming or probing:
func isDescendantOf(pid, ancestor PID) bool// Walks /proc/*/stat to check if pid is a descendant of ancestorDebouncing
Section titled “Debouncing”State changes are debounced to prevent flicker. The daemon requires EMEET_PIXYD_DEBOUNCE_COUNT (default 3) consecutive agreeing cycles before triggering a state change.
| Setting | Default | Total delay |
|---|---|---|
| Poll interval | 2s | - |
| Debounce count | 3 | 6s |
This means:
- A call start is detected within ~6 seconds
- A call end is detected within ~6 seconds
- Momentary camera probes (e.g., from browser permission checks) don’t trigger false positives
Debounce Counters
Section titled “Debounce Counters”Two independent counters track consecutive “in use” and “idle” readings:
// skip-validatedebounceInUse int // incremented when camera is in usedebounceIdle int // incremented when camera is idleBoth are capped at debounceCount to prevent unbounded growth.
Call Lifecycle
Section titled “Call Lifecycle”Call Start
Section titled “Call Start”When the debounce threshold is crossed from idle to in-use:
handleCallStart()is called- Depending on auto mode:
- full: face tracking + noise cancellation + PipeWire source switch
- tracking-only: face tracking only
- privacy-only: nothing
- off: nothing
- Desktop notification sent
Call End
Section titled “Call End”When the debounce threshold is crossed from in-use to idle:
handleCallEnd()is called- Depending on auto mode:
- full: privacy mode + audio source restored
- tracking-only: privacy mode
- privacy-only: privacy mode
- off: nothing
- Desktop notification sent
PipeWire Integration
Section titled “PipeWire Integration”On call start (full mode), the daemon:
- Finds the PIXY audio source via
wpctl status - Switches the default source to PIXY via
wpctl set-default - On call end, restores the previous default source
The findSource and setSource functions are dependency-injected for testability.
Why /proc Instead of inotify?
Section titled “Why /proc Instead of inotify?”/proc/*/fd scanning is chosen over alternatives because:
- Universal: works with any app, no library integration needed
- Reliable: the fd is held open the entire time the camera is in use
- No permissions issues:
/procis readable by the owning user - No kernel modules: no custom driver or kernel patch required
The tradeoff is polling overhead, but at 2s intervals with efficient /proc traversal, the impact is negligible.
Where to go next
Section titled “Where to go next”- Auto Modes — what happens on call start and end
- HID Protocol — how state changes reach the hardware
- Configuration — tune debounce and poll interval
- Architecture Overview — full daemon structure
- Troubleshooting — fix detection and debounce issues