diff --git a/Sources/Pesty/AppController.swift b/Sources/Pesty/AppController.swift index ee649f5..66b38ef 100644 --- a/Sources/Pesty/AppController.swift +++ b/Sources/Pesty/AppController.swift @@ -13,6 +13,7 @@ final class AppController: NSObject, NSApplicationDelegate { private var statusItem: NSStatusItem? private var settingsWindow: NSWindow? private var keyMonitor: Any? + private var isReopenPresentationPending = false private(set) var previousApp: NSRunningApplication? private(set) var lastActiveApp: NSRunningApplication? @@ -43,12 +44,42 @@ final class AppController: NSObject, NSApplicationDelegate { return } + // Pesty's primary interface should be immediately discoverable after + // every normal launch, including the first launch after quitting. if !Settings.shared.onboarded { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) { [weak self] in - self?.showSettings() - } Settings.shared.onboarded = true } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + self?.showBar() + } + } + + func applicationShouldHandleReopen(_ sender: NSApplication, + hasVisibleWindows flag: Bool) -> Bool { + // Finder, Spotlight, and the Dock send a reopen event when the user + // invokes an app that is already running. The clipboard bar is an + // NSPanel, so AppKit's `hasVisibleWindows` value does not reliably + // describe whether Pesty already has a surface on screen. + // Settings and previews are secondary surfaces. Only an already + // visible Paste Bar should suppress a new presentation. + guard !isBarVisible else { return true } + guard !isReopenPresentationPending else { return false } + + isReopenPresentationPending = true + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.isReopenPresentationPending = false + + // A duplicate reopen event can arrive while AppKit finishes the + // first one. Avoid presenting the Paste Bar twice. + guard !self.isBarVisible else { return } + self.showBar() + } + return false + } + + private var isBarVisible: Bool { + barController?.window?.isVisible == true } @objc private func appActivated(_ note: Notification) { @@ -133,6 +164,10 @@ final class AppController: NSObject, NSApplicationDelegate { let front = NSWorkspace.shared.frontmostApplication if front?.bundleIdentifier != Bundle.main.bundleIdentifier { previousApp = front + } else if let lastActiveApp, !lastActiveApp.isTerminated { + // Reopen events arrive after Pesty becomes active, so retain the + // most recently active non-Pesty app as the eventual paste target. + previousApp = lastActiveApp } store.searchText = "" store.source = .history