From 2ead8c42fb6792095d7cb02f7b89e467421dc8a0 Mon Sep 17 00:00:00 2001 From: John Tur Date: Tue, 4 Nov 2025 20:28:12 -0500 Subject: [PATCH] Improve Windows text input for international keyboard layouts and IMEs (#41259) - Custom handling of dead keys has been removed. UX for dead keys is now the same as other applications on Windows. - We could bring back some kind of custom UI, but only if UX is fully compatible with expected Windows behavior (e.g. ability to move the cursor after typing a dead key). - Fixes https://github.com/zed-industries/zed/issues/38838 - Character input via AltGr shift state now always has priority over keybindings. This applies regardless of whether the keystroke used the AltGr key or Ctrl+Alt to enter the shift state. - In particular, we use the following heuristic to determine whether a keystroke should trigger character input first or trigger keybindings first: - If the keystroke does not have any of Ctrl/Alt/Win down, trigger keybindings first. - Otherwise, determine the character that would be entered by the keystroke. If it is a control character, or no character at all, trigger keybindings first. - Otherwise, the keystroke has _any_ of Ctrl/Alt/Win down and generates a printable character. Compare this character against the character that would be generated if the keystroke had _none_ of Ctrl/Alt/Win down: - If the character is the same, the modifiers are not significant; trigger keybindings first. - If there is no active input handler, or the active input handler indicates that it isn't accepting text input (e.g. when an operator is pending in Vim mode), character entry is not useful; trigger keybindings first. - Otherwise, assume the modifiers enable access to an otherwise difficult-to-enter key; trigger character entry first. - Fixes https://github.com/zed-industries/zed/issues/35862 - Fixes https://github.com/zed-industries/zed/issues/40054#issuecomment-3447833349 - Fixes https://github.com/zed-industries/zed/issues/41486 - TranslateMessage calls are no longer skipped for unhandled keystrokes. This fixes language input keys on Japanese and Korean keyboards (and surely other cases as well). - To avoid any other missing-TranslateMessage headaches in the future, the message loop has been rewritten in a "traditional" Win32 style, where accelerators are handled in the message loop and TranslateMessage is called in the intended manner. - Fixes https://github.com/zed-industries/zed/issues/39971 - Fixes https://github.com/zed-industries/zed/issues/40300 - Fixes https://github.com/zed-industries/zed/issues/40321 - Fixes https://github.com/zed-industries/zed/issues/40335 - Fixes https://github.com/zed-industries/zed/issues/40592 - Fixes https://github.com/zed-industries/zed/issues/40638 - As a bonus, Alt+Space now opens the system menu, since it is triggered by the WM_SYSCHAR generated by TranslateMessage. - VK_PROCESSKEYs are now ignored rather than being unwrapped and matched against keybindings. This ensures that IMEs will reliably receieve keystrokes that they express interest in. This matches the behavior of native Windows applications. - Fixes https://github.com/zed-industries/zed/issues/36736 - Fixes https://github.com/zed-industries/zed/issues/39608 - Fixes https://github.com/zed-industries/zed/issues/39991 - Fixes https://github.com/zed-industries/zed/issues/41223 - Fixes https://github.com/zed-industries/zed/issues/41656 - Fixes https://github.com/zed-industries/zed/issues/34180 - Fixes https://github.com/zed-industries/zed/issues/41766 Release Notes: - windows: Improved keyboard input handling for international keyboard layouts and IMEs --- crates/editor/src/editor.rs | 4 + crates/gpui/src/input.rs | 10 + crates/gpui/src/interactive.rs | 6 + crates/gpui/src/platform.rs | 10 + .../gpui/src/platform/linux/wayland/client.rs | 3 + crates/gpui/src/platform/linux/x11/client.rs | 1 + crates/gpui/src/platform/mac/events.rs | 1 + crates/gpui/src/platform/mac/window.rs | 1 + crates/gpui/src/platform/windows/events.rs | 234 ++++++++---------- crates/gpui/src/platform/windows/keyboard.rs | 35 +-- crates/gpui/src/platform/windows/platform.rs | 21 +- crates/gpui/src/platform/windows/window.rs | 3 - crates/gpui/src/window.rs | 42 +++- 13 files changed, 196 insertions(+), 175 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 437c3db7e1..ad837e3260 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -24194,6 +24194,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 dc36ef9e16..c9c0a85cad 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 dd521ff718..4aa0715144 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 prefer_character_input: bool, } impl Sealed for KeyDownEvent {} diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 20a135df51..decdc54735 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -1012,6 +1012,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. @@ -1120,6 +1125,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 6461bf6973..ee2590aa4d 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -1382,6 +1382,7 @@ impl Dispatch for WaylandClientStatePtr { let input = PlatformInput::KeyDown(KeyDownEvent { keystroke: keystroke.clone(), is_held: false, + prefer_character_input: false, }); state.repeat.current_id += 1; @@ -1395,6 +1396,7 @@ impl Dispatch for WaylandClientStatePtr { let input = PlatformInput::KeyDown(KeyDownEvent { keystroke, is_held: true, + prefer_character_input: false, }); move |_event, _metadata, this| { let mut client = this.get_client(); @@ -1479,6 +1481,7 @@ impl Dispatch for WaylandClientStatePtr { key_char: Some(commit_text), }, is_held: false, + prefer_character_input: false, })); } 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 fa9d0181c0..98e70a3071 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, + prefer_character_input: false, })); } Event::KeyRelease(event) => { diff --git a/crates/gpui/src/platform/mac/events.rs b/crates/gpui/src/platform/mac/events.rs index 938db4b762..acc392a5f3 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, + prefer_character_input: 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 95efffa3e7..9c56d24e68 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, + prefer_character_input: 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 9c10dcec4b..ff88bea3fc 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, prefer_character_input| { + PlatformInput::KeyDown(KeyDownEvent { + keystroke, + is_held: lparam.0 & (0x1 << 30) > 0, + prefer_character_input, + }) + }, + ) 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,86 @@ fn parse_normal_key( ); get_keystroke_key(vkey, scan_code as u32, &mut modifiers) })?; - Some(Keystroke { - modifiers, - key, - key_char, - }) + + let prefer_character_input = should_prefer_character_input(vkey, lparam.hiword() & 0xFF); + + Some(( + Keystroke { + modifiers, + key, + key_char, + }, + prefer_character_input, + )) +} + +fn should_prefer_character_input(vkey: VIRTUAL_KEY, scan_code: u16) -> bool { + let mut keyboard_state = [0u8; 256]; + unsafe { + if GetKeyboardState(&mut keyboard_state).is_err() { + return false; + } + } + + 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 false; + } + + let c = &buffer_c[..result_c as usize]; + if char::decode_utf16(c.iter().copied()) + .next() + .and_then(|ch| ch.ok()) + .map(|ch| ch.is_control()) + .unwrap_or(true) + { + return false; + } + + 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 false; + } + + 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 false; + } + + let c_no_modifiers = &buffer_c_no_modifiers[..result_c_no_modifiers as usize]; + c != c_no_modifiers } fn parse_ime_composition_string(ctx: HIMC, comp_type: IME_COMPOSITION_STRING) -> Option { @@ -1460,25 +1456,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 cd0c1da105..627988be57 100644 --- a/crates/gpui/src/platform/windows/keyboard.rs +++ b/crates/gpui/src/platform/windows/keyboard.rs @@ -108,39 +108,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"041A" | // Croatian - b"041B" | // Slovak - b"0418" // Romanian - ) - } else { - false - } - } } impl WindowsKeyboardMapper { @@ -258,7 +225,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 b7f69961c4..a8e1346942 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -272,6 +272,22 @@ impl WindowsPlatform { } } +fn translate_accelerator(msg: &MSG) -> Option<()> { + if msg.message != WM_KEYDOWN && msg.message != WM_SYSKEYDOWN { + return None; + } + + 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 +328,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 e765fa1a22..ea1027f826 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 66ab327a19..e5b0ae4929 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3558,6 +3558,7 @@ impl Window { PlatformInput::KeyDown(KeyDownEvent { keystroke: keystroke.clone(), is_held: false, + prefer_character_input: false, }), cx, ); @@ -3856,17 +3857,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.prefer_character_input) + .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; + } } } @@ -3975,6 +3994,7 @@ impl Window { let event = KeyDownEvent { keystroke: replay.keystroke.clone(), is_held: false, + prefer_character_input: true, }; cx.propagate_event = true;