Files
openclaw/apps/macos/Sources/OpenClaw/DockIconManager.swift
Peter Steinberger 44dd983c0c feat(mac): add canonical elevation host workflow (#123569)
* feat(mac): add canonical elevation host workflow

* fix(mac): clarify elevation host deployment

* test(ui): stabilize cloud startup effort flow

* fix(mac): fail closed on unknown TCC status

* test: synchronize async CI boundaries

* test: remove remaining CI timing races
2026-08-14 05:39:24 -07:00

129 lines
4.5 KiB
Swift

import AppKit
/// Central manager for Dock icon visibility.
/// Shows the Dock icon while any windows are visible, regardless of user preference.
final class DockIconManager: NSObject, @unchecked Sendable {
static let shared = DockIconManager()
private var windowsObservation: NSKeyValueObservation?
private let logger = Logger(subsystem: "ai.openclaw", category: "DockIconManager")
override private init() {
super.init()
self.setupObservers()
Task { @MainActor in
self.updateDockVisibility()
}
}
deinit {
self.windowsObservation?.invalidate()
NotificationCenter.default.removeObserver(self)
}
func updateDockVisibility() {
Task { @MainActor in
guard NSApp != nil else {
self.logger.warning("NSApp not ready, skipping Dock visibility update")
return
}
let userWantsDockHidden = (AppDefaults.standard.object(forKey: showDockIconKey) as? Bool) == false
let visibleWindows = NSApp?.windows.filter { window in
window.isVisible &&
window.frame.width > 1 &&
window.frame.height > 1 &&
!window.isKind(of: NSPanel.self) &&
"\(type(of: window))" != "NSPopupMenuWindow" &&
window.contentViewController != nil
} ?? []
let hasVisibleWindows = !visibleWindows.isEmpty
let policy = Self.activationPolicy(
launchPlan: .current,
userWantsDockHidden: userWantsDockHidden,
hasVisibleWindows: hasVisibleWindows)
guard NSApp.activationPolicy() != policy else { return }
NSApp.setActivationPolicy(policy)
}
}
func temporarilyShowDock() {
Task { @MainActor in
guard AppLaunchRuntimePlan.current.allowsDockIcon else { return }
guard NSApp != nil else {
self.logger.warning("NSApp not ready, cannot show Dock icon")
return
}
guard NSApp.activationPolicy() != .regular else { return }
NSApp.setActivationPolicy(.regular)
}
}
static func activationPolicy(
launchPlan: AppLaunchRuntimePlan,
userWantsDockHidden: Bool,
hasVisibleWindows: Bool) -> NSApplication.ActivationPolicy
{
guard launchPlan.allowsDockIcon else { return .accessory }
return !userWantsDockHidden || hasVisibleWindows ? .regular : .accessory
}
private func setupObservers() {
Task { @MainActor [self] in
guard let app = NSApp else {
self.logger.warning("NSApp not ready, delaying Dock observers")
try? await Task.sleep(for: .milliseconds(200))
self.setupObservers()
return
}
self.windowsObservation = app.observe(\.windows, options: [.new]) { [weak self] _, _ in
Task { @MainActor in
try? await Task.sleep(for: .milliseconds(50))
self?.updateDockVisibility()
}
}
NotificationCenter.default.addObserver(
self,
selector: #selector(self.windowVisibilityChanged),
name: NSWindow.didBecomeKeyNotification,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.windowVisibilityChanged),
name: NSWindow.didResignKeyNotification,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.windowVisibilityChanged),
name: NSWindow.willCloseNotification,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.dockPreferenceChanged),
name: UserDefaults.didChangeNotification,
object: nil)
}
}
@objc
private func windowVisibilityChanged(_: Notification) {
Task { @MainActor in
self.updateDockVisibility()
}
}
@objc
private func dockPreferenceChanged(_ notification: Notification) {
guard let userDefaults = notification.object as? UserDefaults,
userDefaults == AppDefaults.standard
else { return }
Task { @MainActor in
self.updateDockVisibility()
}
}
}