From 4608708d8dc3831925f46fc75882c767416fdb99 Mon Sep 17 00:00:00 2001 From: hut Date: Mon, 17 Jun 2024 16:49:16 +0200 Subject: [PATCH] Fix phantom key presses in winit on focus change (#13299) (#13696) # Objective Fixes #13299 On Linux/X11, changing focus into a winit window will produce winit KeyboardInput events with a "is_synthetic=true" flag that are not intended to be used. Bevy erroneously passes them on to the user, resulting in phantom key presses. ## Solution This patch properly filters out winit KeyboardInput events with "is_synthetic=true". For example, pressing Alt+Tab to focus a bevy winit window results in a permanently stuck Tab key until the user presses Tab once again to produce a winit KeyboardInput release event. The Tab key press event that causes this problem is "synthetic", should not be used according to the winit devs, and simply ignoring it fixes this problem. Synthetic key **releases** are still evaluated though, as they are essential for correct release key handling. For example, if the user binds the key combination Alt+1 to the action "move the window to workspace 1", places the bevy game in workspace 2, focuses the game and presses Alt+1, then the key release event for the "1" key will be synthetic. If we would filter out all synthetic keys, the bevy game would think that the 1 key remains pressed forever, until the user manually presses+releases the key again inside bevy. Reference: https://docs.rs/winit/0.30.0/winit/event/enum.WindowEvent.html#variant.KeyboardInput.field.is_synthetic Relevant discussion: https://github.com/rust-windowing/winit/issues/3543 ## Testing Tested with the "keyboard_input_events" example. Entering/exiting the window with various keys, as well as changing its workspace, produces the correct press/release events. --- crates/bevy_winit/src/state.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index fa9c085a8c2e7..0638a5cf857cc 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -223,16 +223,27 @@ impl ApplicationHandler for WinitAppRunnerState { ); } WindowEvent::CloseRequested => self.winit_events.send(WindowCloseRequested { window }), - WindowEvent::KeyboardInput { ref event, .. } => { - if event.state.is_pressed() { - if let Some(char) = &event.text { - let char = char.clone(); - #[allow(deprecated)] - self.winit_events.send(ReceivedCharacter { window, char }); + WindowEvent::KeyboardInput { + ref event, + is_synthetic, + .. + } => { + // Winit sends "synthetic" key press events when the window gains focus. These + // should not be handled, so we only process key events if they are not synthetic + // key presses. "synthetic" key release events should still be handled though, for + // properly releasing keys when the window loses focus. + if !(is_synthetic && event.state.is_pressed()) { + // Process the keyboard input event, as long as it's not a synthetic key press. + if event.state.is_pressed() { + if let Some(char) = &event.text { + let char = char.clone(); + #[allow(deprecated)] + self.winit_events.send(ReceivedCharacter { window, char }); + } } + self.winit_events + .send(converters::convert_keyboard_input(event, window)); } - self.winit_events - .send(converters::convert_keyboard_input(event, window)); } WindowEvent::CursorMoved { position, .. } => { let physical_position = DVec2::new(position.x, position.y);