| | |
| | | Settings { |
| | | PreferencesView() |
| | | } |
| | | .defaultSize(width: 880, height: 460) |
| | | .defaultSize(width: 1000, height: 480) |
| | | .commands { |
| | | CommandGroup(replacing: .systemServices) { } |
| | | CommandGroup(replacing: .appVisibility) { } |
| | |
| | | private var quickSearchHotkeyRef: EventHotKeyRef? |
| | | private var hotkeyEventHandlerInstalled = false |
| | | private var isQuickSearchOpen = false |
| | | private var quickSearchShouldHideOverlayOnClose = false |
| | | private var quickSearchOnlyOverlaySession = false |
| | | private var lastQuickSearchHotkeyAt = Date.distantPast |
| | | private var isModalInteractionActive = false |
| | | private var isInEditMode = false // Suppress auto-dismiss during editing |
| | | private var isEditingAppNote = false |
| | |
| | | }, |
| | | removeQuickSearchMouseMonitor: { [weak self] in |
| | | self?.removeQuickSearchExternalMouseMonitor() |
| | | self?.quickSearchShouldHideOverlayOnClose = false |
| | | self?.quickSearchOnlyOverlaySession = false |
| | | }, |
| | | detachSettingsWindow: { [weak self] window in |
| | | self?.detachSettingsWindow(window) |
| | |
| | | |
| | | 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, |
| | |
| | | preferredScreen: NSScreen? = nil, |
| | | stagedForAllSpaces: Bool = false |
| | | ) { |
| | | if initialQuickSearchSource == QuickSearchOpenSource.globalHidden { |
| | | isQuickSearchOpen = true |
| | | quickSearchShouldHideOverlayOnClose = true |
| | | quickSearchOnlyOverlaySession = true |
| | | } |
| | | overlayController.show( |
| | | initialQuickSearchSource: initialQuickSearchSource, |
| | | preferredScreen: preferredScreen, |
| | |
| | | |
| | | @discardableResult |
| | | private func handleOverlayKeyEvent(_ event: NSEvent) -> Bool { |
| | | guard event.type == .keyDown, |
| | | shouldHandleOverlayKeyEvent(event) |
| | | else { return false } |
| | | guard event.type == .keyDown else { return false } |
| | | |
| | | if event.keyCode == UInt16(kVK_Escape), |
| | | isQuickSearchOpen || quickSearchOnlyOverlaySession { |
| | | return handleOverlayEscapeKey() |
| | | } |
| | | |
| | | guard shouldHandleOverlayKeyEvent(event) else { return false } |
| | | |
| | | if event.keyCode == UInt16(kVK_Escape) { |
| | | return handleOverlayEscapeKey() |
| | |
| | | } |
| | | |
| | | private func handleOverlayEscapeKey() -> Bool { |
| | | if isQuickSearchOpen { |
| | | isQuickSearchOpen = false |
| | | removeQuickSearchExternalMouseMonitor() |
| | | updateOverlayLevelForTextInput() |
| | | NotificationCenter.default.post(name: .tagLauncherQuickSearchDismissRequested, object: nil) |
| | | if isQuickSearchOpen || quickSearchOnlyOverlaySession { |
| | | dismissQuickSearchFromKeyboard() |
| | | return true |
| | | } |
| | | guard !isSettingsVisible, |
| | |
| | | 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) { |
| | | isQuickSearchOpen = true |
| | | quickSearchShouldHideOverlayOnClose = quickSearchShouldHideOverlayOnClose |
| | | || source == QuickSearchOpenSource.globalHidden |
| | | quickSearchOnlyOverlaySession = quickSearchOnlyOverlaySession |
| | | || source == QuickSearchOpenSource.globalHidden |
| | | promoteOverlayToForegroundInput() |
| | | updateOverlayLevelForTextInput() |
| | | installQuickSearchExternalMouseMonitor() |
| | |
| | | } |
| | | |
| | | private func showQuickSearchFromGlobalHotkey() { |
| | | let now = Date() |
| | | if overlayWindow?.isVisible == true, isQuickSearchOpen || quickSearchOnlyOverlaySession { |
| | | lastQuickSearchHotkeyAt = now |
| | | dismissQuickSearchFromKeyboard() |
| | | return |
| | | } |
| | | |
| | | if now.timeIntervalSince(lastQuickSearchHotkeyAt) < 0.28 { |
| | | promoteOverlayToForegroundInput() |
| | | updateOverlayLevelForTextInput() |
| | | return |
| | | } |
| | | lastQuickSearchHotkeyAt = now |
| | | |
| | | if overlayWindow?.isVisible == true { |
| | | refreshLauncherChromeState( |
| | | activate: !overlayAvoidsSpaceSwitch, |
| | |
| | | requestQuickSearch(source: QuickSearchOpenSource.globalVisible) |
| | | return |
| | | } |
| | | isQuickSearchOpen = true |
| | | quickSearchShouldHideOverlayOnClose = true |
| | | quickSearchOnlyOverlaySession = true |
| | | showOverlay(initialQuickSearchSource: QuickSearchOpenSource.globalHidden) |
| | | } |
| | | |
| | |
| | | /// 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) |
| | |
| | | queue: .main |
| | | ) { [weak self] notification in |
| | | guard let self else { return } |
| | | self.isQuickSearchOpen = (notification.userInfo?["active"] as? Bool) ?? false |
| | | let active = (notification.userInfo?["active"] as? Bool) ?? false |
| | | let hideOverlayOnClose = (notification.userInfo?["hideOverlayOnClose"] as? Bool) ?? false |
| | | self.isQuickSearchOpen = active |
| | | if active, hideOverlayOnClose { |
| | | self.quickSearchShouldHideOverlayOnClose = true |
| | | self.quickSearchOnlyOverlaySession = true |
| | | } |
| | | if self.isQuickSearchOpen { |
| | | self.promoteOverlayToForegroundInput() |
| | | } |
| | |
| | | self.installQuickSearchExternalMouseMonitor() |
| | | } else { |
| | | self.removeQuickSearchExternalMouseMonitor() |
| | | if hideOverlayOnClose || self.quickSearchShouldHideOverlayOnClose { |
| | | self.quickSearchShouldHideOverlayOnClose = false |
| | | self.quickSearchOnlyOverlaySession = false |
| | | self.hideOverlay(force: true) |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | 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], |
| | |
| | | isQuickSearchOpen = false |
| | | removeQuickSearchExternalMouseMonitor() |
| | | updateOverlayLevelForTextInput() |
| | | quickSearchShouldHideOverlayOnClose = false |
| | | quickSearchOnlyOverlaySession = false |
| | | NotificationCenter.default.post(name: .tagLauncherQuickSearchDismissRequested, object: nil) |
| | | } |
| | | |