Detailed changes
@@ -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<Self>) -> bool {
+ self.input_enabled
+ }
}
trait SelectionExt {
@@ -70,6 +70,11 @@ pub trait EntityInputHandler: 'static + Sized {
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<usize>;
+
+ /// See [`InputHandler::accepts_text_input`] for details
+ fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
+ true
+ }
}
/// The canonical implementation of [`crate::PlatformInputHandler`]. Call [`Window::handle_input`]
@@ -177,4 +182,9 @@ impl<V: EntityInputHandler> InputHandler for ElementInputHandler<V> {
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))
+ }
}
@@ -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 {}
@@ -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
@@ -1382,6 +1382,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> 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<wl_keyboard::WlKeyboard, ()> 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<zwp_text_input_v3::ZwpTextInputV3, ()> for WaylandClientStatePtr {
key_char: Some(commit_text),
},
is_held: false,
+ prefer_character_input: false,
}));
} else {
window.handle_ime(ImeInput::InsertText(commit_text));
@@ -1047,6 +1047,7 @@ impl X11Client {
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
keystroke,
is_held: false,
+ prefer_character_input: false,
}));
}
Event::KeyRelease(event) => {
@@ -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),
@@ -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);
}
@@ -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<isize> {
+ fn handle_syskeyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
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<isize> {
- 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<isize> {
+ fn handle_keydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
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<isize> {
+ fn handle_keyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
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<isize> {
- 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<isize> {
- 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<isize> {
// 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<F>(
- handle: HWND,
wparam: WPARAM,
lparam: LPARAM,
state: &mut WindowsWindowState,
f: F,
) -> Option<PlatformInput>
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<Keystroke> {
+) -> 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<String> {
@@ -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,
@@ -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])
@@ -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);
+ }
}
}
@@ -45,7 +45,6 @@ pub struct WindowsWindowState {
pub pending_surrogate: Option<u16>,
pub last_reported_modifiers: Option<Modifiers>,
pub last_reported_capslock: Option<Capslock>,
- 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,
@@ -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::<KeyDownEvent>()
+ .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;