Skip to content

Commit 6bd03fc

Browse files
core: Improve logic for ignoring spurious keypress events
1 parent d156c26 commit 6bd03fc

File tree

2 files changed

+165
-53
lines changed

2 files changed

+165
-53
lines changed

core/src/input.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ impl ClickEventData {
7272
}
7373

7474
pub struct InputManager {
75-
key_descriptors_down: HashSet<KeyDescriptor>,
75+
/// Tracks which physical keys (identified by physical key + location) are currently pressed.
76+
/// This allows us to match key-up events even when the logical key changes
77+
/// due to modifier state changes (e.g., shift pressed/released).
78+
keys_down_phys_loc: HashSet<(PhysicalKey, KeyLocation)>,
7679

7780
keys_down: HashSet<KeyCode>,
7881
keys_toggled: HashSet<KeyCode>,
@@ -89,7 +92,7 @@ pub struct InputManager {
8992
impl InputManager {
9093
pub fn new(gamepad_button_mapping: HashMap<GamepadButton, KeyCode>) -> Self {
9194
Self {
92-
key_descriptors_down: HashSet::new(),
95+
keys_down_phys_loc: HashSet::new(),
9396
keys_down: HashSet::new(),
9497
keys_toggled: HashSet::new(),
9598
last_key: KeyCode::UNKNOWN,
@@ -156,22 +159,34 @@ impl InputManager {
156159
}
157160

158161
PlayerEvent::KeyDown { key } => {
159-
self.key_descriptors_down.insert(key);
162+
// Track the physical key + location, so we can match key-up events
163+
// even when the logical key changes due to modifier state changes
164+
// (e.g., shift pressed/released).
165+
self.keys_down_phys_loc
166+
.insert((key.physical_key, key.key_location));
160167

161168
let key_code = self.map_to_key_code(key)?;
162169
let key_char = self.map_to_key_char(key);
163170
let key_location = self.map_to_key_location(key);
171+
164172
InputEvent::KeyDown {
165173
key_code,
166174
key_char,
167175
key_location,
168176
}
169177
}
170178
PlayerEvent::KeyUp { key } => {
171-
if !self.key_descriptors_down.remove(&key) {
179+
// Match key-up events by physical key and location, not the exact
180+
// KeyDescriptor. This handles cases where modifier state changes
181+
// between key-down and key-up (e.g., shift pressed/released),
182+
// which would change the logical key (e.g., 'w' -> 'W').
183+
if !self
184+
.keys_down_phys_loc
185+
.remove(&(key.physical_key, key.key_location))
186+
{
172187
// Ignore spurious KeyUp events that may happen e.g. during IME.
173188
// We assume that in order for a key to generate KeyUp, it had to
174-
// generate KeyDown for the same exact KeyDescriptor.
189+
// generate KeyDown for the same physical key and location.
175190

176191
// TODO Apparently this behavior is platform-dependent and
177192
// doesn't happen on Windows. We cannot remove it fully
@@ -183,6 +198,7 @@ impl InputManager {
183198
let key_code = self.map_to_key_code(key)?;
184199
let key_char = self.map_to_key_char(key);
185200
let key_location = self.map_to_key_location(key);
201+
186202
InputEvent::KeyUp {
187203
key_code,
188204
key_char,

tests/framework/src/runner/automation.rs

Lines changed: 144 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -117,53 +117,149 @@ pub fn perform_automated_event(evt: &AutomatedEvent, player: &mut Player) {
117117
}
118118

119119
pub fn automated_key_to_descriptor(automated_key: AutomatedKey) -> KeyDescriptor {
120-
let logical_key = match automated_key {
121-
AutomatedKey::Char(ch) | AutomatedKey::Numpad(ch) => LogicalKey::Character(ch),
122-
AutomatedKey::ArrowDown => LogicalKey::Named(NamedKey::ArrowDown),
123-
AutomatedKey::ArrowLeft => LogicalKey::Named(NamedKey::ArrowLeft),
124-
AutomatedKey::ArrowRight => LogicalKey::Named(NamedKey::ArrowRight),
125-
AutomatedKey::ArrowUp => LogicalKey::Named(NamedKey::ArrowUp),
126-
AutomatedKey::Backspace => LogicalKey::Named(NamedKey::Backspace),
127-
AutomatedKey::CapsLock => LogicalKey::Named(NamedKey::CapsLock),
128-
AutomatedKey::Delete => LogicalKey::Named(NamedKey::Delete),
129-
AutomatedKey::End => LogicalKey::Named(NamedKey::End),
130-
AutomatedKey::Enter => LogicalKey::Named(NamedKey::Enter),
131-
AutomatedKey::Escape => LogicalKey::Named(NamedKey::Escape),
132-
AutomatedKey::F1 => LogicalKey::Named(NamedKey::F1),
133-
AutomatedKey::F2 => LogicalKey::Named(NamedKey::F2),
134-
AutomatedKey::F3 => LogicalKey::Named(NamedKey::F3),
135-
AutomatedKey::F4 => LogicalKey::Named(NamedKey::F4),
136-
AutomatedKey::F5 => LogicalKey::Named(NamedKey::F5),
137-
AutomatedKey::F6 => LogicalKey::Named(NamedKey::F6),
138-
AutomatedKey::F7 => LogicalKey::Named(NamedKey::F7),
139-
AutomatedKey::F8 => LogicalKey::Named(NamedKey::F8),
140-
AutomatedKey::F9 => LogicalKey::Named(NamedKey::F9),
141-
AutomatedKey::Home => LogicalKey::Named(NamedKey::Home),
142-
AutomatedKey::Insert => LogicalKey::Named(NamedKey::Insert),
143-
AutomatedKey::LeftAlt => LogicalKey::Named(NamedKey::Alt),
144-
AutomatedKey::LeftControl => LogicalKey::Named(NamedKey::Control),
145-
AutomatedKey::LeftShift => LogicalKey::Named(NamedKey::Shift),
146-
AutomatedKey::NumLock => LogicalKey::Named(NamedKey::NumLock),
147-
AutomatedKey::NumpadDelete => LogicalKey::Named(NamedKey::Delete),
148-
AutomatedKey::NumpadDown => LogicalKey::Named(NamedKey::ArrowDown),
149-
AutomatedKey::NumpadEnd => LogicalKey::Named(NamedKey::End),
150-
AutomatedKey::NumpadHome => LogicalKey::Named(NamedKey::Home),
151-
AutomatedKey::NumpadInsert => LogicalKey::Named(NamedKey::Insert),
152-
AutomatedKey::NumpadLeft => LogicalKey::Named(NamedKey::ArrowLeft),
153-
AutomatedKey::NumpadPageDown => LogicalKey::Named(NamedKey::PageDown),
154-
AutomatedKey::NumpadPageUp => LogicalKey::Named(NamedKey::PageUp),
155-
AutomatedKey::NumpadRight => LogicalKey::Named(NamedKey::ArrowRight),
156-
AutomatedKey::NumpadUp => LogicalKey::Named(NamedKey::ArrowUp),
157-
AutomatedKey::PageDown => LogicalKey::Named(NamedKey::PageDown),
158-
AutomatedKey::PageUp => LogicalKey::Named(NamedKey::PageUp),
159-
AutomatedKey::Pause => LogicalKey::Named(NamedKey::Pause),
160-
AutomatedKey::RightControl => LogicalKey::Named(NamedKey::Control),
161-
AutomatedKey::RightShift => LogicalKey::Named(NamedKey::Shift),
162-
AutomatedKey::ScrollLock => LogicalKey::Named(NamedKey::ScrollLock),
163-
AutomatedKey::Space => LogicalKey::Character(' '),
164-
AutomatedKey::Tab => LogicalKey::Named(NamedKey::Tab),
165-
AutomatedKey::Unknown => LogicalKey::Unknown,
120+
let (logical_key, physical_key) = match automated_key {
121+
AutomatedKey::Char(ch) | AutomatedKey::Numpad(ch) => (
122+
LogicalKey::Character(ch),
123+
match ch {
124+
'a' | 'A' => PhysicalKey::KeyA,
125+
'b' | 'B' => PhysicalKey::KeyB,
126+
'c' | 'C' => PhysicalKey::KeyC,
127+
'd' | 'D' => PhysicalKey::KeyD,
128+
'e' | 'E' => PhysicalKey::KeyE,
129+
'f' | 'F' => PhysicalKey::KeyF,
130+
'g' | 'G' => PhysicalKey::KeyG,
131+
'h' | 'H' => PhysicalKey::KeyH,
132+
'i' | 'I' => PhysicalKey::KeyI,
133+
'j' | 'J' => PhysicalKey::KeyJ,
134+
'k' | 'K' => PhysicalKey::KeyK,
135+
'l' | 'L' => PhysicalKey::KeyL,
136+
'm' | 'M' => PhysicalKey::KeyM,
137+
'n' | 'N' => PhysicalKey::KeyN,
138+
'o' | 'O' => PhysicalKey::KeyO,
139+
'p' | 'P' => PhysicalKey::KeyP,
140+
'q' | 'Q' => PhysicalKey::KeyQ,
141+
'r' | 'R' => PhysicalKey::KeyR,
142+
's' | 'S' => PhysicalKey::KeyS,
143+
't' | 'T' => PhysicalKey::KeyT,
144+
'u' | 'U' => PhysicalKey::KeyU,
145+
'v' | 'V' => PhysicalKey::KeyV,
146+
'w' | 'W' => PhysicalKey::KeyW,
147+
'x' | 'X' => PhysicalKey::KeyX,
148+
'y' | 'Y' => PhysicalKey::KeyY,
149+
'z' | 'Z' => PhysicalKey::KeyZ,
150+
'0' => PhysicalKey::Digit0,
151+
'1' => PhysicalKey::Digit1,
152+
'2' => PhysicalKey::Digit2,
153+
'3' => PhysicalKey::Digit3,
154+
'4' => PhysicalKey::Digit4,
155+
'5' => PhysicalKey::Digit5,
156+
'6' => PhysicalKey::Digit6,
157+
'7' => PhysicalKey::Digit7,
158+
'8' => PhysicalKey::Digit8,
159+
'9' => PhysicalKey::Digit9,
160+
'!' => PhysicalKey::Digit1,
161+
'@' => PhysicalKey::Digit2,
162+
'#' => PhysicalKey::Digit3,
163+
'$' => PhysicalKey::Digit4,
164+
'%' => PhysicalKey::Digit5,
165+
'^' => PhysicalKey::Digit6,
166+
'&' => PhysicalKey::Digit7,
167+
'*' => PhysicalKey::Digit8,
168+
'(' => PhysicalKey::Digit9,
169+
')' => PhysicalKey::Digit0,
170+
'-' | '_' => PhysicalKey::Minus,
171+
'=' | '+' => PhysicalKey::Equal,
172+
'[' | '{' => PhysicalKey::BracketLeft,
173+
']' | '}' => PhysicalKey::BracketRight,
174+
'\\' | '|' => PhysicalKey::Backslash,
175+
';' | ':' => PhysicalKey::Semicolon,
176+
'\'' | '"' => PhysicalKey::Quote,
177+
',' | '<' => PhysicalKey::Comma,
178+
'.' | '>' => PhysicalKey::Period,
179+
'/' | '?' => PhysicalKey::Slash,
180+
'`' | '~' => PhysicalKey::Backquote,
181+
' ' => PhysicalKey::Space,
182+
_ => todo!("Key {ch} is unmapped"),
183+
},
184+
),
185+
AutomatedKey::ArrowDown => (
186+
LogicalKey::Named(NamedKey::ArrowDown),
187+
PhysicalKey::ArrowDown,
188+
),
189+
AutomatedKey::ArrowLeft => (
190+
LogicalKey::Named(NamedKey::ArrowLeft),
191+
PhysicalKey::ArrowLeft,
192+
),
193+
AutomatedKey::ArrowRight => (
194+
LogicalKey::Named(NamedKey::ArrowRight),
195+
PhysicalKey::ArrowRight,
196+
),
197+
AutomatedKey::ArrowUp => (LogicalKey::Named(NamedKey::ArrowUp), PhysicalKey::ArrowUp),
198+
AutomatedKey::Backspace => (
199+
LogicalKey::Named(NamedKey::Backspace),
200+
PhysicalKey::Backspace,
201+
),
202+
AutomatedKey::CapsLock => (LogicalKey::Named(NamedKey::CapsLock), PhysicalKey::CapsLock),
203+
AutomatedKey::Delete => (LogicalKey::Named(NamedKey::Delete), PhysicalKey::Delete),
204+
AutomatedKey::End => (LogicalKey::Named(NamedKey::End), PhysicalKey::End),
205+
AutomatedKey::Enter => (LogicalKey::Named(NamedKey::Enter), PhysicalKey::Enter),
206+
AutomatedKey::Escape => (LogicalKey::Named(NamedKey::Escape), PhysicalKey::Escape),
207+
AutomatedKey::F1 => (LogicalKey::Named(NamedKey::F1), PhysicalKey::F1),
208+
AutomatedKey::F2 => (LogicalKey::Named(NamedKey::F2), PhysicalKey::F2),
209+
AutomatedKey::F3 => (LogicalKey::Named(NamedKey::F3), PhysicalKey::F3),
210+
AutomatedKey::F4 => (LogicalKey::Named(NamedKey::F4), PhysicalKey::F4),
211+
AutomatedKey::F5 => (LogicalKey::Named(NamedKey::F5), PhysicalKey::F5),
212+
AutomatedKey::F6 => (LogicalKey::Named(NamedKey::F6), PhysicalKey::F6),
213+
AutomatedKey::F7 => (LogicalKey::Named(NamedKey::F7), PhysicalKey::F7),
214+
AutomatedKey::F8 => (LogicalKey::Named(NamedKey::F8), PhysicalKey::F8),
215+
AutomatedKey::F9 => (LogicalKey::Named(NamedKey::F9), PhysicalKey::F9),
216+
AutomatedKey::Home => (LogicalKey::Named(NamedKey::Home), PhysicalKey::Home),
217+
AutomatedKey::Insert => (LogicalKey::Named(NamedKey::Insert), PhysicalKey::Insert),
218+
AutomatedKey::LeftAlt => (LogicalKey::Named(NamedKey::Alt), PhysicalKey::AltLeft),
219+
AutomatedKey::LeftControl => (
220+
LogicalKey::Named(NamedKey::Control),
221+
PhysicalKey::ControlLeft,
222+
),
223+
AutomatedKey::LeftShift => (LogicalKey::Named(NamedKey::Shift), PhysicalKey::ShiftLeft),
224+
AutomatedKey::NumLock => (LogicalKey::Named(NamedKey::NumLock), PhysicalKey::NumLock),
225+
AutomatedKey::NumpadDelete => (LogicalKey::Named(NamedKey::Delete), PhysicalKey::Delete),
226+
AutomatedKey::NumpadDown => (
227+
LogicalKey::Named(NamedKey::ArrowDown),
228+
PhysicalKey::ArrowDown,
229+
),
230+
AutomatedKey::NumpadEnd => (LogicalKey::Named(NamedKey::End), PhysicalKey::End),
231+
AutomatedKey::NumpadHome => (LogicalKey::Named(NamedKey::Home), PhysicalKey::Home),
232+
AutomatedKey::NumpadInsert => (LogicalKey::Named(NamedKey::Insert), PhysicalKey::Insert),
233+
AutomatedKey::NumpadLeft => (
234+
LogicalKey::Named(NamedKey::ArrowLeft),
235+
PhysicalKey::ArrowLeft,
236+
),
237+
AutomatedKey::NumpadPageDown => {
238+
(LogicalKey::Named(NamedKey::PageDown), PhysicalKey::PageDown)
239+
}
240+
AutomatedKey::NumpadPageUp => (LogicalKey::Named(NamedKey::PageUp), PhysicalKey::PageUp),
241+
AutomatedKey::NumpadRight => (
242+
LogicalKey::Named(NamedKey::ArrowRight),
243+
PhysicalKey::ArrowRight,
244+
),
245+
AutomatedKey::NumpadUp => (LogicalKey::Named(NamedKey::ArrowUp), PhysicalKey::ArrowUp),
246+
AutomatedKey::PageDown => (LogicalKey::Named(NamedKey::PageDown), PhysicalKey::PageDown),
247+
AutomatedKey::PageUp => (LogicalKey::Named(NamedKey::PageUp), PhysicalKey::PageUp),
248+
AutomatedKey::Pause => (LogicalKey::Named(NamedKey::Pause), PhysicalKey::Pause),
249+
AutomatedKey::RightControl => (
250+
LogicalKey::Named(NamedKey::Control),
251+
PhysicalKey::ControlRight,
252+
),
253+
AutomatedKey::RightShift => (LogicalKey::Named(NamedKey::Shift), PhysicalKey::ShiftRight),
254+
AutomatedKey::ScrollLock => (
255+
LogicalKey::Named(NamedKey::ScrollLock),
256+
PhysicalKey::ScrollLock,
257+
),
258+
AutomatedKey::Space => (LogicalKey::Character(' '), PhysicalKey::Space),
259+
AutomatedKey::Tab => (LogicalKey::Named(NamedKey::Tab), PhysicalKey::Tab),
260+
AutomatedKey::Unknown => (LogicalKey::Unknown, PhysicalKey::Unknown),
166261
};
262+
167263
let key_location = match automated_key {
168264
AutomatedKey::Numpad(_) => KeyLocation::Numpad,
169265
AutomatedKey::LeftAlt => KeyLocation::Left,
@@ -184,9 +280,9 @@ pub fn automated_key_to_descriptor(automated_key: AutomatedKey) -> KeyDescriptor
184280
AutomatedKey::RightShift => KeyLocation::Right,
185281
_ => KeyLocation::Standard,
186282
};
283+
187284
KeyDescriptor {
188-
// We don't use physical keys in tests
189-
physical_key: PhysicalKey::Unknown,
285+
physical_key,
190286
logical_key,
191287
key_location,
192288
}

0 commit comments

Comments
 (0)