This commit is contained in:
2026-09-09 11:44:23 +02:00
parent 1367b03b97
commit 50caed0110
3 changed files with 144 additions and 12 deletions
+99 -11
View File
@@ -69,6 +69,26 @@ fn is_trigger_modifier(code: u32) -> bool {
)
}
/// Keys that should fire repeatedly while held.
fn is_repeatable_key(code: u32) -> bool {
matches!(
code,
KEY_TAB
| KEY_RIGHT
| KEY_LEFT
| KEY_DOWN
| KEY_UP
| KEY_HOME
| KEY_END
| KEY_PGUP
| KEY_PGDN
| KEY_H
| KEY_J
| KEY_K
| KEY_L
)
}
/// evdev button code, as wl_pointer reports it.
const BTN_LEFT: u32 = 0x110;
@@ -321,6 +341,16 @@ impl App {
if self.is_alt_tab && is_trigger_modifier(code) {
self.latched_modifiers.insert(code);
}
// Arm client-side repeat for navigation keys.
if is_repeatable_key(code) {
let delay = std::time::Duration::from_millis(self.repeat_delay_ms as u64);
self.repeat_key = Some(code);
self.repeat_next = Some(std::time::Instant::now() + delay);
} else {
// Non-repeating key clears any held repeat.
self.repeat_key = None;
self.repeat_next = None;
}
match code {
KEY_LEFTSHIFT | KEY_RIGHTSHIFT => self.shift = true,
KEY_ESC | KEY_Q => self.ending = Ending::Cancelled,
@@ -345,6 +375,11 @@ impl App {
if code == KEY_LEFTSHIFT || code == KEY_RIGHTSHIFT {
self.shift = false;
}
// Clear repeat if this is the key that was held.
if self.repeat_key == Some(code) {
self.repeat_key = None;
self.repeat_next = None;
}
if self.is_alt_tab
&& self.latched_modifiers.remove(&code)
&& self.latched_modifiers.is_empty()
@@ -356,6 +391,27 @@ impl App {
}
}
/// Called by the main loop when the key-repeat timer fires. Fires the
/// currently held navigation action, then arms the next repeat tick.
pub fn fire_repeat(&mut self, qh: &QueueHandle<Self>) {
let Some(code) = self.repeat_key else { return };
let rate = std::time::Duration::from_millis(self.repeat_rate_ms as u64);
self.repeat_next = Some(std::time::Instant::now() + rate);
// Re-run the navigation action without re-arming the delay.
match code {
KEY_TAB if self.shift => self.move_sel(-1, qh),
KEY_TAB | KEY_RIGHT | KEY_L => self.move_sel(1, qh),
KEY_LEFT | KEY_H => self.move_sel(-1, qh),
KEY_DOWN | KEY_J => self.move_row(1, qh),
KEY_UP | KEY_K => self.move_row(-1, qh),
KEY_HOME => self.select(0, qh),
KEY_END => self.select(self.tiles.len().saturating_sub(1), qh),
KEY_PGUP => self.move_row(-self.layout.visible_rows, qh),
KEY_PGDN => self.move_row(self.layout.visible_rows, qh),
_ => {}
}
}
fn keyboard_enter(&mut self, keys: Vec<u8>, qh: &QueueHandle<Self>) {
self.focused = true;
let held_keys: Vec<u32> = keys
@@ -382,17 +438,31 @@ impl App {
if self.is_alt_tab && !self.initial_stepped {
self.initial_stepped = true;
if self.alt_tab == AltTabMode::Yes && self.latched_modifiers.is_empty() {
// In explicit alt-tab mode, if no modifier was held on enter,
// the modifier was released before focus was acquired: commit immediately!
if self.ending == Ending::Running {
self.picked = self.tiles.get(self.sel).map(|t| t.target.clone());
self.ending = Ending::Picked;
if self.alt_tab == AltTabMode::Auto {
if self.latched_modifiers.is_empty() {
// Auto mode: no modifier held on enter means the mod was
// released before focus arrived — commit immediately.
if self.ending == Ending::Running {
self.picked = self.tiles.get(self.sel).map(|t| t.target.clone());
self.ending = Ending::Picked;
}
} else {
// Modifier is held: step selection now.
let step = if self.shift { -1 } else { 1 };
self.move_sel(step, qh);
}
} else if self.alt_tab == AltTabMode::Auto {
// In auto mode, step selection now that we know a modifier was held:
let step = if self.shift { -1 } else { 1 };
self.move_sel(step, qh);
}
}
// For both Yes and Auto: if we are in alt-tab mode but no modifier is
// currently held, the mod was released before keyboard focus arrived.
// Commit the current selection immediately.
if self.is_alt_tab && self.latched_modifiers.is_empty() && self.ending == Ending::Running {
if self.alt_tab == AltTabMode::Yes
|| (self.alt_tab == AltTabMode::Auto && self.initial_stepped)
{
self.picked = self.tiles.get(self.sel).map(|t| t.target.clone());
self.ending = Ending::Picked;
}
}
}
@@ -463,12 +533,30 @@ impl Dispatch<WlKeyboard, ()> for App {
WEnum::Value(wl_keyboard::KeyState::Released) => app.key_up(key),
_ => {}
},
// Store compositor key-repeat settings for our client-side timer.
wl_keyboard::Event::RepeatInfo { rate, delay } => {
// rate == 0 means repeat is disabled.
if rate > 0 {
app.repeat_rate_ms = (1000 / rate as u32).max(1);
app.repeat_delay_ms = delay as u32;
} else {
app.repeat_key = None;
app.repeat_next = None;
app.repeat_delay_ms = 0;
app.repeat_rate_ms = 0;
}
}
// Focus is only tracked here. sway sends leave immediately
// followed by enter on the same surface when the pointer crosses
// it, so whether the grab is really gone is decided by the main
// loop, once the event batch has been dispatched.
wl_keyboard::Event::Enter { keys, .. } => app.keyboard_enter(keys, qh),
wl_keyboard::Event::Leave { .. } => app.focused = false,
wl_keyboard::Event::Leave { .. } => {
app.focused = false;
// Clear any held repeat — we no longer have the keyboard.
app.repeat_key = None;
app.repeat_next = None;
}
_ => {}
}
}