From c9d64c385828b33ee2846b74861e0a1e4feef862 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 25 Sep 2024 23:16:17 -0600 Subject: [PATCH] keyboard-fun --- crates/gpui/src/app.rs | 11 ++++ crates/gpui/src/platform.rs | 3 ++ crates/gpui/src/platform/mac/events.rs | 8 +-- crates/gpui/src/platform/mac/platform.rs | 66 ++++++++++++++++++++++++ crates/gpui/src/window.rs | 33 +++++++++++- 5 files changed, 116 insertions(+), 5 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 6cb491b100..07b530d993 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -227,6 +227,7 @@ pub struct AppContext { text_system: Arc, flushing_effects: bool, pending_updates: usize, + keyboard_layout_id: String, pub(crate) actions: Rc, pub(crate) active_drag: Option, pub(crate) background_executor: BackgroundExecutor, @@ -274,11 +275,13 @@ impl AppContext { let text_system = Arc::new(TextSystem::new(platform.text_system())); let entities = EntityMap::new(); + let keyboard_layout_id = platform.keyboard_layout_id(); let app = Rc::new_cyclic(|this| AppCell { app: RefCell::new(AppContext { this: this.clone(), platform: platform.clone(), + keyboard_layout_id, text_system, actions: Rc::new(ActionRegistry::default()), flushing_effects: false, @@ -314,6 +317,14 @@ impl AppContext { init_app_menus(platform.as_ref(), &mut app.borrow_mut()); + platform.on_keyboard_layout_changed(Box::new({ + let cx = app.clone(); + move || { + let layout = cx.borrow().platform.keyboard_layout_id(); + cx.borrow_mut().keyboard_layout_id = layout; + } + })); + platform.on_quit(Box::new({ let cx = app.clone(); move || { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 680c813078..7fcd7b5b0a 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -159,6 +159,9 @@ pub(crate) trait Platform: 'static { None } + fn on_keyboard_layout_changed(&self, callback: Box); + fn keyboard_layout_id(&self) -> String; + fn set_dock_menu(&self, menu: Vec, keymap: &Keymap); fn add_recent_document(&self, _path: &Path) {} fn on_app_menu_action(&self, callback: Box); diff --git a/crates/gpui/src/platform/mac/events.rs b/crates/gpui/src/platform/mac/events.rs index 698ecedbae..1aaebdbdc5 100644 --- a/crates/gpui/src/platform/mac/events.rs +++ b/crates/gpui/src/platform/mac/events.rs @@ -274,6 +274,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { && first_char.map_or(true, |ch| { !(NSUpArrowFunctionKey..=NSModeSwitchFunctionKey).contains(&ch) }); + let mut ime_key = None; #[allow(non_upper_case_globals)] let key = match first_char { @@ -317,9 +318,10 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { let mut chars_ignoring_modifiers_and_shift = chars_for_modified_key(native_event.keyCode(), false, false); - // Honor ⌘ when Dvorak-QWERTY is used. + // Use the command keyboard for bindings by default. let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), true, false); - if command && chars_ignoring_modifiers_and_shift != chars_with_cmd { + if chars_ignoring_modifiers_and_shift != chars_with_cmd { + ime_key = Some(chars_ignoring_modifiers); chars_ignoring_modifiers = chars_for_modified_key(native_event.keyCode(), true, shift); chars_ignoring_modifiers_and_shift = chars_with_cmd; @@ -351,7 +353,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { function, }, key, - ime_key: None, + ime_key, } } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 5873d8fe39..0a7d226132 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -67,6 +67,17 @@ const MAC_PLATFORM_IVAR: &str = "platform"; static mut APP_CLASS: *const Class = ptr::null(); static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); +#[link(name = "Carbon", kind = "framework")] +extern "C" { + fn TISCopyCurrentKeyboardInputSource() -> *mut c_void; + fn TISGetInputSourceProperty( + inputSource: *mut c_void, + propertyKey: CFStringRef, + ) -> *const c_void; +} + +static kTISPropertyInputSourceID: &[u8] = b"TISPropertyInputSourceID\0"; + #[ctor] unsafe fn build_classes() { APP_CLASS = { @@ -135,6 +146,10 @@ unsafe fn build_classes() { sel!(application:openURLs:), open_urls as extern "C" fn(&mut Object, Sel, id, id), ); + decl.add_method( + sel!(keyboardLayoutDidChange:), + keyboard_layout_did_change as extern "C" fn(&mut Object, Sel, id), + ); decl.register() } @@ -159,6 +174,7 @@ pub(crate) struct MacPlatformState { menu_actions: Vec>, open_urls: Option)>>, finish_launching: Option>, + keyboard_layout_changed: Option>, dock_menu: Option, } @@ -188,6 +204,7 @@ impl MacPlatform { menu_actions: Default::default(), open_urls: None, finish_launching: None, + keyboard_layout_changed: None, dock_menu: None, })) } @@ -620,6 +637,27 @@ impl Platform for MacPlatform { self.0.lock().open_urls = Some(callback); } + fn on_keyboard_layout_changed(&self, callback: Box) { + self.0.lock().keyboard_layout_changed = Some(callback); + } + + fn keyboard_layout_id(&self) -> String { + unsafe { + let current_input_source = TISCopyCurrentKeyboardInputSource(); + let property = CFString::new("TISPropertyInputSourceID"); + dbg!(¤t_input_source); + let input_source_id = + TISGetInputSourceProperty(current_input_source, property.as_concrete_TypeRef()); + dbg!(&input_source_id); + let cf_string = CFString::wrap_under_get_rule(input_source_id as _); + let result = cf_string.to_string(); + + CFRelease(current_input_source as *mut _); + + dbg!(result) + } + } + fn prompt_for_paths( &self, options: PathPromptOptions, @@ -1214,7 +1252,19 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { unsafe { let app: id = msg_send![APP_CLASS, sharedApplication]; app.setActivationPolicy_(NSApplicationActivationPolicyRegular); + let observer: id = this as *mut _ as id; let platform = get_mac_platform(this); + + let notification_center: id = msg_send![class!(NSNotificationCenter), defaultCenter]; + let selector = sel!(keyboardLayoutDidChange:); + let name = NSString::alloc(nil) + .init_str("NSTextInputContextKeyboardSelectionDidChangeNotification"); + let _: () = msg_send![notification_center, + addObserver:observer + selector:selector + name:name + object:nil]; + let callback = platform.0.lock().finish_launching.take(); if let Some(callback) = callback { callback(); @@ -1222,6 +1272,22 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { } } +// Add this new method to handle the keyboard layout change notification +extern "C" fn keyboard_layout_did_change(this: &mut Object, _: Sel, _: id) { + unsafe { + let platform = get_mac_platform(this); + let callback = platform.0.lock().keyboard_layout_changed.take(); + if let Some(mut callback) = callback { + callback(); + platform + .0 + .lock() + .keyboard_layout_changed + .get_or_insert(callback); + } + } +} + extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) { if !has_open_windows { let platform = unsafe { get_mac_platform(this) }; diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 57066b0ce6..f7c2d14313 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3271,11 +3271,19 @@ impl<'a> WindowContext<'a> { keystroke = Some(key_down_event.keystroke.clone()); } - let Some(keystroke) = keystroke else { + let Some(mut keystroke) = keystroke else { self.finish_dispatch_key_event(event, dispatch_path); return; }; + dbg!(&keystroke.key); + for (key, replacement) in [("ö", "["), ("Ö", "{"), ("ä", "]"), ("Ä", "}")].into_iter() { + if keystroke.key == key { + keystroke.key = replacement.to_string(); + } + } + dbg!(&keystroke.key); + let mut currently_pending = self.window.pending_input.take().unwrap_or_default(); if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus { currently_pending = PendingInput::default(); @@ -3703,6 +3711,16 @@ impl<'a> WindowContext<'a> { action, &self.window.rendered_frame.dispatch_tree.context_stack, ) + .into_iter() + .map(|mut binding| { + for keystroke in binding.keystrokes.iter_mut() { + if keystroke.key == "[" { + keystroke.key = "ö".to_string() + } + } + binding + }) + .collect() } /// Returns any bindings that would invoke the given action on the given focus handle if it were focused. @@ -3721,7 +3739,18 @@ impl<'a> WindowContext<'a> { .into_iter() .filter_map(|node_id| dispatch_tree.node(node_id).context.clone()) .collect(); - dispatch_tree.bindings_for_action(action, &context_stack) + dispatch_tree + .bindings_for_action(action, &context_stack) + .into_iter() + .map(|mut binding| { + for keystroke in binding.keystrokes.iter_mut() { + if keystroke.key == "[" { + keystroke.key = "ö".to_string() + } + } + binding + }) + .collect() } /// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle.