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>main^2
parent
65ac398de6
commit
9638fd337a
|
|
@ -20,4 +20,5 @@ hl.on("hyprland.start", function()
|
||||||
hl.exec_cmd("blueman-applet")
|
hl.exec_cmd("blueman-applet")
|
||||||
hl.exec_cmd("blueman-tray")
|
hl.exec_cmd("blueman-tray")
|
||||||
hl.exec_cmd("hypridle")
|
hl.exec_cmd("hypridle")
|
||||||
|
hl.exec_cmd("bash ~/Dotfiles/desktopenvs/hyprlua/scripts/presence-detect.sh")
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,7 @@ hl.bind(mainMod .. " + CTRL + Z", hl.dsp.exec_cmd("chamel clear-and-deactivate")
|
||||||
hl.bind(mainMod .. " + CTRL + C", hl.dsp.exec_cmd("dunstctl close-all"))
|
hl.bind(mainMod .. " + CTRL + C", hl.dsp.exec_cmd("dunstctl close-all"))
|
||||||
hl.bind(mainMod .. " + CTRL + G", hl.dsp.exec_cmd("~/.config/scripts/onscreenkb.sh"))
|
hl.bind(mainMod .. " + CTRL + G", hl.dsp.exec_cmd("~/.config/scripts/onscreenkb.sh"))
|
||||||
hl.bind(mainMod .. " + SHIFT + C", hl.dsp.exec_cmd("~/.config/scripts/caffeine.sh"))
|
hl.bind(mainMod .. " + SHIFT + C", hl.dsp.exec_cmd("~/.config/scripts/caffeine.sh"))
|
||||||
|
hl.bind(mainMod .. " + SHIFT + B", hl.dsp.exec_cmd("[tag +centered-S] kitty bash ~/.config/scripts/enroll-biometrics.sh"))
|
||||||
hl.bind(mainMod .. " + SHIFT + X", hl.dsp.exec_cmd("~/.config/scripts/hyprland-toggle-touchpad.sh"))
|
hl.bind(mainMod .. " + SHIFT + X", hl.dsp.exec_cmd("~/.config/scripts/hyprland-toggle-touchpad.sh"))
|
||||||
|
|
||||||
hl.bind(mainMod .. " + CTRL + E", hl.dsp.exec_cmd("~/.config/scripts/screenrotationwcw.sh"))
|
hl.bind(mainMod .. " + CTRL + E", hl.dsp.exec_cmd("~/.config/scripts/screenrotationwcw.sh"))
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,19 @@
|
||||||
#source ~/.config/idle.conf
|
|
||||||
general {
|
general {
|
||||||
lock_cmd = pidof hyprlock || hyprlock # avoid starting multiple hyprlock instances.
|
lock_cmd = pidof hyprlock || hyprlock
|
||||||
before_sleep_cmd = loginctl lock-session # lock before suspend.
|
before_sleep_cmd = loginctl lock-session
|
||||||
after_sleep_cmd = systemctl restart fprintd.service ;; hyprctl dispatch dpms on # to avoid having to press a key twice to turn on the display.
|
# fprintd restart ensures fingerprint sensor is ready after resume
|
||||||
|
after_sleep_cmd = systemctl restart fprintd.service ; hyprctl dispatch dpms on
|
||||||
|
ignore_dbus_inhibit = false # respect systemd-inhibit locks (presence-detect, caffeine)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Presence detection resets the idle timer every 2 minutes while you're visible,
|
||||||
|
# so these timeouts only run when you've actually stepped away.
|
||||||
listener {
|
listener {
|
||||||
timeout = 120
|
timeout = 180 # 3 min — lock screen
|
||||||
on-timeout = loginctl lock-session # lock screen when timeout has passed
|
on-timeout = loginctl lock-session
|
||||||
}
|
}
|
||||||
|
|
||||||
listener {
|
listener {
|
||||||
timeout = 600 #10min
|
timeout = 600 # 10 min — suspend
|
||||||
on-timeout = systemctl suspend-then-hibernate # suspend pc
|
on-timeout = systemctl suspend-then-hibernate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#listener {
|
|
||||||
# timeout = 18000 #5h
|
|
||||||
# on-timeout = /usr/bin/reboot #reboot
|
|
||||||
#}
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Toggle idle inhibit via systemd-inhibit (hypridle respects the logind idle hint).
|
||||||
|
PID_FILE="/tmp/caffeine-inhibit.pid"
|
||||||
|
|
||||||
statecon=$( pidof hypridle | grep "[1234567890]" )
|
if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
||||||
#echo $statecon
|
kill "$(cat "$PID_FILE")"
|
||||||
if [ "$statecon" != "" ]; then
|
rm -f "$PID_FILE"
|
||||||
|
notify-send -t 2000 "Caffeine" "Idle inhibit OFF"
|
||||||
notify-send -t 1000 "caffeine mode on"
|
|
||||||
killall hypridle
|
|
||||||
|
|
||||||
else
|
else
|
||||||
|
systemd-inhibit --what=idle:sleep \
|
||||||
notify-send -t 1000 "caffeine mode off"
|
--who="caffeine" \
|
||||||
hypridle &
|
--why="Caffeine mode active" \
|
||||||
|
--mode=block \
|
||||||
|
sleep infinity &
|
||||||
|
echo $! > "$PID_FILE"
|
||||||
|
notify-send -t 2000 "Caffeine" "Idle inhibited"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# enroll-biometrics.sh — TUI for face biometric setup.
|
||||||
|
#
|
||||||
|
# Two subsystems:
|
||||||
|
# 1. Presence detection — configure/test the webcam used by presence-detect.sh
|
||||||
|
# 2. Howdy face auth — enroll/manage/test face models for PAM authentication
|
||||||
|
|
||||||
|
BACKTITLE="Biometric Enrollment"
|
||||||
|
PRESENCE_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/presence-detect.conf"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PYTHON_DETECT="$SCRIPT_DIR/python/presence_detect.py"
|
||||||
|
|
||||||
|
# ── Dialog theme (Cyberqueer) ─────────────────────────────────────────────────
|
||||||
|
TMP_D=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMP_D"' EXIT INT TERM
|
||||||
|
|
||||||
|
export DIALOGRC="$TMP_D/dialogrc"
|
||||||
|
cat > "$DIALOGRC" << 'RCEOF'
|
||||||
|
use_shadow = ON
|
||||||
|
use_colors = ON
|
||||||
|
screen_color = (BLACK,BLACK,ON)
|
||||||
|
shadow_color = (BLACK,BLACK,ON)
|
||||||
|
title_color = (MAGENTA,BLACK,ON)
|
||||||
|
border_color = (MAGENTA,BLACK,ON)
|
||||||
|
button_active_color = (BLACK,MAGENTA,ON)
|
||||||
|
button_inactive_color = (WHITE,BLACK,OFF)
|
||||||
|
button_key_active_color = (BLACK,CYAN,ON)
|
||||||
|
button_key_inactive_color = (CYAN,BLACK,ON)
|
||||||
|
button_label_active_color = (BLACK,MAGENTA,ON)
|
||||||
|
button_label_inactive_color = (WHITE,BLACK,OFF)
|
||||||
|
inputbox_color = (WHITE,BLACK,OFF)
|
||||||
|
inputbox_border_color = (MAGENTA,BLACK,ON)
|
||||||
|
menubox_color = (WHITE,BLACK,OFF)
|
||||||
|
menubox_border_color = (MAGENTA,BLACK,ON)
|
||||||
|
item_color = (WHITE,BLACK,OFF)
|
||||||
|
item_selected_color = (BLACK,MAGENTA,ON)
|
||||||
|
tag_color = (CYAN,BLACK,ON)
|
||||||
|
tag_selected_color = (BLACK,CYAN,ON)
|
||||||
|
check_color = (WHITE,BLACK,OFF)
|
||||||
|
check_selected_color = (BLACK,MAGENTA,ON)
|
||||||
|
uarrow_color = (MAGENTA,BLACK,ON)
|
||||||
|
darrow_color = (MAGENTA,BLACK,ON)
|
||||||
|
RCEOF
|
||||||
|
|
||||||
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
die() { clear; printf "\nError: %s\n\n" "$1" >&2; exit 1; }
|
||||||
|
|
||||||
|
msg() {
|
||||||
|
dialog --backtitle "$BACKTITLE" --title " $1 " --msgbox "\n$2\n" "$3" "$4"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_dialog() {
|
||||||
|
command -v dialog &>/dev/null && return
|
||||||
|
sudo pacman -S --noconfirm dialog || die "dialog not found"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Camera helpers ─────────────────────────────────────────────────────────────
|
||||||
|
list_cameras() {
|
||||||
|
for dev in /dev/video*; do
|
||||||
|
[[ -c "$dev" ]] || continue
|
||||||
|
local id="${dev#/dev/video}"
|
||||||
|
local name
|
||||||
|
name=$(v4l2-ctl --device="$dev" --info 2>/dev/null \
|
||||||
|
| awk -F': ' '/Card type/{print $2}' | head -1)
|
||||||
|
[[ -z "$name" ]] && name="Camera $id"
|
||||||
|
printf '%s\n%s\n' "$id" "$name"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
get_camera_id() {
|
||||||
|
if [[ -n "$PRESENCE_DETECT_CAMERA" ]]; then
|
||||||
|
echo "$PRESENCE_DETECT_CAMERA"
|
||||||
|
elif [[ -f "$PRESENCE_CFG" ]]; then
|
||||||
|
grep -oP 'CAMERA=\K[0-9]+' "$PRESENCE_CFG" 2>/dev/null || echo 0
|
||||||
|
else
|
||||||
|
echo 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
set_camera_id() {
|
||||||
|
mkdir -p "$(dirname "$PRESENCE_CFG")"
|
||||||
|
printf 'CAMERA=%s\n' "$1" > "$PRESENCE_CFG"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Presence detection ────────────────────────────────────────────────────────
|
||||||
|
presence_configure_camera() {
|
||||||
|
local -a cam_items
|
||||||
|
mapfile -t cam_items < <(list_cameras)
|
||||||
|
|
||||||
|
if [[ ${#cam_items[@]} -eq 0 ]]; then
|
||||||
|
msg "No Cameras Found" \
|
||||||
|
"No video devices found at /dev/video*.\n\nMake sure your webcam is connected." \
|
||||||
|
10 55
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local current; current=$(get_camera_id)
|
||||||
|
local choice
|
||||||
|
choice=$(dialog --backtitle "$BACKTITLE" \
|
||||||
|
--title " Select Camera " \
|
||||||
|
--menu "\nCurrent: /dev/video${current}\n\nSelect camera to use for presence detection:" \
|
||||||
|
16 62 6 \
|
||||||
|
"${cam_items[@]}" \
|
||||||
|
3>&1 1>&2 2>&3) || return
|
||||||
|
|
||||||
|
set_camera_id "$choice"
|
||||||
|
msg "Camera Set" \
|
||||||
|
"Presence detection will use /dev/video${choice}.\n\nRestart presence-detect.sh for the change to take effect." \
|
||||||
|
9 62
|
||||||
|
}
|
||||||
|
|
||||||
|
presence_test_camera() {
|
||||||
|
local cam; cam=$(get_camera_id)
|
||||||
|
|
||||||
|
clear
|
||||||
|
printf "\n\033[1;35m Testing presence detection on /dev/video%s...\033[0m\n" "$cam"
|
||||||
|
printf "\033[35m ─────────────────────────────────────────\033[0m\n\n"
|
||||||
|
printf " Look at the camera and stay still.\n\n"
|
||||||
|
|
||||||
|
python3 "$PYTHON_DETECT" "$cam" 2>/dev/null
|
||||||
|
local rc=$?
|
||||||
|
|
||||||
|
case $rc in
|
||||||
|
0) msg "Test Result" "Face detected!\n\nPresence detection is working correctly." 8 52 ;;
|
||||||
|
1) msg "Test Result" \
|
||||||
|
"No face detected.\n\nMake sure you are in front of the camera\nand there is adequate lighting." \
|
||||||
|
10 56 ;;
|
||||||
|
2) msg "Camera Error" \
|
||||||
|
"Could not open /dev/video${cam}.\n\nTry configuring a different camera first." \
|
||||||
|
9 56 ;;
|
||||||
|
*) msg "Error" "Unexpected exit code ($rc) from detection script." 7 52 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Howdy helpers ─────────────────────────────────────────────────────────────
|
||||||
|
howdy_installed() { command -v howdy &>/dev/null; }
|
||||||
|
|
||||||
|
howdy_require() {
|
||||||
|
howdy_installed && return 0
|
||||||
|
dialog --backtitle "$BACKTITLE" --title " Howdy Not Found " \
|
||||||
|
--yesno "\nhowdy is not installed.\n\nInstall it now via yay?" 8 48 || return 1
|
||||||
|
clear
|
||||||
|
printf "\nInstalling howdy...\n\n"
|
||||||
|
if command -v yay &>/dev/null; then
|
||||||
|
yay -S --noconfirm --needed howdy
|
||||||
|
else
|
||||||
|
sudo pacman -S --noconfirm --needed howdy
|
||||||
|
fi
|
||||||
|
howdy_installed || { msg "Install Failed" "howdy installation failed.\nInstall it manually: yay -S howdy" 8 52; return 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Howdy operations ──────────────────────────────────────────────────────────
|
||||||
|
howdy_add() {
|
||||||
|
howdy_require || return
|
||||||
|
|
||||||
|
local name
|
||||||
|
name=$(dialog --backtitle "$BACKTITLE" \
|
||||||
|
--title " Add Face Model " \
|
||||||
|
--inputbox "\nEnter a label for this face model:" 8 52 "$USER" \
|
||||||
|
3>&1 1>&2 2>&3) || return
|
||||||
|
[[ -z "$name" ]] && return
|
||||||
|
|
||||||
|
clear
|
||||||
|
printf "\n\033[1;35m Enrolling face model '%s'...\033[0m\n" "$name"
|
||||||
|
printf "\033[35m ─────────────────────────────────────────\033[0m\n\n"
|
||||||
|
printf " Look at the camera and hold still.\n\n"
|
||||||
|
sudo howdy add -n "$name"
|
||||||
|
local rc=$?
|
||||||
|
|
||||||
|
if [[ $rc -eq 0 ]]; then
|
||||||
|
msg "Enrolled" \
|
||||||
|
"Face model '$name' added.\n\nYou may need to relogin for PAM changes to take effect." \
|
||||||
|
9 58
|
||||||
|
else
|
||||||
|
msg "Failed" "Enrollment failed (code $rc).\n\nCheck camera and lighting." 8 52
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
howdy_list() {
|
||||||
|
howdy_require || return
|
||||||
|
local out; out=$(sudo howdy list 2>&1)
|
||||||
|
msg "Enrolled Face Models" "$out" 20 64
|
||||||
|
}
|
||||||
|
|
||||||
|
howdy_remove() {
|
||||||
|
howdy_require || return
|
||||||
|
|
||||||
|
local models; models=$(sudo howdy list 2>&1)
|
||||||
|
if [[ -z "$models" || "$models" == *"no models"* ]]; then
|
||||||
|
msg "No Models" "No face models are currently enrolled." 7 44
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local id
|
||||||
|
id=$(dialog --backtitle "$BACKTITLE" \
|
||||||
|
--title " Remove Face Model " \
|
||||||
|
--inputbox "\nCurrent models:\n\n${models}\n\nEnter model ID to remove:" 20 64 \
|
||||||
|
3>&1 1>&2 2>&3) || return
|
||||||
|
[[ -z "$id" || ! "$id" =~ ^[0-9]+$ ]] && {
|
||||||
|
msg "Invalid ID" "Please enter a numeric model ID." 6 36; return
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog --backtitle "$BACKTITLE" --title " Confirm " \
|
||||||
|
--yesno "\nRemove face model ID $id?" 7 40 || return
|
||||||
|
|
||||||
|
sudo howdy remove -I "$id"
|
||||||
|
msg "Done" "Face model $id removed." 6 38
|
||||||
|
}
|
||||||
|
|
||||||
|
howdy_test() {
|
||||||
|
howdy_require || return
|
||||||
|
clear
|
||||||
|
printf "\n\033[1;35m Testing howdy authentication...\033[0m\n"
|
||||||
|
printf "\033[35m ─────────────────────────────────────────\033[0m\n\n"
|
||||||
|
printf " Look at the camera.\n\n"
|
||||||
|
sudo howdy test
|
||||||
|
local rc=$?
|
||||||
|
if [[ $rc -eq 0 ]]; then
|
||||||
|
msg "Result" "Authentication successful." 6 38
|
||||||
|
else
|
||||||
|
msg "Result" "Authentication failed (code $rc).\n\nCheck enrolled models and camera setup." 9 52
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Main menu ─────────────────────────────────────────────────────────────────
|
||||||
|
main_menu() {
|
||||||
|
local choice
|
||||||
|
choice=$(dialog --backtitle "$BACKTITLE" \
|
||||||
|
--title " Biometric Enrollment " \
|
||||||
|
--menu "\nSelect an option:" 20 66 8 \
|
||||||
|
"1" "Presence detection — configure camera" \
|
||||||
|
"2" "Presence detection — test detection" \
|
||||||
|
"3" "Howdy face auth — add face model" \
|
||||||
|
"4" "Howdy face auth — list enrolled models" \
|
||||||
|
"5" "Howdy face auth — remove face model" \
|
||||||
|
"6" "Howdy face auth — test authentication" \
|
||||||
|
3>&1 1>&2 2>&3) || { clear; exit 0; }
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
1) presence_configure_camera ;;
|
||||||
|
2) presence_test_camera ;;
|
||||||
|
3) howdy_add ;;
|
||||||
|
4) howdy_list ;;
|
||||||
|
5) howdy_remove ;;
|
||||||
|
6) howdy_test ;;
|
||||||
|
esac
|
||||||
|
main_menu
|
||||||
|
}
|
||||||
|
|
||||||
|
require_dialog
|
||||||
|
main_menu
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Webcam presence detection daemon.
|
||||||
|
# Checks for a face every 2 minutes; holds a systemd-inhibit idle lock while
|
||||||
|
# the user is detected so hypridle never fires during an active session.
|
||||||
|
#
|
||||||
|
# Camera selection: set PRESENCE_DETECT_CAMERA env var or write
|
||||||
|
# CAMERA=<id> to ~/.config/presence-detect.conf
|
||||||
|
#
|
||||||
|
# Exit codes from python helper: 0=face, 1=no face, 2=camera error
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PYTHON_DETECT="$SCRIPT_DIR/python/presence_detect.py"
|
||||||
|
INHIBIT_PID_FILE="/tmp/presence-inhibit.pid"
|
||||||
|
PRESENCE_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/presence-detect.conf"
|
||||||
|
INTERVAL=120 # seconds between checks
|
||||||
|
|
||||||
|
_camera_id() {
|
||||||
|
if [[ -n "$PRESENCE_DETECT_CAMERA" ]]; then
|
||||||
|
echo "$PRESENCE_DETECT_CAMERA"
|
||||||
|
elif [[ -f "$PRESENCE_CFG" ]]; then
|
||||||
|
grep -oP 'CAMERA=\K[0-9]+' "$PRESENCE_CFG" 2>/dev/null || echo 0
|
||||||
|
else
|
||||||
|
echo 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_inhibit_running() {
|
||||||
|
[[ -f "$INHIBIT_PID_FILE" ]] && kill -0 "$(cat "$INHIBIT_PID_FILE")" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
_start_inhibit() {
|
||||||
|
_inhibit_running && return
|
||||||
|
systemd-inhibit --what=idle --who="presence-detect" \
|
||||||
|
--why="User presence detected" --mode=block \
|
||||||
|
sleep infinity &
|
||||||
|
echo $! > "$INHIBIT_PID_FILE"
|
||||||
|
logger -t presence-detect "Presence detected — idle inhibited"
|
||||||
|
}
|
||||||
|
|
||||||
|
_stop_inhibit() {
|
||||||
|
_inhibit_running || return
|
||||||
|
kill "$(cat "$INHIBIT_PID_FILE")" 2>/dev/null
|
||||||
|
rm -f "$INHIBIT_PID_FILE"
|
||||||
|
logger -t presence-detect "No presence — idle inhibit released"
|
||||||
|
}
|
||||||
|
|
||||||
|
_cleanup() {
|
||||||
|
_stop_inhibit
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
trap _cleanup SIGTERM SIGINT SIGHUP
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
CAMERA="$(_camera_id)"
|
||||||
|
python3 "$PYTHON_DETECT" "$CAMERA" 2>/dev/null
|
||||||
|
rc=$?
|
||||||
|
case $rc in
|
||||||
|
0) _start_inhibit ;;
|
||||||
|
1) _stop_inhibit ;;
|
||||||
|
# rc=2 = camera unavailable — leave current inhibit state unchanged
|
||||||
|
esac
|
||||||
|
sleep "$INTERVAL"
|
||||||
|
done
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Webcam face presence detector using OpenCV haar cascades.
|
||||||
|
Exit codes: 0 = face detected, 1 = no face, 2 = camera error
|
||||||
|
Usage: presence_detect.py [camera_id]
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import cv2
|
||||||
|
|
||||||
|
FRAMES_TO_CHECK = 6
|
||||||
|
FACES_NEEDED = 2 # require face in at least N frames to count as present
|
||||||
|
SCALE_FACTOR = 1.1
|
||||||
|
MIN_NEIGHBORS = 4
|
||||||
|
|
||||||
|
|
||||||
|
def detect(camera_id: int) -> int:
|
||||||
|
cap = cv2.VideoCapture(camera_id)
|
||||||
|
if not cap.isOpened():
|
||||||
|
return 2
|
||||||
|
|
||||||
|
cascade = cv2.CascadeClassifier(
|
||||||
|
cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
|
||||||
|
)
|
||||||
|
detected = 0
|
||||||
|
try:
|
||||||
|
for _ in range(FRAMES_TO_CHECK):
|
||||||
|
ok, frame = cap.read()
|
||||||
|
if not ok:
|
||||||
|
continue
|
||||||
|
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||||
|
faces = cascade.detectMultiScale(gray, SCALE_FACTOR, MIN_NEIGHBORS)
|
||||||
|
if len(faces) > 0:
|
||||||
|
detected += 1
|
||||||
|
finally:
|
||||||
|
cap.release()
|
||||||
|
|
||||||
|
return 0 if detected >= FACES_NEEDED else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
camera_id = int(sys.argv[1]) if len(sys.argv) > 1 else 0
|
||||||
|
sys.exit(detect(camera_id))
|
||||||
|
|
@ -9,4 +9,5 @@ spawn-at-startup "bash" "-c" "gammastep -O 4500"
|
||||||
spawn-at-startup "nm-applet"
|
spawn-at-startup "nm-applet"
|
||||||
spawn-at-startup "dunst"
|
spawn-at-startup "dunst"
|
||||||
spawn-at-startup "bash" "-c" "swayidle -w timeout 300 'swaylock -f' timeout 600 'systemctl suspend' before-sleep 'swaylock -f'"
|
spawn-at-startup "bash" "-c" "swayidle -w timeout 300 'swaylock -f' timeout 600 'systemctl suspend' before-sleep 'swaylock -f'"
|
||||||
|
spawn-at-startup "bash" "-c" "~/.config/scripts/presence-detect.sh"
|
||||||
spawn-at-startup "blueman-applet"
|
spawn-at-startup "blueman-applet"
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,7 @@ binds {
|
||||||
Mod+Ctrl+C { spawn "dunstctl" "close-all"; }
|
Mod+Ctrl+C { spawn "dunstctl" "close-all"; }
|
||||||
Mod+Ctrl+G { spawn "bash" "-c" "~/.config/scripts/onscreenkb.sh"; }
|
Mod+Ctrl+G { spawn "bash" "-c" "~/.config/scripts/onscreenkb.sh"; }
|
||||||
Mod+Shift+C { spawn "bash" "-c" "~/.config/scripts/caffeine.sh"; }
|
Mod+Shift+C { spawn "bash" "-c" "~/.config/scripts/caffeine.sh"; }
|
||||||
|
Mod+Shift+B { spawn "bash" "-c" "kitty -e ~/.config/scripts/enroll-biometrics.sh"; }
|
||||||
Mod+Shift+X { spawn "bash" "-c" "~/.config/scripts/niri-toggle-touchpad.sh"; }
|
Mod+Shift+X { spawn "bash" "-c" "~/.config/scripts/niri-toggle-touchpad.sh"; }
|
||||||
|
|
||||||
Mod+Ctrl+E { spawn "bash" "-c" "~/.config/scripts/screenrotationwcw.sh"; }
|
Mod+Ctrl+E { spawn "bash" "-c" "~/.config/scripts/screenrotationwcw.sh"; }
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Toggle idle inhibit via systemd-inhibit (swayidle respects the logind idle hint).
|
||||||
|
PID_FILE="/tmp/caffeine-inhibit.pid"
|
||||||
|
|
||||||
statecon=$(pidof swayidle | grep "[1234567890]")
|
if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
||||||
|
kill "$(cat "$PID_FILE")"
|
||||||
if [ "$statecon" != "" ]; then
|
rm -f "$PID_FILE"
|
||||||
notify-send -t 1000 "Caffeine mode ON — idle inhibited"
|
notify-send -t 2000 "Caffeine" "Idle inhibit OFF"
|
||||||
killall swayidle
|
|
||||||
else
|
else
|
||||||
notify-send -t 1000 "Caffeine mode OFF — idle active"
|
systemd-inhibit --what=idle:sleep \
|
||||||
swayidle -w \
|
--who="caffeine" \
|
||||||
timeout 300 'swaylock -f' \
|
--why="Caffeine mode active" \
|
||||||
timeout 600 'systemctl suspend' \
|
--mode=block \
|
||||||
before-sleep 'swaylock -f' &
|
sleep infinity &
|
||||||
|
echo $! > "$PID_FILE"
|
||||||
|
notify-send -t 2000 "Caffeine" "Idle inhibited"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
../../hyprlua/scripts/enroll-biometrics.sh
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
../../hyprlua/scripts/presence-detect.sh
|
||||||
|
|
@ -26,7 +26,8 @@ sudo pacman -Syu --noconfirm --needed \
|
||||||
cool-retro-term qalculate-gtk iwd dbus \
|
cool-retro-term qalculate-gtk iwd dbus \
|
||||||
thunar tumbler thunar-archive-plugin thunar-shares-plugin thunar-volman \
|
thunar tumbler thunar-archive-plugin thunar-shares-plugin thunar-volman \
|
||||||
hyprpicker pcmanfm-qt udisks2 ly kew \
|
hyprpicker pcmanfm-qt udisks2 ly kew \
|
||||||
hyprpolkitagent pavucontrol playerctl wf-recorder sound-theme-freedesktop
|
hyprpolkitagent pavucontrol playerctl wf-recorder sound-theme-freedesktop \
|
||||||
|
python-opencv v4l-utils
|
||||||
|
|
||||||
# 3. Enable essential services
|
# 3. Enable essential services
|
||||||
log "Enabling essential services..."
|
log "Enabling essential services..."
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ sudo pacman -Syu --noconfirm --needed \
|
||||||
thunar tumbler thunar-archive-plugin thunar-shares-plugin thunar-volman \
|
thunar tumbler thunar-archive-plugin thunar-shares-plugin thunar-volman \
|
||||||
pcmanfm-qt udisks2 ly kew \
|
pcmanfm-qt udisks2 ly kew \
|
||||||
pavucontrol playerctl wf-recorder sound-theme-freedesktop \
|
pavucontrol playerctl wf-recorder sound-theme-freedesktop \
|
||||||
xinput jq
|
xinput jq python-opencv v4l-utils
|
||||||
|
|
||||||
# 3. Enable essential services
|
# 3. Enable essential services
|
||||||
log "Enabling essential services..."
|
log "Enabling essential services..."
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue