keyboard/state: handle repeat rate 0, let widgets see Escape

- wl_keyboard.repeat_info with rate 0 means "no repeat"; we divided by it.
- an unknown keymap format hit unreachable!() instead of being ignored.
- raw keycode 1 (Escape) returned before widgets were dispatched, so the
  launcher's Escape handler (which saves the prompt for Ctrl+R) never ran.
  Dispatch first, then fall back to quitting.
- always lay out on the first layer-surface configure, even when the
  compositor hands us exactly the placeholder dimensions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Bendik Aagaard Lynghaug
2026-10-03 09:15:56 +00:00
co-authored by Claude Fable 5.1
parent 450a20bf63
commit 8c319cffc6
3 changed files with 29 additions and 12 deletions
+5 -2
View File
@@ -14,7 +14,7 @@
use std::{
default::Default,
os::unix::io::{AsRawFd, OwnedFd},
os::unix::io::OwnedFd,
};
use wayland_client::{protocol::wl_keyboard, WEnum};
@@ -31,6 +31,7 @@ pub use self::state::ModifiersState;
#[derive(Debug, Clone)]
pub struct KeyEvent {
#[allow(dead_code)]
pub rawkey: u32,
pub keysym: u32,
pub state: WEnum<wl_keyboard::KeyState>,
@@ -77,7 +78,9 @@ impl Keyboard {
WEnum::Value(wl_keyboard::KeymapFormat::NoKeymap) => {
// TODO: how to handle this (hopefully never occuring) case?
}
_ => unreachable!(),
// Unknown format from a newer protocol version: ignore rather than
// abort the whole process.
_ => (),
}
}
+12 -5
View File
@@ -89,11 +89,18 @@ impl EventSource for KeyRepeatSource {
_ => (),
},
RepeatMessage::RepeatInfo((new_rate, new_delay)) => {
*rate = Duration::from_micros(1_000_000 / new_rate as u64);
*delay = Duration::from_millis(new_delay as u64);
*disabled = false;
if key.is_some() {
timer.set_deadline(now + *delay);
// Per wl_keyboard.repeat_info, a rate of 0 disables
// key repeat entirely. Dividing by it would panic.
if new_rate == 0 {
*disabled = true;
key.take();
} else {
*rate = Duration::from_micros(1_000_000 / new_rate as u64);
*delay = Duration::from_millis(new_delay as u64);
*disabled = false;
if key.is_some() {
timer.set_deadline(now + *delay);
}
}
}
},
+12 -5
View File
@@ -461,12 +461,15 @@ impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ()> for State {
(width, height) = (640, 480);
}
layer_surface.ack_configure(serial);
let first_configure = !state.configured;
state.configured = true;
state.dirty = true;
let dim = (width as i32, height as i32);
if state.configured && state.dimensions == dim {
// Always lay out on the first configure, even if the
// compositor happens to hand us our placeholder dimensions.
if !first_configure && state.dimensions == dim {
return;
}
state.bufmgr.clear_buffers();
@@ -656,17 +659,21 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for State {
state: kbstate,
..
} => {
if key == 1 {
state.running = false;
return;
}
state.keyboard.resolve();
let k = state.keyboard.key(key, kbstate);
let repeats = k.repeats;
let ev = Event::KeyEvent(k);
// Widgets see the key first so the launcher gets to save its
// prompt on Escape (the early-return used to swallow it).
for widget in state.widgets.iter_mut() {
widget.event(&ev);
}
// Raw keycode 1 is KEY_ESC: fall back to quitting even when no
// keymap could be loaded and no widget handled it.
if key == 1 && kbstate == WEnum::Value(wl_keyboard::KeyState::Pressed) {
state.running = false;
return;
}
if repeats {
if let Event::KeyEvent(k) = ev {
state