| | |
| | | private var lastShowDockIcon: Bool? |
| | | private var statusMenuScreenForNextOverlay: NSScreen? |
| | | private var suppressReopenUntil = Date.distantPast |
| | | private var overlayOpenedByQuickSearchOnly = false |
| | | |
| | | private lazy var overlayController = OverlayWindowController( |
| | | dependencies: OverlayWindowController.Dependencies( |
| | |
| | | self?.removeOverlayKeyMonitor() |
| | | }, |
| | | removeQuickSearchMouseMonitor: { [weak self] in |
| | | Diagnostics.log("app.quickSearch.removeMouseMonitor", [ |
| | | "quickSearchOpen": self?.isQuickSearchOpen, |
| | | "quickSearchOnlyOverlaySession": self?.quickSearchOnlyOverlaySession, |
| | | "quickSearchShouldHideOverlayOnClose": self?.quickSearchShouldHideOverlayOnClose |
| | | ]) |
| | | self?.removeQuickSearchExternalMouseMonitor() |
| | | self?.quickSearchShouldHideOverlayOnClose = false |
| | | self?.quickSearchOnlyOverlaySession = false |
| | |
| | | onWillHide: { |
| | | TagDatabase.flushPendingCategorySchemeBackupBatch() |
| | | }, |
| | | onDidHide: { |
| | | onDidHide: { [weak self] in |
| | | self?.overlayOpenedByQuickSearchOnly = false |
| | | NotificationCenter.default.post(name: .tagLauncherOverlayDidHide, object: nil) |
| | | }, |
| | | onDidShow: { |
| | |
| | | return false |
| | | } |
| | | guard UserDefaults.standard.bool(forKey: Self.showDockIconKey) else { return false } |
| | | guard isPointerNearDockArea() else { return false } |
| | | guard isRecentUserClickNearDockArea() else { return false } |
| | | showOrFocusOverlay() |
| | | return false // Suppress default "unhide all windows" behavior |
| | | } |
| | | |
| | | private func isRecentUserClickNearDockArea() -> Bool { |
| | | let lastMouseDown = CGEventSource.secondsSinceLastEventType( |
| | | .combinedSessionState, |
| | | eventType: .leftMouseDown |
| | | ) |
| | | let lastMouseUp = CGEventSource.secondsSinceLastEventType( |
| | | .combinedSessionState, |
| | | eventType: .leftMouseUp |
| | | ) |
| | | let recentMouseClick = min(lastMouseDown, lastMouseUp) < 1.2 |
| | | return recentMouseClick && isPointerNearDockArea() |
| | | } |
| | | |
| | | private func isPointerNearDockArea() -> Bool { |
| | |
| | | } |
| | | |
| | | private func performToggleOverlay(preferredScreen: NSScreen?) { |
| | | overlayOpenedByQuickSearchOnly = false |
| | | overlayController.toggle(preferredScreen: preferredScreen) |
| | | } |
| | | |
| | | private func showOrFocusOverlay(preferredScreen: NSScreen? = nil) { |
| | | overlayOpenedByQuickSearchOnly = false |
| | | overlayController.showOrFocus(preferredScreen: preferredScreen) |
| | | } |
| | | |
| | |
| | | preferredScreen: NSScreen? = nil, |
| | | stagedForAllSpaces: Bool = false |
| | | ) { |
| | | Diagnostics.log("app.overlay.showRequest", [ |
| | | "initialQuickSearchSource": initialQuickSearchSource, |
| | | "stagedForAllSpaces": stagedForAllSpaces, |
| | | "isQuickSearchOpen": isQuickSearchOpen, |
| | | "quickSearchOnlyOverlaySession": quickSearchOnlyOverlaySession, |
| | | "overlayOpenedByQuickSearchOnly": overlayOpenedByQuickSearchOnly |
| | | ]) |
| | | if initialQuickSearchSource == QuickSearchOpenSource.globalHidden { |
| | | isQuickSearchOpen = true |
| | | quickSearchShouldHideOverlayOnClose = true |
| | | quickSearchOnlyOverlaySession = true |
| | | overlayOpenedByQuickSearchOnly = true |
| | | } else if initialQuickSearchSource == nil { |
| | | overlayOpenedByQuickSearchOnly = false |
| | | } |
| | | overlayController.show( |
| | | initialQuickSearchSource: initialQuickSearchSource, |
| | |
| | | if shouldHideOverlayAfterDismiss { |
| | | quickSearchShouldHideOverlayOnClose = false |
| | | quickSearchOnlyOverlaySession = false |
| | | overlayOpenedByQuickSearchOnly = true |
| | | DispatchQueue.main.async { [weak self] in |
| | | self?.hideOverlay(force: true) |
| | | self?.hideOverlay(force: true, discardWindow: true) |
| | | } |
| | | } |
| | | } |
| | |
| | | || source == QuickSearchOpenSource.globalHidden |
| | | quickSearchOnlyOverlaySession = quickSearchOnlyOverlaySession |
| | | || source == QuickSearchOpenSource.globalHidden |
| | | if source == QuickSearchOpenSource.globalHidden { |
| | | overlayOpenedByQuickSearchOnly = true |
| | | } |
| | | promoteOverlayToForegroundInput() |
| | | updateOverlayLevelForTextInput() |
| | | installQuickSearchExternalMouseMonitor() |
| | |
| | | } |
| | | |
| | | private func hideOverlay(force: Bool = false, discardWindow: Bool = false) { |
| | | Diagnostics.log("app.overlay.hideRequest", [ |
| | | "force": force, |
| | | "discardWindow": discardWindow, |
| | | "isQuickSearchOpen": isQuickSearchOpen, |
| | | "quickSearchOnlyOverlaySession": quickSearchOnlyOverlaySession, |
| | | "quickSearchShouldHideOverlayOnClose": quickSearchShouldHideOverlayOnClose, |
| | | "overlayOpenedByQuickSearchOnly": overlayOpenedByQuickSearchOnly |
| | | ]) |
| | | overlayController.hide(force: force, discardWindow: discardWindow) |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | private func handleHotkeyEvent(id: UInt32) { |
| | | Diagnostics.log("app.hotkey", [ |
| | | "id": id, |
| | | "isOverlayVisible": isOverlayVisible, |
| | | "isQuickSearchOpen": isQuickSearchOpen, |
| | | "quickSearchOnlyOverlaySession": quickSearchOnlyOverlaySession, |
| | | "overlayOpenedByQuickSearchOnly": overlayOpenedByQuickSearchOnly |
| | | ]) |
| | | if id == LauncherHotkeyKind.quickSearch.eventID { |
| | | showQuickSearchFromGlobalHotkey() |
| | | } else { |
| | |
| | | |
| | | private func showQuickSearchFromGlobalHotkey() { |
| | | let now = Date() |
| | | if overlayWindow?.isVisible == true, isQuickSearchOpen || quickSearchOnlyOverlaySession { |
| | | if overlayWindow?.isVisible == true, |
| | | isQuickSearchOpen || quickSearchOnlyOverlaySession || overlayOpenedByQuickSearchOnly { |
| | | lastQuickSearchHotkeyAt = now |
| | | dismissQuickSearchFromKeyboard() |
| | | if isQuickSearchOpen || quickSearchOnlyOverlaySession { |
| | | dismissQuickSearchFromKeyboard() |
| | | } else { |
| | | hideOverlay(force: true) |
| | | } |
| | | return |
| | | } |
| | | |
| | |
| | | guard let self else { return } |
| | | let active = (notification.userInfo?["active"] as? Bool) ?? false |
| | | let hideOverlayOnClose = (notification.userInfo?["hideOverlayOnClose"] as? Bool) ?? false |
| | | let wasQuickSearchOnlyOverlay = self.quickSearchOnlyOverlaySession |
| | | || self.quickSearchShouldHideOverlayOnClose |
| | | || self.overlayOpenedByQuickSearchOnly |
| | | Diagnostics.log("app.quickSearch.visibilityChanged", [ |
| | | "active": active, |
| | | "hideOverlayOnClose": hideOverlayOnClose, |
| | | "isQuickSearchOpenBefore": self.isQuickSearchOpen, |
| | | "quickSearchOnlyOverlaySessionBefore": self.quickSearchOnlyOverlaySession, |
| | | "quickSearchShouldHideOverlayOnCloseBefore": self.quickSearchShouldHideOverlayOnClose, |
| | | "overlayOpenedByQuickSearchOnlyBefore": self.overlayOpenedByQuickSearchOnly, |
| | | "wasQuickSearchOnlyOverlay": wasQuickSearchOnlyOverlay |
| | | ]) |
| | | self.isQuickSearchOpen = active |
| | | if active, hideOverlayOnClose { |
| | | self.quickSearchShouldHideOverlayOnClose = true |
| | | self.quickSearchOnlyOverlaySession = true |
| | | self.overlayOpenedByQuickSearchOnly = true |
| | | } |
| | | if self.isQuickSearchOpen { |
| | | self.promoteOverlayToForegroundInput() |
| | |
| | | self.installQuickSearchExternalMouseMonitor() |
| | | } else { |
| | | self.removeQuickSearchExternalMouseMonitor() |
| | | if hideOverlayOnClose || self.quickSearchShouldHideOverlayOnClose { |
| | | if hideOverlayOnClose || wasQuickSearchOnlyOverlay { |
| | | self.quickSearchShouldHideOverlayOnClose = false |
| | | self.quickSearchOnlyOverlaySession = false |
| | | self.hideOverlay(force: true) |
| | | self.hideOverlay(force: true, discardWindow: wasQuickSearchOnlyOverlay) |
| | | } |
| | | } |
| | | } |