| | |
| | | private var lastShowDockIcon: Bool? |
| | | private var statusMenuScreenForNextOverlay: NSScreen? |
| | | private var overlayGeneration = 0 |
| | | private var overlayAvoidsSpaceSwitch = false |
| | | private var suppressReopenUntil = Date.distantPast |
| | | |
| | | private struct OverlayPlacementContext { |
| | |
| | | let showDock = UserDefaults.standard.bool(forKey: Self.showDockIconKey) |
| | | lastShowDockIcon = showDock |
| | | |
| | | let shouldStayAccessoryForCurrentFullscreenSpace = avoidSpaceSwitch |
| | | && isOverlayVisible |
| | | && !isSettingsVisible |
| | | let shouldStayAccessoryForCurrentFullscreenSpace = isOverlayVisible |
| | | && (avoidSpaceSwitch || overlayAvoidsSpaceSwitch) |
| | | let desiredPolicy: NSApplication.ActivationPolicy = shouldStayAccessoryForCurrentFullscreenSpace |
| | | ? .accessory |
| | | : (requiresForegroundOwnership |
| | |
| | | let placement = overlayPlacementContextForNextOverlay(preferredScreen: preferredScreen) |
| | | let shouldAvoidSpaceSwitch = placement.map { hasFullscreenWindowOnScreen($0.screen) } ?? false |
| | | if let overlayWindow, overlayWindow.isVisible { |
| | | overlayAvoidsSpaceSwitch = shouldAvoidSpaceSwitch |
| | | moveOverlayToCurrentPlacement(preferredScreen: preferredScreen) |
| | | overlayWindow.level = currentOverlayLevel |
| | | refreshLauncherChromeState(activate: !shouldAvoidSpaceSwitch, avoidSpaceSwitch: shouldAvoidSpaceSwitch) |
| | |
| | | existingWindow.orderOut(nil) |
| | | overlayWindow = nil |
| | | } |
| | | overlayAvoidsSpaceSwitch = shouldAvoidSpaceSwitch |
| | | |
| | | let window = makeOverlayWindow( |
| | | on: placement.screen, |
| | |
| | | for startIndex in tallWindows.indices { |
| | | var union = tallWindows[startIndex].frame |
| | | var lastMaxX = union.maxX |
| | | var distinctOwners = Swift.Set<String>() |
| | | distinctOwners.insert(tallWindows[startIndex].owner) |
| | | |
| | | for window in tallWindows.dropFirst(startIndex + 1) { |
| | | let gap = window.frame.minX - lastMaxX |
| | |
| | | } |
| | | union = union.union(window.frame) |
| | | lastMaxX = max(lastMaxX, window.frame.maxX) |
| | | distinctOwners.insert(window.owner) |
| | | |
| | | let touchesLeft = abs(union.minX - screenFrame.minX) <= 32 |
| | | let touchesRight = abs(union.maxX - screenFrame.maxX) <= 32 |
| | | let coversWidth = union.width >= screenFrame.width * 0.92 |
| | | let coversHeight = union.height >= screenFrame.height * 0.86 |
| | | if distinctOwners.count >= 2 && touchesLeft && touchesRight && coversWidth && coversHeight { |
| | | if touchesLeft && touchesRight && coversWidth && coversHeight { |
| | | return true |
| | | } |
| | | } |
| | |
| | | overlayWindow?.orderOut(nil) |
| | | removeOverlayKeyMonitor() |
| | | removeQuickSearchExternalMouseMonitor() |
| | | overlayAvoidsSpaceSwitch = false |
| | | refreshLauncherChromeState() |
| | | NotificationCenter.default.post(name: .tagLauncherOverlayDidHide, object: nil) |
| | | if discardWindow { |
| | |
| | | |
| | | private func showQuickSearchFromGlobalHotkey() { |
| | | if overlayWindow?.isVisible == true { |
| | | refreshLauncherChromeState(activate: true) |
| | | refreshLauncherChromeState( |
| | | activate: !overlayAvoidsSpaceSwitch, |
| | | avoidSpaceSwitch: overlayAvoidsSpaceSwitch |
| | | ) |
| | | requestQuickSearch(source: QuickSearchOpenSource.globalVisible) |
| | | return |
| | | } |
| | |
| | | |
| | | var behavior = window.collectionBehavior |
| | | behavior.remove(.canJoinAllSpaces) |
| | | behavior.formUnion([.fullScreenAuxiliary, .moveToActiveSpace]) |
| | | if overlayAvoidsSpaceSwitch { |
| | | behavior.remove(.moveToActiveSpace) |
| | | behavior.formUnion([.fullScreenAuxiliary, .stationary, .transient, .ignoresCycle]) |
| | | } else { |
| | | behavior.formUnion([.fullScreenAuxiliary, .moveToActiveSpace]) |
| | | } |
| | | window.collectionBehavior = behavior |
| | | window.makeKeyAndOrderFront(nil) |
| | | window.orderFrontRegardless() |
| | | settingsWindow = window |
| | | refreshLauncherChromeState(activate: true) |
| | | refreshLauncherChromeState( |
| | | activate: !overlayAvoidsSpaceSwitch, |
| | | avoidSpaceSwitch: overlayAvoidsSpaceSwitch |
| | | ) |
| | | } |
| | | |
| | | private func attachSettingsWindow(_ window: NSWindow, to overlayWindow: NSWindow) { |
| | |
| | | let preferredScreen = self.overlayWindow?.screen |
| | | self.detachSettingsWindow(closingWindow) |
| | | self.settingsWindow = nil |
| | | self.refreshLauncherChromeState(activate: shouldRefocusOverlay) |
| | | self.refreshLauncherChromeState( |
| | | activate: shouldRefocusOverlay && !self.overlayAvoidsSpaceSwitch, |
| | | avoidSpaceSwitch: self.overlayAvoidsSpaceSwitch |
| | | ) |
| | | guard shouldRefocusOverlay else { return } |
| | | DispatchQueue.main.asyncAfter(deadline: .now() + 0.12) { [weak self] in |
| | | self?.showOrFocusOverlay(preferredScreen: preferredScreen) |
| | |
| | | } |
| | | |
| | | private func promoteOverlayToForegroundInput() { |
| | | if overlayAvoidsSpaceSwitch { |
| | | refreshLauncherChromeState(activate: false, avoidSpaceSwitch: true) |
| | | guard let overlayWindow else { return } |
| | | overlayWindow.makeKeyAndOrderFront(nil) |
| | | overlayWindow.orderFrontRegardless() |
| | | return |
| | | } |
| | | beginLauncherForegroundOwnership() |
| | | guard let overlayWindow else { return } |
| | | overlayWindow.makeKeyAndOrderFront(nil) |
| | |
| | | |
| | | @objc private func openPreferences(_ sender: Any? = nil) { |
| | | TagDatabase.flushPendingCategorySchemeBackupBatch() |
| | | beginLauncherForegroundOwnership() |
| | | if overlayAvoidsSpaceSwitch { |
| | | refreshLauncherChromeState(activate: false, avoidSpaceSwitch: true) |
| | | } else { |
| | | beginLauncherForegroundOwnership() |
| | | } |
| | | // Don't hide overlay — keep it visible for real-time setting preview. |
| | | if let overlayWindow, overlayWindow.isVisible { |
| | | overlayWindow.makeKeyAndOrderFront(nil) |