From dd5103ea5e95720be5815c255491087482ad12e3 Mon Sep 17 00:00:00 2001 From: yuni Date: Wed, 5 Jun 2024 22:34:57 +0200 Subject: [PATCH] Fix phantom key presses in winit on focus change (#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. This patch properly filters them out. 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. 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 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. --- crates/bevy_winit/src/state.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index fa9c085a8c2e7..020b7b3f0c925 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -20,7 +20,7 @@ use std::marker::PhantomData; use winit::application::ApplicationHandler; use winit::dpi::PhysicalSize; use winit::event; -use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; +use winit::event::{DeviceEvent, DeviceId, ElementState, StartCause, WindowEvent}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; use winit::window::WindowId; @@ -223,16 +223,22 @@ 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, + .. + } => { + if !(is_synthetic && event.state == ElementState::Pressed) { + 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);