From ae47a562bd423df856ec6c735d9e4c29a8e1e3c6 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:10:00 +0200 Subject: Redesign the native lock screen: clock/avatar header, on-screen keyboard, wrong-password shake The native lock UI was a flat bordered rectangle with three left-aligned text lines and no shadow, clock, or identity marker - reported directly as looking unfinished. Splits the redesign across a new transparent-canvas header (time, date, circular avatar, username) above a redesigned, centered password box with a real drop shadow and a dimmed placeholder prompt, plus a genuine on-screen QWERTY-shaped keyboard with working Shift/Backspace/Return/Space and real click hit-testing shared with the render path via one `lock_stack_layout` function, and a damped-sine shake on a failed attempt. LockConfig gains show_clock/show_keyboard/avatar_bg, each independently srd.set-able and documented in a new theme.lock.* section in DEFAULTS.md. native_lock_render_elements now takes one NativeLockFrame struct instead of positional buffer arguments now that it composites five optional layers instead of two. Full workspace build/test/clippy clean (152 wayland tests, +6 new). --- Cargo.lock | 1 + crates/core/src/lock_config.rs | 22 + crates/srdwm/src/main.rs | 5 + crates/wayland/Cargo.toml | 6 + crates/wayland/src/decoration.rs | 6 +- crates/wayland/src/input/pointer.rs | 11 +- crates/wayland/src/native_lock.rs | 792 ++++++++++++++++++++++++++++++++---- crates/wayland/src/udev/render.rs | 14 +- crates/wayland/src/winit/render.rs | 14 +- docs/DEFAULTS.md | 36 ++ docs/TODO.md | 16 + 11 files changed, 843 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f661d54..19da143 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2514,6 +2514,7 @@ name = "srdwm-wayland" version = "0.1.0" dependencies = [ "fontdue", + "libc", "log", "memmap2", "pixman", diff --git a/crates/core/src/lock_config.rs b/crates/core/src/lock_config.rs index 7edeeb5..92ba0d0 100644 --- a/crates/core/src/lock_config.rs +++ b/crates/core/src/lock_config.rs @@ -34,6 +34,25 @@ pub struct LockConfig { pub show_caps_lock: bool, pub show_failed_attempts: bool, pub fail_message: String, + /// A large time+date readout above the password box, plus a circular + /// initial-letter avatar and the username - the set of things every + /// mainstream lock screen (GNOME, macOS, Windows) shows and this one + /// didn't, reported live as the box on its own "looks ugly/AI-like". + /// `true` by default; `false` reduces the lock screen to just the + /// password box, the previous look, for anyone who'd rather not have + /// the time visible on a locked screen. + pub show_clock: bool, + /// An on-screen keyboard below the password box, for a session with no + /// physical keyboard reachable (a touchscreen device, primarily) -- + /// see `native_lock.rs`'s own `render_keyboard`/`keyboard_hit_test`. + /// `true` by default; a real physical keyboard still works identically + /// either way, so this only ever adds a second input method, never + /// removes the first. + pub show_keyboard: bool, + /// The avatar circle's fill colour - defaults to `box_border` (the + /// same accent every other lock-screen element already uses) rather + /// than a third independent colour to keep track of. + pub avatar_bg: (u8, u8, u8), } impl Default for LockConfig { @@ -49,6 +68,9 @@ impl Default for LockConfig { show_caps_lock: true, show_failed_attempts: true, fail_message: "Wrong password".to_string(), + show_clock: true, + show_keyboard: true, + avatar_bg: (0x88, 0xc0, 0xd0), // Nord blue, matches box_border } } } diff --git a/crates/srdwm/src/main.rs b/crates/srdwm/src/main.rs index a3c9bf4..6b8724a 100644 --- a/crates/srdwm/src/main.rs +++ b/crates/srdwm/src/main.rs @@ -298,6 +298,11 @@ fn apply_general_settings(engine: &Engine, wm: &Rc>) { if let Some(ch) = engine.get_string("theme.lock.dot_char", &lock.dot_char.to_string()).chars().next() { lock.dot_char = ch; } + lock.show_clock = engine.get_bool("theme.lock.show_clock", lock.show_clock); + lock.show_keyboard = engine.get_bool("theme.lock.show_keyboard", lock.show_keyboard); + if let Some(rgb) = srdwm_core::parse_hex_color(&engine.get_string("theme.lock.avatar_bg", "#88c0d0")) { + lock.avatar_bg = rgb; + } let mut wm = wm.borrow_mut(); wm.tiling.gap_inner = gap; diff --git a/crates/wayland/Cargo.toml b/crates/wayland/Cargo.toml index 5eb82ac..3e2c40d 100644 --- a/crates/wayland/Cargo.toml +++ b/crates/wayland/Cargo.toml @@ -55,6 +55,12 @@ xkbcommon = { version = "0.8", features = ["wayland"] } resvg = "0.48.1" usvg = "0.48.1" tiny-skia = "0.12.0" +# `native_lock.rs`'s own clock/date: `libc::localtime_r` gives the +# system's real local time (respecting `/etc/localtime`/`TZ`), which +# `std::time` alone has no way to do (`SystemTime` is UTC-only). Already +# a transitive dependency of this crate (smithay itself depends on it), +# just not previously used directly. +libc = "0.2" [dependencies.smithay] version = "0.7" diff --git a/crates/wayland/src/decoration.rs b/crates/wayland/src/decoration.rs index ee3f34c..aa12a54 100644 --- a/crates/wayland/src/decoration.rs +++ b/crates/wayland/src/decoration.rs @@ -44,7 +44,7 @@ pub(crate) use color::{mix_rgb, rgb_to_bgra}; pub(crate) use corners::{round_bottom_corners, round_top_corners}; pub(crate) use font::{blit_glyph, find_system_font, FONT_PIXELS, TEXT_LEFT_PADDING}; pub use shadow::{shadow_bitmap, shadow_rect}; -pub(crate) use shadow::SHADOW_MAX_ALPHA; +pub(crate) use shadow::{SHADOW_MAX_ALPHA, SHADOW_SIZE}; pub use titlebar::render_titlebar; /// Default corner radius, in pixels, applied to a window at creation @@ -389,7 +389,7 @@ pub(crate) fn render_desktop_icon( /// canvas), so there's no premultiplication to get right here - straight /// and premultiplied colour are identical at full opacity. #[allow(clippy::too_many_arguments)] -fn fill_rect(buf: &mut [u8], width: usize, height: usize, x0: i32, y0: i32, x1: i32, y1: i32, color: (u8, u8, u8), alpha: u8) { +pub(crate) fn fill_rect(buf: &mut [u8], width: usize, height: usize, x0: i32, y0: i32, x1: i32, y1: i32, color: (u8, u8, u8), alpha: u8) { let px = rgb_to_bgra(color, alpha); for y in y0.max(0)..y1.min(height as i32) { for x in x0.max(0)..x1.min(width as i32) { @@ -410,7 +410,7 @@ fn fill_rect(buf: &mut [u8], width: usize, height: usize, x0: i32, y0: i32, x1: /// partial-alpha pixel is a real, previously-hit correctness bug here, not /// a style choice. #[allow(clippy::too_many_arguments)] -fn blit_glyph_on_transparent(buf: &mut [u8], width: usize, height: usize, glyph_x: i32, glyph_y: i32, metrics: &fontdue::Metrics, coverage: &[u8], color: (u8, u8, u8)) { +pub(crate) fn blit_glyph_on_transparent(buf: &mut [u8], width: usize, height: usize, glyph_x: i32, glyph_y: i32, metrics: &fontdue::Metrics, coverage: &[u8], color: (u8, u8, u8)) { for row in 0..metrics.height { let y = glyph_y + row as i32; if y < 0 || y as usize >= height { diff --git a/crates/wayland/src/input/pointer.rs b/crates/wayland/src/input/pointer.rs index 44b0bd7..2ecc15c 100644 --- a/crates/wayland/src/input/pointer.rs +++ b/crates/wayland/src/input/pointer.rs @@ -485,9 +485,16 @@ pub(crate) fn handle_pointer_button(state: &mut CompState, pos: Point