From 6d98c09ece3729e444278706e06d9cecb259ee16 Mon Sep 17 00:00:00 2001 From: Emil Date: Sat, 20 Jun 2026 23:48:03 +0300 Subject: [PATCH] fix: request keyboard enhancement flags for proper Release events Root cause of both issues: terminals don't send Release events by default. When pressing W while holding A, terminal stops repeating A (but doesn't send Release), so A times out and strafing breaks. On key release, 400ms timeout means 400ms of extra movement. Fix: PushKeyboardEnhancementFlags(REPORT_EVENT_TYPES) in terminal init. This asks the terminal to send proper Press/Release/Repeat events for ALL keys, not just special ones. With Release events: - Key release is instant (no 400ms timeout delay) - Pressing W doesn't cancel A's held state (terminal sends Release only when A is actually released) - A/D strafing works while W is held Fallback: if terminal doesn't support enhancement flags (old xterm), got_release flag stays false and 150ms timeout is used. Once any Release event is received, all keys switch to Release-based mode (infinite timeout, rely on actual Release events). 109 tests, 0 warnings, 0 failures --- src/input.rs | 38 +++++++++++++++++++------------------- src/render/terminal.rs | 7 ++++++- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/input.rs b/src/input.rs index dbe0437..a3a1446 100644 --- a/src/input.rs +++ b/src/input.rs @@ -44,11 +44,10 @@ pub enum HeldKey { struct HeldState { last_seen: Instant, - seen_repeat: bool, + got_release: bool, } -const HOLD_TIMEOUT_PRESS: Duration = Duration::from_millis(400); -const HOLD_TIMEOUT_REPEAT: Duration = Duration::from_millis(80); +const FALLBACK_TIMEOUT: Duration = Duration::from_millis(150); pub struct InputHandler { pub paint_brush: MaterialBrush, @@ -104,13 +103,6 @@ impl InputHandler { } } - fn mark_held(&mut self, key: HeldKey, is_repeat: bool) { - self.held.insert(key, HeldState { - last_seen: Instant::now(), - seen_repeat: is_repeat, - }); - } - fn release(&mut self, key: HeldKey) { self.held.remove(&key); } @@ -124,11 +116,6 @@ impl InputHandler { self.jump_pressed = false; let now = Instant::now(); - self.held.retain(|_, state| { - let timeout = if state.seen_repeat { HOLD_TIMEOUT_REPEAT } else { HOLD_TIMEOUT_PRESS }; - now.duration_since(state.last_seen) < timeout - }); - let events: Vec = match &self.receiver { Some(rx) => rx.try_iter().collect(), None => return one_shots, @@ -171,17 +158,30 @@ impl InputHandler { } if let Some(held_key) = Self::key_to_held(code) { - if is_press { - self.mark_held(held_key, false); - } else if is_repeat { - self.mark_held(held_key, true); + if is_press || is_repeat { + let prev_got_release = self.held.get(&held_key).map(|s| s.got_release).unwrap_or(false); + self.held.insert(held_key, HeldState { + last_seen: Instant::now(), + got_release: prev_got_release, + }); } else if is_release { self.release(held_key); + for state in self.held.values_mut() { + state.got_release = true; + } } } } } + self.held.retain(|_, state| { + if state.got_release { + true + } else { + now.duration_since(state.last_seen) < FALLBACK_TIMEOUT + } + }); + one_shots } diff --git a/src/render/terminal.rs b/src/render/terminal.rs index 593f2e3..5141ffc 100644 --- a/src/render/terminal.rs +++ b/src/render/terminal.rs @@ -1,7 +1,7 @@ use std::io::{self, Write, stdout}; use crossterm::{ cursor::{Hide, MoveTo, Show}, - event::{DisableMouseCapture, EnableMouseCapture}, + event::{DisableMouseCapture, EnableMouseCapture, KeyboardEnhancementFlags, PushKeyboardEnhancementFlags, PopKeyboardEnhancementFlags}, execute, queue, style::{Color, SetBackgroundColor, SetForegroundColor, ResetColor, Print}, terminal::{self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, size as term_size}, @@ -53,6 +53,10 @@ impl Renderer for TerminalRenderer { stdout(), EnterAlternateScreen, Hide, + PushKeyboardEnhancementFlags( + KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES + | KeyboardEnhancementFlags::REPORT_EVENT_TYPES + ), EnableMouseCapture, Clear(ClearType::All), )?; @@ -169,6 +173,7 @@ impl Renderer for TerminalRenderer { stdout(), ResetColor, Show, + PopKeyboardEnhancementFlags, LeaveAlternateScreen, DisableMouseCapture, )?;