diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index a68e67a98..d6af4d296 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -149,6 +149,12 @@ class AppDelegate: NSObject, // This registers the Ghostty => Services menu to exist. NSApp.servicesMenu = menuServices + // Setup a local event monitor for app-level keyboard shortcuts. See + // localEventHandler for more info why. + _ = NSEvent.addLocalMonitorForEvents( + matching: [.keyDown], + handler: localEventHandler) + // Configure user notifications let actions = [ UNNotificationAction(identifier: Ghostty.userNotificationActionShow, title: "Show") @@ -361,6 +367,53 @@ class AppDelegate: NSObject, return terminalManager.focusedSurface?.surface } + // MARK: Notifications and Events + + /// This handles events from the NSEvent.addLocalEventMonitor. We use this so we can get + /// events without any terminal windows open. + private func localEventHandler(_ event: NSEvent) -> NSEvent? { + return switch event.type { + case .keyDown: + localEventKeyDown(event) + + default: + event + } + } + + private func localEventKeyDown(_ event: NSEvent) -> NSEvent? { + // If we have a main window then we don't process any of the keys + // because we let it capture and propagate. + guard NSApp.mainWindow == nil else { return event } + + // If this event would be handled by our menu then we do nothing. + if let mainMenu = NSApp.mainMenu, + mainMenu.performKeyEquivalent(with: event) { + return nil + } + + // If we reach this point then we try to process the key event + // through the Ghostty key mechanism. + + // Ghostty must be loaded + guard let ghostty = self.ghostty.app else { return event } + + // Build our event input and call ghostty + var key_ev = ghostty_input_key_s() + key_ev.action = GHOSTTY_ACTION_PRESS + key_ev.mods = Ghostty.ghosttyMods(event.modifierFlags) + key_ev.keycode = UInt32(event.keyCode) + key_ev.text = nil + key_ev.composing = false + if (ghostty_app_key(ghostty, key_ev)) { + // The key was used so we want to stop it from going to our Mac app + Ghostty.logger.debug("local key event handled event=\(event)") + return nil + } + + return event + } + //MARK: - Restorable State /// We support NSSecureCoding for restorable state. Required as of macOS Sonoma (14) but a good idea anyways. diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 9198e48b6..3ed082d87 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -87,7 +87,7 @@ extension Ghostty { // Subscribe to notifications for keyboard layout change so that we can update Ghostty. NotificationCenter.default.addObserver( self, - selector: #selector(self.keyboardSelectionDidChange(notification:)), + selector: #selector(keyboardSelectionDidChange(notification:)), name: NSTextInputContext.keyboardSelectionDidChangeNotification, object: nil) #endif diff --git a/src/App.zig b/src/App.zig index 7e82bf007..df305ae45 100644 --- a/src/App.zig +++ b/src/App.zig @@ -294,9 +294,6 @@ pub fn keyEvent( .leaf => |leaf| leaf, }; - // We only care about global keybinds - if (!leaf.flags.global) return false; - // Perform the action self.performAllAction(rt_app, leaf.action) catch |err| { log.warn("error performing global keybind action action={s} err={}", .{