blob: f7ff733cf410b26bb6db3639d8d9840530e421d9 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
#!/usr/bin/env bash
# webcam-toggle — Bind or unbind a specific webcam's USB device.
#
# Reads the target device id from /var/lib/webcam-toggle/device (written by
# webcam-toggle-setup). If the file does not exist, falls back to auto-
# detection — but when multiple webcams are present, it exits with an error
# directing the user to run the setup command.
#
# Outputs the new state on stdout: "on" or "off"
#
# This script writes to /sys/bus/usb/drivers/usb/{bind,unbind} which requires
# root. It is meant to be called via:
# sudo webcam-toggle
# A sudoers drop-in shipped with the package allows members of the "video"
# group to run this without a password.
#
# State is stored in /var/lib/webcam-toggle.
# --- Hardened PATH — only trusted system directories ----------------------
export PATH="/usr/bin:/bin:/usr/sbin:/sbin"
set -euo pipefail
STATE_DIR="/var/lib/webcam-toggle"
DEVICE_FILE="${STATE_DIR}/device"
UNBOUND_MARKER="${STATE_DIR}/unbound"
LOCKFILE="/run/webcam-toggle.lock"
# State directory: world-readable so the indicator can stat the marker file.
# The device file itself is restricted to root (0600).
mkdir -p "$STATE_DIR"
chmod 0755 "$STATE_DIR"
# ---------------------------------------------------------------------------
# Concurrency guard — prevent double-toggles from rapid key presses.
# Uses an fd-based flock so the lock auto-releases on exit/crash.
# ---------------------------------------------------------------------------
exec 9>"$LOCKFILE"
if ! flock -n 9; then
echo "error: another webcam-toggle instance is running" >&2
exit 1
fi
# ---------------------------------------------------------------------------
# validate_dev_id — ensure a device id matches the expected USB bus-port
# pattern (e.g. "1-5", "2-1.6", "1-1.3.2") before using it.
# ---------------------------------------------------------------------------
validate_dev_id() {
local id="$1"
if [[ ! "$id" =~ ^[0-9]+-[0-9]+(\.[0-9]+)*$ ]]; then
echo "error: invalid device id: '$id'" >&2
exit 1
fi
}
# ---------------------------------------------------------------------------
# list_webcam_devices — enumerate all USB devices currently bound to the
# uvcvideo driver. Outputs unique bus-port ids, one per line.
# ---------------------------------------------------------------------------
list_webcam_devices() {
local seen="" intf intf_dir intf_name dev_id
for intf in /sys/bus/usb/drivers/uvcvideo/*/driver; do
[ -e "$intf" ] || continue
intf_dir="$(dirname "$intf")"
intf_name="$(basename "$intf_dir")"
dev_id="${intf_name%%:*}"
# Deduplicate (a single webcam exposes multiple interfaces)
case "$seen" in
*"|${dev_id}|"*) continue ;;
esac
seen="${seen}|${dev_id}|"
echo "$dev_id"
done
}
# ---------------------------------------------------------------------------
# is_device_bound — check whether a given USB device is currently bound to
# its driver. This is the single source of truth for webcam state.
# ---------------------------------------------------------------------------
is_device_bound() {
[ -e "/sys/bus/usb/devices/$1/driver" ]
}
# ---------------------------------------------------------------------------
# Resolve the target device id
# ---------------------------------------------------------------------------
if [ -f "$DEVICE_FILE" ]; then
# A device has been explicitly configured (by webcam-toggle-setup).
DEV_ID="$(cat "$DEVICE_FILE")"
validate_dev_id "$DEV_ID"
else
# No config — try auto-detection.
mapfile -t CAMS < <(list_webcam_devices)
if [ "${#CAMS[@]}" -eq 0 ]; then
echo "error: no webcam detected — is a webcam connected?" >&2
exit 1
elif [ "${#CAMS[@]}" -eq 1 ]; then
DEV_ID="${CAMS[0]}"
validate_dev_id "$DEV_ID"
# Persist so future toggles (including re-bind after unbind) work.
echo "$DEV_ID" > "$DEVICE_FILE"
chmod 0600 "$DEVICE_FILE"
else
echo "error: multiple webcams detected — run 'sudo webcam-toggle-setup' to choose one" >&2
exit 1
fi
fi
# ---------------------------------------------------------------------------
# Determine current state from sysfs (the only source of truth).
# The unbound marker is kept in sync for the tray indicator and the boot
# restore service, but never used to determine runtime state here.
# ---------------------------------------------------------------------------
if is_device_bound "$DEV_ID"; then
CURRENT_STATE="on"
else
CURRENT_STATE="off"
fi
# ---------------------------------------------------------------------------
# Toggle
# ---------------------------------------------------------------------------
if [ "$CURRENT_STATE" = "on" ]; then
echo "$DEV_ID" > /sys/bus/usb/drivers/usb/unbind
touch "$UNBOUND_MARKER"
echo "off"
else
echo "$DEV_ID" > /sys/bus/usb/drivers/usb/bind
rm -f "$UNBOUND_MARKER"
echo "on"
fi
|