Skip to content

Commit efac4d7

Browse files
robtfmclaude
andcommitted
bevy_winit: synthesize key events for modifier transitions reported via ModifiersChanged
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 306aaed commit efac4d7

1 file changed

Lines changed: 135 additions & 1 deletion

File tree

crates/bevy_winit/src/state.rs

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use bevy_ecs::{
1010
};
1111
use bevy_input::{
1212
gestures::*,
13+
keyboard::{Key, KeyCode, KeyboardInput},
1314
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
1415
};
1516
use bevy_log::{trace, warn};
@@ -23,8 +24,9 @@ use winit::{
2324
application::ApplicationHandler,
2425
dpi::PhysicalSize,
2526
event,
26-
event::{DeviceEvent, DeviceId, StartCause, WindowEvent},
27+
event::{DeviceEvent, DeviceId, Modifiers, StartCause, WindowEvent},
2728
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
29+
keyboard::ModifiersKeyState,
2830
window::WindowId,
2931
};
3032

@@ -78,6 +80,10 @@ pub(crate) struct WinitAppRunnerState {
7880
bevy_window_events: Vec<bevy_window::WindowEvent>,
7981
/// Raw Winit window events to send
8082
raw_winit_events: Vec<RawWinitWindowEvent>,
83+
/// Latest winit modifier state per window, buffered for reconciliation
84+
/// against `WinitWindowPressedKeys` once all of this batch's key events
85+
/// have been seen (see `reconcile_pending_modifiers`).
86+
pending_modifiers: Vec<(Entity, Modifiers)>,
8187

8288
windows_system_state: SystemState<
8389
Query<
@@ -116,6 +122,7 @@ impl WinitAppRunnerState {
116122
startup_forced_updates: 5,
117123
bevy_window_events: Vec::new(),
118124
raw_winit_events: Vec::new(),
125+
pending_modifiers: Vec::new(),
119126
windows_system_state,
120127
scheduled_tick_start: None,
121128
}
@@ -281,6 +288,21 @@ impl ApplicationHandler<WinitUserEvent> for WinitAppRunnerState {
281288
}
282289
self.bevy_window_events.send(keyboard_input);
283290
}
291+
WindowEvent::ModifiersChanged(mods) => {
292+
// Buffer the latest state; reconciled at end-of-batch (see
293+
// `reconcile_pending_modifiers`) so real key events in the
294+
// same batch land in `WinitWindowPressedKeys` first and are
295+
// not double-reported.
296+
if let Some(entry) = self
297+
.pending_modifiers
298+
.iter_mut()
299+
.find(|(entity, _)| *entity == window)
300+
{
301+
entry.1 = mods;
302+
} else {
303+
self.pending_modifiers.push((window, mods));
304+
}
305+
}
284306
WindowEvent::CursorMoved { position, .. } => {
285307
let physical_position = DVec2::new(position.x, position.y);
286308

@@ -773,7 +795,119 @@ impl WinitAppRunnerState {
773795
}
774796
}
775797

798+
/// Apply the final per-window modifier state of this event batch to
799+
/// `WinitWindowPressedKeys`, synthesizing `KeyboardInput` events for any
800+
/// modifier key whose transition winit never delivered as a key event.
801+
/// That happens when the OS consumes a shortcut chord (the keyup lands
802+
/// elsewhere — e.g. the macOS Cmd+Shift+5 screen-capture overlay takes
803+
/// the keyups without any focus transition, especially on the web) or
804+
/// when focus returns with a modifier already held (the keydown predates
805+
/// focus). Winit re-derives modifier state from input events, so a stale
806+
/// modifier heals on the next input event even when no focus transition
807+
/// was ever reported.
808+
///
809+
/// Running at end-of-batch means the batch's real key events have
810+
/// already been applied to `WinitWindowPressedKeys`, so ordinary
811+
/// modifier presses and releases synthesize nothing.
812+
fn reconcile_pending_modifiers(&mut self) {
813+
if self.pending_modifiers.is_empty() {
814+
return;
815+
}
816+
817+
let pending = core::mem::take(&mut self.pending_modifiers);
818+
let mut synthesized = Vec::new();
819+
820+
for (window, mods) in pending {
821+
let Some(mut pressed_keys) = self.world_mut().get_mut::<WinitWindowPressedKeys>(window)
822+
else {
823+
continue;
824+
};
825+
826+
let state = mods.state();
827+
for (reported, logical_key, sides) in [
828+
(
829+
state.super_key(),
830+
Key::Super,
831+
[
832+
(KeyCode::SuperLeft, mods.lsuper_state()),
833+
(KeyCode::SuperRight, mods.rsuper_state()),
834+
],
835+
),
836+
(
837+
state.control_key(),
838+
Key::Control,
839+
[
840+
(KeyCode::ControlLeft, mods.lcontrol_state()),
841+
(KeyCode::ControlRight, mods.rcontrol_state()),
842+
],
843+
),
844+
(
845+
state.alt_key(),
846+
Key::Alt,
847+
[
848+
(KeyCode::AltLeft, mods.lalt_state()),
849+
(KeyCode::AltRight, mods.ralt_state()),
850+
],
851+
),
852+
(
853+
state.shift_key(),
854+
Key::Shift,
855+
[
856+
(KeyCode::ShiftLeft, mods.lshift_state()),
857+
(KeyCode::ShiftRight, mods.rshift_state()),
858+
],
859+
),
860+
] {
861+
let held: Vec<KeyCode> = sides
862+
.iter()
863+
.map(|(key_code, _)| *key_code)
864+
.filter(|key_code| pressed_keys.0.contains_key(key_code))
865+
.collect();
866+
867+
if reported && held.is_empty() {
868+
// pick the side winit reports as pressed where the
869+
// platform knows it, defaulting to left (the web backend
870+
// can't distinguish sides)
871+
let key_code = sides
872+
.iter()
873+
.find(|(_, side)| *side == ModifiersKeyState::Pressed)
874+
.map(|(key_code, _)| *key_code)
875+
.unwrap_or(sides[0].0);
876+
pressed_keys.0.insert(key_code, logical_key.clone());
877+
synthesized.push(KeyboardInput {
878+
key_code,
879+
logical_key,
880+
state: bevy_input::ButtonState::Pressed,
881+
repeat: false,
882+
window,
883+
text: None,
884+
});
885+
} else if !reported {
886+
for key_code in held {
887+
let Some(logical_key) = pressed_keys.0.remove(&key_code) else {
888+
continue;
889+
};
890+
synthesized.push(KeyboardInput {
891+
key_code,
892+
logical_key,
893+
state: bevy_input::ButtonState::Released,
894+
repeat: false,
895+
window,
896+
text: None,
897+
});
898+
}
899+
}
900+
}
901+
}
902+
903+
for event in synthesized {
904+
self.bevy_window_events.send(event);
905+
}
906+
}
907+
776908
fn forward_bevy_events(&mut self) {
909+
self.reconcile_pending_modifiers();
910+
777911
let raw_winit_events = self.raw_winit_events.drain(..).collect::<Vec<_>>();
778912
let window_events = self.bevy_window_events.drain(..).collect::<Vec<_>>();
779913
let world = self.world_mut();

0 commit comments

Comments
 (0)