From e680cc848a58033684c9fb1294c5235a68fe1358 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Tue, 12 Apr 2022 17:41:37 -0700 Subject: [PATCH] macos: workaround weird CMD-. behavior There are certain key combinations that macOS prefers to handle without giving the application much opportunity to process them. CTRL-ESC and CMD-. both cause doCommandBySelector(cancel:) to be called. The former we had already special cased but since we can't disambiguate the two things, we need a better way. performKeyEquivalent: is a method we can implement to have an opportunity to "do something" and prevent the default macOS behavior. So we implement that. What's interesting is that saying that we handled CMD-. results in no further processing at all by macOS, whereas saying that we handled CTRL-ESC results in macOS doing the normal key event dispatch. So we need to route that event for ourselves in that one case. Doesn't feel great! refs: https://github.com/wez/wezterm/issues/1867 --- window/src/os/macos/window.rs | 64 +++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/window/src/os/macos/window.rs b/window/src/os/macos/window.rs index ca5425d79..38e0d3694 100644 --- a/window/src/os/macos/window.rs +++ b/window/src/os/macos/window.rs @@ -1481,14 +1481,7 @@ impl WindowView { let selector = format!("{:?}", a_selector); log::trace!("do_command_by_selector {:?}", selector); - let (key, modifiers) = match selector.as_ref() { - "cancel:" => { - // FIXME: this isn't scalable to various keys - // and we lose eg: SHIFT if that is also pressed at the same time. - // However, CTRL-ESC is processed BEFORE sending any other - // key events so we have no choice but to do it this way. - (KeyCode::Char('\x1b'), Modifiers::CTRL) - } + match selector.as_ref() { "scrollToBeginningOfDocument:" | "scrollToEndOfDocument:" | "scrollPageUp:" @@ -1510,7 +1503,6 @@ impl WindowView { inner.ime_state = ImeDisposition::Continue; inner.ime_last_event.take(); } - return; } _ => { log::warn!("UNHANDLED: IME: do_command_by_selector: {:?}", selector); @@ -1520,24 +1512,7 @@ impl WindowView { inner.ime_state = ImeDisposition::Continue; inner.ime_last_event.take(); } - return; } - }; - - let event = KeyEvent { - key, - modifiers, - repeat_count: 1, - key_is_down: true, - raw: None, - } - .normalize_shift(); - - if let Some(myself) = Self::get_this(this) { - let mut inner = myself.inner.borrow_mut(); - inner.ime_state = ImeDisposition::Acted; - inner.ime_last_event.replace(event.clone()); - inner.events.dispatch(WindowEvent::KeyEvent(event)); } } @@ -2338,6 +2313,38 @@ impl WindowView { } } + extern "C" fn perform_key_equivalent(this: &mut Object, _sel: Sel, nsevent: id) -> BOOL { + let chars = unsafe { nsstring_to_str(nsevent.characters()) }; + let modifier_flags = unsafe { nsevent.modifierFlags() }; + let modifiers = key_modifiers(modifier_flags); + + log::trace!( + "perform_key_equivalent: chars=`{}` modifiers=`{:?}`", + chars.escape_debug(), + modifiers, + ); + + if chars == "." && modifiers == Modifiers::SUPER { + // Synthesize a key down event for this, because macOS will + // not do that, even though we tell it that we handled this event. + // + Self::key_common(this, nsevent, true); + + // Prevent macOS from calling doCommandBySelector(cancel:) + YES + } else if chars == "\u{1b}" && modifiers == Modifiers::CTRL { + // We don't need to synthesize a key down event for this, + // because macOS will do that once we return YES. + // We need to return YES to prevent macOS from calling + // doCommandBySelector(cancel:) on us. + YES + } else { + // Allow macOS to process built-in shortcuts like CMD-` + // to cycle though windows + NO + } + } + extern "C" fn key_down(this: &mut Object, _sel: Sel, nsevent: id) { Self::key_common(this, nsevent, true); } @@ -2597,6 +2604,11 @@ impl WindowView { Self::key_up as extern "C" fn(&mut Object, Sel, id), ); + cls.add_method( + sel!(performKeyEquivalent:), + Self::perform_key_equivalent as extern "C" fn(&mut Object, Sel, id) -> BOOL, + ); + cls.add_method( sel!(acceptsFirstResponder), Self::accepts_first_responder as extern "C" fn(&mut Object, Sel) -> BOOL,