feat(hardware): hide dual-sensor webcam IR node (fixes dark image)

A built-in RGB+IR webcam (common on recent ThinkPads, e.g. the T14s AMD
Gen 4) exposes its IR face-unlock sensor as a second, identically-named
"Integrated Camera"; apps that pick it show a dark, 8-bit-greyscale
image. nomarchy.hardware.camera.hideIrSensor disables that node on the
V4L2 PipeWire path (WirePlumber monitor.v4l2.rules) so only the colour
camera is offered, matched by card name (overridable irMatch).

- libcamera is left untouched: an external camera you plug in is never
  affected, and surgical internal-only libcamera scoping isn't possible
  (its distinguishing props bind after the monitor rule runs).
- Only the PipeWire node is hidden; the kernel /dev/video* stays open,
  so Howdy face-unlock still reads the IR sensor directly.
- The installer's hardware-db.sh auto-detects a paired RGB+IR webcam
  from /sys/class/video4linux/*/name and bakes the toggle into
  system.nix; commented example added to the downstream template.

Validated live on a T14s: collapses the four enumerated "Integrated
Camera" entries to one colour source; the exact shipped regex disables
the IR node (confirmed in the WirePlumber log) and leaves libcamera
intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Bernardo Magri
2026-06-27 00:39:49 +01:00
parent 431af618cc
commit cdfe92a089
5 changed files with 152 additions and 5 deletions

View File

@@ -184,6 +184,31 @@ nomarchy_detect_hw() {
echo "DETAIL fingerprint reader detected → fprintd (PAM login/sudo opt-in)"
fi
# Dual-sensor webcam (RGB + IR face-unlock). Such modules expose the IR
# sensor as a SECOND, identically-named "Integrated Camera"; an app that
# picks it shows a dark, 8-bit-greyscale image. When an IR-companion node
# sits alongside a normal camera node, enable hiding the IR one from
# PipeWire (the colour camera stays; the kernel /dev/video* is untouched, so
# Howdy can still read the IR sensor directly). Read from /sys — no
# v4l2-ctl needed in the installer env.
local cam_name have_ir_cam=0 have_color_cam=0 vf
local ir_re='Integrated I|IR Camera|Infrared'
for vf in /sys/class/video4linux/video*/name; do
[[ -e "$vf" ]] || continue
cam_name=$(cat "$vf" 2>/dev/null)
shopt -s nocasematch
if [[ "$cam_name" =~ $ir_re ]]; then
have_ir_cam=1
elif [[ "$cam_name" =~ (Camera|Webcam) ]]; then
have_color_cam=1
fi
shopt -u nocasematch
done
if [[ $have_ir_cam -eq 1 && $have_color_cam -eq 1 ]]; then
echo "NOMARCHY hardware.camera.hideIrSensor=true"
echo "DETAIL dual-sensor webcam (RGB+IR) → hide the IR node so apps get the colour camera"
fi
# NPU (detect-only → a commented, experimental opt-in). Match the PCI
# "Processing accelerators" class [1200] (or accelerator keywords) and
# attribute by vendor — future-proof vs a per-device-ID list, so new gens

View File

@@ -446,14 +446,15 @@ fi
# experimental opt-ins written commented for the user to flip on.
HARDWARE_CONFIG=""
if [[ ${#HW_NOMARCHY[@]} -gt 0 || -n "$NPU_VENDOR" ]]; then
has_intel=0; has_amd=0; has_fp=0; intel_guc_off=0
has_intel=0; has_amd=0; has_fp=0; intel_guc_off=0; has_camera_ir=0
if [[ ${#HW_NOMARCHY[@]} -gt 0 ]]; then
for nm in "${HW_NOMARCHY[@]}"; do
case "$nm" in
hardware.intel.enable=true) has_intel=1 ;;
hardware.intel.guc=false) intel_guc_off=1 ;;
hardware.amd.enable=true) has_amd=1 ;;
hardware.fingerprint.enable=true) has_fp=1 ;;
hardware.intel.enable=true) has_intel=1 ;;
hardware.intel.guc=false) intel_guc_off=1 ;;
hardware.amd.enable=true) has_amd=1 ;;
hardware.fingerprint.enable=true) has_fp=1 ;;
hardware.camera.hideIrSensor=true) has_camera_ir=1 ;;
esac
done
fi
@@ -479,6 +480,10 @@ if [[ ${#HW_NOMARCHY[@]} -gt 0 || -n "$NPU_VENDOR" ]]; then
hw_lines+="
nomarchy.hardware.fingerprint.enable = true; # fprintd (enroll: fprintd-enroll)
# nomarchy.hardware.fingerprint.pam = true; # use it for login + sudo (opt-in)"
fi
if [[ $has_camera_ir -eq 1 ]]; then
hw_lines+="
nomarchy.hardware.camera.hideIrSensor = true; # dual-sensor webcam: hide the IR node (color cam only)"
fi
if [[ -n "$NPU_VENDOR" ]]; then
hw_lines+="