diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 40576d90c54cd8..72e27789f4178d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -23964,6 +23964,10 @@ impl EntityInputHandler for Editor { let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot()); Some(utf16_offset.0) } + + fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context) -> bool { + self.input_enabled + } } trait SelectionExt { diff --git a/crates/gpui/src/input.rs b/crates/gpui/src/input.rs index dc36ef9e16feed..c9c0a85cad2283 100644 --- a/crates/gpui/src/input.rs +++ b/crates/gpui/src/input.rs @@ -70,6 +70,11 @@ pub trait EntityInputHandler: 'static + Sized { window: &mut Window, cx: &mut Context, ) -> Option; + + /// See [`InputHandler::accepts_text_input`] for details + fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context) -> bool { + true + } } /// The canonical implementation of [`crate::PlatformInputHandler`]. Call [`Window::handle_input`] @@ -177,4 +182,9 @@ impl InputHandler for ElementInputHandler { view.character_index_for_point(point, window, cx) }) } + + fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool { + self.view + .update(cx, |view, cx| view.accepts_text_input(window, cx)) + } } diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index dafe623dfada7b..07b0cd52f33450 100644 --- a/crates/gpui/src/interactive.rs +++ b/crates/gpui/src/interactive.rs @@ -25,6 +25,12 @@ pub struct KeyDownEvent { /// Whether the key is currently held down. pub is_held: bool, + + /// Whether the modifiers are excessive for producing this character. + /// When false, the modifiers are essential for character input (e.g., AltGr), + /// and character input should be prioritized over keybindings. + /// When true, the modifiers are for keybindings (e.g., Ctrl+A). + pub are_modifiers_excessive: bool, } impl Sealed for KeyDownEvent {} diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index a5f4a368e377d0..9062293400080e 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -974,6 +974,11 @@ impl PlatformInputHandler { .ok() .flatten() } + + #[allow(dead_code)] + pub(crate) fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool { + self.handler.accepts_text_input(window, cx) + } } /// A struct representing a selection in a text buffer, in UTF16 characters. @@ -1082,6 +1087,11 @@ pub trait InputHandler: 'static { fn apple_press_and_hold_enabled(&mut self) -> bool { true } + + /// Returns whether this handler is accepting text input to be inserted. + fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool { + true + } } /// The variables that can be configured when creating a new window diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 1ebdda3a266af0..570c8577ba569f 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -1350,6 +1350,7 @@ impl Dispatch for WaylandClientStatePtr { let input = PlatformInput::KeyDown(KeyDownEvent { keystroke: keystroke.clone(), is_held: false, + are_modifiers_excessive: true, }); state.repeat.current_id += 1; @@ -1363,6 +1364,7 @@ impl Dispatch for WaylandClientStatePtr { let input = PlatformInput::KeyDown(KeyDownEvent { keystroke, is_held: true, + are_modifiers_excessive: true, }); move |_event, _metadata, this| { let mut client = this.get_client(); @@ -1447,6 +1449,7 @@ impl Dispatch for WaylandClientStatePtr { key_char: Some(commit_text), }, is_held: false, + are_modifiers_excessive: true, })); } else { window.handle_ime(ImeInput::InsertText(commit_text)); diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index fa9d0181c09581..574a88040e11e6 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -1047,6 +1047,7 @@ impl X11Client { window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent { keystroke, is_held: false, + are_modifiers_excessive: true, })); } Event::KeyRelease(event) => { diff --git a/crates/gpui/src/platform/mac/events.rs b/crates/gpui/src/platform/mac/events.rs index 938db4b76205ee..84ff84fc31bca0 100644 --- a/crates/gpui/src/platform/mac/events.rs +++ b/crates/gpui/src/platform/mac/events.rs @@ -131,6 +131,7 @@ impl PlatformInput { NSEventType::NSKeyDown => Some(Self::KeyDown(KeyDownEvent { keystroke: parse_keystroke(native_event), is_held: native_event.isARepeat() == YES, + are_modifiers_excessive: false, })), NSEventType::NSKeyUp => Some(Self::KeyUp(KeyUpEvent { keystroke: parse_keystroke(native_event), diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 95efffa3e77cdb..945e62fa469d21 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -2318,6 +2318,7 @@ extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) { let handled = (callback)(PlatformInput::KeyDown(KeyDownEvent { keystroke, is_held: false, + are_modifiers_excessive: false, })); state.as_ref().lock().do_command_handled = Some(!handled.propagate); } diff --git a/crates/gpui/src/platform/windows/events.rs b/crates/gpui/src/platform/windows/events.rs index 9c10dcec4bb629..723eb2c177c74f 100644 --- a/crates/gpui/src/platform/windows/events.rs +++ b/crates/gpui/src/platform/windows/events.rs @@ -26,6 +26,7 @@ pub(crate) const WM_GPUI_DOCK_MENU_ACTION: u32 = WM_USER + 4; pub(crate) const WM_GPUI_FORCE_UPDATE_WINDOW: u32 = WM_USER + 5; pub(crate) const WM_GPUI_KEYBOARD_LAYOUT_CHANGED: u32 = WM_USER + 6; pub(crate) const WM_GPUI_GPU_DEVICE_LOST: u32 = WM_USER + 7; +pub(crate) const WM_GPUI_KEYDOWN: u32 = WM_USER + 8; const SIZE_MOVE_LOOP_TIMER_ID: usize = 1; const AUTO_HIDE_TASKBAR_THICKNESS_PX: i32 = 1; @@ -92,13 +93,10 @@ impl WindowsWindowInner { } WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(handle, wparam, lparam), WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(handle, wparam, lparam), - WM_SYSKEYDOWN => self.handle_syskeydown_msg(handle, wparam, lparam), - WM_SYSKEYUP => self.handle_syskeyup_msg(handle, wparam, lparam), - WM_SYSCOMMAND => self.handle_system_command(wparam), - WM_KEYDOWN => self.handle_keydown_msg(handle, wparam, lparam), - WM_KEYUP => self.handle_keyup_msg(handle, wparam, lparam), + WM_SYSKEYUP => self.handle_syskeyup_msg(wparam, lparam), + WM_KEYUP => self.handle_keyup_msg(wparam, lparam), + WM_GPUI_KEYDOWN => self.handle_keydown_msg(wparam, lparam), WM_CHAR => self.handle_char_msg(wparam), - WM_DEADCHAR => self.handle_dead_char_msg(wparam), WM_IME_STARTCOMPOSITION => self.handle_ime_position(handle), WM_IME_COMPOSITION => self.handle_ime_composition(handle, lparam), WM_SETCURSOR => self.handle_set_cursor(handle, lparam), @@ -327,35 +325,9 @@ impl WindowsWindowInner { Some(0) } - fn handle_syskeydown_msg(&self, handle: HWND, wparam: WPARAM, lparam: LPARAM) -> Option { + fn handle_syskeyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option { let mut lock = self.state.borrow_mut(); - let input = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| { - PlatformInput::KeyDown(KeyDownEvent { - keystroke, - is_held: lparam.0 & (0x1 << 30) > 0, - }) - })?; - let mut func = lock.callbacks.input.take()?; - drop(lock); - - let handled = !func(input).propagate; - - let mut lock = self.state.borrow_mut(); - lock.callbacks.input = Some(func); - - if handled { - lock.system_key_handled = true; - Some(0) - } else { - // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}` - // shortcuts. - None - } - } - - fn handle_syskeyup_msg(&self, handle: HWND, wparam: WPARAM, lparam: LPARAM) -> Option { - let mut lock = self.state.borrow_mut(); - let input = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| { + let input = handle_key_event(wparam, lparam, &mut lock, |keystroke, _| { PlatformInput::KeyUp(KeyUpEvent { keystroke }) })?; let mut func = lock.callbacks.input.take()?; @@ -369,27 +341,24 @@ impl WindowsWindowInner { // It's a known bug that you can't trigger `ctrl-shift-0`. See: // https://superuser.com/questions/1455762/ctrl-shift-number-key-combination-has-stopped-working-for-a-few-numbers - fn handle_keydown_msg(&self, handle: HWND, wparam: WPARAM, lparam: LPARAM) -> Option { + fn handle_keydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option { let mut lock = self.state.borrow_mut(); - let Some(input) = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| { - PlatformInput::KeyDown(KeyDownEvent { - keystroke, - is_held: lparam.0 & (0x1 << 30) > 0, - }) - }) else { + let Some(input) = handle_key_event( + wparam, + lparam, + &mut lock, + |keystroke, are_modifiers_excessive| { + PlatformInput::KeyDown(KeyDownEvent { + keystroke, + is_held: lparam.0 & (0x1 << 30) > 0, + are_modifiers_excessive, + }) + }, + ) else { return Some(1); }; drop(lock); - let is_composing = self - .with_input_handler(|input_handler| input_handler.marked_text_range()) - .flatten() - .is_some(); - if is_composing { - translate_message(handle, wparam, lparam); - return Some(0); - } - let Some(mut func) = self.state.borrow_mut().callbacks.input.take() else { return Some(1); }; @@ -398,17 +367,12 @@ impl WindowsWindowInner { self.state.borrow_mut().callbacks.input = Some(func); - if handled { - Some(0) - } else { - translate_message(handle, wparam, lparam); - Some(1) - } + if handled { Some(0) } else { Some(1) } } - fn handle_keyup_msg(&self, handle: HWND, wparam: WPARAM, lparam: LPARAM) -> Option { + fn handle_keyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option { let mut lock = self.state.borrow_mut(); - let Some(input) = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| { + let Some(input) = handle_key_event(wparam, lparam, &mut lock, |keystroke, _| { PlatformInput::KeyUp(KeyUpEvent { keystroke }) }) else { return Some(1); @@ -434,14 +398,6 @@ impl WindowsWindowInner { Some(0) } - fn handle_dead_char_msg(&self, wparam: WPARAM) -> Option { - let ch = char::from_u32(wparam.0 as u32)?.to_string(); - self.with_input_handler(|input_handler| { - input_handler.replace_and_mark_text_in_range(None, &ch, None); - }); - None - } - fn handle_mouse_down_msg( &self, handle: HWND, @@ -1127,17 +1083,6 @@ impl WindowsWindowInner { Some(0) } - fn handle_system_command(&self, wparam: WPARAM) -> Option { - if wparam.0 == SC_KEYMENU as usize { - let mut lock = self.state.borrow_mut(); - if lock.system_key_handled { - lock.system_key_handled = false; - return Some(0); - } - } - None - } - fn handle_system_theme_changed(&self, handle: HWND, lparam: LPARAM) -> Option { // lParam is a pointer to a string that indicates the area containing the system parameter // that was changed. @@ -1281,30 +1226,14 @@ impl WindowsWindowInner { } } -#[inline] -fn translate_message(handle: HWND, wparam: WPARAM, lparam: LPARAM) { - let msg = MSG { - hwnd: handle, - message: WM_KEYDOWN, - wParam: wparam, - lParam: lparam, - // It seems like leaving the following two parameters empty doesn't break key events, they still work as expected. - // But if any bugs pop up after this PR, this is probably the place to look first. - time: 0, - pt: POINT::default(), - }; - unsafe { TranslateMessage(&msg).ok().log_err() }; -} - fn handle_key_event( - handle: HWND, wparam: WPARAM, lparam: LPARAM, state: &mut WindowsWindowState, f: F, ) -> Option where - F: FnOnce(Keystroke) -> PlatformInput, + F: FnOnce(Keystroke, bool) -> PlatformInput, { let virtual_key = VIRTUAL_KEY(wparam.loword()); let modifiers = current_modifiers(); @@ -1323,10 +1252,7 @@ where capslock: current_capslock(), })) } - VK_PACKET => { - translate_message(handle, wparam, lparam); - None - } + VK_PACKET => None, VK_CAPITAL => { let capslock = current_capslock(); if state @@ -1342,13 +1268,8 @@ where })) } vkey => { - let vkey = if vkey == VK_PROCESSKEY { - VIRTUAL_KEY(unsafe { ImmGetVirtualKey(handle) } as u16) - } else { - vkey - }; let keystroke = parse_normal_key(vkey, lparam, modifiers)?; - Some(f(keystroke)) + Some(f(keystroke.0, keystroke.1)) } } } @@ -1408,7 +1329,7 @@ fn parse_normal_key( vkey: VIRTUAL_KEY, lparam: LPARAM, mut modifiers: Modifiers, -) -> Option { +) -> Option<(Keystroke, bool)> { let mut key_char = None; let key = parse_immutable(vkey).or_else(|| { let scan_code = lparam.hiword() & 0xFF; @@ -1421,11 +1342,89 @@ fn parse_normal_key( ); get_keystroke_key(vkey, scan_code as u32, &mut modifiers) })?; - Some(Keystroke { - modifiers, - key, - key_char, - }) + + let are_modifiers_excessive = compute_are_modifiers_excessive(vkey, lparam.hiword() & 0xFF); + + Some(( + Keystroke { + modifiers, + key, + key_char, + }, + are_modifiers_excessive, + )) +} + +fn compute_are_modifiers_excessive(vkey: VIRTUAL_KEY, scan_code: u16) -> bool { + let mut keyboard_state = [0u8; 256]; + unsafe { + if GetKeyboardState(&mut keyboard_state).is_err() { + return true; + } + } + + let mut buffer_c = [0u16; 8]; + let result_c = unsafe { + ToUnicode( + vkey.0 as u32, + scan_code as u32, + Some(&keyboard_state), + &mut buffer_c, + 0x4, + ) + }; + + if result_c < 0 { + return true; + } + + let c = String::from_utf16_lossy(&buffer_c[..result_c as usize]); + + let ctrl_down = (keyboard_state[VK_CONTROL.0 as usize] & 0x80) != 0; + let alt_down = (keyboard_state[VK_MENU.0 as usize] & 0x80) != 0; + let win_down = (keyboard_state[VK_LWIN.0 as usize] & 0x80) != 0 + || (keyboard_state[VK_RWIN.0 as usize] & 0x80) != 0; + + let has_modifiers = ctrl_down || alt_down || win_down; + + if !has_modifiers { + return true; + } + + if c.chars().next().map_or(true, |ch| ch.is_control()) { + return true; + } + + let mut state_no_modifiers = keyboard_state; + state_no_modifiers[VK_CONTROL.0 as usize] = 0; + state_no_modifiers[VK_LCONTROL.0 as usize] = 0; + state_no_modifiers[VK_RCONTROL.0 as usize] = 0; + state_no_modifiers[VK_MENU.0 as usize] = 0; + state_no_modifiers[VK_LMENU.0 as usize] = 0; + state_no_modifiers[VK_RMENU.0 as usize] = 0; + state_no_modifiers[VK_LWIN.0 as usize] = 0; + state_no_modifiers[VK_RWIN.0 as usize] = 0; + + let mut buffer_c_no_modifiers = [0u16; 8]; + let result_c_no_modifiers = unsafe { + ToUnicode( + vkey.0 as u32, + scan_code as u32, + Some(&state_no_modifiers), + &mut buffer_c_no_modifiers, + 0x4, + ) + }; + + if result_c_no_modifiers <= 0 { + return true; + } + + let c_no_modifiers = + String::from_utf16_lossy(&buffer_c_no_modifiers[..result_c_no_modifiers as usize]); + + // If characters differ, modifiers are NOT excessive (they're essential for the character) + c == c_no_modifiers } fn parse_ime_composition_string(ctx: HIMC, comp_type: IME_COMPOSITION_STRING) -> Option { @@ -1460,25 +1459,11 @@ fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool { unsafe { GetKeyState(vkey.0 as i32) < 0 } } -fn keyboard_uses_altgr() -> bool { - use crate::platform::windows::keyboard::WindowsKeyboardLayout; - WindowsKeyboardLayout::new() - .map(|layout| layout.uses_altgr()) - .unwrap_or(false) -} - #[inline] pub(crate) fn current_modifiers() -> Modifiers { - let lmenu_pressed = is_virtual_key_pressed(VK_LMENU); - let rmenu_pressed = is_virtual_key_pressed(VK_RMENU); - let lcontrol_pressed = is_virtual_key_pressed(VK_LCONTROL); - - // Only treat right Alt + left Ctrl as AltGr on keyboards that actually use it - let altgr = keyboard_uses_altgr() && rmenu_pressed && lcontrol_pressed; - Modifiers { - control: is_virtual_key_pressed(VK_CONTROL) && !altgr, - alt: (lmenu_pressed || rmenu_pressed) && !altgr, + control: is_virtual_key_pressed(VK_CONTROL), + alt: is_virtual_key_pressed(VK_MENU), shift: is_virtual_key_pressed(VK_SHIFT), platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN), function: false, diff --git a/crates/gpui/src/platform/windows/keyboard.rs b/crates/gpui/src/platform/windows/keyboard.rs index 7a8478d5910d35..7d94116e5e34dd 100644 --- a/crates/gpui/src/platform/windows/keyboard.rs +++ b/crates/gpui/src/platform/windows/keyboard.rs @@ -110,38 +110,6 @@ impl WindowsKeyboardLayout { name: "unknown".to_string(), } } - - pub(crate) fn uses_altgr(&self) -> bool { - // Check if this is a known AltGr layout by examining the layout ID - // The layout ID is a hex string like "00000409" (US) or "00000407" (German) - // Extract the language ID (last 4 bytes) - let id_bytes = self.id.as_bytes(); - if id_bytes.len() >= 4 { - let lang_id = &id_bytes[id_bytes.len() - 4..]; - // List of keyboard layouts that use AltGr (non-exhaustive) - matches!( - lang_id, - b"0407" | // German - b"040C" | // French - b"040A" | // Spanish - b"0415" | // Polish - b"0413" | // Dutch - b"0816" | // Portuguese - b"041D" | // Swedish - b"0414" | // Norwegian - b"040B" | // Finnish - b"041F" | // Turkish - b"0419" | // Russian - b"0405" | // Czech - b"040E" | // Hungarian - b"0424" | // Slovenian - b"041B" | // Slovak - b"0418" // Romanian - ) - } else { - false - } - } } impl WindowsKeyboardMapper { @@ -259,7 +227,7 @@ pub(crate) fn generate_key_char( } let mut buffer = [0; 8]; - let len = unsafe { ToUnicode(vkey.0 as u32, scan_code, Some(&state), &mut buffer, 1 << 2) }; + let len = unsafe { ToUnicode(vkey.0 as u32, scan_code, Some(&state), &mut buffer, 0x5) }; match len { len if len > 0 => String::from_utf16(&buffer[..len as usize]) diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 361d8e11430832..113642b896b9ef 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -272,6 +272,23 @@ impl WindowsPlatform { } } +fn translate_accelerator(msg: &MSG) -> Option<()> { + if msg.message != WM_KEYDOWN && msg.message != WM_SYSKEYDOWN { + return None; + } + + // Dispatch the key down event and check if it was handled + let result = unsafe { + SendMessageW( + msg.hwnd, + WM_GPUI_KEYDOWN, + Some(msg.wParam), + Some(msg.lParam), + ) + }; + (result.0 == 0).then_some(()) +} + impl Platform for WindowsPlatform { fn background_executor(&self) -> BackgroundExecutor { self.background_executor.clone() @@ -312,7 +329,10 @@ impl Platform for WindowsPlatform { let mut msg = MSG::default(); unsafe { while GetMessageW(&mut msg, None, 0, 0).as_bool() { - DispatchMessageW(&msg); + if translate_accelerator(&msg).is_none() { + _ = TranslateMessage(&msg); + DispatchMessageW(&msg); + } } } diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index e765fa1a22d54a..ea1027f826ae91 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -45,7 +45,6 @@ pub struct WindowsWindowState { pub pending_surrogate: Option, pub last_reported_modifiers: Option, pub last_reported_capslock: Option, - pub system_key_handled: bool, pub hovered: bool, pub renderer: DirectXRenderer, @@ -112,7 +111,6 @@ impl WindowsWindowState { let pending_surrogate = None; let last_reported_modifiers = None; let last_reported_capslock = None; - let system_key_handled = false; let hovered = false; let click_state = ClickState::new(); let nc_button_pressed = None; @@ -133,7 +131,6 @@ impl WindowsWindowState { pending_surrogate, last_reported_modifiers, last_reported_capslock, - system_key_handled, hovered, renderer, click_state, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 0610ea96cb5150..1bbd2c105e05ad 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3555,6 +3555,7 @@ impl Window { PlatformInput::KeyDown(KeyDownEvent { keystroke: keystroke.clone(), is_held: false, + are_modifiers_excessive: true, }), cx, ); @@ -3852,17 +3853,35 @@ impl Window { return; } - for binding in match_result.bindings { - self.dispatch_action_on_node(node_id, binding.action.as_ref(), cx); - if !cx.propagate_event { - self.dispatch_keystroke_observers( - event, - Some(binding.action), - match_result.context_stack, - cx, - ); - self.pending_input_changed(cx); - return; + let skip_bindings = event + .downcast_ref::() + .filter(|key_down_event| !key_down_event.are_modifiers_excessive) + .map(|_| { + self.platform_window + .take_input_handler() + .map_or(false, |mut input_handler| { + let accepts = input_handler.accepts_text_input(self, cx); + self.platform_window.set_input_handler(input_handler); + // If modifiers are not excessive (e.g. AltGr), and the input handler is accepting text input, + // we prefer the text input over bindings. + accepts + }) + }) + .unwrap_or(false); + + if !skip_bindings { + for binding in match_result.bindings { + self.dispatch_action_on_node(node_id, binding.action.as_ref(), cx); + if !cx.propagate_event { + self.dispatch_keystroke_observers( + event, + Some(binding.action), + match_result.context_stack, + cx, + ); + self.pending_input_changed(cx); + return; + } } } @@ -3971,6 +3990,7 @@ impl Window { let event = KeyDownEvent { keystroke: replay.keystroke.clone(), is_held: false, + are_modifiers_excessive: true, }; cx.propagate_event = true;