git.lucas.co / cce-display-manager
login greeter
git clone https://git.lucas.co/cce-display-manager.git

commitb228ec1410323bfd4bdd6a1bc94ba1efc29077fb
parent41c2a0370d
authorLucas Galante <[email protected]>
date2026-05-28 17:46
feat: integrate background PAM authentication via calloop

 Cargo.toml                |  1 +
 pam/clear-display-manager |  5 +++
 src/main.rs               | 96 ++++++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 96 insertions(+), 6 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 0fd90df..aff4872 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,7 @@ edition = "2021"
 
 [dependencies]
 clear-ui = { path = "../clear-ui" }
+pam = "0.7.0"
 smithay-client-toolkit = { version = "0.19.2", features = ["calloop"] }
 calloop = "0.13.0"
 calloop-wayland-source = "0.3.0"
diff --git a/pam/clear-display-manager b/pam/clear-display-manager
new file mode 100644
index 0000000..665a12c
--- /dev/null
+++ b/pam/clear-display-manager
@@ -0,0 +1,5 @@
+#%PAM-1.0
+auth       include      system-local-login
+account    include      system-local-login
+session    include      system-local-login
+password   include      system-local-login
diff --git a/src/main.rs b/src/main.rs
index 63673cd..11bda6b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -28,7 +28,7 @@ use wayland_client::{
     protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_surface},
     Connection, QueueHandle, Proxy,
 };
-use calloop::EventLoop;
+use calloop::{EventLoop, channel};
 use calloop_wayland_source::WaylandSource;
 
 use clear_ui::widget::{
@@ -217,6 +217,7 @@ struct State {
     sessions: Vec<&'static str>,
     session_idx: usize,
     login_success: bool,
+    is_authenticating: bool,
 }
 
 impl State {
@@ -352,6 +353,7 @@ impl State {
             sessions: vec!["River WM", "Bash Shell"],
             session_idx: 0,
             login_success: false,
+            is_authenticating: false,
         };
 
         state.apply_layout();
@@ -572,6 +574,49 @@ impl State {
     }
 }
 
+#[derive(Debug)]
+enum AuthEvent {
+    Success { username: String },
+    Failure(String),
+}
+
+fn authenticate_user(username: String, password: String, sender: channel::Sender<AuthEvent>) {
+    std::thread::spawn(move || {
+        let service = "clear-display-manager";
+        
+        let mut auth = match pam::Authenticator::with_password(service) {
+            Ok(a) => a,
+            Err(_) => {
+                match pam::Authenticator::with_password("login") {
+                    Ok(a) => a,
+                    Err(e) => {
+                        let _ = sender.send(AuthEvent::Failure(format!("PAM Init Error: {}", e)));
+                        return;
+                    }
+                }
+            }
+        };
+
+        auth.get_handler().set_credentials(username.clone(), password);
+
+        match auth.authenticate() {
+            Ok(_) => {
+                match auth.open_session() {
+                    Ok(_) => {
+                        let _ = sender.send(AuthEvent::Success { username });
+                    }
+                    Err(e) => {
+                        let _ = sender.send(AuthEvent::Failure(format!("PAM Session Error: {}", e)));
+                    }
+                }
+            }
+            Err(e) => {
+                let _ = sender.send(AuthEvent::Failure(format!("Authentication failed: {}", e)));
+            }
+        }
+    });
+}
+
 // ── AppState and Client Callbacks ──
 struct AppState {
     registry_state: RegistryState,
@@ -594,6 +639,7 @@ struct AppState {
     ctrl_pressed: bool,
     shift_pressed: bool,
     pressed_key: Option<PressedKey>,
+    auth_sender: channel::Sender<AuthEvent>,
 }
 
 #[allow(dead_code)]
@@ -710,6 +756,9 @@ impl PointerHandler for AppState {
                         _ => continue,
                     };
                     if let Some(st) = &mut self.state {
+                        if st.is_authenticating {
+                            continue;
+                        }
                         let mut changed = false;
                         let cx = st.cursor_x;
                         let cy = st.cursor_y;
@@ -799,6 +848,9 @@ impl PointerHandler for AppState {
                         _ => continue,
                     };
                     if let Some(st) = &mut self.state {
+                        if st.is_authenticating {
+                            continue;
+                        }
                         let cx = st.cursor_x;
                         let cy = st.cursor_y;
                         let mut changed = false;
@@ -828,11 +880,11 @@ impl PointerHandler for AppState {
                                     st.status_lbl.text = "Password cannot be empty".to_string();
                                     st.status_lbl.is_error = true;
                                 } else {
-                                    // Accept login
-                                    st.status_lbl.text = format!("Logging in as {}...", username);
+                                    st.status_lbl.text = "Authenticating...".to_string();
                                     st.status_lbl.is_error = false;
-                                    st.login_success = true;
-                                    self.exit = true; // terminate Wayland GUI and start the session
+                                    st.is_authenticating = true;
+                                    st.login_btn.label = Some("Authenticating...".to_string());
+                                    authenticate_user(username, password, self.auth_sender.clone());
                                 }
                                 changed = true;
                             }
@@ -887,6 +939,9 @@ impl AppState {
 
         if state == ElementState::Pressed {
             if let Some(st) = &mut self.state {
+                if st.is_authenticating {
+                    return;
+                }
                 let mut changed = false;
 
                 // Handle Tab navigation between username and password boxes
@@ -1035,6 +1090,8 @@ fn main() {
     let seat_state = SeatState::new(&globals, &qh);
     let output_state = OutputState::new(&globals, &qh);
 
+    let (auth_sender, auth_receiver) = channel::channel::<AuthEvent>();
+
     let mut app = AppState {
         registry_state: RegistryState::new(&globals),
         compositor_state,
@@ -1053,6 +1110,7 @@ fn main() {
         ctrl_pressed: false,
         shift_pressed: false,
         pressed_key: None,
+        auth_sender,
     };
 
     event_queue.roundtrip(&mut app).unwrap();
@@ -1083,7 +1141,33 @@ fn main() {
 
     let mut event_loop = EventLoop::try_new().unwrap();
     let loop_handle = event_loop.handle();
-    WaylandSource::new(conn, event_queue).insert(loop_handle).unwrap();
+    WaylandSource::new(conn, event_queue).insert(loop_handle.clone()).unwrap();
+
+    loop_handle.insert_source(auth_receiver, |event, _metadata, app_state| {
+        match event {
+            channel::Event::Msg(msg) => {
+                if let Some(st) = &mut app_state.state {
+                    st.is_authenticating = false;
+                    st.login_btn.label = Some("Log In".to_string());
+                    match msg {
+                        AuthEvent::Success { username } => {
+                            st.status_lbl.text = format!("Welcome, {}!", username);
+                            st.status_lbl.is_error = false;
+                            st.login_success = true;
+                            app_state.exit = true;
+                        }
+                        AuthEvent::Failure(err_msg) => {
+                            st.status_lbl.text = err_msg;
+                            st.status_lbl.is_error = true;
+                        }
+                    }
+                    st.upload_vertices();
+                    app_state.redraw = true;
+                }
+            }
+            channel::Event::Closed => {}
+        }
+    }).unwrap();
 
     loop {
         event_loop.dispatch(std::time::Duration::from_millis(16), &mut app).unwrap();