diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index bbd59fa7bc..c1abf87d28 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -37,10 +37,10 @@ use crate::{ AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId, EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext, Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, - PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle, - PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource, - SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance, - WindowHandle, WindowId, WindowInvalidator, + PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, PromptBuilder, + PromptButton, PromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle, + Reservation, ScreenCaptureSource, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, + Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator, colors::{Colors, GlobalColors}, current_platform, hash, init_app_menus, }; @@ -263,6 +263,7 @@ pub struct App { pub(crate) focus_handles: Arc, pub(crate) keymap: Rc>, pub(crate) keyboard_layout: Box, + pub(crate) keyboard_mapper: Rc, pub(crate) global_action_listeners: FxHashMap>>, pending_effects: VecDeque, @@ -312,6 +313,7 @@ impl App { let text_system = Arc::new(TextSystem::new(platform.text_system())); let entities = EntityMap::new(); let keyboard_layout = platform.keyboard_layout(); + let keyboard_mapper = platform.keyboard_mapper(); let app = Rc::new_cyclic(|this| AppCell { app: RefCell::new(App { @@ -337,6 +339,7 @@ impl App { focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), keymap: Rc::new(RefCell::new(Keymap::default())), keyboard_layout, + keyboard_mapper, global_action_listeners: FxHashMap::default(), pending_effects: VecDeque::new(), pending_notifications: FxHashSet::default(), @@ -376,6 +379,7 @@ impl App { if let Some(app) = app.upgrade() { let cx = &mut app.borrow_mut(); cx.keyboard_layout = cx.platform.keyboard_layout(); + cx.keyboard_mapper = cx.platform.keyboard_mapper(); cx.keyboard_layout_observers .clone() .retain(&(), move |callback| (callback)(cx)); @@ -424,6 +428,11 @@ impl App { self.keyboard_layout.as_ref() } + /// Get the current keyboard mapper. + pub fn keyboard_mapper(&self) -> &dyn PlatformKeyboardMapper { + self.keyboard_mapper.as_ref() + } + /// Invokes a handler when the current keyboard layout changes pub fn on_keyboard_layout_change(&self, mut callback: F) -> Subscription where diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 4d2feeaf1d..f64710bc56 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -231,7 +231,6 @@ pub(crate) trait Platform: 'static { fn on_quit(&self, callback: Box); fn on_reopen(&self, callback: Box); - fn on_keyboard_layout_change(&self, callback: Box); fn set_menus(&self, menus: Vec, keymap: &Keymap); fn get_menus(&self) -> Option> { @@ -251,7 +250,6 @@ pub(crate) trait Platform: 'static { fn on_app_menu_action(&self, callback: Box); fn on_will_open_app_menu(&self, callback: Box); fn on_validate_app_menu_command(&self, callback: Box bool>); - fn keyboard_layout(&self) -> Box; fn compositor_name(&self) -> &'static str { "" @@ -272,6 +270,10 @@ pub(crate) trait Platform: 'static { fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task>; fn read_credentials(&self, url: &str) -> Task)>>>; fn delete_credentials(&self, url: &str) -> Task>; + + fn keyboard_layout(&self) -> Box; + fn keyboard_mapper(&self) -> Rc; + fn on_keyboard_layout_change(&self, callback: Box); } /// A handle to a platform's display, e.g. a monitor or laptop screen. diff --git a/crates/gpui/src/platform/keyboard.rs b/crates/gpui/src/platform/keyboard.rs index e28d781520..b751ceebeb 100644 --- a/crates/gpui/src/platform/keyboard.rs +++ b/crates/gpui/src/platform/keyboard.rs @@ -1,3 +1,5 @@ +use crate::{KeybindingKeystroke, Keystroke}; + /// A trait for platform-specific keyboard layouts pub trait PlatformKeyboardLayout { /// Get the keyboard layout ID, which should be unique to the layout @@ -5,3 +7,9 @@ pub trait PlatformKeyboardLayout { /// Get the keyboard layout display name fn name(&self) -> &str; } + +/// A trait for platform-specific keyboard mappings +pub trait PlatformKeyboardMapper { + /// Map a key equivalent to its platform-specific representation + fn map_key_equivalent(&self, keystroke: Keystroke) -> KeybindingKeystroke; +} diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index fcd70a57e6..5c5fce0116 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -300,13 +300,11 @@ impl Keystroke { impl KeybindingKeystroke { /// Create a new keybinding keystroke from the given keystroke - pub fn new(keystroke: Keystroke) -> Self { - let (key, modifiers) = to_unshifted_key(&keystroke.key, &keystroke.modifiers); - let inner = keystroke.into_shifted(); + pub fn new(inner: Keystroke) -> Self { KeybindingKeystroke { inner, - key, - modifiers, + modifiers: inner.modifiers, + key: inner.key.clone(), } } } diff --git a/crates/gpui/src/platform/windows/keyboard.rs b/crates/gpui/src/platform/windows/keyboard.rs index 371feb70c2..138c356ab6 100644 --- a/crates/gpui/src/platform/windows/keyboard.rs +++ b/crates/gpui/src/platform/windows/keyboard.rs @@ -1,22 +1,28 @@ +use std::borrow::Cow; + use anyhow::Result; use windows::Win32::UI::{ 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, + GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MAPVK_VK_TO_VSC, 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::{Modifiers, PlatformKeyboardLayout}; +use crate::{ + KeybindingKeystroke, Keystroke, Modifiers, PlatformKeyboardLayout, PlatformKeyboardMapper, +}; pub(crate) struct WindowsKeyboardLayout { id: String, name: String, } +pub(crate) struct WindowsKeyboardMapper; + impl PlatformKeyboardLayout for WindowsKeyboardLayout { fn id(&self) -> &str { &self.id @@ -27,6 +33,65 @@ impl PlatformKeyboardLayout for WindowsKeyboardLayout { } } +impl PlatformKeyboardMapper for WindowsKeyboardMapper { + fn map_key_equivalent(&self, mut keystroke: Keystroke) -> KeybindingKeystroke { + let Some((vkey, shift)) = key_needs_processing(&keystroke.key) else { + return KeybindingKeystroke::new(keystroke); + }; + if shift && keystroke.modifiers.shift { + log::warn!( + "Keystroke '{}' has both shift and a shifted key, this is likely a bug", + keystroke.key + ); + keystroke.modifiers.shift = false; + } + // translate to unshifted key first + let Some(key) = get_key_from_vkey(vkey) else { + log::error!( + "Failed to map key equivalent '{:?}' to a valid key", + keystroke + ); + return KeybindingKeystroke::new(keystroke); + }; + let modifiers = Modifiers { + control: keystroke.modifiers.control, + alt: keystroke.modifiers.alt, + shift, + platform: keystroke.modifiers.platform, + function: keystroke.modifiers.function, + }; + + keystroke.key = if shift { + let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) }; + if scan_code == 0 { + log::error!( + "Failed to map keystroke {:?} with virtual key '{:?}' to a scan code", + keystroke, + vkey + ); + return KeybindingKeystroke::new(keystroke); + } + let Some(shifted_key) = get_shifted_key(vkey, scan_code) else { + log::error!( + "Failed to map keystroke {:?} with virtual key '{:?}' to a shifted key", + keystroke, + vkey + ); + return KeybindingKeystroke::new(keystroke); + }; + shifted_key + } else { + key.clone() + }; + + KeybindingKeystroke { + inner: keystroke, + modifiers, + key, + } + } +} + impl WindowsKeyboardLayout { pub(crate) fn new() -> Result { let mut buffer = [0u16; KL_NAMELENGTH as usize]; @@ -48,6 +113,12 @@ impl WindowsKeyboardLayout { } } +impl WindowsKeyboardMapper { + pub(crate) fn new() -> Self { + Self + } +} + pub(crate) fn get_keystroke_key( vkey: VIRTUAL_KEY, scan_code: u32, @@ -140,3 +211,51 @@ pub(crate) fn generate_key_char( _ => None, } } + +fn key_needs_processing(key: &str) -> Option<(VIRTUAL_KEY, bool)> { + match key { + "`" => Some((VK_OEM_3, false)), + "~" => Some((VK_OEM_3, true)), + "1" => Some((VK_1, false)), + "!" => Some((VK_1, true)), + "2" => Some((VK_2, false)), + "@" => Some((VK_2, true)), + "3" => Some((VK_3, false)), + "#" => Some((VK_3, true)), + "4" => Some((VK_4, false)), + "$" => Some((VK_4, true)), + "5" => Some((VK_5, false)), + "%" => Some((VK_5, true)), + "6" => Some((VK_6, false)), + "^" => Some((VK_6, true)), + "7" => Some((VK_7, false)), + "&" => Some((VK_7, true)), + "8" => Some((VK_8, false)), + "*" => Some((VK_8, true)), + "9" => Some((VK_9, false)), + "(" => Some((VK_9, true)), + "0" => Some((VK_0, false)), + ")" => Some((VK_0, true)), + "-" => Some((VK_OEM_MINUS, false)), + "_" => Some((VK_OEM_MINUS, true)), + "=" => Some((VK_OEM_PLUS, false)), + "+" => Some((VK_OEM_PLUS, true)), + "[" => Some((VK_OEM_4, false)), + "{" => Some((VK_OEM_4, true)), + "]" => Some((VK_OEM_6, false)), + "}" => Some((VK_OEM_6, true)), + "\\" => Some((VK_OEM_5, false)), + "|" => Some((VK_OEM_5, true)), + ";" => Some((VK_OEM_1, false)), + ":" => Some((VK_OEM_1, true)), + "'" => Some((VK_OEM_7, false)), + "\"" => Some((VK_OEM_7, true)), + "," => Some((VK_OEM_COMMA, false)), + "<" => Some((VK_OEM_COMMA, true)), + "." => Some((VK_OEM_PERIOD, false)), + ">" => Some((VK_OEM_PERIOD, true)), + "/" => Some((VK_OEM_2, false)), + "?" => Some((VK_OEM_2, true)), + _ => None, + } +} diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 6202e05fb3..5ac2be2f23 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -351,6 +351,10 @@ impl Platform for WindowsPlatform { ) } + fn keyboard_mapper(&self) -> Rc { + Rc::new(WindowsKeyboardMapper::new()) + } + fn on_keyboard_layout_change(&self, callback: Box) { self.state.borrow_mut().callbacks.keyboard_layout_change = Some(callback); }