diff --git a/bin/omarchy-hw-fingerprint b/bin/omarchy-hw-fingerprint index 03af4fdb..6f4b407b 100755 --- a/bin/omarchy-hw-fingerprint +++ b/bin/omarchy-hw-fingerprint @@ -9,6 +9,7 @@ # also make USB touchscreens) are left out to avoid nagging laptops with no # reader — those still match on the product string below when present. fingerprint_vendors=" 27c6 138a 06cb 08ff 1c7a 147e " +usb_devices_path="${OMARCHY_USB_DEVICES_PATH:-/sys/bus/usb/devices}" # libfprint drives every reader it supports from userspace over libusb, so a # real reader sits there with no kernel driver bound to any of its interfaces. @@ -24,16 +25,19 @@ has_kernel_driver() { return 1 } -for dev in /sys/bus/usb/devices/*; do +for dev in "$usb_devices_path"/*; do # The device's own product descriptor usually names it, e.g. "Goodix # Fingerprint USB Device" — driver-independent and vendor-agnostic. if [[ -r $dev/product ]]; then product=$(<"$dev/product") product=${product,,} - # Elan's match-on-chip readers report "ELAN:ARM-M4", the family name - # rather than the function. Elan is left out of the vendor list above - # on purpose, so without this they match nothing. - [[ $product == *fingerprint* || $product == *biometric* || $product == *elan:arm-m4* ]] && exit 0 + # Elan's match-on-chip readers report "ELAN:ARM-M4" and Fingerprint Cards' + # report "FPC Sensor Controller" or "FPC L:0000 FW:1425046" — the family or + # the manufacturer rather than the function. Both vendors are left out of + # the list above on purpose (Elan also makes touchscreens), so without these + # they match nothing. FPC leads the string on every reader on record, and + # three letters are little to match on, so require the prefix. + [[ $product == *fingerprint* || $product == *biometric* || $product == *elan:arm-m4* || $product == "fpc "* ]] && exit 0 fi if [[ -r $dev/idVendor ]]; then diff --git a/test/shell.d/hw-fingerprint-test.sh b/test/shell.d/hw-fingerprint-test.sh new file mode 100755 index 00000000..78525c86 --- /dev/null +++ b/test/shell.d/hw-fingerprint-test.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +tmp_dir=$(mktemp -d) +trap 'rm -rf "$tmp_dir"' EXIT + +write_usb_devices() { + rm -rf "$tmp_dir/devices" + mkdir -p "$tmp_dir/devices" + + local index=0 + local spec + for spec in "$@"; do + local vendor=${spec%%:*} + local remainder=${spec#*:} + local product_id=${remainder%%:*} + # A spec with no third field describes a device with no product + # descriptor. Without this guard ${remainder#*:} would return the product + # id unchanged and quietly write it out as the product string. + local product="" + if [[ $remainder == *:* ]]; then + product=${remainder#*:} + fi + local dev="$tmp_dir/devices/1-$index" + + mkdir -p "$dev" + printf '%s\n' "$vendor" >"$dev/idVendor" + printf '%s\n' "$product_id" >"$dev/idProduct" + [[ -n $product ]] && printf '%s\n' "$product" >"$dev/product" + index=$((index + 1)) + done +} + +hw_fingerprint() { + OMARCHY_USB_DEVICES_PATH="$tmp_dir/devices" "$ROOT/bin/omarchy-hw-fingerprint" +} + +assert_detects() { + local description="$1" + + hw_fingerprint || fail "$description" + pass "$description" +} + +assert_rejects() { + local description="$1" + + if hw_fingerprint; then + fail "$description" + fi + pass "$description" +} + +write_usb_devices '10a5:a305:FPC L:0000 FW:1425046' +assert_detects "an FPC reader is detected by its product string" + +write_usb_devices '10a5:1234:Generic USB Device' +assert_rejects "a generic 10a5 USB device is not detected" + +write_usb_devices '10a5:9800:FPC Sensor Controller L:0002 FW:25.26.23.14' +assert_detects "an FPC reader is detected by its sensor-controller string" + +# Pins the match to a prefix. FPC abbreviates unrelated things too, and this +# branch is trusted with no kernel-driver check, so the token is not enough. +write_usb_devices '0bda:5842:USB2.0 FPC Camera' +assert_rejects "an FPC token mid-string is not detected" + +write_usb_devices '1234:5678:Goodix Fingerprint USB Device' +assert_detects "a reader is detected by an existing product-name match" + +write_usb_devices '27c6:1234' +assert_detects "a reader is detected by an existing vendor match" + +bind_driver() { + local dev="$1" driver="$2" + + # sysfs links the interface at a driver directory, and the detector's [[ -e ]] + # follows the link, so the target has to exist for this to model anything. + mkdir -p "$tmp_dir/drivers/$driver" "$tmp_dir/devices/$dev" + ln -sf "$tmp_dir/drivers/$driver" "$tmp_dir/devices/$dev/driver" +} + +write_usb_devices '27c6:1234' +bind_driver '1-0/1-0:1.0' uvcvideo +assert_rejects "a vendor guess bound to a kernel driver is rejected" + +write_usb_devices '1234:5678:Generic USB Device' +assert_rejects "a machine with no matching USB devices detects nothing"