Watch
1
0
Fork
You've already forked souveraine
0

sessiond: quiet fallback until first input, dismissnotify on handoff

fallback surface renders only the dark field until touched, so a normal
boot no longer flashes a second PIN style under the shell lockscreen.
hyprctl dismissnotify fires once the shell confirms its lock, clearing
the bogus lockscreen-crashed banner from the handoff connection drop.
This commit is contained in:
Fimeg 2026-07-17 10:48:06 -04:00
commit c91c3a7796
3 changed files with 57 additions and 2 deletions

View file

@ -76,6 +76,10 @@ pub struct Scene {
pub pin_len: usize,
pub mood: Mood,
pub pressed: Option<Key>,
/// Boot-handoff face: render nothing but the dark field. The keypad
/// appears on first input, so a normal boot (shell takes over in
/// seconds) never flashes a second PIN style at the user.
pub quiet: bool,
}
/// The keypad geometry for a surface of w x h. Pure function of size so
@ -186,6 +190,9 @@ fn draw_glyph(buf: &mut [u32], w: i32, h: i32, key: Key, cell: Rect, color: u32)
pub fn render(buf: &mut [u32], w: i32, h: i32, scene: &Scene) {
debug_assert_eq!(buf.len(), (w * h) as usize);
buf.fill(BG);
if scene.quiet {
return;
}
// PIN dots, centered in the upper region above the keypad.
let dot_color = match scene.mood {
@ -244,10 +251,23 @@ mod tests {
&mut buf,
w,
h,
&Scene { pin_len: 3, mood: Mood::Entering, pressed: Some(Key::Digit(5)) },
&Scene { pin_len: 3, mood: Mood::Entering, pressed: Some(Key::Digit(5)), quiet: false },
);
assert!(buf.iter().any(|&p| p == BG));
assert!(buf.iter().any(|&p| p == KEY_FILL));
assert!(buf.iter().any(|&p| p == KEY_PRESSED));
}
#[test]
fn quiet_scene_is_bare_field() {
let (w, h) = (400, 800);
let mut buf = vec![0u32; (w * h) as usize];
render(
&mut buf,
w,
h,
&Scene { pin_len: 0, mood: Mood::Entering, pressed: None, quiet: true },
);
assert!(buf.iter().all(|&p| p == BG));
}
}

View file

@ -137,6 +137,11 @@ struct LockState {
pointer_pos: (f64, f64),
pointer_surface: Option<u32>, // protocol id of the entered wl_surface
splash_poked: bool,
/// True until the user touches the surface. While quiet we render only
/// the dark field, so the boot handoff to the shell never flashes the
/// fallback keypad. The first input reveals the keypad (and is
/// swallowed — it's a "show me" gesture, not a digit).
quiet: bool,
}
impl LockState {
@ -160,9 +165,21 @@ impl LockState {
pointer_pos: (0.0, 0.0),
pointer_surface: None,
splash_poked: false,
quiet: true,
}
}
/// First input while quiet reveals the keypad instead of registering.
/// Returns true when the input was consumed by the reveal.
fn reveal(&mut self) -> bool {
if self.quiet {
self.quiet = false;
self.dirty = true;
return true;
}
false
}
fn press(&mut self, key: Key) {
if self.authing {
return;
@ -410,7 +427,12 @@ fn ensure_surfaces(state: &mut LockState, qh: &QueueHandle<LockState>) {
}
fn redraw_all(state: &mut LockState, qh: &QueueHandle<LockState>) -> Result<()> {
let scene = Scene { pin_len: state.pin.len(), mood: state.mood, pressed: state.pressed };
let scene = Scene {
pin_len: state.pin.len(),
mood: state.mood,
pressed: state.pressed,
quiet: state.quiet,
};
let Some(shm) = state.shm.clone() else { return Ok(()) };
let mut drew = false;
for ctx in &mut state.surfaces {
@ -603,6 +625,9 @@ impl Dispatch<WlTouch, ()> for LockState {
) {
match event {
wl_touch::Event::Down { surface, x, y, .. } => {
if state.reveal() {
return;
}
let pid = surface.id().protocol_id();
if let Some((w, h)) = state.surface_size_by_proto_id(pid) {
if let Some(key) = hit_test(w, h, x, y) {
@ -642,6 +667,9 @@ impl Dispatch<WlPointer, ()> for LockState {
}
wl_pointer::Event::Button { state: WEnum::Value(st), .. } => match st {
wl_pointer::ButtonState::Pressed => {
if state.reveal() {
return;
}
if let Some(pid) = state.pointer_surface {
if let Some((w, h)) = state.surface_size_by_proto_id(pid) {
let (px, py) = state.pointer_pos;
@ -676,6 +704,9 @@ impl Dispatch<WlKeyboard, ()> for LockState {
wl_keyboard::Event::Key { key, state: WEnum::Value(st), .. } => {
match st {
wl_keyboard::KeyState::Pressed => {
if state.reveal() {
return;
}
if let Some(k) = LockState::key_from_evdev(key) {
state.press(k);
}

View file

@ -294,6 +294,10 @@ fn handle_request(
}
d.phase = Phase::Released;
info!("shell lock confirmed");
// The handoff is a deliberate connection drop, but Hyprland
// announces any dying lock client as a crashed lockscreen.
// The shell's lock is confirmed, so clear that banner.
let _ = std::process::Command::new("hyprctl").arg("dismissnotify").spawn();
serde_json::json!({ "ok": true })
}
Request::Lock => {