From 2ed4deb77b61ddfae7b470bab3b47db4b859f161 Mon Sep 17 00:00:00 2001 From: pankaj-raikar Date: Thu, 13 Aug 2026 10:19:24 +0530 Subject: [PATCH 1/2] Preserve frontmost app focus for chat overlay - Convert the chat window to a non-activating panel - Remove explicit application activation when showing chat and menus - Keep the model dropdown non-activating and interactive --- Sources/Ghostbar/App/AppDelegate.swift | 3 ++- Sources/Ghostbar/UI/ChatWindow.swift | 10 +++++++--- Sources/Ghostbar/UI/MovableWindow.swift | 7 ++++++- Sources/Ghostbar/UI/PrivateDropdown.swift | 4 ++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Sources/Ghostbar/App/AppDelegate.swift b/Sources/Ghostbar/App/AppDelegate.swift index 1034a41..0d94007 100644 --- a/Sources/Ghostbar/App/AppDelegate.swift +++ b/Sources/Ghostbar/App/AppDelegate.swift @@ -69,7 +69,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { } func menuWillOpen(_ menu: NSMenu) { - NSApp.activate(ignoringOtherApps: true) + // Intentionally do NOT activate the app — opening the status-bar menu + // should not pull focus away from the frontmost application. } @objc func toggleChat() { diff --git a/Sources/Ghostbar/UI/ChatWindow.swift b/Sources/Ghostbar/UI/ChatWindow.swift index 73d4b55..8026e13 100644 --- a/Sources/Ghostbar/UI/ChatWindow.swift +++ b/Sources/Ghostbar/UI/ChatWindow.swift @@ -17,7 +17,7 @@ class ChatWindow: NSObject, NSWindowDelegate { window = MovableWindow( contentRect: initialFrame, - styleMask: [.titled, .closable, .resizable, .miniaturizable, .fullSizeContentView], + styleMask: [.titled, .closable, .resizable, .miniaturizable, .fullSizeContentView, .nonactivatingPanel], backing: .buffered, defer: false ) @@ -28,6 +28,8 @@ class ChatWindow: NSObject, NSWindowDelegate { window.standardWindowButton(.miniaturizeButton)?.isHidden = true window.standardWindowButton(.zoomButton)?.isHidden = true window.level = .floating + window.isFloatingPanel = true + window.hidesOnDeactivate = false // keep overlay visible while App X is active if shouldCenter { window.center() } window.isReleasedWhenClosed = false window.sharingType = .none @@ -50,8 +52,10 @@ class ChatWindow: NSObject, NSWindowDelegate { } func showAndFocus() { - NSApp.activate(ignoringOtherApps: true) - window.makeKeyAndOrderFront(nil) + // Show above other apps and become the keyboard target WITHOUT activating + // Ghostbar — App X stays the frontmost/active application. + window.orderFrontRegardless() + window.makeKey() window.sharingType = .none } diff --git a/Sources/Ghostbar/UI/MovableWindow.swift b/Sources/Ghostbar/UI/MovableWindow.swift index 274cf9e..52be4f9 100644 --- a/Sources/Ghostbar/UI/MovableWindow.swift +++ b/Sources/Ghostbar/UI/MovableWindow.swift @@ -1,9 +1,14 @@ import AppKit -class MovableWindow: NSWindow { +class MovableWindow: NSPanel { private var dragging = false private let headerHeight: CGFloat = 38 + // Become the keyboard target so the user can type into the overlay, without + // making Ghostbar the active application. canBecomeMain stays false (NSPanel + // default) so App X keeps ownership of the menu bar / frontmost status. + override var canBecomeKey: Bool { true } + override func sendEvent(_ event: NSEvent) { let h = contentView?.frame.height ?? frame.height let inHeader = event.locationInWindow.y >= h - headerHeight diff --git a/Sources/Ghostbar/UI/PrivateDropdown.swift b/Sources/Ghostbar/UI/PrivateDropdown.swift index c97dedb..7940059 100644 --- a/Sources/Ghostbar/UI/PrivateDropdown.swift +++ b/Sources/Ghostbar/UI/PrivateDropdown.swift @@ -40,7 +40,7 @@ class PrivateDropdown: NSObject, NSTableViewDataSource, NSTableViewDelegate { panel = NSPanel( contentRect: NSRect(x: 0, y: 0, width: 250, height: 200), - styleMask: [.borderless], + styleMask: [.borderless, .nonactivatingPanel], backing: .buffered, defer: false) panel.sharingType = .none panel.backgroundColor = NSColor.controlBackgroundColor @@ -79,7 +79,7 @@ class PrivateDropdown: NSObject, NSTableViewDataSource, NSTableViewDelegate { tableView.scrollRowToVisible(idx) } win.addChildWindow(panel, ordered: .above) - panel.makeKeyAndOrderFront(nil) + panel.orderFrontRegardless() } @objc private func rowClicked() { From d6658bb655ffbe7b56ad633974b0bd733e137003 Mon Sep 17 00:00:00 2001 From: pankaj-raikar Date: Thu, 13 Aug 2026 10:31:40 +0530 Subject: [PATCH 2/2] Remove comments from focus changes - Remove explanatory comments added with the focus-preservation update\n- Keep the implementation aligned with the repository's existing style --- Sources/Ghostbar/App/AppDelegate.swift | 2 -- Sources/Ghostbar/UI/ChatWindow.swift | 4 +--- Sources/Ghostbar/UI/MovableWindow.swift | 3 --- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/Sources/Ghostbar/App/AppDelegate.swift b/Sources/Ghostbar/App/AppDelegate.swift index 0d94007..dcd9ee6 100644 --- a/Sources/Ghostbar/App/AppDelegate.swift +++ b/Sources/Ghostbar/App/AppDelegate.swift @@ -69,8 +69,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { } func menuWillOpen(_ menu: NSMenu) { - // Intentionally do NOT activate the app — opening the status-bar menu - // should not pull focus away from the frontmost application. } @objc func toggleChat() { diff --git a/Sources/Ghostbar/UI/ChatWindow.swift b/Sources/Ghostbar/UI/ChatWindow.swift index 8026e13..f818d54 100644 --- a/Sources/Ghostbar/UI/ChatWindow.swift +++ b/Sources/Ghostbar/UI/ChatWindow.swift @@ -29,7 +29,7 @@ class ChatWindow: NSObject, NSWindowDelegate { window.standardWindowButton(.zoomButton)?.isHidden = true window.level = .floating window.isFloatingPanel = true - window.hidesOnDeactivate = false // keep overlay visible while App X is active + window.hidesOnDeactivate = false if shouldCenter { window.center() } window.isReleasedWhenClosed = false window.sharingType = .none @@ -52,8 +52,6 @@ class ChatWindow: NSObject, NSWindowDelegate { } func showAndFocus() { - // Show above other apps and become the keyboard target WITHOUT activating - // Ghostbar — App X stays the frontmost/active application. window.orderFrontRegardless() window.makeKey() window.sharingType = .none diff --git a/Sources/Ghostbar/UI/MovableWindow.swift b/Sources/Ghostbar/UI/MovableWindow.swift index 52be4f9..f558271 100644 --- a/Sources/Ghostbar/UI/MovableWindow.swift +++ b/Sources/Ghostbar/UI/MovableWindow.swift @@ -4,9 +4,6 @@ class MovableWindow: NSPanel { private var dragging = false private let headerHeight: CGFloat = 38 - // Become the keyboard target so the user can type into the overlay, without - // making Ghostbar the active application. canBecomeMain stays false (NSPanel - // default) so App X keeps ownership of the menu bar / frontmost status. override var canBecomeKey: Bool { true } override func sendEvent(_ event: NSEvent) {