diff --git a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift index bb15cc753..ecf0114de 100644 --- a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift +++ b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift @@ -13,6 +13,11 @@ class QuickTerminalController: BaseTerminalController { /// The current state of the quick terminal private(set) var visible: Bool = false + /// The previously running application when the terminal is shown. This is NEVER Ghostty. + /// If this is set then when the quick terminal is animated out then we will restore this + /// application to the front. + private var previousApp: NSRunningApplication? = nil + init(_ ghostty: Ghostty.App, position: QuickTerminalPosition = .top, baseConfig base: Ghostty.SurfaceConfiguration? = nil, @@ -58,10 +63,23 @@ class QuickTerminalController: BaseTerminalController { override func windowDidResignKey(_ notification: Notification) { super.windowDidResignKey(notification) + // If we're not visible then we don't want to run any of the logic below + // because things like resetting our previous app assume we're visible. + // windowDidResignKey will also get called after animateOut so this + // ensures we don't run logic twice. + guard visible else { return } + // We don't animate out if there is a modal sheet being shown currently. // This lets us show alerts without causing the window to disappear. guard window?.attachedSheet == nil else { return } + // If our app is still active, then it means that we're switching + // to another window within our app, so we remove the previous app + // so we don't restore it. + if NSApp.isActive { + self.previousApp = nil + } + animateOut() } @@ -103,6 +121,14 @@ class QuickTerminalController: BaseTerminalController { // If our application is not active, then we grab focus. The quick terminal // always grabs focus on animation in. if !NSApp.isActive { + // If we have a previously focused application and it isn't us, then + // we want to store it so we can restore state later. + if let previousApp = NSWorkspace.shared.frontmostApplication, + previousApp.bundleIdentifier != Bundle.main.bundleIdentifier + { + self.previousApp = previousApp + } + NSApp.activate(ignoringOtherApps: true) } @@ -178,6 +204,18 @@ class QuickTerminalController: BaseTerminalController { // This causes the window to be removed from the screen list and macOS // handles what should be focused next. window.orderOut(self) + + // If we have a previously active application, restore focus to it. + if let previousApp = self.previousApp { + // Make sure we unset the state no matter what + self.previousApp = nil + + // If the app is terminated to nothing + guard !previousApp.isTerminated else { return } + + // Ignore the result, it doesn't change our behavior. + _ = previousApp.activate(options: []) + } }) }