login greeter
git clone https://git.lucas.co/cce-display-manager.git
chore: remove one-off debug/inspection bins from src/bin
These undeclared bins (pixel inspectors, ascii dumpers, dbus/pam probes, etc.)
were auto-compiled by every `cargo build`, slowed builds, and carried hardcoded
paths. Recoverable from history if needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
src/bin/test_pam.rs | 128 ----
src/bin/test_window.rs | 1523 ------------------------------------------------
2 files changed, 1651 deletions(-)
diff --git a/src/bin/test_pam.rs b/src/bin/test_pam.rs
deleted file mode 100644
index 05baff9..0000000
--- a/src/bin/test_pam.rs
+++ /dev/null
@@ -1,128 +0,0 @@
-use pam_sys;
-use libc;
-use std::ffi::{CString, CStr};
-use std::ptr;
-
-struct PamSessionData {
- username: String,
- password: String,
-}
-
-extern "C" fn pam_conversation_fn(
- num_msg: libc::c_int,
- msg: *mut *mut pam_sys::PamMessage,
- out_resp: *mut *mut pam_sys::PamResponse,
- appdata_ptr: *mut libc::c_void,
-) -> libc::c_int {
- let data = unsafe { &*(appdata_ptr as *const PamSessionData) };
- let resp_size = std::mem::size_of::<pam_sys::PamResponse>();
- let resp = unsafe { libc::calloc(num_msg as usize, resp_size) as *mut pam_sys::PamResponse };
- if resp.is_null() {
- return pam_sys::PamReturnCode::BUF_ERR as libc::c_int;
- }
-
- for i in 0..num_msg as isize {
- unsafe {
- let m = &**msg.offset(i);
- let r = &mut *resp.offset(i);
- let style = m.msg_style;
- if style == pam_sys::PamMessageStyle::PROMPT_ECHO_ON as libc::c_int {
- let user_c = CString::new(data.username.clone()).unwrap();
- r.resp = libc::strdup(user_c.as_ptr());
- } else if style == pam_sys::PamMessageStyle::PROMPT_ECHO_OFF as libc::c_int {
- let pass_c = CString::new(data.password.clone()).unwrap();
- r.resp = libc::strdup(pass_c.as_ptr());
- }
- }
- }
-
- unsafe { *out_resp = resp };
- pam_sys::PamReturnCode::SUCCESS as libc::c_int
-}
-
-fn main() {
- let args: Vec<String> = std::env::args().collect();
- if args.len() < 3 {
- println!("Usage: test_pam <username> <password>");
- return;
- }
- let username = &args[1];
- let password = &args[2];
-
- println!("Starting test PAM for user: {}", username);
-
- let mut handle: *mut pam_sys::PamHandle = ptr::null_mut();
- let data = Box::new(PamSessionData {
- username: username.to_string(),
- password: password.to_string(),
- });
-
- let conv = pam_sys::PamConversation {
- conv: Some(pam_conversation_fn),
- data_ptr: &*data as *const PamSessionData as *mut libc::c_void,
- };
-
- unsafe {
- let service = "cce-display-manager";
- let rc = pam_sys::start(service, Some(username), &conv, &mut handle);
- if rc != pam_sys::PamReturnCode::SUCCESS {
- println!("pam_start failed: {:?}", rc);
- return;
- }
-
- let pass_c = CString::new(password.clone()).unwrap();
- pam_sys::raw::pam_set_item(handle, pam_sys::PamItemType::AUTHTOK as libc::c_int, pass_c.as_ptr() as *const libc::c_void);
-
- let tty_c = CString::new("tty1").unwrap();
- pam_sys::raw::pam_set_item(handle, pam_sys::PamItemType::TTY as libc::c_int, tty_c.as_ptr() as *const libc::c_void);
-
- let rc = pam_sys::authenticate(&mut *handle, pam_sys::PamFlag::NONE);
- if rc != pam_sys::PamReturnCode::SUCCESS {
- println!("pam_authenticate failed: {:?}", rc);
- return;
- }
-
- let rc = pam_sys::acct_mgmt(&mut *handle, pam_sys::PamFlag::NONE);
- if rc != pam_sys::PamReturnCode::SUCCESS {
- println!("pam_acct_mgmt failed: {:?}", rc);
- return;
- }
-
- let rc = pam_sys::setcred(&mut *handle, pam_sys::PamFlag::ESTABLISH_CRED);
- if rc != pam_sys::PamReturnCode::SUCCESS {
- println!("pam_setcred (establish) failed: {:?}", rc);
- return;
- }
-
- let rc = pam_sys::open_session(&mut *handle, pam_sys::PamFlag::NONE);
- if rc != pam_sys::PamReturnCode::SUCCESS {
- println!("pam_open_session failed: {:?}", rc);
- return;
- }
-
- println!("PAM Session opened successfully!");
-
- let env_list = pam_sys::getenvlist(&mut *handle);
- if !env_list.is_null() {
- let mut idx = 0;
- loop {
- let env_ptr = *env_list.offset(idx);
- if !env_ptr.is_null() {
- idx += 1;
- let env_str = CStr::from_ptr(env_ptr).to_string_lossy();
- println!("PAM_ENV: {}", env_str);
- } else {
- break;
- }
- }
- pam_sys::raw::pam_misc_drop_env(env_list as *mut *mut libc::c_char);
- } else {
- println!("pam_getenvlist returned NULL");
- }
-
- // Clean up
- pam_sys::close_session(&mut *handle, pam_sys::PamFlag::NONE);
- pam_sys::setcred(&mut *handle, pam_sys::PamFlag::DELETE_CRED);
- pam_sys::end(&mut *handle, pam_sys::PamReturnCode::SUCCESS);
- }
-}
diff --git a/src/bin/test_window.rs b/src/bin/test_window.rs
deleted file mode 100644
index 12de5b3..0000000
--- a/src/bin/test_window.rs
+++ /dev/null
@@ -1,1523 +0,0 @@
-use glyphon::{
- Attrs, Buffer, FontSystem, Metrics,
-};
-
-use cce_ui::widget::{
- Button, ContentBg, TextLabel, Element, ElementState, MouseButton, Key, NamedKey, KeyEvent, TextBox,
- Widget, Container, focus, MouseScrollDelta, TextItem
-};
-use cce_ui::context::UiContext;
-use wayland_client::QueueHandle;
-use cce_ui::engine::{EngineState, LogicalPosition, LogicalSize, WindowSettings, Vertex, quad_vertices};
-use calloop::channel;
-
-
-
-
-
-fn widget_vertices(w: &dyn Element, sw: f32, sh: f32) -> Vec<Vertex> {
- let (x, y, ww, h) = w.rect();
- quad_vertices(x, y, ww, h, sw, sh, w.color()).to_vec()
-}
-
-fn make_text_buffer(font_system: &mut FontSystem, text: &str, size: f32) -> Buffer {
- let metrics = Metrics::new(size, size * 1.4);
- let mut buffer = Buffer::new(font_system, metrics);
- buffer.set_text(font_system, text, Attrs::new(), glyphon::Shaping::Advanced);
- buffer.shape_until_scroll(font_system, true);
- buffer
-}
-
-
-
-#[derive(Debug, Clone)]
-struct Session {
- name: String,
- exec: String,
- is_wayland: bool,
-}
-
-fn sanitize_exec(exec: &str) -> (String, Vec<String>) {
- let mut parts = Vec::new();
- for part in exec.split_whitespace() {
- if part.starts_with('%') {
- continue; // ignore desktop entry field codes
- }
- parts.push(part.to_string());
- }
- if parts.is_empty() {
- return (String::new(), Vec::new());
- }
- let cmd = parts.remove(0);
- (cmd, parts)
-}
-
-fn parse_desktop_file(path: &std::path::Path, is_wayland: bool) -> Result<Session, std::io::Error> {
- let content = std::fs::read_to_string(path)?;
- let mut name = None;
- let mut exec = None;
- for line in content.lines() {
- let line = line.trim();
- if line.starts_with("Name=") {
- name = Some(line["Name=".len()..].to_string());
- } else if line.starts_with("Exec=") {
- exec = Some(line["Exec=".len()..].to_string());
- }
- }
- if let (Some(n), Some(e)) = (name, exec) {
- Ok(Session { name: n, exec: e, is_wayland })
- } else {
- Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid desktop file"))
- }
-}
-
-fn discover_sessions() -> Vec<Session> {
- let mut sessions = Vec::new();
- if let Ok(entries) = std::fs::read_dir("/usr/share/wayland-sessions") {
- for entry in entries.flatten() {
- if entry.path().extension().map_or(false, |ext| ext == "desktop") {
- if let Ok(s) = parse_desktop_file(&entry.path(), true) {
- sessions.push(s);
- }
- }
- }
- }
- if let Ok(entries) = std::fs::read_dir("/usr/share/xsessions") {
- for entry in entries.flatten() {
- if entry.path().extension().map_or(false, |ext| ext == "desktop") {
- if let Ok(s) = parse_desktop_file(&entry.path(), false) {
- sessions.push(s);
- }
- }
- }
- }
- sessions.push(Session {
- name: "Bash Shell".to_string(),
- exec: "bash".to_string(),
- is_wayland: true,
- });
- sessions
-}
-
-// ── Custom LoginCard Container Element ──
-#[derive(Debug, Clone)]
-struct LoginCard {
- base: Widget,
-}
-
-impl LoginCard {
- fn new() -> Self {
- Self { base: Widget::new() }
- }
-}
-
-impl Element for LoginCard {
- fn base(&self) -> Option<&Widget> { Some(&self.base) }
- fn base_mut(&mut self) -> Option<&mut Widget> { Some(&mut self.base) }
- fn as_ptr(&self) -> *mut (dyn Element + 'static) {
- self as *const Self as *mut Self as *mut (dyn Element + 'static)
- }
- fn as_ptr_mut(&mut self) -> *mut (dyn Element + 'static) {
- self as *mut Self as *mut (dyn Element + 'static)
- }
- fn color(&self) -> [f32; 4] { [0.25, 0.25, 0.28, 0.75] } // Premium gray card background with transparency
-
- fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
- vec![]
- }
-
- fn text_labels(&self) -> Vec<TextLabel> {
- let sw = self.base.w;
- let sh = self.base.h;
- let card_x = (sw - 360.0) / 2.0;
- let card_y = (sh - 300.0) / 2.0;
- vec![
- TextLabel {
- text: "CCE DISPLAY MANAGER".to_string(),
- x: card_x + 30.0,
- y: card_y + 30.0,
- font_size: 15.0,
- color: [0xee, 0xee, 0xf5],
- },
- TextLabel {
- text: "Authenticate to begin your session".to_string(),
- x: card_x + 30.0,
- y: card_y + 50.0,
- font_size: 11.0,
- color: [0x83, 0x83, 0x8a],
- },
- ]
- }
-}
-
-#[derive(Debug, Clone)]
-struct StatusLabel {
- base: Widget,
- pub text: String,
- pub is_error: bool,
-}
-
-impl StatusLabel {
- fn new(text: String) -> Self {
- Self { base: Widget::new(), text, is_error: false }
- }
-}
-
-impl Element for StatusLabel {
- fn base(&self) -> Option<&Widget> { Some(&self.base) }
- fn base_mut(&mut self) -> Option<&mut Widget> { Some(&mut self.base) }
- fn as_ptr(&self) -> *mut (dyn Element + 'static) {
- self as *const Self as *mut Self as *mut (dyn Element + 'static)
- }
- fn as_ptr_mut(&mut self) -> *mut (dyn Element + 'static) {
- self as *mut Self as *mut (dyn Element + 'static)
- }
- fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] } // Transparent background
-
- fn text_labels(&self) -> Vec<TextLabel> {
- let col = if self.is_error {
- [0xee, 0x5c, 0x5c] // Soft red
- } else {
- [0x83, 0x83, 0x8a] // Dim text
- };
- vec![TextLabel {
- text: self.text.clone(),
- x: self.base.x,
- y: self.base.y,
- font_size: 11.0,
- color: col,
- }]
- }
-}
-
-#[derive(Debug, Clone)]
-struct SessionList {
- base: Widget,
- sessions: Vec<Session>,
- selected_idx: usize,
- hovered_idx: Option<usize>,
-}
-
-impl SessionList {
- fn new(sessions: Vec<Session>) -> Self {
- Self {
- base: Widget::new(),
- sessions,
- selected_idx: 0,
- hovered_idx: None,
- }
- }
-
- fn selected_session(&self) -> Option<&Session> {
- self.sessions.get(self.selected_idx)
- }
-}
-
-impl Element for SessionList {
- fn base(&self) -> Option<&Widget> { Some(&self.base) }
- fn base_mut(&mut self) -> Option<&mut Widget> { Some(&mut self.base) }
- fn as_ptr(&self) -> *mut (dyn Element + 'static) {
- self as *const Self as *mut Self as *mut (dyn Element + 'static)
- }
- fn as_ptr_mut(&mut self) -> *mut (dyn Element + 'static) {
- self as *mut Self as *mut (dyn Element + 'static)
- }
- fn color(&self) -> [f32; 4] { [0.07, 0.07, 0.10, 0.70] } // Semi-transparent sleek dark card background
-
- fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
- let mut quads = Vec::new();
-
- // 1. Panel borders (subtle blue accent)
- let border_color = [0.20, 0.40, 0.65, 0.5];
- quads.push((self.base.x, self.base.y, self.base.w, 1.5, border_color)); // top
- quads.push((self.base.x, self.base.y + self.base.h - 1.5, self.base.w, 1.5, border_color)); // bottom
- quads.push((self.base.x, self.base.y, 1.5, self.base.h, border_color)); // left
- quads.push((self.base.x + self.base.w - 1.5, self.base.y, 1.5, self.base.h, border_color)); // right
-
- let item_w = self.base.w - 20.0;
-
- // 2. Selected item background
- let selected_color = [0.20, 0.40, 0.65, 0.8]; // Solid blue highlight
- let sel_y = self.base.y + 40.0 + self.selected_idx as f32 * 36.0;
- quads.push((self.base.x + 10.0, sel_y, item_w, 32.0, selected_color));
-
- // 3. Hovered item background
- if let Some(h_idx) = self.hovered_idx {
- if h_idx != self.selected_idx && h_idx < self.sessions.len() {
- let hover_color = [1.0, 1.0, 1.0, 0.06]; // Subtle white overlay
- let h_y = self.base.y + 40.0 + h_idx as f32 * 36.0;
- quads.push((self.base.x + 10.0, h_y, item_w, 32.0, hover_color));
- }
- }
-
- quads
- }
-
- fn text_labels(&self) -> Vec<TextLabel> {
- let mut labels = Vec::new();
-
- // Header title
- labels.push(TextLabel {
- text: "SESSION MANAGER".to_string(),
- x: self.base.x + 15.0,
- y: self.base.y + 18.0,
- font_size: 11.0,
- color: [0x83, 0x83, 0x8a],
- });
-
- // Session items
- for (i, session) in self.sessions.iter().enumerate() {
- let item_y = self.base.y + 40.0 + i as f32 * 36.0;
-
- let display_name = if session.name == "Bash Shell" {
- "Bash Shell".to_string()
- } else {
- format!("{} ({})", session.name, if session.is_wayland { "Wayland" } else { "X11" })
- };
-
- let color = if i == self.selected_idx {
- [0xff, 0xff, 0xff]
- } else {
- [0xee, 0xee, 0xf5]
- };
-
- labels.push(TextLabel {
- text: display_name,
- x: self.base.x + 20.0,
- y: item_y + 10.0,
- font_size: 12.0,
- color,
- });
- }
-
- labels
- }
-
- fn on_cursor_moved(&mut self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
- let old_hovered = self.hovered_idx;
- self.hovered_idx = None;
- if self.hit_test(px, py, ctx) {
- let item_w = self.base.w - 20.0;
- for i in 0..self.sessions.len() {
- let item_y = self.base.y + 40.0 + i as f32 * 36.0;
- let item_x = self.base.x + 10.0;
- if px >= item_x && px <= item_x + item_w && py >= item_y && py <= item_y + 32.0 {
- self.hovered_idx = Some(i);
- break;
- }
- }
- }
- self.hovered_idx != old_hovered
- }
-
- fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32, ctx: &mut UiContext) -> bool {
- if button == MouseButton::Left && state == ElementState::Pressed {
- if self.hit_test(px, py, ctx) {
- let item_w = self.base.w - 20.0;
- for i in 0..self.sessions.len() {
- let item_y = self.base.y + 40.0 + i as f32 * 36.0;
- let item_x = self.base.x + 10.0;
- if px >= item_x && px <= item_x + item_w && py >= item_y && py <= item_y + 32.0 {
- if self.selected_idx != i {
- self.selected_idx = i;
- return true;
- }
- }
- }
- }
- }
- false
- }
-}
-
-// ── App State and Renderer ──
-struct State {
- bg: ContentBg,
- card: LoginCard,
- username_box: TextBox,
- password_box: TextBox,
- login_btn: Button,
- status_lbl: StatusLabel,
- session_list: SessionList,
- ui_context: cce_ui::context::UiContext,
- root_container: Container,
-
- font_system: FontSystem,
- info_buffer: Buffer,
-
- cursor_x: f32,
- cursor_y: f32,
-
- width: f32,
- height: f32,
- physical_width: u32,
- physical_height: u32,
- scale: f64,
-
- // State tracking
- login_success: bool,
- is_authenticating: bool,
- auth_request_id: u64,
- auth_sender: channel::Sender<AuthEvent>,
- auth_receiver: Option<channel::Channel<AuthEvent>>,
-
- text_items: Vec<TextItem>,
-}
-
-impl State {
- fn widgets_iter(&self) -> Vec<&dyn Element> {
- vec![
- &self.bg,
- &self.card,
- &self.username_box,
- &self.password_box,
- &self.login_btn,
- &self.status_lbl,
- &self.session_list,
- ]
- }
-
- #[allow(dead_code)]
- fn widgets_iter_mut(&mut self) -> Vec<&mut dyn Element> {
- vec![
- &mut self.bg,
- &mut self.card,
- &mut self.username_box,
- &mut self.password_box,
- &mut self.login_btn,
- &mut self.status_lbl,
- &mut self.session_list,
- ]
- }
-
- fn apply_layout(&mut self) {
- let sw = self.width;
- let sh = self.height;
-
- // Background spans the whole screen
- self.bg.set_rect(0.0, 0.0, sw, sh);
-
- // Root container spans the whole screen
- self.root_container.set_rect(0.0, 0.0, sw, sh);
-
- // Center card configuration
- let card_w = 360.0;
- let card_h = 280.0;
- let card_x = (sw - card_w) / 2.0;
- let card_y = (sh - card_h) / 2.0;
-
- // Card is full screen to render aspect-ratio centered oval custom graphic
- self.card.set_rect(0.0, 0.0, sw, sh);
-
- // Child components inside login card
- let content_x = card_x + 30.0;
-
- // Username text box
- self.username_box.set_rect(content_x, card_y + 80.0, 300.0, 36.0);
-
- // Password password box
- self.password_box.set_rect(content_x, card_y + 145.0, 300.0, 36.0);
-
- // Login button (full-width of the contents)
- self.login_btn.set_rect(content_x, card_y + 205.0, 300.0, 36.0);
-
- // Status message
- self.status_lbl.set_rect(content_x, card_y + 252.0, 300.0, 20.0);
-
- // Session list on top left
- let list_w = 260.0;
- let list_h = 40.0 + self.session_list.sessions.len() as f32 * 36.0;
- self.session_list.set_rect(30.0, 30.0, list_w, list_h);
- }
-
- fn rebuild_text_items(&mut self) {
- self.text_items.clear();
-
- self.text_items.push(TextItem {
- buffer: self.info_buffer.clone(),
- x: 20.0,
- y: self.height - 24.0,
- color: glyphon::Color::rgb(0x60, 0x60, 0x6e),
- bounds: None,
- });
-
- let mut widget_labels = Vec::new();
- for w in self.widgets_iter() {
- widget_labels.extend(w.text_labels());
- }
-
- for label in &widget_labels {
- let buf = make_text_buffer(&mut self.font_system, &label.text, label.font_size);
- self.text_items.push(TextItem {
- buffer: buf,
- x: label.x,
- y: label.y,
- color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
- bounds: None,
- });
- }
- }
-
- pub fn widgets_cursor_moved(&mut self, cx: f32, cy: f32) -> bool {
- let mut changed = false;
- if self.bg.cursor_moved(cx, cy, &mut self.ui_context) {
- changed = true;
- }
- let event = cce_ui::widget::Event::PointerMove {
- x: cx,
- y: cy,
- local_x: cx,
- local_y: cy,
- };
- let root_ptr = self.root_container.as_ptr_mut();
- if self.ui_context.propagate_event(&event, root_ptr) {
- changed = true;
- }
- changed
- }
-
- pub fn widgets_mouse_input(&mut self, button: MouseButton, state: ElementState, cx: f32, cy: f32) -> bool {
- let mut changed = false;
- if self.bg.mouse_input(button, state, cx, cy, &mut self.ui_context) {
- changed = true;
- }
- let event = cce_ui::widget::Event::MouseButton {
- button,
- state,
- x: cx,
- y: cy,
- local_x: cx,
- local_y: cy,
- };
- let root_ptr = self.root_container.as_ptr_mut();
- let handled = self.ui_context.propagate_event(&event, root_ptr);
- if button == MouseButton::Left && state == ElementState::Pressed {
- if !handled {
- self.ui_context.clear_focus();
- self.username_box.unfocus();
- self.password_box.unfocus();
- changed = true;
- }
- }
- if handled {
- changed = true;
- }
- changed
- }
-
- pub fn widgets_keyboard_input(&mut self, event: &KeyEvent) -> bool {
- let mut changed = false;
- if self.bg.keyboard_input(event, &mut self.ui_context) {
- changed = true;
- }
- let ui_event = cce_ui::widget::Event::KeyInput(event.clone());
- let root_ptr = self.root_container.as_ptr_mut();
- if self.ui_context.propagate_event(&ui_event, root_ptr) {
- changed = true;
- }
- changed
- }
- fn trigger_auth(&mut self) {
- let username = self.username_box.text.trim().to_string();
- let password = self.password_box.text.trim().to_string();
-
- if username.is_empty() {
- self.status_lbl.text = "Username cannot be empty".to_string();
- self.status_lbl.is_error = true;
- self.ui_context.set_focused(&mut self.username_box);
- self.username_box.focus();
- } else if password.is_empty() {
- if is_fprint_enabled() {
- self.auth_request_id += 1;
- self.status_lbl.text = "Scan finger to login or type password".to_string();
- self.status_lbl.is_error = false;
- self.is_authenticating = true;
- self.login_btn.base_mut().unwrap().label = Some("Authenticating...".to_string());
- authenticate_user(self.auth_request_id, username, password, self.auth_sender.clone());
- } else {
- self.status_lbl.text = "Password cannot be empty".to_string();
- self.status_lbl.is_error = true;
- self.ui_context.set_focused(&mut self.password_box);
- self.password_box.focus();
- }
- } else {
- self.auth_request_id += 1;
- self.status_lbl.text = "Authenticating...".to_string();
- self.status_lbl.is_error = false;
- self.is_authenticating = true;
- self.login_btn.base_mut().unwrap().label = Some("Authenticating...".to_string());
- authenticate_user(self.auth_request_id, username, password, self.auth_sender.clone());
- }
- }
-}
-
-impl cce_ui::engine::Application for State {
- type Message = String;
-
- fn new(_qh: &QueueHandle<cce_ui::engine::EngineState<Self>>, _sender: channel::Sender<Self::Message>) -> Self {
- let (auth_sender, auth_receiver) = channel::channel::<AuthEvent>();
- let mut font_system = cce_ui::create_font_system();
- let info_buffer = make_text_buffer(&mut font_system, "Press Tab to switch fields • Session selector: Click current session label", 11.0);
-
- // Prepopulate username from last_user file if it exists
- let last_user_path = "/var/lib/cce-display-manager/last_user";
- let current_user = if std::path::Path::new(last_user_path).exists() {
- std::fs::read_to_string(last_user_path)
- .map(|s| s.trim().to_string())
- .unwrap_or_else(|_| String::new())
- } else {
- let env_user = std::env::var("USER").unwrap_or_else(|_| String::new());
- if env_user == "root" || env_user == "cce-display-manager" {
- String::new()
- } else {
- env_user
- }
- };
-
- let sessions = discover_sessions();
- let last_session_path = "/var/lib/cce-display-manager/last_session";
- let last_session_exec = if std::path::Path::new(last_session_path).exists() {
- std::fs::read_to_string(last_session_path)
- .map(|s| s.trim().to_string())
- .unwrap_or_else(|_| String::new())
- } else {
- String::new()
- };
-
- let mut selected_idx = 0;
- if !last_session_exec.is_empty() {
- if let Some(pos) = sessions.iter().position(|s| s.exec == last_session_exec) {
- selected_idx = pos;
- }
- }
-
- let bg = ContentBg::new();
- let card = LoginCard::new();
- let username_box = TextBox::new(current_user).with_label("USERNAME");
- let password_box = TextBox::new(String::new()).with_password(true).with_label("PASSWORD");
- let login_btn = Button::new(0.0, 0.0, 300.0, 36.0).with_label("Log In");
- let status_lbl = StatusLabel::new("Enter password to start".to_string());
- let mut session_list = SessionList::new(sessions);
- session_list.selected_idx = selected_idx;
-
- let mut app = Self {
- bg,
- card,
- username_box,
- password_box,
- login_btn,
- status_lbl,
- session_list,
- ui_context: cce_ui::context::UiContext::new(),
- root_container: Container::new(),
- info_buffer,
- font_system,
- cursor_x: 0.0,
- cursor_y: 0.0,
- width: 1024.0,
- height: 768.0,
- physical_width: 1024,
- physical_height: 768,
- scale: 1.0,
- login_success: false,
- is_authenticating: false,
- auth_request_id: 0,
- auth_sender,
- auth_receiver: Some(auth_receiver),
- text_items: Vec::new(),
- };
-
- // Establish cce-ui parent-child widget tree hierarchy
- let ctx = &mut app.ui_context;
- focus::link_parent_child(&mut app.root_container, &mut app.card, ctx);
- focus::link_parent_child(&mut app.root_container, &mut app.session_list, ctx);
-
- focus::link_parent_child(&mut app.card, &mut app.username_box, ctx);
- focus::link_parent_child(&mut app.card, &mut app.password_box, ctx);
- focus::link_parent_child(&mut app.card, &mut app.login_btn, ctx);
- focus::link_parent_child(&mut app.card, &mut app.status_lbl, ctx);
-
- let has_username = !app.username_box.text.trim().to_string().is_empty();
- if has_username {
- app.ui_context.set_focused(&mut app.password_box);
- app.password_box.focus();
- } else {
- app.ui_context.set_focused(&mut app.username_box);
- app.username_box.focus();
- }
-
- // Start background fingerprint/empty-password authentication if username is prepopulated and fprintd is enabled!
- let username = app.username_box.text.trim().to_string();
- if !username.is_empty() {
- if is_fprint_enabled() {
- app.auth_request_id += 1;
- app.is_authenticating = true;
- app.login_btn.base_mut().unwrap().label = Some("Authenticating...".to_string());
- app.status_lbl.text = "Scan finger to login or type password".to_string();
- authenticate_user(app.auth_request_id, username, String::new(), app.auth_sender.clone());
- }
- }
-
- app
- }
-
- fn settings(&self) -> WindowSettings {
- WindowSettings {
- title: "CCE Display Manager".to_string(),
- app_id: "cce-display-manager".to_string(),
- width: 1024,
- height: 768,
- fullscreen: false,
- min_size: Some((1024, 768)),
- }
- }
-
- fn update(&mut self, _msg: Self::Message, _needs_rebuild: &mut bool, _exit: &mut bool) {}
-
- fn tick(&mut self, _dt: f32, _needs_rebuild: &mut bool) {}
-
- fn view(&mut self, quads: &mut Vec<(f32, f32, f32, f32, [f32; 4])>, size: LogicalSize, scale: f64) {
- if (self.width - size.width as f32).abs() > 0.001 || (self.height - size.height as f32).abs() > 0.001 || (self.scale - scale).abs() > 0.001 {
- self.width = size.width as f32;
- self.height = size.height as f32;
- self.physical_width = (size.width * scale as f32) as u32;
- self.physical_height = (size.height * scale as f32) as u32;
- self.scale = scale;
- self.apply_layout();
- self.rebuild_text_items();
- }
-
- for w in self.widgets_iter() {
- let is_card = w.base().map_or(false, |b| std::ptr::eq(b, &self.card.base));
- if !is_card {
- quads.extend(w.all_quads(&self.ui_context));
- }
- }
- }
-
- fn view_rounded_quads(&mut self, quads: &mut Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))>, _size: LogicalSize, _scale: f64) {
- for w in self.widgets_iter() {
- let is_card = w.base().map_or(false, |b| std::ptr::eq(b, &self.card.base));
- if !is_card {
- quads.extend(w.all_rounded_quads(&self.ui_context));
- }
- }
- }
-
- fn custom_vertices(&mut self, verts: &mut Vec<Vertex>, _size: LogicalSize, _scale: f64) {
- let sw = self.width;
- let sh = self.height;
-
- let mut card_verts = widget_vertices(&self.card, sw, sh);
- for v in &mut card_verts {
- v.clip_circle = [-999.0, 0.0, 0.0];
- }
- verts.extend(card_verts);
-
- for (qx, qy, qw, qh, qc) in self.card.all_quads(&self.ui_context) {
- let mut q_verts = quad_vertices(qx, qy, qw, qh, sw, sh, qc).to_vec();
- for v in &mut q_verts {
- v.clip_circle = [-999.0, 0.0, 0.0];
- }
- verts.extend(q_verts);
- }
- }
-
- fn text_items(&self) -> &[TextItem] {
- &self.text_items
- }
-
- fn register_sources(&mut self, handle: &calloop::LoopHandle<'_, EngineState<Self>>) {
- if let Some(auth_receiver) = self.auth_receiver.take() {
- handle.insert_source(auth_receiver, |event, _metadata, engine_state| {
- let app = engine_state.inner.as_mut().unwrap();
- let mut redraw = false;
- match event {
- channel::Event::Msg(msg) => {
- let ev_request_id = match &msg {
- AuthEvent::Success { request_id, .. } => *request_id,
- AuthEvent::Failure { request_id, .. } => *request_id,
- AuthEvent::Info { request_id, .. } => *request_id,
- };
-
- if ev_request_id != app.auth_request_id {
- return;
- }
-
- match msg {
- AuthEvent::Success { username, .. } => {
- app.is_authenticating = false;
- app.login_btn.base_mut().unwrap().label = Some("Log In".to_string());
- app.status_lbl.text = format!("Welcome, {}!", username);
- app.status_lbl.is_error = false;
- app.login_success = true;
- if let Some(session) = app.session_list.selected_session() {
- println!("AUTH_SUCCESS|{}|{}|{}|{}", app.username_box.text.trim(), session.exec, session.is_wayland, app.password_box.text.trim());
- std::process::exit(0);
- }
- }
- AuthEvent::Failure { err_msg, .. } => {
- app.is_authenticating = false;
- app.login_btn.base_mut().unwrap().label = Some("Log In".to_string());
- app.status_lbl.text = err_msg;
- app.status_lbl.is_error = true;
- app.password_box.text.clear();
- app.password_box.edit_buffer.clear();
- app.ui_context.set_focused(&mut app.password_box);
- app.password_box.focus();
- }
- AuthEvent::Info { msg, .. } => {
- app.status_lbl.text = msg;
- app.status_lbl.is_error = false;
- }
- }
- redraw = true;
- }
- channel::Event::Closed => {}
- }
- if redraw {
- app.rebuild_text_items();
- engine_state.redraw = true;
- }
- }).unwrap();
- }
- }
-
- fn handle_pointer_move(&mut self, pos: LogicalPosition, needs_rebuild: &mut bool) {
- let lx = pos.x as f32;
- let ly = pos.y as f32;
- self.cursor_x = lx;
- self.cursor_y = ly;
- if self.widgets_cursor_moved(lx, ly) {
- *needs_rebuild = true;
- self.rebuild_text_items();
- }
- }
-
- fn handle_mouse_input(&mut self, button: MouseButton, state: ElementState, pos: LogicalPosition, needs_rebuild: &mut bool) -> Option<Self::Message> {
- if self.is_authenticating {
- return None;
- }
- let lx = pos.x as f32;
- let ly = pos.y as f32;
- let mut changed = false;
- if self.widgets_mouse_input(button, state, lx, ly) {
- changed = true;
- }
- if button == MouseButton::Left && state == ElementState::Pressed {
- if self.login_btn.take_click() {
- self.trigger_auth();
- changed = true;
- }
- }
- if changed {
- *needs_rebuild = true;
- self.rebuild_text_items();
- }
- None
- }
-
- fn handle_mouse_wheel(&mut self, _delta: &MouseScrollDelta, _pos: LogicalPosition, _needs_rebuild: &mut bool) {}
-
- fn handle_key_input(&mut self, event: &KeyEvent, needs_rebuild: &mut bool) -> Option<Self::Message> {
- let logical_key = &event.logical_key;
- let ctrl_pressed = event.ctrl;
-
- // Check for Ctrl+C to abort/exit back to TTY
- if ctrl_pressed && (logical_key == &Key::Character("c".to_string()) || logical_key == &Key::Character("C".to_string())) {
- log::error!("Ctrl+C pressed. Aborting greeter.");
- std::process::exit(130);
- }
-
- // Check for F5 to request daemon restart
- if logical_key == &Key::Named(NamedKey::F5) {
- log::info!("F5 pressed. Requesting daemon restart.");
- std::process::exit(135);
- }
-
- let is_ctrl_p = ctrl_pressed && (logical_key == &Key::Character("p".to_string()) || logical_key == &Key::Character("P".to_string()));
- let is_ctrl_n = ctrl_pressed && (logical_key == &Key::Character("n".to_string()) || logical_key == &Key::Character("N".to_string()));
-
- if event.state == ElementState::Pressed {
- // If we are currently in fingerprint authentication and the user starts typing a password,
- // cancel the fingerprint auth and let them type.
- if self.is_authenticating {
- if self.password_box.text.is_empty() {
- let is_typing = !ctrl_pressed && match logical_key {
- Key::Character(_) | Key::Named(NamedKey::Backspace) | Key::Named(NamedKey::Delete) | Key::Named(NamedKey::Space) => true,
- _ => false,
- };
- if is_typing {
- self.auth_request_id += 1;
- self.is_authenticating = false;
- self.login_btn.base_mut().unwrap().label = Some("Log In".to_string());
- self.status_lbl.text = "Enter password to start".to_string();
- self.status_lbl.is_error = false;
- } else {
- let is_nav = match logical_key {
- Key::Named(NamedKey::ArrowUp) | Key::Named(NamedKey::ArrowDown) | Key::Named(NamedKey::Tab) => true,
- _ => is_ctrl_p || is_ctrl_n,
- };
- if !is_nav {
- return None;
- }
- }
- } else {
- return None;
- }
- }
-
- let mut changed = false;
-
- // Handle Up/Down or Ctrl+P/N navigation to cycle sessions
- let cycle_up = (logical_key == &Key::Named(NamedKey::ArrowUp) || is_ctrl_p) && !self.session_list.sessions.is_empty();
- let cycle_down = (logical_key == &Key::Named(NamedKey::ArrowDown) || is_ctrl_n) && !self.session_list.sessions.is_empty();
-
- if cycle_up {
- let len = self.session_list.sessions.len();
- self.session_list.selected_idx = (self.session_list.selected_idx + len - 1) % len;
- self.session_list.hovered_idx = None;
- changed = true;
- } else if cycle_down {
- let len = self.session_list.sessions.len();
- self.session_list.selected_idx = (self.session_list.selected_idx + 1) % len;
- self.session_list.hovered_idx = None;
- changed = true;
- } else if logical_key == &Key::Named(NamedKey::Tab) {
- let is_user_focused = self.username_box.focused(&self.ui_context);
- if is_user_focused {
- self.ui_context.set_focused(&mut self.password_box);
- self.username_box.unfocus();
- self.password_box.focus();
- } else {
- self.ui_context.set_focused(&mut self.username_box);
- self.password_box.unfocus();
- self.username_box.focus();
- }
- changed = true;
- } else if logical_key == &Key::Named(NamedKey::Enter) && self.password_box.focused(&self.ui_context) {
- self.password_box.keyboard_input(event, &mut self.ui_context);
- self.trigger_auth();
- changed = true;
- } else if logical_key == &Key::Named(NamedKey::Enter) && self.username_box.focused(&self.ui_context) {
- self.username_box.keyboard_input(event, &mut self.ui_context);
- self.ui_context.set_focused(&mut self.password_box);
- self.username_box.unfocus();
- self.password_box.focus();
- changed = true;
- } else {
- if self.widgets_keyboard_input(event) {
- changed = true;
- }
- }
-
- if changed {
- *needs_rebuild = true;
- self.rebuild_text_items();
- }
- }
-
- None
- }
-
- fn clear_color(&self) -> [f32; 4] {
- [0.03, 0.03, 0.05, 1.0]
- }
-}
-
-#[derive(Debug, Clone)]
-enum AuthEvent {
- Success { request_id: u64, username: String },
- Failure { request_id: u64, err_msg: String },
- Info { request_id: u64, msg: String },
-}
-
-fn is_fprint_enabled() -> bool {
- std::fs::read_to_string("/etc/pam.d/cce-display-manager")
- .map(|content| {
- content.lines().any(|line| {
- let trimmed = line.trim();
- trimmed.contains("pam_fprintd.so") && !trimmed.starts_with('#')
- })
- })
- .unwrap_or(false)
-}
-
-fn authenticate_user(request_id: u64, username: String, password: String, sender: channel::Sender<AuthEvent>) {
- std::thread::spawn(move || {
- let service = if password.is_empty() {
- "cce-display-manager"
- } else {
- "cce-display-manager-password"
- };
-
- let mut auth = match PamSession::new(service, &username, &password, request_id, Some(sender.clone())) {
- Ok(a) => a,
- Err(e) => {
- let _ = sender.send(AuthEvent::Failure { request_id, err_msg: format!("{:?}", e) });
- return;
- }
- };
-
- if let Err(e) = auth.authenticate() {
- let _ = sender.send(AuthEvent::Failure { request_id, err_msg: format!("{:?}", e) });
- return;
- }
-
- if let Err(e) = auth.open_session() {
- let _ = sender.send(AuthEvent::Failure { request_id, err_msg: format!("{:?}", e) });
- return;
- }
-
- let _ = sender.send(AuthEvent::Success { request_id, username });
- });
-}
-
-#[derive(serde::Deserialize, Debug, Default)]
-struct SystemConfig {
- scale: Option<f64>,
-}
-
-fn load_system_config() -> SystemConfig {
- let path = "/etc/cce/cce.json";
- if std::path::Path::new(path).exists() {
- if let Ok(content) = std::fs::read_to_string(path) {
- if let Ok(config) = serde_json::from_str(&content) {
- return config;
- }
- }
- }
- SystemConfig::default()
-}
-
-fn run_greeter() {
- let sys_config = load_system_config();
- let layout_scale = sys_config.scale.unwrap_or(1.0);
- let cursor_size = (24.0 * layout_scale) as u32;
- std::env::set_var("XCURSOR_SIZE", cursor_size.to_string());
-
- cce_ui::engine::run::<State>();
-
- std::process::exit(1);
-}
-
-struct PamSessionData {
- username: String,
- password: String,
- request_id: u64,
- sender: Option<channel::Sender<AuthEvent>>,
-}
-
-extern "C" fn pam_conversation_fn(
- num_msg: libc::c_int,
- msg: *mut *mut pam_sys::PamMessage,
- out_resp: *mut *mut pam_sys::PamResponse,
- appdata_ptr: *mut libc::c_void,
-) -> libc::c_int {
- let data = unsafe { &*(appdata_ptr as *const PamSessionData) };
- let resp_size = std::mem::size_of::<pam_sys::PamResponse>();
- let resp = unsafe { libc::calloc(num_msg as usize, resp_size) as *mut pam_sys::PamResponse };
- if resp.is_null() {
- return pam_sys::PamReturnCode::BUF_ERR as libc::c_int;
- }
-
- for i in 0..num_msg as isize {
- unsafe {
- let m = &**msg.offset(i);
- let r = &mut *resp.offset(i);
- let style = m.msg_style;
- if style == pam_sys::PamMessageStyle::PROMPT_ECHO_ON as libc::c_int {
- let user_c = std::ffi::CString::new(data.username.clone()).unwrap();
- r.resp = libc::strdup(user_c.as_ptr());
- } else if style == pam_sys::PamMessageStyle::PROMPT_ECHO_OFF as libc::c_int {
- let pass_c = std::ffi::CString::new(data.password.clone()).unwrap();
- r.resp = libc::strdup(pass_c.as_ptr());
- } else if style == pam_sys::PamMessageStyle::ERROR_MSG as libc::c_int || style == pam_sys::PamMessageStyle::TEXT_INFO as libc::c_int {
- if !m.msg.is_null() {
- let msg_str = std::ffi::CStr::from_ptr(m.msg).to_string_lossy().into_owned();
- if let Some(ref sender) = data.sender {
- let _ = sender.send(AuthEvent::Info { request_id: data.request_id, msg: msg_str });
- }
- }
- }
- }
- }
-
- unsafe { *out_resp = resp };
- pam_sys::PamReturnCode::SUCCESS as libc::c_int
-}
-
-struct PamSession {
- handle: *mut pam_sys::PamHandle,
- _data: Box<PamSessionData>,
- has_open_session: bool,
-}
-
-impl PamSession {
- fn new(service: &str, username: &str, password: &str, request_id: u64, sender: Option<channel::Sender<AuthEvent>>) -> Result<Self, pam_sys::PamReturnCode> {
- let mut handle: *mut pam_sys::PamHandle = std::ptr::null_mut();
- let data = Box::new(PamSessionData {
- username: username.to_string(),
- password: password.to_string(),
- request_id,
- sender,
- });
-
- let conv = pam_sys::PamConversation {
- conv: Some(pam_conversation_fn),
- data_ptr: &*data as *const PamSessionData as *mut libc::c_void,
- };
-
- let rc = pam_sys::start(service, Some(username), &conv, &mut handle);
- if rc != pam_sys::PamReturnCode::SUCCESS {
- return Err(rc);
- }
-
- unsafe {
- let pass_c = std::ffi::CString::new(password).unwrap();
- let _ = pam_sys::raw::pam_set_item(handle, pam_sys::PamItemType::AUTHTOK as libc::c_int, pass_c.as_ptr() as *const libc::c_void);
-
- let raw_tty = std::fs::read_link("/proc/self/fd/0")
- .ok()
- .and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
- .unwrap_or_else(|| "tty1".to_string());
- let is_real_tty = raw_tty.starts_with("tty");
- let tty_name = if is_real_tty { raw_tty } else { "tty1".to_string() };
-
- let tty_c = std::ffi::CString::new(tty_name).unwrap();
- let _ = pam_sys::raw::pam_set_item(handle, pam_sys::PamItemType::TTY as libc::c_int, tty_c.as_ptr() as *const libc::c_void);
- }
-
- Ok(Self { handle, _data: data, has_open_session: false })
- }
-
- fn putenv(&mut self, name_value: &str) -> Result<(), pam_sys::PamReturnCode> {
- let c_str = std::ffi::CString::new(name_value).unwrap();
- let rc = unsafe { pam_sys::raw::pam_putenv(self.handle, c_str.as_ptr()) };
- if rc == 0 {
- Ok(())
- } else {
- Err(unsafe { std::mem::transmute(rc as u8) })
- }
- }
-
- fn authenticate(&mut self) -> Result<(), pam_sys::PamReturnCode> {
- unsafe {
- let rc = pam_sys::authenticate(&mut *self.handle, pam_sys::PamFlag::NONE);
- if rc != pam_sys::PamReturnCode::SUCCESS {
- return Err(rc);
- }
-
- let rc = pam_sys::acct_mgmt(&mut *self.handle, pam_sys::PamFlag::NONE);
- if rc != pam_sys::PamReturnCode::SUCCESS {
- return Err(rc);
- }
- }
- Ok(())
- }
-
- fn open_session(&mut self) -> Result<(), pam_sys::PamReturnCode> {
- unsafe {
- let rc = pam_sys::setcred(&mut *self.handle, pam_sys::PamFlag::ESTABLISH_CRED);
- if rc != pam_sys::PamReturnCode::SUCCESS {
- return Err(rc);
- }
-
- let rc = pam_sys::open_session(&mut *self.handle, pam_sys::PamFlag::NONE);
- if rc != pam_sys::PamReturnCode::SUCCESS {
- return Err(rc);
- }
-
- // Follow openSSH and call pam_setcred before and after open_session
- let rc = pam_sys::setcred(&mut *self.handle, pam_sys::PamFlag::REINITIALIZE_CRED);
- if rc != pam_sys::PamReturnCode::SUCCESS {
- return Err(rc);
- }
- }
- self.has_open_session = true;
- Ok(())
- }
-
- fn get_env(&mut self) -> Vec<(String, String)> {
- let mut vec = Vec::new();
- unsafe {
- let env_list = pam_sys::getenvlist(&mut *self.handle);
- if !env_list.is_null() {
- let mut idx = 0;
- loop {
- let env_ptr = *env_list.offset(idx);
- if !env_ptr.is_null() {
- idx += 1;
- let env_str = std::ffi::CStr::from_ptr(env_ptr).to_string_lossy();
- let split: Vec<_> = env_str.splitn(2, '=').collect();
- if split.len() == 2 {
- vec.push((split[0].to_string(), split[1].to_string()));
- }
- } else {
- break;
- }
- }
- pam_sys::raw::pam_misc_drop_env(env_list as *mut *mut libc::c_char);
- }
- }
- vec
- }
-}
-
-impl Drop for PamSession {
- fn drop(&mut self) {
- unsafe {
- if self.has_open_session {
- pam_sys::close_session(&mut *self.handle, pam_sys::PamFlag::NONE);
- }
- let rc = pam_sys::setcred(&mut *self.handle, pam_sys::PamFlag::DELETE_CRED);
- pam_sys::end(&mut *self.handle, rc);
- }
- }
-}
-
-fn run_daemon() {
- use users::os::unix::UserExt;
- let uid = users::get_current_uid();
- if uid != 0 {
- log::error!("Error: Daemon mode must be run as root (UID 0). Effective UID: {}", uid);
- log::info!("For local development/testing, run with: cargo run -- --greeter");
- std::process::exit(1);
- }
-
- let raw_tty = std::fs::read_link("/proc/self/fd/0")
- .ok()
- .and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
- .unwrap_or_else(|| "tty1".to_string());
- let is_real_tty = raw_tty.starts_with("tty");
- let tty_name = if is_real_tty { raw_tty } else { "tty1".to_string() };
-
- // Redirect stdout and stderr of the daemon to a log file
- let log_path = format!("/tmp/cce-display-manager-daemon-{}.log", tty_name);
- if let Ok(log_file) = std::fs::OpenOptions::new()
- .create(true)
- .write(true)
- .truncate(true)
- .open(&log_path)
- {
- use std::os::unix::io::AsRawFd;
- let fd = log_file.as_raw_fd();
- unsafe {
- libc::dup2(fd, 1);
- libc::dup2(fd, 2);
- }
- }
-
- log::info!("Starting display manager daemon on {}...", tty_name);
-
- let runtime_dir = format!("/run/cce-display-manager-{}", tty_name);
- if !std::path::Path::new(&runtime_dir).exists() {
- std::fs::create_dir_all(&runtime_dir).expect("failed to create runtime dir");
- use std::os::unix::fs::PermissionsExt;
- std::fs::set_permissions(&runtime_dir, std::fs::Permissions::from_mode(0o700))
- .expect("failed to set runtime dir permissions");
- }
- // Ensure polkit-agent-helper-1 has SUID root permissions so cce-authenticator can authenticate sessions
- let helper_paths = [
- "/usr/lib/polkit-1/polkit-agent-helper-1",
- "/usr/lib/policykit-1/polkit-agent-helper-1",
- ];
- for path in &helper_paths {
- if std::path::Path::new(path).exists() {
- use std::os::unix::fs::PermissionsExt;
- if let Ok(metadata) = std::fs::metadata(path) {
- let mut perms = metadata.permissions();
- let mode = perms.mode();
- if (mode & 0o4000) == 0 {
- log::info!("Restoring SUID root permissions to {} (current mode: {:o})", path, mode);
- perms.set_mode(mode | 0o4000 | 0o0111);
- if let Err(e) = std::fs::set_permissions(path, perms) {
- log::error!("Failed to set permissions on {}: {}", path, e);
- }
- }
- }
- }
- }
-
- loop {
- if is_real_tty {
- log::info!("Waiting for {} to become the active TTY...", tty_name);
- loop {
- if let Ok(active_tty) = std::fs::read_to_string("/sys/class/tty/tty0/active") {
- let active_tty = active_tty.trim();
- if active_tty == tty_name {
- log::info!("{} is now active. Spawning greeter.", tty_name);
- break;
- }
- }
- std::thread::sleep(std::time::Duration::from_millis(250));
- }
- }
-
- log::info!("Spawning greeter session via cage...");
-
- let mut exe_path = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("/usr/bin/cce-display-manager"));
- if !exe_path.exists() {
- exe_path = std::path::PathBuf::from("/usr/bin/cce-display-manager");
- }
-
- let mut child = std::process::Command::new("cage")
- .arg("-s")
- .arg("--")
- .arg(exe_path)
- .arg("--greeter")
- .env("XDG_RUNTIME_DIR", &runtime_dir)
- .env("LIBSEAT_BACKEND", "seatd")
- .env("WLR_DRM_NO_MODIFIERS", "1")
- .env("WLR_DRM_DEVICES", "/dev/dri/card1:/dev/dri/card0")
- .stdout(std::process::Stdio::piped())
- .spawn()
- .expect("failed to spawn cage compositor wrapper. Is cage installed?");
-
- let stdout = child.stdout.take().expect("failed to open child stdout");
- let reader = std::io::BufReader::new(stdout);
- let mut auth_success = None;
-
- use std::io::BufRead;
- for line in reader.lines() {
- if let Ok(line_str) = line {
- if line_str.starts_with("AUTH_SUCCESS|") {
- let parts: Vec<&str> = line_str.split('|').collect();
- if parts.len() == 5 {
- let username = parts[1].to_string();
- let exec = parts[2].to_string();
- let is_wayland = parts[3].parse::<bool>().unwrap_or(true);
- let password = parts[4].to_string();
- log::info!("[greeter-stdout] AUTH_SUCCESS|{}|{}|{}", username, exec, is_wayland);
- auth_success = Some((username, exec, is_wayland, password));
- }
- } else {
- log::info!("[greeter-stdout] {}", line_str);
- }
- }
- }
-
- let status = child.wait().expect("failed to wait on child process");
- log::info!("Greeter session exited with status: {}", status);
-
- if status.code() == Some(130) {
- log::info!("Abort requested via Ctrl+C. Exiting display manager daemon.");
- std::process::exit(0);
- }
-
- if status.code() == Some(135) {
- log::info!("Restart requested via F5. Re-executing daemon...");
- let mut exe_path = std::path::PathBuf::from("/usr/bin/cce-display-manager");
- if !exe_path.exists() {
- exe_path = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("/usr/bin/cce-display-manager"));
- }
- let args: Vec<String> = std::env::args().collect();
- use std::os::unix::process::CommandExt;
- let mut cmd = std::process::Command::new(&exe_path);
- cmd.args(&args[1..]);
- let err = cmd.exec();
- log::error!("Failed to re-exec daemon: {:?}", err);
- }
-
- if auth_success.is_none() {
- // Sleep briefly to prevent high CPU usage if the greeter keeps crashing on startup
- std::thread::sleep(std::time::Duration::from_millis(1000));
- }
-
- if let Some((username, exec, is_wayland, password)) = auth_success {
- // Write last logged-in user and session to persistent files
- let var_lib = "/var/lib/cce-display-manager";
- if let Err(e) = std::fs::create_dir_all(var_lib) {
- log::error!("Failed to create var lib dir: {:?}", e);
- } else {
- if let Err(e) = std::fs::write(format!("{}/last_user", var_lib), &username) {
- log::error!("Failed to write last_user file: {:?}", e);
- }
- if let Err(e) = std::fs::write(format!("{}/last_session", var_lib), &exec) {
- log::error!("Failed to write last_session file: {:?}", e);
- }
- }
-
- log::info!("Launching user session Exec: '{}' (Wayland: {}) for user: '{}'", exec, is_wayland, username);
-
- let pid = unsafe { libc::fork() };
- if pid < 0 {
- log::error!("Fork failed: {}", std::io::Error::last_os_error());
- continue;
- } else if pid == 0 {
- // Child process: execute PAM session and spawn the compositor/user session
- let user = match users::get_user_by_name(&username) {
- Some(u) => u,
- None => {
- log::error!("Error: User '{}' not found in system.", username);
- std::process::exit(1);
- }
- };
-
- let user_uid = user.uid();
- let user_gid = user.primary_group_id();
- let home_dir = user.home_dir().to_path_buf();
- let shell = user.shell().to_str().unwrap_or("/bin/bash").to_string();
-
- let user_runtime_dir = format!("/run/user/{}", user_uid);
-
- // Set environment variables in the session worker process before PAM open_session.
- // This is crucial for pam_gnome_keyring.so / pam_kwallet5.so to run successfully.
- std::env::set_var("USER", &username);
- std::env::set_var("LOGNAME", &username);
- std::env::set_var("HOME", home_dir.to_str().unwrap_or(""));
- std::env::set_var("SHELL", &shell);
- std::env::set_var("XDG_RUNTIME_DIR", &user_runtime_dir);
-
- let service = if password.is_empty() {
- "cce-display-manager-autologin"
- } else {
- "cce-display-manager-password"
- };
- let mut auth = match PamSession::new(service, &username, &password, 0, None) {
- Ok(a) => a,
- Err(_) => {
- let fallback_service = if password.is_empty() {
- "ly-autologin"
- } else {
- "login"
- };
- match PamSession::new(fallback_service, &username, &password, 0, None) {
- Ok(a) => a,
- Err(e) => {
- log::error!("PAM Init Error in child: {:?}", e);
- std::process::exit(1);
- }
- }
- }
- };
-
- let session_type_env = if is_wayland {
- "XDG_SESSION_TYPE=wayland"
- } else {
- "XDG_SESSION_TYPE=x11"
- };
- let _ = auth.putenv(session_type_env);
- let _ = auth.putenv("XDG_SESSION_CLASS=user");
-
- if let Err(e) = auth.authenticate() {
- log::error!("PAM Authentication failed in child: {:?}", e);
- std::process::exit(1);
- }
-
- if let Err(e) = auth.open_session() {
- log::error!("PAM Session failed in child: {:?}", e);
- std::process::exit(1);
- }
-
- let pam_env = auth.get_env();
- log::info!("PAM Environment variables: {:?}", pam_env);
-
- if let Some((_, session_id)) = pam_env.iter().find(|(k, _)| k == "XDG_SESSION_ID") {
- log::info!("Explicitly activating logind session {} via loginctl...", session_id);
- let _ = std::process::Command::new("loginctl")
- .arg("activate")
- .arg(session_id)
- .status();
- }
-
- let (cmd_bin, cmd_args): (String, Vec<String>) = if is_wayland {
- sanitize_exec(&exec)
- } else {
- let (client_bin, client_args) = sanitize_exec(&exec);
- let xinit_bin = "/usr/sbin/xinit".to_string();
- let mut args = vec![client_bin];
- args.extend(client_args);
- args.push("--".to_string());
- args.push("-keeptty".to_string());
- (xinit_bin, args)
- };
-
- if cmd_bin.is_empty() {
- log::error!("Error: Resolved execution command is empty.");
- std::process::exit(1);
- }
-
- log::info!("Spawning session: {} with args {:?} for UID={}, GID={}", cmd_bin, cmd_args, user_uid, user_gid);
-
- use std::os::unix::process::CommandExt;
- let mut session_cmd = std::process::Command::new(&cmd_bin);
- session_cmd
- .args(&cmd_args)
- .envs(pam_env)
- .current_dir(&home_dir)
- .env("USER", &username)
- .env("LOGNAME", &username)
- .env("HOME", home_dir.to_str().unwrap())
- .env("SHELL", &shell)
- .env("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
- .env("XDG_RUNTIME_DIR", &user_runtime_dir)
- .env("XDG_SESSION_TYPE", if is_wayland { "wayland" } else { "x11" })
- .env("XDG_SESSION_CLASS", "user")
- .stdin(std::process::Stdio::inherit())
- .stdout(std::process::Stdio::inherit())
- .stderr(std::process::Stdio::inherit());
-
- // Filter out sudo env vars so they don't leak into the user session
- for key in &["SUDO_USER", "SUDO_UID", "SUDO_GID", "SUDO_COMMAND"] {
- session_cmd.env_remove(key);
- }
-
- let username_c = std::ffi::CString::new(username.clone()).unwrap();
- unsafe {
- session_cmd.pre_exec(move || {
- if libc::initgroups(username_c.as_ptr(), user_gid as libc::gid_t) != 0 {
- return Err(std::io::Error::last_os_error());
- }
- if libc::setgid(user_gid as libc::gid_t) != 0 {
- return Err(std::io::Error::last_os_error());
- }
- if libc::setuid(user_uid as libc::uid_t) != 0 {
- return Err(std::io::Error::last_os_error());
- }
- Ok(())
- });
- }
-
- match session_cmd.spawn() {
- Ok(mut child_proc) => {
- let _ = child_proc.wait();
- }
- Err(e) => {
- log::error!("Failed to launch session: {}", e);
- }
- }
- log::info!("User session ended.");
- std::mem::drop(auth);
- std::process::exit(0);
- } else {
- // Parent process: block until the session worker child terminates
- let mut status: libc::c_int = 0;
- unsafe {
- libc::waitpid(pid, &mut status, 0);
- }
- log::info!("Session worker child (PID {}) exited with status: {}", pid, status);
-
- if let Some(vt) = tty_name.strip_prefix("tty").and_then(|s| s.parse::<u32>().ok()) {
- log::info!("Switching back to VT {}...", vt);
- let _ = std::process::Command::new("chvt")
- .arg(vt.to_string())
- .status();
- }
- }
- }
- }
-}
-
-fn main() {
- if std::env::var("RUST_LOG").is_err() {
- std::env::set_var("RUST_LOG", "info");
- }
- env_logger::init();
- let args: Vec<String> = std::env::args().collect();
- if args.len() > 1 && args[1] == "--greeter" {
- run_greeter();
- } else {
- run_daemon();
- }
-}