Browse Source

Refocus launcher after secondary window closes/minimizes

When the Widgets hub or in-app browser window is closed or minimized, bring the main launcher window back to front.

Co-authored-by: Cursor <cursoragent@cursor.com>
huzaifahayat12 2 tháng trước cách đây
mục cha
commit
4eda53752f

+ 25 - 0
google_apps/AppDelegate.swift

@@ -14,6 +14,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
     private var statusItem: NSStatusItem?
     private weak var launcherWindowReference: NSWindow?
     private var launcherWindowResignObserver: NSObjectProtocol?
+    private var refocusLauncherObserver: NSObjectProtocol?
     private var startupPaywallWindow: NSWindow?
     private var hasPresentedStartupPaywall = false
     private var premiumUnlockObservation: NSKeyValueObservation?
@@ -35,11 +36,31 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
     func applicationDidFinishLaunching(_ aNotification: Notification) {
         applyBundledAppIcon()
         configureMainWindowIfNeeded(centerOnLaunch: true)
+        observeRefocusLauncherRequests()
         setupStatusBarItem()
         StatusBarAppIconsController.shared.start()
         presentLauncherAndPaywallOnLaunch()
     }
 
+    private func observeRefocusLauncherRequests() {
+        if let refocusLauncherObserver {
+            NotificationCenter.default.removeObserver(refocusLauncherObserver)
+            self.refocusLauncherObserver = nil
+        }
+
+        refocusLauncherObserver = NotificationCenter.default.addObserver(
+            forName: .refocusLauncherWindowRequested,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            guard let self else { return }
+            // Defer a tick so window-close/miniaturize animations complete cleanly.
+            DispatchQueue.main.async {
+                self.showMainWindow()
+            }
+        }
+    }
+
     private func presentLauncherAndPaywallOnLaunch() {
         // Bring the launcher up-front on cold launch, then show the paywall on top
         // for non-premium users. The paywall is presented as an AppKit child window
@@ -374,6 +395,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
             NotificationCenter.default.removeObserver(observer)
             launcherWindowResignObserver = nil
         }
+        if let observer = refocusLauncherObserver {
+            NotificationCenter.default.removeObserver(observer)
+            refocusLauncherObserver = nil
+        }
         premiumStateMonitorTask?.cancel()
         premiumStateMonitorTask = nil
         premiumUnlockObservation?.invalidate()

+ 8 - 0
google_apps/InAppBrowserWindow.swift

@@ -527,9 +527,17 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
         }
         if w === window {
             onClose?()
+            NotificationCenter.default.post(name: .refocusLauncherWindowRequested, object: nil)
         }
     }
 
+    func windowDidMiniaturize(_ notification: Notification) {
+        guard let w = notification.object as? NSWindow else { return }
+        // Ignore the premium child window; only refocus when the primary browser window is minimized.
+        guard w === window else { return }
+        NotificationCenter.default.post(name: .refocusLauncherWindowRequested, object: nil)
+    }
+
     @objc private func goBack() {
         webView.goBack()
     }

+ 3 - 0
google_apps/LauncherApp.swift

@@ -3,6 +3,9 @@ import Foundation
 extension Notification.Name {
     /// Posted when `statusBarAppIDsData` changes so extra menu bar icons can refresh.
     static let statusBarShortcutIDsChanged = Notification.Name("google_apps.statusBarShortcutIDsChanged")
+    /// Posted when a secondary window (widgets hub, in-app browser, etc.) closes or minimizes,
+    /// to bring focus back to the main launcher window.
+    static let refocusLauncherWindowRequested = Notification.Name("google_apps.refocusLauncherWindowRequested")
 }
 
 struct LauncherApp: Identifiable, Hashable {

+ 31 - 0
google_apps/Widgets/WidgetsWindowManager.swift

@@ -6,6 +6,7 @@ final class WidgetsWindowManager {
 
     private var windowController: NSWindowController?
     private let defaultWindowSize = CGSize(width: 1120, height: 760)
+    private var windowNotificationObservers: [NSObjectProtocol] = []
 
     private init() {}
 
@@ -17,6 +18,7 @@ final class WidgetsWindowManager {
             if let window = controller.window {
                 applyScreenBoundedMaxSize(to: window)
                 resizeAndCenter(window: window)
+                attachRefocusObservers(to: window)
             }
             controller.showWindow(nil)
             controller.window?.makeKeyAndOrderFront(nil)
@@ -40,6 +42,7 @@ final class WidgetsWindowManager {
         applyScreenBoundedMaxSize(to: window)
         window.standardWindowButton(.zoomButton)?.isHidden = true
         resizeAndCenter(window: window)
+        attachRefocusObservers(to: window)
 
         let controller = NSWindowController(window: window)
         self.windowController = controller
@@ -61,6 +64,34 @@ final class WidgetsWindowManager {
         window.setFrame(frame, display: true)
     }
 
+    private func attachRefocusObservers(to window: NSWindow) {
+        // Ensure we don't accumulate observers across repeated opens.
+        for token in windowNotificationObservers {
+            NotificationCenter.default.removeObserver(token)
+        }
+        windowNotificationObservers.removeAll()
+
+        let willClose = NotificationCenter.default.addObserver(
+            forName: NSWindow.willCloseNotification,
+            object: window,
+            queue: .main
+        ) { [weak self] _ in
+            // Drop the cached controller so a new instance can be created next open.
+            self?.windowController = nil
+            NotificationCenter.default.post(name: .refocusLauncherWindowRequested, object: nil)
+        }
+
+        let didMiniaturize = NotificationCenter.default.addObserver(
+            forName: NSWindow.didMiniaturizeNotification,
+            object: window,
+            queue: .main
+        ) { _ in
+            NotificationCenter.default.post(name: .refocusLauncherWindowRequested, object: nil)
+        }
+
+        windowNotificationObservers = [willClose, didMiniaturize]
+    }
+
     /// Keeps the Widgets window from growing past the visible screen when hosted SwiftUI reports a wide fitting size.
     private func applyScreenBoundedMaxSize(to window: NSWindow) {
         let screenFrame = (window.screen ?? NSScreen.main)?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1440, height: 900)