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:
parent
401a6609ea
commit
c91c3a7796
3 changed files with 57 additions and 2 deletions
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue