All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 2s
- Add zeroize dependency, wrap password in Zeroizing<String> from entry extraction through to login_worker (prevents heap-resident plaintext) - Add MAX_BLUR_DIMENSION (1920px) downscale before GPU blur to reduce 4K workload - Wallpaper: use symlink_metadata + is_symlink rejection in greeter.rs and config.rs - Avatar: add is_file() check, swap lookup order to ~/.face first (consistent with moonlock/moonset) - greetd errors: show generic fallback in UI, log raw PAM details at debug level only - fprintd: validate device path prefix before creating D-Bus proxy - Locale: cache detected locale via OnceLock (avoid repeated env/file reads)
143 lines
4.2 KiB
Rust
143 lines
4.2 KiB
Rust
// ABOUTME: fprintd D-Bus probe for fingerprint device availability.
|
|
// ABOUTME: Checks if fprintd is running and the user has enrolled fingerprints.
|
|
|
|
use gio::prelude::*;
|
|
use gtk4::gio;
|
|
|
|
const FPRINTD_BUS_NAME: &str = "net.reactivated.Fprint";
|
|
const FPRINTD_MANAGER_PATH: &str = "/net/reactivated/Fprint/Manager";
|
|
const FPRINTD_MANAGER_IFACE: &str = "net.reactivated.Fprint.Manager";
|
|
const FPRINTD_DEVICE_IFACE: &str = "net.reactivated.Fprint.Device";
|
|
|
|
const DBUS_TIMEOUT_MS: i32 = 3000;
|
|
const FPRINTD_DEVICE_PREFIX: &str = "/net/reactivated/Fprint/Device/";
|
|
|
|
/// Lightweight fprintd probe — detects device availability and finger enrollment.
|
|
/// Does NOT perform verification (that happens through greetd/PAM).
|
|
pub struct FingerprintProbe {
|
|
device_proxy: Option<gio::DBusProxy>,
|
|
}
|
|
|
|
impl FingerprintProbe {
|
|
/// Create a probe without any D-Bus connections.
|
|
/// Call `init_async().await` to connect to fprintd.
|
|
pub fn new() -> Self {
|
|
FingerprintProbe {
|
|
device_proxy: None,
|
|
}
|
|
}
|
|
|
|
/// Connect to fprintd on the system bus and discover the default device.
|
|
pub async fn init_async(&mut self) {
|
|
let manager = match gio::DBusProxy::for_bus_future(
|
|
gio::BusType::System,
|
|
gio::DBusProxyFlags::NONE,
|
|
None,
|
|
FPRINTD_BUS_NAME,
|
|
FPRINTD_MANAGER_PATH,
|
|
FPRINTD_MANAGER_IFACE,
|
|
)
|
|
.await
|
|
{
|
|
Ok(m) => m,
|
|
Err(e) => {
|
|
log::debug!("fprintd manager not available: {e}");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let result = match manager
|
|
.call_future("GetDefaultDevice", None, gio::DBusCallFlags::NONE, DBUS_TIMEOUT_MS)
|
|
.await
|
|
{
|
|
Ok(r) => r,
|
|
Err(e) => {
|
|
log::debug!("fprintd GetDefaultDevice failed: {e}");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let device_path = match result.child_value(0).get::<String>() {
|
|
Some(p) => p,
|
|
None => {
|
|
log::debug!("fprintd: unexpected GetDefaultDevice response type");
|
|
return;
|
|
}
|
|
};
|
|
if device_path.is_empty() {
|
|
return;
|
|
}
|
|
if !device_path.starts_with(FPRINTD_DEVICE_PREFIX) {
|
|
log::warn!("Unexpected fprintd device path: {device_path}");
|
|
return;
|
|
}
|
|
|
|
match gio::DBusProxy::for_bus_future(
|
|
gio::BusType::System,
|
|
gio::DBusProxyFlags::NONE,
|
|
None,
|
|
FPRINTD_BUS_NAME,
|
|
&device_path,
|
|
FPRINTD_DEVICE_IFACE,
|
|
)
|
|
.await
|
|
{
|
|
Ok(proxy) => {
|
|
self.device_proxy = Some(proxy);
|
|
}
|
|
Err(e) => {
|
|
log::debug!("fprintd device proxy failed: {e}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Check if the user has enrolled fingerprints on the default device.
|
|
/// Returns false if fprintd is unavailable or the user has no enrollments.
|
|
pub async fn is_available_async(&self, username: &str) -> bool {
|
|
let proxy = match &self.device_proxy {
|
|
Some(p) => p,
|
|
None => return false,
|
|
};
|
|
|
|
let args = glib::Variant::from((&username,));
|
|
match proxy
|
|
.call_future(
|
|
"ListEnrolledFingers",
|
|
Some(&args),
|
|
gio::DBusCallFlags::NONE,
|
|
DBUS_TIMEOUT_MS,
|
|
)
|
|
.await
|
|
{
|
|
Ok(result) => match result.child_value(0).get::<Vec<String>>() {
|
|
Some(fingers) => !fingers.is_empty(),
|
|
None => {
|
|
log::debug!("fprintd: unexpected ListEnrolledFingers response type");
|
|
false
|
|
}
|
|
},
|
|
Err(_) => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn new_probe_has_no_device() {
|
|
let probe = FingerprintProbe::new();
|
|
assert!(probe.device_proxy.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn constants_are_defined() {
|
|
assert!(!FPRINTD_BUS_NAME.is_empty());
|
|
assert!(!FPRINTD_MANAGER_PATH.is_empty());
|
|
assert!(!FPRINTD_MANAGER_IFACE.is_empty());
|
|
assert!(!FPRINTD_DEVICE_IFACE.is_empty());
|
|
assert!(DBUS_TIMEOUT_MS > 0);
|
|
}
|
|
}
|