From a9c159a1f44142e6e1e6509bd9e7bad8099834af Mon Sep 17 00:00:00 2001 From: Vtorrealba <102916964+Vtorrealba@users.noreply.github.com> Date: Wed, 12 Aug 2026 07:59:01 -0400 Subject: [PATCH] Restore FPC fingerprint detection in Quattro (#6737) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Restore FPC fingerprint detection * Anchor the FPC product match to a prefix *fpc* is an unanchored three-letter token on the one branch that is trusted outright, with none of the kernel-driver checking the vendor guess gets. Every FPC reader on record leads with it — "FPC Sensor Controller", "FPC Sensor Controller L:0002 FW:25.26.23.14", and this branch's "FPC L:0000 FW:1425046" — so requiring the prefix costs no coverage while keeping three letters from matching mid-string, where FPC abbreviates unrelated things like flexible printed circuit. Also restore the note about why Elan is kept out of the vendor list, so the two signatures read as the same deliberate exception. Co-Authored-By: Claude Opus 5 (1M context) * Write no product descriptor for a two-field device spec ${remainder#*:} returns the string unchanged when there is no second colon, so a spec meant to describe a device with no product descriptor wrote the product id out as its product string instead. Every call site happened to pass a trailing colon, so the suite was right by accident. Guard the split and drop the trailing colons, so the two vendor-match cases exercise the path they were written for. Co-Authored-By: Claude Opus 5 (1M context) * Bind a named driver in the kernel-driver fixture Touching a bare `driver` file asserts that any driver at all disqualifies a vendor guess, which is more than the detector should promise: libusb claims an interface through a synthetic `usbfs` driver, so a reader in active use looks bound by that rule. Link the interface at a named driver directory instead, the way sysfs does. The case still covers what it was written for — a Synaptics bridge or a camera on a fingerprint vendor ID — without fixing the shape of the answer for drivers it was never about. Co-Authored-By: Claude Opus 5 (1M context) --------- Co-authored-by: David Heinemeier Hansson Co-authored-by: Claude Opus 5 (1M context) --- bin/omarchy-hw-fingerprint | 14 +++-- test/shell.d/hw-fingerprint-test.sh | 91 +++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 5 deletions(-) create mode 100755 test/shell.d/hw-fingerprint-test.sh 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"