Skip to content

core: Add USB device detection and fingerprint reader support#240

Open
ptr1337 wants to merge 7 commits intomasterfrom
fprint
Open

core: Add USB device detection and fingerprint reader support#240
ptr1337 wants to merge 7 commits intomasterfrom
fprint

Conversation

@ptr1337
Copy link
Copy Markdown
Member

@ptr1337 ptr1337 commented Apr 1, 2026

Extend chwd to detect USB devices via sysfs (/sys/bus/usb/devices/)
alongside existing PCI detection. Add profile infrastructure for USB
device types with separate config/database directories.

Include an fprint profile that matches fingerprint sensor vendor IDs
from all manufacturers supported by libfprint (Goodix, Synaptics, Elan,
Validity, AuthenTec, FPC, etc.) and installs fprintd.

List is fetched from https://fprint.freedesktop.org/supported-devices.html

Tested on Framework Laptop with supported fingerprint. Works fine.
Polkit implementation I did not got to work, not sure why.

When this is installed/setup on a computer with a fingerprint sensor, but no fingerprint being not set yet it just directly jumps to use password.

ptr1337 added 4 commits March 31, 2026 17:59
Extend chwd to detect USB devices via sysfs (/sys/bus/usb/devices/)
alongside existing PCI detection. Add profile infrastructure for USB
device types with separate config/database directories.

Include an fprint profile that matches fingerprint sensor vendor IDs
from all manufacturers supported by libfprint (Goodix, Synaptics, Elan,
Validity, AuthenTec, FPC, etc.) and installs fprintd.

Signed-off-by: Peter Jung <admin@ptr1337.dev>
Signed-off-by: Peter Jung <admin@ptr1337.dev>
Signed-off-by: Peter Jung <admin@ptr1337.dev>
@ptr1337 ptr1337 self-assigned this Apr 1, 2026
@ptr1337 ptr1337 added the enhancement New feature or request label Apr 1, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends chwd’s hardware detection and profile infrastructure to support USB devices (via sysfs) alongside existing PCI detection, and introduces an initial USB profile for fingerprint readers (fprintd).

Changes:

  • Add USB device discovery from /sys/bus/usb/devices and include USB devices in profile availability/autoconfigure flows.
  • Split profile storage into PCI vs USB config/database directories and update listing/install logic accordingly.
  • Add an initial USB fprint profile and update English i18n strings for new/renamed list headings/messages.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/profile.rs Include both PCI and USB devices when collecting available profiles.
src/misc.rs Document that USB directories are optional in environment checks.
src/main.rs Use combined installed profiles/devices; choose DB dir based on profile location.
src/device_misc.rs Parameterize detailed listing by bus type (PCI/USB).
src/data.rs Add USB device discovery; split PCI/USB installed/all profiles; make profile-dir reading tolerant of missing dirs.
src/consts.rs Add USB config/database directory constants.
src/console_writer.rs List PCI+USB profiles/devices and adjust message IDs.
profiles/usb/fprint/profiles.toml Add USB fingerprint reader profile that installs/enables fprintd and writes PAM sudo config.
i18n/en/chwd.ftl Add/update English strings for USB and generalized “installed profiles” messaging.
Comments suppressed due to low confidence (2)

