diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index 5fc0141858656c799fc1b61f94170e2c7b2196a0..765e8c43beefadde7565baf6db188d95bb411be5 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -56,6 +56,7 @@ impl Keystroke { /// This method assumes that `self` was typed and `target' is in the keymap, and checks /// both possibilities for self against the target. pub(crate) fn should_match(&self, target: &Keystroke) -> bool { + #[cfg(not(target_os = "windows"))] if let Some(key_char) = self .key_char .as_ref() @@ -72,6 +73,18 @@ impl Keystroke { } } + #[cfg(target_os = "windows")] + if let Some(key_char) = self + .key_char + .as_ref() + .filter(|key_char| key_char != &&self.key) + { + // On Windows, if key_char is set, then the typed keystroke produced the key_char + if &target.key == key_char && target.modifiers == Modifiers::none() { + return true; + } + } + target.modifiers == self.modifiers && target.key == self.key } diff --git a/crates/gpui/src/platform/windows/events.rs b/crates/gpui/src/platform/windows/events.rs index 911e487fe5a08ea3f38128732d0b9dcdccc8aff5..ec50a071fa972d2813051cdadf01e2de223f220c 100644 --- a/crates/gpui/src/platform/windows/events.rs +++ b/crates/gpui/src/platform/windows/events.rs @@ -84,11 +84,11 @@ pub(crate) fn handle_msg( WM_MOUSEWHEEL => handle_mouse_wheel_msg(handle, wparam, lparam, state_ptr), WM_MOUSEHWHEEL => handle_mouse_horizontal_wheel_msg(handle, wparam, lparam, state_ptr), WM_SYSKEYDOWN => handle_syskeydown_msg(wparam, lparam, state_ptr), - WM_SYSKEYUP => handle_syskeyup_msg(wparam, state_ptr), + WM_SYSKEYUP => handle_syskeyup_msg(wparam, lparam, state_ptr), WM_SYSCOMMAND => handle_system_command(wparam, state_ptr), WM_KEYDOWN => handle_keydown_msg(wparam, lparam, state_ptr), - WM_KEYUP => handle_keyup_msg(wparam, state_ptr), - WM_CHAR => handle_char_msg(wparam, lparam, state_ptr), + WM_KEYUP => handle_keyup_msg(wparam, lparam, state_ptr), + WM_CHAR => handle_char_msg(wparam, state_ptr), WM_DEADCHAR => handle_dead_char_msg(wparam, state_ptr), WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr), WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr), @@ -344,132 +344,102 @@ fn handle_syskeydown_msg( state_ptr: Rc, ) -> Option { let mut lock = state_ptr.state.borrow_mut(); - let vkey = wparam.loword(); - let input = if is_modifier(VIRTUAL_KEY(vkey)) { - let modifiers = current_modifiers(); - if let Some(prev_modifiers) = lock.last_reported_modifiers { - if prev_modifiers == modifiers { - return Some(0); - } - } - lock.last_reported_modifiers = Some(modifiers); - PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers }) - } else { - let keystroke = parse_syskeydown_msg_keystroke(wparam)?; + let input = handle_key_event(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 result = if !func(input).propagate { - state_ptr.state.borrow_mut().system_key_handled = true; + + let handled = !func(input).propagate; + + let mut lock = state_ptr.state.borrow_mut(); + lock.callbacks.input = Some(func); + + if handled { + lock.system_key_handled = true; + lock.suppress_next_char_msg = true; Some(0) } else { // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}` // shortcuts. None - }; - state_ptr.state.borrow_mut().callbacks.input = Some(func); - - result + } } -fn handle_syskeyup_msg(wparam: WPARAM, state_ptr: Rc) -> Option { +fn handle_syskeyup_msg( + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { let mut lock = state_ptr.state.borrow_mut(); - let vkey = wparam.loword(); - let input = if is_modifier(VIRTUAL_KEY(vkey)) { - let modifiers = current_modifiers(); - if let Some(prev_modifiers) = lock.last_reported_modifiers { - if prev_modifiers == modifiers { - return Some(0); - } - } - lock.last_reported_modifiers = Some(modifiers); - PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers }) - } else { - let keystroke = parse_syskeydown_msg_keystroke(wparam)?; + let input = handle_key_event(wparam, lparam, &mut lock, |keystroke| { PlatformInput::KeyUp(KeyUpEvent { keystroke }) - }; + })?; let mut func = lock.callbacks.input.take()?; drop(lock); - let result = if !func(input).propagate { - Some(0) - } else { - // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}` - // shortcuts. - None - }; + func(input); state_ptr.state.borrow_mut().callbacks.input = Some(func); - result + // Always return 0 to indicate that the message was handled, so we could properly handle `ModifiersChanged` event. + Some(0) } +// 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( wparam: WPARAM, lparam: LPARAM, state_ptr: Rc, ) -> Option { - let Some(keystroke_or_modifier) = parse_keystroke_from_vkey(wparam, false) else { - return Some(1); - }; let mut lock = state_ptr.state.borrow_mut(); - - let event = match keystroke_or_modifier { - KeystrokeOrModifier::Keystroke(keystroke) => PlatformInput::KeyDown(KeyDownEvent { + let Some(input) = handle_key_event(wparam, lparam, &mut lock, |keystroke| { + PlatformInput::KeyDown(KeyDownEvent { keystroke, is_held: lparam.0 & (0x1 << 30) > 0, - }), - KeystrokeOrModifier::Modifier(modifiers) => { - if let Some(prev_modifiers) = lock.last_reported_modifiers { - if prev_modifiers == modifiers { - return Some(0); - } - } - lock.last_reported_modifiers = Some(modifiers); - PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers }) - } + }) + }) else { + return Some(1); }; + let Some(mut func) = lock.callbacks.input.take() else { return Some(1); }; drop(lock); - let result = if func(event).default_prevented { + let handled = !func(input).propagate; + + let mut lock = state_ptr.state.borrow_mut(); + lock.callbacks.input = Some(func); + + if handled { + lock.suppress_next_char_msg = true; Some(0) } else { Some(1) - }; - state_ptr.state.borrow_mut().callbacks.input = Some(func); - - result + } } -fn handle_keyup_msg(wparam: WPARAM, state_ptr: Rc) -> Option { - let Some(keystroke_or_modifier) = parse_keystroke_from_vkey(wparam, true) else { +fn handle_keyup_msg( + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + let mut lock = state_ptr.state.borrow_mut(); + let Some(input) = handle_key_event(wparam, lparam, &mut lock, |keystroke| { + PlatformInput::KeyUp(KeyUpEvent { keystroke }) + }) else { return Some(1); }; - let mut lock = state_ptr.state.borrow_mut(); - let event = match keystroke_or_modifier { - KeystrokeOrModifier::Keystroke(keystroke) => PlatformInput::KeyUp(KeyUpEvent { keystroke }), - KeystrokeOrModifier::Modifier(modifiers) => { - if let Some(prev_modifiers) = lock.last_reported_modifiers { - if prev_modifiers == modifiers { - return Some(0); - } - } - lock.last_reported_modifiers = Some(modifiers); - PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers }) - } - }; let Some(mut func) = lock.callbacks.input.take() else { return Some(1); }; drop(lock); - let result = if func(event).default_prevented { + let result = if func(input).default_prevented { Some(0) } else { Some(1) @@ -479,35 +449,15 @@ fn handle_keyup_msg(wparam: WPARAM, state_ptr: Rc) -> Opt result } -fn handle_char_msg( - wparam: WPARAM, - lparam: LPARAM, - state_ptr: Rc, -) -> Option { - let Some(keystroke) = parse_char_msg_keystroke(wparam) else { - return Some(1); - }; - let mut lock = state_ptr.state.borrow_mut(); - let Some(mut func) = lock.callbacks.input.take() else { - return Some(1); - }; - drop(lock); - let key_char = keystroke.key_char.clone(); - let event = KeyDownEvent { - keystroke, - is_held: lparam.0 & (0x1 << 30) > 0, - }; - let dispatch_event_result = func(PlatformInput::KeyDown(event)); - state_ptr.state.borrow_mut().callbacks.input = Some(func); - - if dispatch_event_result.default_prevented || !dispatch_event_result.propagate { - return Some(0); - } - let Some(ime_char) = key_char else { +fn handle_char_msg(wparam: WPARAM, state_ptr: Rc) -> Option { + let Some(input) = char::from_u32(wparam.0 as u32) + .filter(|c| !c.is_control()) + .map(String::from) + else { return Some(1); }; with_input_handler(&state_ptr, |input_handler| { - input_handler.replace_text_in_range(None, &ime_char); + input_handler.replace_text_in_range(None, &input); }); Some(0) @@ -1297,151 +1247,116 @@ fn handle_input_language_changed( Some(0) } -fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option { - let modifiers = current_modifiers(); - let vk_code = wparam.loword(); - - // on Windows, F10 can trigger this event, not just the alt key, - // so when F10 was pressed, handle only it - if !modifiers.alt { - if vk_code == VK_F10.0 { - let offset = vk_code - VK_F1.0; - return Some(Keystroke { - modifiers, - key: format!("f{}", offset + 1), - key_char: None, - }); - } else { - return None; - } - } +fn handle_key_event( + wparam: WPARAM, + lparam: LPARAM, + state: &mut WindowsWindowState, + f: F, +) -> Option +where + F: FnOnce(Keystroke) -> PlatformInput, +{ + state.suppress_next_char_msg = false; + let virtual_key = VIRTUAL_KEY(wparam.loword()); + let mut modifiers = current_modifiers(); - let key = match VIRTUAL_KEY(vk_code) { - VK_BACK => "backspace", - VK_RETURN => "enter", - VK_TAB => "tab", - VK_UP => "up", - VK_DOWN => "down", - VK_RIGHT => "right", - VK_LEFT => "left", - VK_HOME => "home", - VK_END => "end", - VK_PRIOR => "pageup", - VK_NEXT => "pagedown", - VK_BROWSER_BACK => "back", - VK_BROWSER_FORWARD => "forward", - VK_ESCAPE => "escape", - VK_INSERT => "insert", - VK_DELETE => "delete", - VK_APPS => "menu", - _ => { - let basic_key = basic_vkcode_to_string(vk_code, modifiers); - if basic_key.is_some() { - return basic_key; - } else { - if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 { - let offset = vk_code - VK_F1.0; - return Some(Keystroke { - modifiers, - key: format!("f{}", offset + 1), - key_char: None, - }); - } else { - return None; - } + match virtual_key { + VK_PROCESSKEY => { + // IME composition + None + } + VK_SHIFT | VK_CONTROL | VK_MENU | VK_LWIN | VK_RWIN => { + if state + .last_reported_modifiers + .is_some_and(|prev_modifiers| prev_modifiers == modifiers) + { + return None; } + state.last_reported_modifiers = Some(modifiers); + Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent { + modifiers, + })) + } + vkey => { + let keystroke = parse_normal_key(vkey, lparam, modifiers)?; + Some(f(keystroke)) } } - .to_owned(); - - Some(Keystroke { - modifiers, - key, - key_char: None, - }) } -enum KeystrokeOrModifier { - Keystroke(Keystroke), - Modifier(Modifiers), -} - -fn parse_keystroke_from_vkey(wparam: WPARAM, is_keyup: bool) -> Option { - let vk_code = wparam.loword(); - - let modifiers = current_modifiers(); - - let key = match VIRTUAL_KEY(vk_code) { - VK_BACK => "backspace", - VK_RETURN => "enter", - VK_TAB => "tab", - VK_UP => "up", - VK_DOWN => "down", - VK_RIGHT => "right", - VK_LEFT => "left", - VK_HOME => "home", - VK_END => "end", - VK_PRIOR => "pageup", - VK_NEXT => "pagedown", - VK_BROWSER_BACK => "back", - VK_BROWSER_FORWARD => "forward", - VK_ESCAPE => "escape", - VK_INSERT => "insert", - VK_DELETE => "delete", - VK_APPS => "menu", - _ => { - if is_modifier(VIRTUAL_KEY(vk_code)) { - return Some(KeystrokeOrModifier::Modifier(modifiers)); - } - - if modifiers.control || modifiers.alt || is_keyup { - let basic_key = basic_vkcode_to_string(vk_code, modifiers); - if let Some(basic_key) = basic_key { - return Some(KeystrokeOrModifier::Keystroke(basic_key)); - } - } - - if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 { - let offset = vk_code - VK_F1.0; - return Some(KeystrokeOrModifier::Keystroke(Keystroke { - modifiers, - key: format!("f{}", offset + 1), - key_char: None, - })); - }; - return None; +fn parse_immutable(vkey: VIRTUAL_KEY) -> Option { + Some( + match vkey { + VK_SPACE => "space", + VK_BACK => "backspace", + VK_RETURN => "enter", + VK_TAB => "tab", + VK_UP => "up", + VK_DOWN => "down", + VK_RIGHT => "right", + VK_LEFT => "left", + VK_HOME => "home", + VK_END => "end", + VK_PRIOR => "pageup", + VK_NEXT => "pagedown", + VK_BROWSER_BACK => "back", + VK_BROWSER_FORWARD => "forward", + VK_ESCAPE => "escape", + VK_INSERT => "insert", + VK_DELETE => "delete", + VK_APPS => "menu", + VK_F1 => "f1", + VK_F2 => "f2", + VK_F3 => "f3", + VK_F4 => "f4", + VK_F5 => "f5", + VK_F6 => "f6", + VK_F7 => "f7", + VK_F8 => "f8", + VK_F9 => "f9", + VK_F10 => "f10", + VK_F11 => "f11", + VK_F12 => "f12", + VK_F13 => "f13", + VK_F14 => "f14", + VK_F15 => "f15", + VK_F16 => "f16", + VK_F17 => "f17", + VK_F18 => "f18", + VK_F19 => "f19", + VK_F20 => "f20", + VK_F21 => "f21", + VK_F22 => "f22", + VK_F23 => "f23", + VK_F24 => "f24", + _ => return None, } - } - .to_owned(); + .to_string(), + ) +} - Some(KeystrokeOrModifier::Keystroke(Keystroke { +fn parse_normal_key( + vkey: VIRTUAL_KEY, + lparam: LPARAM, + mut modifiers: Modifiers, +) -> Option { + let mut key_char = None; + let key = parse_immutable(vkey).or_else(|| { + let scan_code = lparam.hiword() & 0xFF; + key_char = generate_key_char( + vkey, + scan_code as u32, + modifiers.control, + modifiers.shift, + modifiers.alt, + ); + get_keystroke_key(vkey, scan_code as u32, &mut modifiers) + })?; + Some(Keystroke { modifiers, key, - key_char: None, - })) -} - -fn parse_char_msg_keystroke(wparam: WPARAM) -> Option { - let first_char = char::from_u32((wparam.0 as u16).into())?; - if first_char.is_control() { - None - } else { - let mut modifiers = current_modifiers(); - // for characters that use 'shift' to type it is expected that the - // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported - if first_char.to_ascii_uppercase() == first_char.to_ascii_lowercase() { - modifiers.shift = false; - } - let key = match first_char { - ' ' => "space".to_string(), - first_char => first_char.to_lowercase().to_string(), - }; - Some(Keystroke { - modifiers, - key, - key_char: Some(first_char.to_string()), - }) - } + key_char, + }) } fn parse_ime_compostion_string(ctx: HIMC) -> Option { @@ -1494,40 +1409,11 @@ fn parse_ime_compostion_result(ctx: HIMC) -> Option { } } -fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option { - let mapped_code = unsafe { MapVirtualKeyW(code as u32, MAPVK_VK_TO_CHAR) }; - - let key = match mapped_code { - 0 => None, - raw_code => char::from_u32(raw_code), - }? - .to_ascii_lowercase(); - - let key = if matches!(code as u32, 112..=135) { - format!("f{key}") - } else { - key.to_string() - }; - - Some(Keystroke { - modifiers, - key, - key_char: None, - }) -} - #[inline] fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool { unsafe { GetKeyState(vkey.0 as i32) < 0 } } -fn is_modifier(virtual_key: VIRTUAL_KEY) -> bool { - matches!( - virtual_key, - VK_CONTROL | VK_MENU | VK_SHIFT | VK_LWIN | VK_RWIN - ) -} - #[inline] pub(crate) fn current_modifiers() -> Modifiers { Modifiers { @@ -1639,7 +1525,12 @@ fn with_input_handler(state_ptr: &Rc, f: F) -> Opti where F: FnOnce(&mut PlatformInputHandler) -> R, { - let mut input_handler = state_ptr.state.borrow_mut().input_handler.take()?; + let mut lock = state_ptr.state.borrow_mut(); + if lock.suppress_next_char_msg { + return None; + } + let mut input_handler = lock.input_handler.take()?; + drop(lock); let result = f(&mut input_handler); state_ptr.state.borrow_mut().input_handler = Some(input_handler); Some(result) @@ -1653,6 +1544,9 @@ where F: FnOnce(&mut PlatformInputHandler, f32) -> Option, { let mut lock = state_ptr.state.borrow_mut(); + if lock.suppress_next_char_msg { + return None; + } let mut input_handler = lock.input_handler.take()?; let scale_factor = lock.scale_factor; drop(lock); diff --git a/crates/gpui/src/platform/windows/keyboard.rs b/crates/gpui/src/platform/windows/keyboard.rs index 131f708e7e190d759a2723280e29a09455d3d207..f5a148a97e986c8c33d30cb516474d8103d0c5f7 100644 --- a/crates/gpui/src/platform/windows/keyboard.rs +++ b/crates/gpui/src/platform/windows/keyboard.rs @@ -1,10 +1,16 @@ use anyhow::Result; use windows::Win32::UI::{ - Input::KeyboardAndMouse::GetKeyboardLayoutNameW, WindowsAndMessaging::KL_NAMELENGTH, + Input::KeyboardAndMouse::{ + GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MapVirtualKeyW, ToUnicode, VIRTUAL_KEY, VK_0, + VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_ABNT_C1, VK_CONTROL, VK_MENU, + VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_102, + VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT, + }, + WindowsAndMessaging::KL_NAMELENGTH, }; use windows_core::HSTRING; -use crate::PlatformKeyboardLayout; +use crate::{Modifiers, PlatformKeyboardLayout}; pub(crate) struct WindowsKeyboardLayout { id: String, @@ -41,3 +47,94 @@ impl WindowsKeyboardLayout { } } } + +pub(crate) fn get_keystroke_key( + vkey: VIRTUAL_KEY, + scan_code: u32, + modifiers: &mut Modifiers, +) -> Option { + if modifiers.shift && need_to_convert_to_shifted_key(vkey) { + get_shifted_key(vkey, scan_code).inspect(|_| { + modifiers.shift = false; + }) + } else { + get_key_from_vkey(vkey) + } +} + +fn get_key_from_vkey(vkey: VIRTUAL_KEY) -> Option { + let key_data = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_CHAR) }; + if key_data == 0 { + return None; + } + + // The high word contains dead key flag, the low word contains the character + let key = char::from_u32(key_data & 0xFFFF)?; + + Some(key.to_ascii_lowercase().to_string()) +} + +#[inline] +fn need_to_convert_to_shifted_key(vkey: VIRTUAL_KEY) -> bool { + matches!( + vkey, + VK_OEM_3 + | VK_OEM_MINUS + | VK_OEM_PLUS + | VK_OEM_4 + | VK_OEM_5 + | VK_OEM_6 + | VK_OEM_1 + | VK_OEM_7 + | VK_OEM_COMMA + | VK_OEM_PERIOD + | VK_OEM_2 + | VK_OEM_102 + | VK_OEM_8 + | VK_ABNT_C1 + | VK_0 + | VK_1 + | VK_2 + | VK_3 + | VK_4 + | VK_5 + | VK_6 + | VK_7 + | VK_8 + | VK_9 + ) +} + +fn get_shifted_key(vkey: VIRTUAL_KEY, scan_code: u32) -> Option { + generate_key_char(vkey, scan_code, false, true, false) +} + +pub(crate) fn generate_key_char( + vkey: VIRTUAL_KEY, + scan_code: u32, + control: bool, + shift: bool, + alt: bool, +) -> Option { + let mut state = [0; 256]; + if control { + state[VK_CONTROL.0 as usize] = 0x80; + } + if shift { + state[VK_SHIFT.0 as usize] = 0x80; + } + if alt { + state[VK_MENU.0 as usize] = 0x80; + } + + let mut buffer = [0; 8]; + let len = unsafe { ToUnicode(vkey.0 as u32, scan_code, Some(&state), &mut buffer, 1 << 2) }; + + if len > 0 { + let candidate = String::from_utf16_lossy(&buffer[..len as usize]); + if !candidate.is_empty() && !candidate.chars().next().unwrap().is_control() { + return Some(candidate); + } + } + None +} diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index 313fcd715b65cd39ee77e978ea1ea4a867fb1d27..ccca2b664adef5e24b1e4ff52e06b5a824a10001 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -43,6 +43,7 @@ pub struct WindowsWindowState { pub callbacks: Callbacks, pub input_handler: Option, pub last_reported_modifiers: Option, + pub suppress_next_char_msg: bool, pub system_key_handled: bool, pub hovered: bool, @@ -102,6 +103,7 @@ impl WindowsWindowState { let callbacks = Callbacks::default(); let input_handler = None; let last_reported_modifiers = None; + let suppress_next_char_msg = false; let system_key_handled = false; let hovered = false; let click_state = ClickState::new(); @@ -121,6 +123,7 @@ impl WindowsWindowState { callbacks, input_handler, last_reported_modifiers, + suppress_next_char_msg, system_key_handled, hovered, renderer,