srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/native_lock.rs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2026-07-07 23:32:00 +0200
committersrdusr <[email protected]>2026-07-07 23:32:00 +0200
commita6f98eba60f71f7250f4383e76642a901c5b8439 (patch)
treecf85acae377514cc8e14751347b4b19655884a1a /crates/wayland/src/native_lock.rs
parentad5a7349d28ad52032fff6885e2d8b80ea28dbce (diff)
downloadsrdwm-a6f98eba60f71f7250f4383e76642a901c5b8439.tar.gz
srdwm-a6f98eba60f71f7250f4383e76642a901c5b8439.zip
Lock screen: show each key's alternate character, use a UI font, space the dots
Three reports, three separate causes. Alternate characters were invisible. A keycap drew only the character the current shift state types, so there was no way to find a symbol without pressing Shift and hunting for it - a physical key is labelled with both. Each cap now draws the other state's character small and dimmed in its top-right, skipped where the two are the same (every letter differs only by case, which the cap already shows) and for named keys like Enter. "Enter Password" rendered in a monospace font. find_system_font deliberately prefers a mono face, which is right for a titlebar title or a menu row sitting in columns and wrong for a sentence; on this machine it resolves to DejaVu Sans Mono. New find_ui_font prefers a proportional face from a ranked list of widely-installed families, falling back to the mono one, and the lock screen's prose - prompt, clock, date, username, status -- uses it. Ranked rather than first-found so the result does not depend on directory order, which is how the mono scan once picked an italic face and rendered every titlebar in italic. The password dots had no spacing. They were drawn as a plain string, so a run of identical bullets separated only by their own advance read as one smeared blob rather than countable characters. Added explicit tracking, excluded from the centring width so the row does not sit half a gap left. 531 tests pass, clippy clean.
Diffstat (limited to 'crates/wayland/src/native_lock.rs')
-rw-r--r--crates/wayland/src/native_lock.rs66
1 files changed, 55 insertions, 11 deletions
diff --git a/crates/wayland/src/native_lock.rs b/crates/wayland/src/native_lock.rs
index b0a5c44..7ec3e9e 100644
--- a/crates/wayland/src/native_lock.rs
+++ b/crates/wayland/src/native_lock.rs
@@ -803,7 +803,7 @@ fn fill_circle_on_transparent(buf: &mut [u8], width: usize, height: usize, cx: i
/// box`, which is a genuinely opaque panel, nothing here is a filled
/// background at all.
fn render_header_box(native: &NativeLock, theme: &srdwm_core::LockConfig) -> (Vec<u8>, (i32, i32)) {
- use crate::decoration::{blit_glyph_on_transparent, find_system_font, FONT_PIXELS};
+ use crate::decoration::{blit_glyph_on_transparent, find_ui_font, FONT_PIXELS};
const WIDTH: usize = 420;
const HEIGHT: usize = 200;
@@ -812,7 +812,7 @@ fn render_header_box(native: &NativeLock, theme: &srdwm_core::LockConfig) -> (Ve
const AVATAR_RADIUS: i32 = 28;
let mut buf = vec![0u8; WIDTH * HEIGHT * 4];
- let font = find_system_font();
+ let font = find_ui_font();
let (hour, minute, weekday, day, month, _year) = local_time_now();
let time_str = format!("{hour:02}:{minute:02}");
let date_str = format!("{}, {} {day}", WEEKDAY_NAMES[weekday.clamp(0, 6) as usize], MONTH_NAMES[(month - 1).clamp(0, 11) as usize]);
@@ -880,7 +880,7 @@ fn render_header_box(native: &NativeLock, theme: &srdwm_core::LockConfig) -> (Ve
/// `rgb_to_bgra`), promoted to `pub(crate)` there rather than duplicated
/// here.
fn render_ui_box(native: &NativeLock, theme: &srdwm_core::LockConfig) -> (Vec<u8>, (i32, i32)) {
- use crate::decoration::{blit_glyph_over, find_system_font, rgb_to_bgra, round_bottom_corners, round_top_corners, FONT_PIXELS, TEXT_LEFT_PADDING};
+ use crate::decoration::{blit_glyph_over, find_ui_font, rgb_to_bgra, round_bottom_corners, round_top_corners, FONT_PIXELS, TEXT_LEFT_PADDING};
const WIDTH: usize = 340;
const HEIGHT: usize = 120;
@@ -898,7 +898,7 @@ fn render_ui_box(native: &NativeLock, theme: &srdwm_core::LockConfig) -> (Vec<u8
}
}
- let font = find_system_font();
+ let font = find_ui_font();
let text_color = if native.show_error { theme.error_color } else { theme.text_color };
// Centered, not left-padded like every other text row this codebase
@@ -909,9 +909,18 @@ fn render_ui_box(native: &NativeLock, theme: &srdwm_core::LockConfig) -> (Vec<u8
// (the same two-pass "measure, then centre" `render_header_box`'s own
// `draw_centered` already does) rather than repeating that closure
// here for one extra parameter's difference.
- let mut draw_line_centered = |text: &str, y: f32, color: (u8, u8, u8)| {
+ // `tracking` is extra space added after every glyph. Zero for prose;
+ // the password dots use it because a run of identical bullets with only
+ // their own advance between them reads as one smeared blob rather than
+ // as countable characters - reported as "no spacing in between the
+ // obfuscated password entry". Every real password field spaces them.
+ let mut draw_line_centered_tracked = |text: &str, y: f32, color: (u8, u8, u8), tracking: f32| {
let Some(font) = &font else { return };
- let total_width: f32 = text.chars().map(|ch| font.rasterize(ch, FONT_PIXELS).0.advance_width).sum();
+ let count = text.chars().filter(|c| !c.is_control()).count() as f32;
+ // The trailing gap is not part of the drawn run, so it must not be
+ // counted when centring or the row sits half a gap to the left.
+ let glyphs: f32 = text.chars().map(|ch| font.rasterize(ch, FONT_PIXELS).0.advance_width).sum();
+ let total_width: f32 = glyphs + tracking * (count - 1.0).max(0.0);
let start_x = ((WIDTH as f32 - total_width) / 2.0).max(TEXT_LEFT_PADDING);
let mut pen_x = start_x;
for ch in text.chars() {
@@ -924,7 +933,7 @@ fn render_ui_box(native: &NativeLock, theme: &srdwm_core::LockConfig) -> (Vec<u8
let glyph_y = y - metrics.height as f32 - metrics.ymin as f32;
blit_glyph_over(&mut buf, WIDTH, HEIGHT, glyph_x.round() as i32, glyph_y.round() as i32, &metrics, &coverage, color);
}
- pen_x += metrics.advance_width;
+ pen_x += metrics.advance_width + tracking;
}
};
@@ -940,20 +949,20 @@ fn render_ui_box(native: &NativeLock, theme: &srdwm_core::LockConfig) -> (Vec<u8
// actually drawn would tint the placeholder for no visible reason.
let toward = if panel_alpha > 0 { theme.box_bg } else { (0, 0, 0) };
let placeholder = crate::decoration::mix_rgb(theme.text_color, toward, 0.5);
- draw_line_centered("Enter Password", 65.0, placeholder);
+ draw_line_centered_tracked("Enter Password", 65.0, placeholder, 0.0);
} else {
let dots: String = std::iter::repeat_n(theme.dot_char, native.password.chars().count()).collect();
- draw_line_centered(&dots, 65.0, text_color);
+ draw_line_centered_tracked(&dots, 65.0, text_color, DOT_TRACKING);
}
let mut status_y = 100.0;
if theme.show_caps_lock && native.caps_lock {
- draw_line_centered("Caps Lock is on", status_y, theme.error_color);
+ draw_line_centered_tracked("Caps Lock is on", status_y, theme.error_color, 0.0);
status_y += 20.0;
}
if theme.show_failed_attempts && native.show_error {
let message = if native.failed_attempts > 1 { format!("{} ({} attempts)", theme.fail_message, native.failed_attempts) } else { theme.fail_message.clone() };
- draw_line_centered(&message, status_y, theme.error_color);
+ draw_line_centered_tracked(&message, status_y, theme.error_color, 0.0);
}
// Border, drawn last so it isn't overdrawn by any fill above --
@@ -1099,6 +1108,10 @@ fn keyboard_rows() -> [Vec<KeySpec>; 5] {
]
}
+/// Extra space after each password dot. Without it a run of identical
+/// bullets renders as one smeared blob rather than countable characters.
+const DOT_TRACKING: f32 = 5.0;
+
const KEY_UNIT: i32 = 32;
const KEY_HEIGHT: i32 = 32;
const KEY_GAP: i32 = 6;
@@ -1140,6 +1153,37 @@ fn render_keyboard(native: &NativeLock, theme: &srdwm_core::LockConfig) -> (Vec<
let w = (spec.width * KEY_UNIT as f32).round() as i32;
fill_rect(&mut buf, width, height, x, y, x + w, y + KEY_HEIGHT, keycap_bg, 220);
let label = if native.shift { spec.upper } else { spec.lower };
+ // The character the OTHER shift state would type, drawn small
+ // in the cap's top-right. A physical key is labelled with both
+ // (`2` and `@` on one cap) and this was labelled with only the
+ // active one, so there was no way to find a symbol without
+ // pressing Shift and hunting for it. Reported as not being able
+ // to see the alternative characters.
+ //
+ // Skipped when the two are the same character (every letter
+ // key differs only by case, which the cap already shows, and a
+ // named key like Enter has no alternate at all).
+ let alternate = if native.shift { spec.lower } else { spec.upper };
+ if spec.name.is_empty() && alternate != label && !alternate.is_empty() {
+ if let Some(font) = &font {
+ let size = FONT_PIXELS * 0.62;
+ let alt_width: f32 = alternate.chars().map(|ch| font.rasterize(ch, size).0.advance_width).sum();
+ let mut pen_x = x as f32 + w as f32 - alt_width - 3.0;
+ for ch in alternate.chars() {
+ let (metrics, coverage) = font.rasterize(ch, size);
+ if metrics.width > 0 && metrics.height > 0 {
+ let glyph_x = pen_x + metrics.xmin as f32;
+ let glyph_y = y as f32 + 3.0 + size - metrics.height as f32 - metrics.ymin as f32;
+ // Dimmed toward the cap so it reads as
+ // secondary rather than competing with the
+ // character the key actually types right now.
+ let dim = crate::decoration::mix_rgb(theme.text_color, keycap_bg, 0.45);
+ blit_glyph_on_transparent(&mut buf, width, height, glyph_x.round() as i32, glyph_y.round() as i32, &metrics, &coverage, dim);
+ }
+ pen_x += metrics.advance_width;
+ }
+ }
+ }
if let Some(font) = &font {
let total_width: f32 = label.chars().map(|ch| font.rasterize(ch, FONT_PIXELS).0.advance_width).sum();
let mut pen_x = x as f32 + (w as f32 - total_width) / 2.0;