Wren Linux

wren-linux / bin / wren-camera

#!/bin/bash
# wren-camera on|off|status: written by Wren Linux extras/webcam.sh
# Installed as /usr/local/libexec/wren-camera, owned by root. Your user may run
# "wren-camera on" and "wren-camera off" as root, and nothing else (a rule
# in /etc/sudoers.d); status needs no root. The Wren menu and radios use it.
#
# A USB camera is switched off by telling the kernel to disconnect its USB device
# (/sys/bus/usb/devices/DEVICE/authorized = 0), as if it were unplugged: no
# program can open it until it is switched on again. A camera is any USB device
# with a video interface (USB class 0x0e); a disconnected device no longer lists
# its interfaces, so its raw descriptors are read instead. A camera that isn't
# on USB (some new laptops' MIPI cameras) isn't seen or switched.
#   status        "on", "off", or "none" (no USB camera); exit 0
#   added DEVICE  udev runs this (as root) when a whole USB device is added, at
#                 boot and when plugged in: a camera starts switched off. Not
#                 when one is switched on, which adds only its interfaces.
set -euo pipefail

# Where /sys is: always the real one for root; a test may point it elsewhere
if [ "$(id -u)" -eq 0 ]; then SYS=/sys; else SYS=${WREN_SYS:-/sys}; fi
USB=$SYS/bus/usb/devices

die() { printf 'wren-camera: %s\n' "$*" >&2; exit 2; }

# has_video DEVICE-DIR: its descriptors include an interface of class 0x0e (video).
# Each descriptor is: length, type, ...; an interface descriptor is type 4 and
# has its class at byte 5.
has_video() {
    local -a b
    local i=0 len
    read -ra b < <(od -An -v -tu1 "$1/descriptors" 2>/dev/null | tr -s ' \n' ' ')
    while [ "$i" -lt "${#b[@]}" ]; do
        len=${b[i]}
        [ "${len:-0}" -ge 2 ] || return 1
        if [ "${b[i + 1]}" = 4 ] && [ "$((i + 5))" -lt "${#b[@]}" ] && [ "${b[i + 5]}" = 14 ]; then return 0; fi
        i=$((i + len))
    done
    return 1
}

# cameras: the USB devices (like 1-6 or 3-1.2) that are cameras
cameras() {
    local d n
    for d in "$USB"/*; do
        n=${d##*/}
        [[ $n =~ ^[0-9]+-[0-9]+(\.[0-9]+)*$ ]] || continue
        [ -f "$d/authorized" ] || continue
        if has_video "$d"; then echo "$n"; fi
    done
}

set_all() { # 0|1
    local n found=false
    for n in $(cameras); do
        found=true
        [ "$(cat "$USB/$n/authorized")" = "$1" ] || echo "$1" >"$USB/$n/authorized"
    done
    $found || { echo "wren-camera: no USB camera found." >&2; exit 1; }
}

case ${1:-} in
    status)
        list=$(cameras)
        if [ -z "$list" ]; then echo none; exit 0; fi
        for n in $list; do
            if [ "$(cat "$USB/$n/authorized")" = 1 ]; then echo on; exit 0; fi
        done
        echo off ;;
    added)
        n=${2:-}
        # (root hubs, usb1 and the like, are never cameras)
        [[ $n =~ ^[0-9]+-[0-9]+(\.[0-9]+)*$ ]] || exit 0
        if [ -f "$USB/$n/authorized" ] && has_video "$USB/$n"; then echo 0 >"$USB/$n/authorized"; fi ;;
    on) set_all 1 ;;
    off) set_all 0 ;;
    *) die "usage: wren-camera on|off|status (added DEVICE: for udev)" ;;
esac