| | |
| | | Settings { |
| | | PreferencesView() |
| | | } |
| | | .defaultSize(width: 880, height: 460) |
| | | .defaultSize(width: 1000, height: 480) |
| | | .commands { |
| | | CommandGroup(replacing: .systemServices) { } |
| | | CommandGroup(replacing: .appVisibility) { } |
| | |
| | | configureApplicationMenuWhenAvailable(retries: 12) |
| | | } |
| | | |
| | | /// Dock icon click → show overlay (same as menubar "Show TagLauncher") |
| | | /// Reopening the already-running app must not implicitly show App Grid. |
| | | /// App Grid is only opened by explicit launcher commands: main hotkey or status item/menu. |
| | | func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { |
| | | guard Date() >= suppressReopenUntil else { return false } |
| | | showOrFocusOverlay() |
| | | return false // Suppress default "unhide all windows" behavior |
| | | } |
| | | |
| | |
| | | |
| | | let shouldStayAccessoryForCurrentFullscreenSpace = isOverlayVisible |
| | | && (avoidSpaceSwitch || overlayAvoidsSpaceSwitch) |
| | | let shouldStayAccessoryForQuickOnlySearch = isOverlayVisible |
| | | && quickSearchOnlyOverlaySession |
| | | && !showDock |
| | | let desiredPolicy: NSApplication.ActivationPolicy = shouldStayAccessoryForCurrentFullscreenSpace |
| | | ? .accessory |
| | | : (shouldStayAccessoryForQuickOnlySearch ? .accessory |
| | | : (requiresForegroundOwnership |
| | | ? .regular |
| | | : (showDock ? .regular : .accessory)) |
| | | : (showDock ? .regular : .accessory))) |
| | | if NSApp.activationPolicy() != desiredPolicy { |
| | | NSApp.setActivationPolicy(desiredPolicy) |
| | | } |
| | |
| | | NSApp.presentationOptions = desiredPresentation |
| | | } |
| | | |
| | | if activate && requiresForegroundOwnership && !shouldStayAccessoryForCurrentFullscreenSpace { |
| | | if activate && requiresForegroundOwnership |
| | | && !shouldStayAccessoryForCurrentFullscreenSpace |
| | | && !shouldStayAccessoryForQuickOnlySearch { |
| | | let keyWindow = isSettingsVisible ? settingsWindow : (isOverlayVisible ? overlayWindow : nil) |
| | | claimLauncherForeground( |
| | | keyWindow: keyWindow, |
| | |
| | | |
| | | private func handleOverlayEscapeKey() -> Bool { |
| | | if isQuickSearchOpen || quickSearchOnlyOverlaySession { |
| | | let shouldHideOverlayAfterDismiss = quickSearchShouldHideOverlayOnClose || quickSearchOnlyOverlaySession |
| | | isQuickSearchOpen = false |
| | | removeQuickSearchExternalMouseMonitor() |
| | | updateOverlayLevelForTextInput() |
| | | NotificationCenter.default.post(name: .tagLauncherQuickSearchDismissRequested, object: nil) |
| | | if shouldHideOverlayAfterDismiss { |
| | | quickSearchShouldHideOverlayOnClose = false |
| | | quickSearchOnlyOverlaySession = false |
| | | DispatchQueue.main.async { [weak self] in |
| | | self?.hideOverlay(force: true) |
| | | } |
| | | } |
| | | dismissQuickSearchFromKeyboard() |
| | | return true |
| | | } |
| | | guard !isSettingsVisible, |
| | |
| | | else { return false } |
| | | hideOverlay(force: true) |
| | | return true |
| | | } |
| | | |
| | | private func dismissQuickSearchFromKeyboard() { |
| | | let shouldHideOverlayAfterDismiss = quickSearchShouldHideOverlayOnClose || quickSearchOnlyOverlaySession |
| | | isQuickSearchOpen = false |
| | | removeQuickSearchExternalMouseMonitor() |
| | | updateOverlayLevelForTextInput() |
| | | NotificationCenter.default.post(name: .tagLauncherQuickSearchDismissRequested, object: nil) |
| | | if shouldHideOverlayAfterDismiss { |
| | | quickSearchShouldHideOverlayOnClose = false |
| | | quickSearchOnlyOverlaySession = false |
| | | DispatchQueue.main.async { [weak self] in |
| | | self?.hideOverlay(force: true) |
| | | } |
| | | } |
| | | } |
| | | |
| | | private func requestQuickSearch(source: String) { |
| | |
| | | |
| | | private func showQuickSearchFromGlobalHotkey() { |
| | | let now = Date() |
| | | if overlayWindow?.isVisible == true, isQuickSearchOpen || quickSearchOnlyOverlaySession { |
| | | lastQuickSearchHotkeyAt = now |
| | | dismissQuickSearchFromKeyboard() |
| | | return |
| | | } |
| | | |
| | | if now.timeIntervalSince(lastQuickSearchHotkeyAt) < 0.28 { |
| | | promoteOverlayToForegroundInput() |
| | | updateOverlayLevelForTextInput() |
| | |
| | | } |
| | | lastQuickSearchHotkeyAt = now |
| | | |
| | | if overlayWindow?.isVisible == true, isQuickSearchOpen || quickSearchOnlyOverlaySession { |
| | | promoteOverlayToForegroundInput() |
| | | updateOverlayLevelForTextInput() |
| | | return |
| | | } |
| | | if overlayWindow?.isVisible == true { |
| | | refreshLauncherChromeState( |
| | | activate: !overlayAvoidsSpaceSwitch, |
| | |
| | | /// Settings must always appear centered over the current overlay view and float above it. |
| | | private func prepareSettingsWindow(_ window: NSWindow) { |
| | | dismissQuickSearchIfNeeded() |
| | | let settingsSize = NSSize(width: 880, height: 460) |
| | | let settingsSize = NSSize(width: 1000, height: 480) |
| | | window.identifier = NSUserInterfaceItemIdentifier("TagLauncherPreferencesWindow") |
| | | window.minSize = settingsSize |
| | | window.maxSize = settingsSize |
| | |
| | | overlayWindow.orderFrontRegardless() |
| | | return |
| | | } |
| | | if quickSearchOnlyOverlaySession && !UserDefaults.standard.bool(forKey: Self.showDockIconKey) { |
| | | refreshLauncherChromeState(activate: false) |
| | | guard let overlayWindow else { return } |
| | | NSApp.activate(ignoringOtherApps: true) |
| | | overlayWindow.makeKeyAndOrderFront(nil) |
| | | overlayWindow.orderFrontRegardless() |
| | | return |
| | | } |
| | | beginLauncherForegroundOwnership() |
| | | guard let overlayWindow else { return } |
| | | overlayWindow.makeKeyAndOrderFront(nil) |
| | |
| | | } |
| | | |
| | | @objc private func handleExternalActivationRequest(_ notification: Notification) { |
| | | let shouldShowOverlay = notification.userInfo?["showOverlay"] as? Bool ?? true |
| | | let shouldShowOverlay = notification.userInfo?["showOverlay"] as? Bool ?? false |
| | | guard shouldShowOverlay else { return } |
| | | showOrFocusOverlay() |
| | | } |
| | |
| | | return |
| | | } |
| | | |
| | | let settingsSize = NSSize(width: 880, height: 460) |
| | | let settingsSize = NSSize(width: 1000, height: 480) |
| | | let window = NSWindow( |
| | | contentRect: NSRect(origin: .zero, size: settingsSize), |
| | | styleMask: [.titled, .closable], |