src/data.rs:156

  • In fill_profiles(), read_dir errors are now silently ignored (return), which can mask real problems (e.g., permission/IO errors) and lead to “no profiles found” without any diagnostic. Also, iterating for entry in dir_entries and then calling entry.as_ref().unwrap() can still panic on per-entry errors. Consider only treating ErrorKind::NotFound as optional (especially for USB), logging/propagating other errors, and iterating with filter_map(Result::ok) (or handling Err explicitly) to avoid unwrap panics.
    let dir_entries = match fs::read_dir(conf_path) {
        Ok(entries) => entries,
        Err(_) => return,
    };
    for entry in dir_entries {
        let config_file_path = format!(
            "{}/{}",
            entry.as_ref().unwrap().path().as_os_str().to_str().unwrap(),
            crate::consts::CHWD_CONFIG_FILE

src/device_misc.rs:38

  • The detailed listing logs device.sysfs_id, but sysfs_id is never populated in the codebase (devices are constructed with sysfs_id: String::new()), so the identifier printed here will be empty for both PCI and USB. Use device.sysfs_busid (or populate sysfs_id during device discovery) so --detail output shows a meaningful device identifier.
        log::info!(
            "{} {}: {} ({}:{}:{})",
            bus_type,
            fl!("device"),
            device.sysfs_id,
            device.class_id,
            device.vendor_id,
            device.device_id
        );

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +20 to +28
vendor_ids = "045e 0483 04f3 05ba 06cb 08ff 0bda 10a5 138a 147e 1c7a 2541 27c6 2808 298d"
device_ids = "*"
class_ids = "*"
priority = 5
packages = "fprintd"
post_install = """
systemctl enable fprintd.service
mkdir -p /etc/pam.d/sudo.d
echo 'auth sufficient pam_fprintd.so' > /etc/pam.d/sudo.d/50-fprintd.conf
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This profile matches only on vendor_ids with device_ids = "*"/class_ids = "*", so it will match any USB device from these vendors (e.g., many Realtek/Microsoft devices) even when no fingerprint reader is present. Because the profile installs fprintd and modifies PAM, false positives here have significant side effects. Please narrow the match criteria (e.g., vendor+product ID pairs from the supported-devices list, or a stricter device_name_pattern/class constraint) instead of wildcarding device_ids.

Copilot uses AI. Check for mistakes.
Comment on lines +366 to +372
fn db_dir_for_profile(profile: &Profile) -> &'static str {
if profile.prof_path.contains("/usb/") {
consts::CHWD_USB_DATABASE_DIR
} else {
consts::CHWD_PCI_DATABASE_DIR
}
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

db_dir_for_profile() infers whether a profile is USB by checking whether prof_path contains the substring "/usb/". This is brittle (depends on filesystem layout and path formatting) and can misroute installs if paths change. Prefer deriving the target DB dir via Path prefix checks against the known config roots, or storing an explicit bus/type on Profile (or alongside it) when profiles are loaded.

Copilot uses AI. Check for mistakes.
Comment on lines 24 to +31
invalid-profile = profile '{$invalid_profile}' is invalid!
all-pci-profiles = All PCI profiles:
installed-pci-profiles = Installed PCI profiles:
all-usb-profiles = All USB profiles:
installed-profiles = Installed profiles:
pci-profiles-not-found = No PCI profiles found!
no-installed-pci-profiles = No installed PCI profiles!
no-installed-profile-device = no installed profile for PCI devices found!
usb-profiles-not-found = No USB profiles found!
no-installed-profiles = No installed profiles!
no-installed-profile-device = no installed profile for devices found!
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New/renamed Fluent message IDs were introduced here (e.g., installed-profiles, no-installed-profiles, all-usb-profiles, usb-profiles-not-found). Other locale files under i18n/* still only define the old PCI-specific keys, so non-English locales will fall back to English for these messages. Consider updating other translations (or adding backward-compatible aliases) to keep localization coverage complete.

Copilot uses AI. Check for mistakes.
ptr1337 added 2 commits April 1, 2026 18:53
Replace wildcard device_ids with explicit libfprint-supported product IDs
(~190 entries in ids/fprint.ids) to prevent false matches on non-fingerprint
USB hardware sharing a vendor ID. Add chwd ownership marker to the PAM
drop-in so post_remove only removes files it created. Fix fill_profiles()
to only ignore NotFound errors instead of silently swallowing all IO failures.

Signed-off-by: Peter Jung <admin@ptr1337.dev>
Signed-off-by: Peter Jung <admin@ptr1337.dev>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

src/data.rs:160

  • fill_profiles() iterates for entry in dir_entries but then calls entry.as_ref().unwrap(). A single unreadable directory entry will panic and abort profile loading. Prefer iterating with for entry in dir_entries.filter_map(Result::ok) (optionally logging skipped entries) to make profile discovery robust.
    for entry in dir_entries {
        let config_file_path = format!(
            "{}/{}",
            entry.as_ref().unwrap().path().as_os_str().to_str().unwrap(),
            crate::consts::CHWD_CONFIG_FILE

src/device_misc.rs:38

  • The detailed device log prints device.sysfs_id, but sysfs_id is never populated for either PCI or USB devices (it’s initialized as empty). This makes the output ambiguous—especially for USB where sysfs_busid (e.g. 1-2) is the meaningful identifier. Consider printing sysfs_busid here (or populating sysfs_id).
        log::info!(
            "{} {}: {} ({}:{}:{})",
            bus_type,
            fl!("device"),
            device.sysfs_id,
            device.class_id,
            device.vendor_id,
            device.device_id
        );

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +60 to +70
self.installed_pci_profiles
.iter()
.chain(self.installed_usb_profiles.iter())
.cloned()
.collect()
}

/// Returns combined list of all available profiles (PCI + USB).
#[must_use]
pub fn all_profiles(&self) -> Vec<Profile> {
self.all_pci_profiles.iter().chain(self.all_usb_profiles.iter()).cloned().collect()
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

installed_profiles()/all_profiles() now concatenate the PCI and USB vectors without re-sorting, which can break the previous global priority ordering (previously a single list was sorted by priority). Consider sorting the merged Vec by descending priority (and possibly secondary key like name) before returning to keep behavior stable.

Suggested change
self.installed_pci_profiles
.iter()
.chain(self.installed_usb_profiles.iter())
.cloned()
.collect()
}
/// Returns combined list of all available profiles (PCI + USB).
#[must_use]
pub fn all_profiles(&self) -> Vec<Profile> {
self.all_pci_profiles.iter().chain(self.all_usb_profiles.iter()).cloned().collect()
let mut profiles: Vec<Profile> = self
.installed_pci_profiles
.iter()
.chain(self.installed_usb_profiles.iter())
.cloned()
.collect();
// Preserve global ordering by sorting by descending priority,
// and then by name as a stable secondary key.
profiles.sort_by(|a, b| {
b.priority
.cmp(&a.priority)
.then_with(|| a.name.cmp(&b.name))
});
profiles
}
/// Returns combined list of all available profiles (PCI + USB).
#[must_use]
pub fn all_profiles(&self) -> Vec<Profile> {
let mut profiles: Vec<Profile> = self
.all_pci_profiles
.iter()
.chain(self.all_usb_profiles.iter())
.cloned()
.collect();
// Preserve global ordering by sorting by descending priority,
// and then by name as a stable secondary key.
profiles.sort_by(|a, b| {
b.priority
.cmp(&a.priority)
.then_with(|| a.name.cmp(&b.name))
});
profiles

Copilot uses AI. Check for mistakes.
if grep -q 'Managed by chwd' /etc/pam.d/sudo.d/50-fprintd.conf 2>/dev/null; then
rm -f /etc/pam.d/sudo.d/50-fprintd.conf
fi
if ! systemctl list-dependencies --reverse fprintd.service 2>/dev/null | grep -q .; then
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The post_remove check for disabling fprintd.service is effectively always false: systemctl list-dependencies --reverse fprintd.service | grep -q . will typically match at least one line (often the unit itself), so the service won’t be disabled even when nothing depends on it. Consider a more precise check (e.g., filter out the unit itself / header lines, or use systemctl is-enabled and disable unconditionally if you enabled it).

Suggested change
if ! systemctl list-dependencies --reverse fprintd.service 2>/dev/null | grep -q .; then
if systemctl is-enabled --quiet fprintd.service 2>/dev/null; then

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guess we dont even need to enable service. It should do automatically

Copy link
Copy Markdown
Member

@ventureoo ventureoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think design of this code is not good. Instead of storing the profiles for PCI and USB separately, you should just add a type hint inside profile structure.

But let @vnepogodin sort it out.

}

fn db_dir_for_profile(profile: &Profile) -> &'static str {
if profile.prof_path.contains("/usb/") {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's hacky. Why not just add profile type as enum inside the structure?

@vnepogodin
Copy link
Copy Markdown
Member

I think design of this code is not good. Instead of storing the profiles for PCI and USB separately, you should just add a type hint inside profile structure.

But let @vnepogodin sort it out.

can merge as-is, and later bc6655e
reverting some of the code. native USB detection instead of using sysfs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants