From 13b329cd98d677a39fe1f11bb2919aab7b598bd4 Mon Sep 17 00:00:00 2001 From: nevaforget Date: Sat, 28 Mar 2026 09:57:56 +0100 Subject: [PATCH] perf: async fprintd initialization for instant window display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move all fprintd D-Bus calls (init, availability check, claim, verify) from synchronous to async using gio futures. Windows now appear immediately without waiting for D-Bus — fingerprint label fades in once fprintd is ready. Single shared FingerprintListener across all monitors instead of one per monitor. --- src/fingerprint.rs | 89 ++++++++++------------- src/lockscreen.rs | 175 ++++++++++++++++++++++++++------------------- src/main.rs | 60 ++++++++++++++-- 3 files changed, 194 insertions(+), 130 deletions(-) diff --git a/src/fingerprint.rs b/src/fingerprint.rs index 1e3e12f..f9d8fa6 100644 --- a/src/fingerprint.rs +++ b/src/fingerprint.rs @@ -32,32 +32,31 @@ pub struct FingerprintListener { } impl FingerprintListener { - /// Create a new FingerprintListener. - /// Connects to fprintd synchronously — call before creating GTK windows. + /// Create a lightweight FingerprintListener without any D-Bus calls. + /// Call `init_async().await` afterwards to connect to fprintd. pub fn new() -> Self { - let mut listener = FingerprintListener { + FingerprintListener { device_proxy: None, signal_id: None, running: false, failed_attempts: 0, on_success: None, on_failure: None, - }; - listener.init_device(); - listener + } } - /// Connect to fprintd and get the default device. - fn init_device(&mut self) { - let manager = match gio::DBusProxy::for_bus_sync( + /// Connect to fprintd and get the default device asynchronously. + 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, - gio::Cancellable::NONE, - ) { + ) + .await + { Ok(m) => m, Err(e) => { log::debug!("fprintd manager not available: {e}"); @@ -66,13 +65,10 @@ impl FingerprintListener { }; // Call GetDefaultDevice - let result = match manager.call_sync( - "GetDefaultDevice", - None, - gio::DBusCallFlags::NONE, - -1, - gio::Cancellable::NONE, - ) { + let result = match manager + .call_future("GetDefaultDevice", None, gio::DBusCallFlags::NONE, -1) + .await + { Ok(r) => r, Err(e) => { log::debug!("fprintd GetDefaultDevice failed: {e}"); @@ -86,15 +82,16 @@ impl FingerprintListener { return; } - match gio::DBusProxy::for_bus_sync( + match gio::DBusProxy::for_bus_future( gio::BusType::System, gio::DBusProxyFlags::NONE, None, FPRINTD_BUS_NAME, &device_path, FPRINTD_DEVICE_IFACE, - gio::Cancellable::NONE, - ) { + ) + .await + { Ok(proxy) => { self.device_proxy = Some(proxy); } @@ -104,21 +101,18 @@ impl FingerprintListener { } } - /// Check if fprintd is available and the user has enrolled fingerprints. - pub fn is_available(&self, username: &str) -> bool { + /// Check if fprintd is available and the user has enrolled fingerprints (async). + 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_sync( - "ListEnrolledFingers", - Some(&args), - gio::DBusCallFlags::NONE, - -1, - gio::Cancellable::NONE, - ) { + match proxy + .call_future("ListEnrolledFingers", Some(&args), gio::DBusCallFlags::NONE, -1) + .await + { Ok(result) => { // Result is a tuple of (array of strings) let fingers: Vec = result.child_get::>(0); @@ -129,9 +123,10 @@ impl FingerprintListener { } /// Start listening for fingerprint verification. + /// Claims the device and starts verification using async D-Bus calls. /// Connects the D-Bus g-signal handler internally. The `listener` parameter /// must be the same `Rc>` that owns `self`. - pub fn start( + pub async fn start_async( listener: &Rc>, username: &str, on_success: F, @@ -156,34 +151,24 @@ impl FingerprintListener { // Claim the device let args = glib::Variant::from((&username,)); - if let Err(e) = proxy.call_sync( - "Claim", - Some(&args), - gio::DBusCallFlags::NONE, - -1, - gio::Cancellable::NONE, - ) { + if let Err(e) = proxy + .call_future("Claim", Some(&args), gio::DBusCallFlags::NONE, -1) + .await + { log::error!("Failed to claim fingerprint device: {e}"); return; } // Start verification let start_args = glib::Variant::from((&"any",)); - if let Err(e) = proxy.call_sync( - "VerifyStart", - Some(&start_args), - gio::DBusCallFlags::NONE, - -1, - gio::Cancellable::NONE, - ) { + if let Err(e) = proxy + .call_future("VerifyStart", Some(&start_args), gio::DBusCallFlags::NONE, -1) + .await + { log::error!("Failed to start fingerprint verification: {e}"); - let _ = proxy.call_sync( - "Release", - None, - gio::DBusCallFlags::NONE, - -1, - gio::Cancellable::NONE, - ); + let _ = proxy + .call_future("Release", None, gio::DBusCallFlags::NONE, -1) + .await; return; } diff --git a/src/lockscreen.rs b/src/lockscreen.rs index 696c219..77740ee 100644 --- a/src/lockscreen.rs +++ b/src/lockscreen.rs @@ -19,23 +19,33 @@ use crate::i18n::{faillock_warning, load_strings, Strings}; use crate::power::{self, PowerError}; use crate::users; +/// Handles returned from create_lockscreen_window for post-creation wiring. +pub struct LockscreenHandles { + pub window: gtk::ApplicationWindow, + pub fp_label: gtk::Label, + pub password_entry: gtk::PasswordEntry, + pub unlock_callback: Rc, + pub username: String, + state: Rc>, +} + const AVATAR_SIZE: i32 = 128; const FAILLOCK_MAX_ATTEMPTS: u32 = 3; /// Shared mutable state for the lockscreen. struct LockscreenState { failed_attempts: u32, - fp_listener: FingerprintListener, fp_listener_rc: Option>>, } /// Create a lockscreen window for a single monitor. +/// Fingerprint is not initialized here — use `wire_fingerprint()` after async init. pub fn create_lockscreen_window( bg_path: &Path, - config: &Config, + _config: &Config, app: >k::Application, unlock_callback: Rc, -) -> gtk::ApplicationWindow { +) -> LockscreenHandles { let window = gtk::ApplicationWindow::builder() .application(app) .build(); @@ -46,17 +56,24 @@ pub fn create_lockscreen_window( Some(u) => u, None => { log::error!("Failed to get current user"); - return window; + let fp_label = gtk::Label::new(None); + fp_label.set_visible(false); + return LockscreenHandles { + window, + fp_label, + password_entry: gtk::PasswordEntry::new(), + unlock_callback, + username: String::new(), + state: Rc::new(RefCell::new(LockscreenState { + failed_attempts: 0, + fp_listener_rc: None, + })), + }; } }; - let fp_listener = FingerprintListener::new(); - let fp_available = config.fingerprint_enabled - && fp_listener.is_available(&user.username); - let state = Rc::new(RefCell::new(LockscreenState { failed_attempts: 0, - fp_listener, fp_listener_rc: None, })); @@ -119,15 +136,10 @@ pub fn create_lockscreen_window( error_label.set_visible(false); login_box.append(&error_label); - // Fingerprint label + // Fingerprint label — hidden until async fprintd init completes let fp_label = gtk::Label::new(None); fp_label.add_css_class("fingerprint-label"); - if fp_available { - fp_label.set_text(strings.fingerprint_prompt); - fp_label.set_visible(true); - } else { - fp_label.set_visible(false); - } + fp_label.set_visible(false); login_box.append(&fp_label); // Confirm box area (for power confirm) @@ -293,61 +305,6 @@ pub fn create_lockscreen_window( )); window.add_controller(key_controller); - // Start fingerprint listener - if fp_available { - let unlock_cb_fp = unlock_callback.clone(); - let fp_label_success = fp_label.clone(); - let fp_label_fail = fp_label.clone(); - - let on_success = move || { - let label = fp_label_success.clone(); - let cb = unlock_cb_fp.clone(); - glib::idle_add_local_once(move || { - label.set_text(load_strings(None).fingerprint_success); - label.add_css_class("success"); - cb(); - }); - }; - - let on_failure = move || { - let label = fp_label_fail.clone(); - glib::idle_add_local_once(clone!( - #[weak] - label, - move || { - let strings = load_strings(None); - label.set_text(strings.fingerprint_failed); - label.add_css_class("failed"); - // Reset after 2 seconds - glib::timeout_add_local_once( - std::time::Duration::from_secs(2), - clone!( - #[weak] - label, - move || { - label.set_text(load_strings(None).fingerprint_prompt); - label.remove_css_class("success"); - label.remove_css_class("failed"); - } - ), - ); - } - )); - }; - - // Extract the fp_listener into its own Rc> for signal self-wiring - let fp_rc = { - let mut s = state.borrow_mut(); - let listener = std::mem::replace(&mut s.fp_listener, FingerprintListener::new()); - Rc::new(RefCell::new(listener)) - }; - - FingerprintListener::start(&fp_rc, &user.username, on_success, on_failure); - - // Store back the Rc reference for stop() on unlock - state.borrow_mut().fp_listener_rc = Some(fp_rc); - } - // Fade-in on map window.connect_map(|w| { glib::idle_add_local_once(clone!( @@ -370,7 +327,81 @@ pub fn create_lockscreen_window( } )); - window + LockscreenHandles { + window, + fp_label, + password_entry: password_entry.clone(), + unlock_callback, + username: user.username, + state: state.clone(), + } +} + +/// Show the fingerprint label and store the listener reference for stop-on-unlock. +/// Does NOT start verification — call `start_fingerprint()` on one monitor for that. +pub fn show_fingerprint_label( + handles: &LockscreenHandles, + fp_rc: &Rc>, +) { + let strings = load_strings(None); + handles.fp_label.set_text(strings.fingerprint_prompt); + handles.fp_label.set_visible(true); + + // Store the Rc reference for stop() on unlock + handles.state.borrow_mut().fp_listener_rc = Some(fp_rc.clone()); +} + +/// Start fingerprint verification on a single monitor's handles. +/// Wires up on_success/on_failure callbacks and calls start_async. +pub fn start_fingerprint( + handles: &LockscreenHandles, + fp_rc: &Rc>, +) { + let fp_label_success = handles.fp_label.clone(); + let fp_label_fail = handles.fp_label.clone(); + let unlock_cb_fp = handles.unlock_callback.clone(); + + let on_success = move || { + let label = fp_label_success.clone(); + let cb = unlock_cb_fp.clone(); + glib::idle_add_local_once(move || { + label.set_text(load_strings(None).fingerprint_success); + label.add_css_class("success"); + cb(); + }); + }; + + let on_failure = move || { + let label = fp_label_fail.clone(); + glib::idle_add_local_once(clone!( + #[weak] + label, + move || { + let strings = load_strings(None); + label.set_text(strings.fingerprint_failed); + label.add_css_class("failed"); + // Reset after 2 seconds + glib::timeout_add_local_once( + std::time::Duration::from_secs(2), + clone!( + #[weak] + label, + move || { + label.set_text(load_strings(None).fingerprint_prompt); + label.remove_css_class("success"); + label.remove_css_class("failed"); + } + ), + ); + } + )); + }; + + let username = handles.username.clone(); + let fp_rc_clone = fp_rc.clone(); + glib::spawn_future_local(async move { + FingerprintListener::start_async(&fp_rc_clone, &username, on_success, on_failure).await; + }); } /// Create a Picture widget for the wallpaper background. diff --git a/src/main.rs b/src/main.rs index c679fc3..c24ae04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,9 +13,12 @@ use gdk4 as gdk; use gtk4::prelude::*; use gtk4::{self as gtk, gio}; use gtk4_session_lock; +use std::cell::RefCell; use std::path::PathBuf; use std::rc::Rc; +use crate::fingerprint::FingerprintListener; + fn load_css(display: &gdk::Display) { let css_provider = gtk::CssProvider::new(); css_provider.load_from_resource("/dev/moonarch/moonlock/style.css"); @@ -75,27 +78,67 @@ fn activate_with_session_lock( app_clone.quit(); }); + // Create all monitor windows immediately — no D-Bus calls here + let mut all_handles = Vec::new(); let mut created_any = false; for i in 0..monitors.n_items() { if let Some(monitor) = monitors .item(i) .and_then(|obj| obj.downcast::().ok()) { - let window = lockscreen::create_lockscreen_window( + let handles = lockscreen::create_lockscreen_window( bg_path, config, app, unlock_callback.clone(), ); - lock.assign_window_to_monitor(&window, &monitor); - window.present(); + lock.assign_window_to_monitor(&handles.window, &monitor); + handles.window.present(); + all_handles.push(handles); created_any = true; } } if !created_any { log::error!("No lockscreen windows created — screen stays locked (compositor policy)"); + return; } + + // Async fprintd initialization — runs after windows are visible + if config.fingerprint_enabled { + init_fingerprint_async(all_handles); + } +} + +/// Initialize fprintd asynchronously after windows are visible. +/// Uses a single FingerprintListener shared across all monitors — +/// only the first monitor's handles get the fingerprint UI wired up. +fn init_fingerprint_async(all_handles: Vec) { + glib::spawn_future_local(async move { + let mut listener = FingerprintListener::new(); + listener.init_async().await; + + // Use the first monitor's username to check enrollment + let username = &all_handles[0].username; + if username.is_empty() { + return; + } + + if !listener.is_available_async(username).await { + log::debug!("fprintd not available or no enrolled fingers"); + return; + } + + let fp_rc = Rc::new(RefCell::new(listener)); + + // Show fingerprint label on all monitors + for handles in &all_handles { + lockscreen::show_fingerprint_label(handles, &fp_rc); + } + + // Start verification listener on the first monitor only + lockscreen::start_fingerprint(&all_handles[0], &fp_rc); + }); } #[cfg(debug_assertions)] @@ -109,14 +152,19 @@ fn activate_without_lock( app_clone.quit(); }); - let window = lockscreen::create_lockscreen_window( + let handles = lockscreen::create_lockscreen_window( bg_path, config, app, unlock_callback, ); - window.set_default_size(800, 600); - window.present(); + handles.window.set_default_size(800, 600); + handles.window.present(); + + // Async fprintd initialization for development mode + if config.fingerprint_enabled { + init_fingerprint_async(vec![handles]); + } } fn setup_logging() {