From 40bdea73357ded7a3a753ee8f26d65a07434f087 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Jan 2025 14:07:33 -0800 Subject: [PATCH] macos: handle overridden system bindings with no focused window --- include/ghostty.h | 1 + macos/Sources/App/macOS/AppDelegate.swift | 9 +++++++++ src/App.zig | 19 +++++++++++++++++++ src/apprt/embedded.zig | 22 ++++++++++++++++++++++ 4 files changed, 51 insertions(+) diff --git a/include/ghostty.h b/include/ghostty.h index 8af181051..0e444a2fa 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -686,6 +686,7 @@ void ghostty_app_tick(ghostty_app_t); void* ghostty_app_userdata(ghostty_app_t); void ghostty_app_set_focus(ghostty_app_t, bool); bool ghostty_app_key(ghostty_app_t, ghostty_input_key_s); +bool ghostty_app_key_is_binding(ghostty_app_t, ghostty_input_key_s); void ghostty_app_keyboard_changed(ghostty_app_t); void ghostty_app_open_config(ghostty_app_t); void ghostty_app_update_config(ghostty_app_t, ghostty_config_t); diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 513a6872e..70873236a 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -425,6 +425,15 @@ class AppDelegate: NSObject, // because we let it capture and propagate. guard NSApp.mainWindow == nil else { return event } + // If this event as-is would result in a key binding then we send it. + if let app = ghostty.app, + ghostty_app_key_is_binding( + app, + event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)) { + ghostty_app_key(app, event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)) + return nil + } + // If this event would be handled by our menu then we do nothing. if let mainMenu = NSApp.mainMenu, mainMenu.performKeyEquivalent(with: event) { diff --git a/src/App.zig b/src/App.zig index b0de85c95..a6b54db23 100644 --- a/src/App.zig +++ b/src/App.zig @@ -313,6 +313,25 @@ pub fn focusEvent(self: *App, focused: bool) void { self.focused = focused; } +/// Returns true if the given key event would trigger a keybinding +/// if it were to be processed. This is useful for determining if +/// a key event should be sent to the terminal or not. +pub fn keyEventIsBinding( + self: *App, + rt_app: *apprt.App, + event: input.KeyEvent, +) bool { + _ = self; + + switch (event.action) { + .release => return false, + .press, .repeat => {}, + } + + // If we have a keybinding for this event then we return true. + return rt_app.config.keybind.set.getEvent(event) != null; +} + /// Handle a key event at the app-scope. If this key event is used, /// this will return true and the caller shouldn't continue processing /// the event. If the event is not used, this will return false. diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 758a3ff87..10d09988d 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -1386,6 +1386,28 @@ pub const CAPI = struct { }; } + /// Returns true if the given key event would trigger a binding + /// if it were sent to the surface right now. The "right now" + /// is important because things like trigger sequences are only + /// valid until the next key event. + export fn ghostty_app_key_is_binding( + app: *App, + event: KeyEvent, + ) bool { + const core_event = app.coreKeyEvent( + .app, + event.keyEvent(), + ) catch |err| { + log.warn("error processing key event err={}", .{err}); + return false; + } orelse { + log.warn("error processing key event", .{}); + return false; + }; + + return app.core_app.keyEventIsBinding(app, core_event); + } + /// Notify the app that the keyboard was changed. This causes the /// keyboard layout to be reloaded from the OS. export fn ghostty_app_keyboard_changed(v: *App) void {