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 pin_len: usize,
|
||||||
pub mood: Mood,
|
pub mood: Mood,
|
||||||
pub pressed: Option<Key>,
|
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
|
/// 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) {
|
pub fn render(buf: &mut [u32], w: i32, h: i32, scene: &Scene) {
|
||||||
debug_assert_eq!(buf.len(), (w * h) as usize);
|
debug_assert_eq!(buf.len(), (w * h) as usize);
|
||||||
buf.fill(BG);
|
buf.fill(BG);
|
||||||
|
if scene.quiet {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// PIN dots, centered in the upper region above the keypad.
|
// PIN dots, centered in the upper region above the keypad.
|
||||||
let dot_color = match scene.mood {
|
let dot_color = match scene.mood {
|
||||||
|
|
@ -244,10 +251,23 @@ mod tests {
|
||||||
&mut buf,
|
&mut buf,
|
||||||
w,
|
w,
|
||||||
h,
|
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 == BG));
|
||||||
assert!(buf.iter().any(|&p| p == KEY_FILL));
|
assert!(buf.iter().any(|&p| p == KEY_FILL));
|
||||||
assert!(buf.iter().any(|&p| p == KEY_PRESSED));
|
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_pos: (f64, f64),
|
||||||
pointer_surface: Option<u32>, // protocol id of the entered wl_surface
|
pointer_surface: Option<u32>, // protocol id of the entered wl_surface
|
||||||
splash_poked: bool,
|
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 {
|
impl LockState {
|
||||||
|
|
@ -160,9 +165,21 @@ impl LockState {
|
||||||
pointer_pos: (0.0, 0.0),
|
pointer_pos: (0.0, 0.0),
|
||||||
pointer_surface: None,
|
pointer_surface: None,
|
||||||
splash_poked: false,
|
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) {
|
fn press(&mut self, key: Key) {
|
||||||
if self.authing {
|
if self.authing {
|
||||||
return;
|
return;
|
||||||
|
|
@ -410,7 +427,12 @@ fn ensure_surfaces(state: &mut LockState, qh: &QueueHandle<LockState>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redraw_all(state: &mut LockState, qh: &QueueHandle<LockState>) -> Result<()> {
|
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 Some(shm) = state.shm.clone() else { return Ok(()) };
|
||||||
let mut drew = false;
|
let mut drew = false;
|
||||||
for ctx in &mut state.surfaces {
|
for ctx in &mut state.surfaces {
|
||||||
|
|
@ -603,6 +625,9 @@ impl Dispatch<WlTouch, ()> for LockState {
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
wl_touch::Event::Down { surface, x, y, .. } => {
|
wl_touch::Event::Down { surface, x, y, .. } => {
|
||||||
|
if state.reveal() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let pid = surface.id().protocol_id();
|
let pid = surface.id().protocol_id();
|
||||||
if let Some((w, h)) = state.surface_size_by_proto_id(pid) {
|
if let Some((w, h)) = state.surface_size_by_proto_id(pid) {
|
||||||
if let Some(key) = hit_test(w, h, x, y) {
|
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::Event::Button { state: WEnum::Value(st), .. } => match st {
|
||||||
wl_pointer::ButtonState::Pressed => {
|
wl_pointer::ButtonState::Pressed => {
|
||||||
|
if state.reveal() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if let Some(pid) = state.pointer_surface {
|
if let Some(pid) = state.pointer_surface {
|
||||||
if let Some((w, h)) = state.surface_size_by_proto_id(pid) {
|
if let Some((w, h)) = state.surface_size_by_proto_id(pid) {
|
||||||
let (px, py) = state.pointer_pos;
|
let (px, py) = state.pointer_pos;
|
||||||
|
|
@ -676,6 +704,9 @@ impl Dispatch<WlKeyboard, ()> for LockState {
|
||||||
wl_keyboard::Event::Key { key, state: WEnum::Value(st), .. } => {
|
wl_keyboard::Event::Key { key, state: WEnum::Value(st), .. } => {
|
||||||
match st {
|
match st {
|
||||||
wl_keyboard::KeyState::Pressed => {
|
wl_keyboard::KeyState::Pressed => {
|
||||||
|
if state.reveal() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if let Some(k) = LockState::key_from_evdev(key) {
|
if let Some(k) = LockState::key_from_evdev(key) {
|
||||||
state.press(k);
|
state.press(k);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,10 @@ fn handle_request(
|
||||||
}
|
}
|
||||||
d.phase = Phase::Released;
|
d.phase = Phase::Released;
|
||||||
info!("shell lock confirmed");
|
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 })
|
serde_json::json!({ "ok": true })
|
||||||
}
|
}
|
||||||
Request::Lock => {
|
Request::Lock => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue