login greeter
git clone https://git.lucas.co/cce-display-manager.git
Unlock keyring automatically on login, switch config to JSON, and rename to CCE Display Manager
Cargo.toml | 1 -
README.md | 2 +-
cce.json | 3 +
cce.toml | 2 -
src/main.rs | 316 +++++++++++++++++++++++++++++-------------------------------
5 files changed, 154 insertions(+), 170 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
index d7c5e63..b9b6645 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,6 @@ pam = "0.7.0"
pam-sys = "0.5.6"
libc = "0.2"
users = "0.8.1"
-toml = "0.8"
smithay-client-toolkit = { version = "0.19.2", features = ["calloop"] }
calloop = "0.13.0"
calloop-wayland-source = "0.3.0"
diff --git a/README.md b/README.md
index ff526ce..3515c41 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Clear Display Manager
+# CCE Display Manager
`cce-display-manager` is a premium GUI display manager greeter built using the `cce-ui` framework, leveraging Wayland via `smithay-client-toolkit` and GPU-accelerated graphics via `wgpu`.
diff --git a/cce.json b/cce.json
new file mode 100644
index 0000000..f03453e
--- /dev/null
+++ b/cce.json
@@ -0,0 +1,3 @@
+{
+ "scale": 2.0
+}
diff --git a/cce.toml b/cce.toml
deleted file mode 100644
index c73f1ba..0000000
--- a/cce.toml
+++ /dev/null
@@ -1,2 +0,0 @@
-# System-wide configuration for Clear programs
-scale = 2.0
diff --git a/src/main.rs b/src/main.rs
index d8dd179..e72a5f8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -32,7 +32,8 @@ use calloop::{EventLoop, channel};
use calloop_wayland_source::WaylandSource;
use cce_ui::widget::{
- Button, ContentBg, TextLabel, Element, ElementState, MouseButton, Key, NamedKey, KeyEvent, TextBox
+ Button, ContentBg, TextLabel, Element, ElementState, MouseButton, Key, NamedKey, KeyEvent, TextBox,
+ Widget, Container, focus
};
use cce_ui::context::UiContext;
@@ -167,47 +168,53 @@ fn discover_sessions() -> Vec<Session> {
// ── Custom LoginCard Container Element ──
#[derive(Debug, Clone)]
struct LoginCard {
- x: f32, y: f32, w: f32, h: f32,
+ base: Widget,
}
impl LoginCard {
fn new() -> Self {
- Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0 }
+ Self { base: Widget::new() }
}
}
impl Element for LoginCard {
- fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
- fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
+ 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.90] } // Sleek dark card background
fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
let border_color = [0.20, 0.40, 0.65, 0.6]; // Premium blue border accent
vec![
// Top border
- (self.x, self.y, self.w, 1.5, border_color),
+ (self.base.x, self.base.y, self.base.w, 1.5, border_color),
// Bottom border
- (self.x, self.y + self.h - 1.5, self.w, 1.5, border_color),
+ (self.base.x, self.base.y + self.base.h - 1.5, self.base.w, 1.5, border_color),
// Left border
- (self.x, self.y, 1.5, self.h, border_color),
+ (self.base.x, self.base.y, 1.5, self.base.h, border_color),
// Right border
- (self.x + self.w - 1.5, self.y, 1.5, self.h, border_color),
+ (self.base.x + self.base.w - 1.5, self.base.y, 1.5, self.base.h, border_color),
]
}
fn text_labels(&self) -> Vec<TextLabel> {
vec![
TextLabel {
- text: "CLEAR DISPLAY MANAGER".to_string(),
- x: self.x + 30.0,
- y: self.y + 30.0,
+ text: "CCE DISPLAY MANAGER".to_string(),
+ x: self.base.x + 30.0,
+ y: self.base.y + 30.0,
font_size: 15.0,
color: [0xee, 0xee, 0xf5],
},
TextLabel {
text: "Authenticate to begin your session".to_string(),
- x: self.x + 30.0,
- y: self.y + 50.0,
+ x: self.base.x + 30.0,
+ y: self.base.y + 50.0,
font_size: 11.0,
color: [0x83, 0x83, 0x8a],
},
@@ -215,23 +222,28 @@ impl Element for LoginCard {
}
}
-// ── Custom Status Message Indicator ──
#[derive(Debug, Clone)]
struct StatusLabel {
- x: f32, y: f32, w: f32, h: f32,
+ base: Widget,
pub text: String,
pub is_error: bool,
}
impl StatusLabel {
fn new(text: String) -> Self {
- Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, text, is_error: false }
+ Self { base: Widget::new(), text, is_error: false }
}
}
impl Element for StatusLabel {
- fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
- fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
+ 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> {
@@ -242,18 +254,17 @@ impl Element for StatusLabel {
};
vec![TextLabel {
text: self.text.clone(),
- x: self.x,
- y: self.y,
+ x: self.base.x,
+ y: self.base.y,
font_size: 11.0,
color: col,
}]
}
}
-// ── Custom Session List Panel Element ──
#[derive(Debug, Clone)]
struct SessionList {
- x: f32, y: f32, w: f32, h: f32,
+ base: Widget,
sessions: Vec<Session>,
selected_idx: usize,
hovered_idx: Option<usize>,
@@ -262,7 +273,7 @@ struct SessionList {
impl SessionList {
fn new(sessions: Vec<Session>) -> Self {
Self {
- x: 0.0, y: 0.0, w: 0.0, h: 0.0,
+ base: Widget::new(),
sessions,
selected_idx: 0,
hovered_idx: None,
@@ -275,8 +286,14 @@ impl SessionList {
}
impl Element for SessionList {
- fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
- fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
+ 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])> {
@@ -284,24 +301,24 @@ impl Element for SessionList {
// 1. Panel borders (subtle blue accent)
let border_color = [0.20, 0.40, 0.65, 0.5];
- quads.push((self.x, self.y, self.w, 1.5, border_color)); // top
- quads.push((self.x, self.y + self.h - 1.5, self.w, 1.5, border_color)); // bottom
- quads.push((self.x, self.y, 1.5, self.h, border_color)); // left
- quads.push((self.x + self.w - 1.5, self.y, 1.5, self.h, border_color)); // right
+ 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.w - 20.0;
+ 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.y + 40.0 + self.selected_idx as f32 * 36.0;
- quads.push((self.x + 10.0, sel_y, item_w, 32.0, selected_color));
+ 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.y + 40.0 + h_idx as f32 * 36.0;
- quads.push((self.x + 10.0, h_y, item_w, 32.0, hover_color));
+ 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));
}
}
@@ -314,15 +331,15 @@ impl Element for SessionList {
// Header title
labels.push(TextLabel {
text: "SESSION MANAGER".to_string(),
- x: self.x + 15.0,
- y: self.y + 18.0,
+ 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.y + 40.0 + i as f32 * 36.0;
+ 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()
@@ -338,7 +355,7 @@ impl Element for SessionList {
labels.push(TextLabel {
text: display_name,
- x: self.x + 20.0,
+ x: self.base.x + 20.0,
y: item_y + 10.0,
font_size: 12.0,
color,
@@ -352,10 +369,10 @@ impl Element for SessionList {
let old_hovered = self.hovered_idx;
self.hovered_idx = None;
if self.hit_test(px, py, ctx) {
- let item_w = self.w - 20.0;
+ let item_w = self.base.w - 20.0;
for i in 0..self.sessions.len() {
- let item_y = self.y + 40.0 + i as f32 * 36.0;
- let item_x = self.x + 10.0;
+ 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;
@@ -368,10 +385,10 @@ impl Element for SessionList {
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.w - 20.0;
+ let item_w = self.base.w - 20.0;
for i in 0..self.sessions.len() {
- let item_y = self.y + 40.0 + i as f32 * 36.0;
- let item_x = self.x + 10.0;
+ 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;
@@ -403,6 +420,7 @@ struct State {
status_lbl: StatusLabel,
session_list: SessionList,
ui_context: cce_ui::context::UiContext,
+ root_container: Container,
font_system: FontSystem,
swash_cache: SwashCache,
@@ -580,6 +598,7 @@ impl State {
status_lbl,
session_list,
ui_context: cce_ui::context::UiContext::new(),
+ root_container: Container::new(),
font_system,
swash_cache,
text_atlas,
@@ -598,6 +617,16 @@ impl State {
is_authenticating: false,
};
+ // Establish the cce-ui parent-child widget tree hierarchy
+ let ctx = &mut state.ui_context;
+ focus::link_parent_child(&mut state.root_container, &mut state.card, ctx);
+ focus::link_parent_child(&mut state.root_container, &mut state.session_list, ctx);
+
+ focus::link_parent_child(&mut state.card, &mut state.username_box, ctx);
+ focus::link_parent_child(&mut state.card, &mut state.password_box, ctx);
+ focus::link_parent_child(&mut state.card, &mut state.login_btn, ctx);
+ focus::link_parent_child(&mut state.card, &mut state.status_lbl, ctx);
+
state.apply_layout();
state.upload_vertices();
state
@@ -635,6 +664,9 @@ impl State {
// 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;
@@ -819,41 +851,62 @@ impl State {
}
pub fn widgets_cursor_moved(&mut self, cx: f32, cy: f32) -> bool {
- let ctx = &mut self.ui_context;
let mut changed = false;
- if self.bg.cursor_moved(cx, cy, ctx) { changed = true; }
- if self.card.cursor_moved(cx, cy, ctx) { changed = true; }
- if self.username_box.cursor_moved(cx, cy, ctx) { changed = true; }
- if self.password_box.cursor_moved(cx, cy, ctx) { changed = true; }
- if self.login_btn.cursor_moved(cx, cy, ctx) { changed = true; }
- if self.status_lbl.cursor_moved(cx, cy, ctx) { changed = true; }
- if self.session_list.cursor_moved(cx, cy, ctx) { changed = true; }
+ 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 ctx = &mut self.ui_context;
let mut changed = false;
- if self.bg.mouse_input(button, state, cx, cy, ctx) { changed = true; }
- if self.card.mouse_input(button, state, cx, cy, ctx) { changed = true; }
- if self.username_box.mouse_input(button, state, cx, cy, ctx) { changed = true; }
- if self.password_box.mouse_input(button, state, cx, cy, ctx) { changed = true; }
- if self.login_btn.mouse_input(button, state, cx, cy, ctx) { changed = true; }
- if self.status_lbl.mouse_input(button, state, cx, cy, ctx) { changed = true; }
- if self.session_list.mouse_input(button, state, cx, cy, ctx) { changed = true; }
+ 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 ctx = &mut self.ui_context;
let mut changed = false;
- if self.bg.keyboard_input(event, ctx) { changed = true; }
- if self.card.keyboard_input(event, ctx) { changed = true; }
- if self.username_box.keyboard_input(event, ctx) { changed = true; }
- if self.password_box.keyboard_input(event, ctx) { changed = true; }
- if self.login_btn.keyboard_input(event, ctx) { changed = true; }
- if self.status_lbl.keyboard_input(event, ctx) { changed = true; }
- if self.session_list.keyboard_input(event, ctx) { changed = true; }
+ 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
}
}
@@ -1058,86 +1111,9 @@ impl PointerHandler for AppState {
let mut changed = false;
let cx = st.cursor_x;
let cy = st.cursor_y;
- if btn == MouseButton::Left {
- // Unfocus other elements if a click is made
- let hit_any = st.status_lbl.hit_test(cx, cy, &st.ui_context)
- || st.login_btn.hit_test(cx, cy, &st.ui_context)
- || st.session_list.hit_test(cx, cy, &st.ui_context)
- || st.password_box.hit_test(cx, cy, &st.ui_context)
- || st.username_box.hit_test(cx, cy, &st.ui_context)
- || st.card.hit_test(cx, cy, &st.ui_context)
- || st.bg.hit_test(cx, cy, &st.ui_context);
- if hit_any {
- st.ui_context.clear_focus();
- st.bg.unfocus();
- st.card.unfocus();
- st.username_box.unfocus();
- st.password_box.unfocus();
- st.session_list.unfocus();
- st.login_btn.unfocus();
- st.status_lbl.unfocus();
- }
- }
-
- // Process the click on the topmost hit widget
- if st.status_lbl.hit_test(cx, cy, &st.ui_context) {
- if st.status_lbl.mouse_input(btn, ElementState::Pressed, cx, cy, &mut st.ui_context) {
- changed = true;
- }
- if btn == MouseButton::Left {
- st.ui_context.set_focused(&mut st.status_lbl);
- st.status_lbl.focus();
- }
- } else if st.login_btn.hit_test(cx, cy, &st.ui_context) {
- if st.login_btn.mouse_input(btn, ElementState::Pressed, cx, cy, &mut st.ui_context) {
- changed = true;
- }
- if btn == MouseButton::Left {
- st.ui_context.set_focused(&mut st.login_btn);
- st.login_btn.focus();
- }
- } else if st.session_list.hit_test(cx, cy, &st.ui_context) {
- if st.session_list.mouse_input(btn, ElementState::Pressed, cx, cy, &mut st.ui_context) {
- changed = true;
- }
- if btn == MouseButton::Left {
- st.ui_context.set_focused(&mut st.session_list);
- st.session_list.focus();
- }
- } else if st.password_box.hit_test(cx, cy, &st.ui_context) {
- if st.password_box.mouse_input(btn, ElementState::Pressed, cx, cy, &mut st.ui_context) {
- changed = true;
- }
- if btn == MouseButton::Left {
- st.ui_context.set_focused(&mut st.password_box);
- st.password_box.focus();
- }
- } else if st.username_box.hit_test(cx, cy, &st.ui_context) {
- if st.username_box.mouse_input(btn, ElementState::Pressed, cx, cy, &mut st.ui_context) {
- changed = true;
- }
- if btn == MouseButton::Left {
- st.ui_context.set_focused(&mut st.username_box);
- st.username_box.focus();
- }
- } else if st.card.hit_test(cx, cy, &st.ui_context) {
- if st.card.mouse_input(btn, ElementState::Pressed, cx, cy, &mut st.ui_context) {
- changed = true;
- }
- if btn == MouseButton::Left {
- st.ui_context.set_focused(&mut st.card);
- st.card.focus();
- }
- } else if st.bg.hit_test(cx, cy, &st.ui_context) {
- if st.bg.mouse_input(btn, ElementState::Pressed, cx, cy, &mut st.ui_context) {
- changed = true;
- }
- if btn == MouseButton::Left {
- st.ui_context.set_focused(&mut st.bg);
- st.bg.focus();
- }
+ if st.widgets_mouse_input(btn, ElementState::Pressed, cx, cy) {
+ changed = true;
}
-
if changed {
st.upload_vertices();
self.redraw = true;
@@ -1477,10 +1453,10 @@ struct SystemConfig {
}
fn load_system_config() -> SystemConfig {
- let path = "/etc/cce.toml";
+ 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) = toml::from_str(&content) {
+ if let Ok(config) = serde_json::from_str(&content) {
return config;
}
}
@@ -1543,7 +1519,7 @@ fn run_greeter() {
let ph = (768.0 * compositor_scale) as u32;
let window = app.xdg_shell_state.create_window(surface.clone(), WindowDecorations::None, &qh);
- window.set_title("Clear Display Manager");
+ window.set_title("CCE Display Manager");
window.set_app_id("cce-display-manager");
window.set_min_size(Some((pw, ph)));
window.commit();
@@ -2005,6 +1981,29 @@ fn run_daemon() {
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 {
@@ -2056,21 +2055,6 @@ fn run_daemon() {
.arg(session_id)
.status();
}
-
- 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);
let (cmd_bin, cmd_args): (String, Vec<String>) = if is_wayland {
sanitize_exec(&exec)