fix: show Dock icon during onboarding
This commit is contained in:
@@ -390,6 +390,7 @@ enum AppStateStore {
|
||||
@MainActor
|
||||
enum AppActivationPolicy {
|
||||
static func apply(showDockIcon: Bool) {
|
||||
NSApp.setActivationPolicy(showDockIcon ? .regular : .accessory)
|
||||
_ = showDockIcon
|
||||
DockIconManager.shared.updateDockVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
117
apps/macos/Sources/Clawdis/DockIconManager.swift
Normal file
117
apps/macos/Sources/Clawdis/DockIconManager.swift
Normal file
@@ -0,0 +1,117 @@
|
||||
import AppKit
|
||||
import OSLog
|
||||
|
||||
/// 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: "com.steipete.clawdis", category: "DockIconManager")
|
||||
|
||||
private override 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 = !UserDefaults.standard.bool(forKey: showDockIconKey)
|
||||
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
|
||||
if !userWantsDockHidden || hasVisibleWindows {
|
||||
NSApp?.setActivationPolicy(.regular)
|
||||
} else {
|
||||
NSApp?.setActivationPolicy(.accessory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func temporarilyShowDock() {
|
||||
Task { @MainActor in
|
||||
guard NSApp != nil else {
|
||||
self.logger.warning("NSApp not ready, cannot show Dock icon")
|
||||
return
|
||||
}
|
||||
NSApp.setActivationPolicy(.regular)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupObservers() {
|
||||
Task { @MainActor 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 == UserDefaults.standard
|
||||
else { return }
|
||||
|
||||
Task { @MainActor in
|
||||
self.updateDockVisibility()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ final class OnboardingController {
|
||||
|
||||
func show() {
|
||||
if let window {
|
||||
DockIconManager.shared.temporarilyShowDock()
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
return
|
||||
@@ -28,6 +29,7 @@ final class OnboardingController {
|
||||
window.titleVisibility = .hidden
|
||||
window.isMovableByWindowBackground = true
|
||||
window.center()
|
||||
DockIconManager.shared.temporarilyShowDock()
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
self.window = window
|
||||
@@ -630,7 +632,7 @@ struct OnboardingView: View {
|
||||
.font(.largeTitle.weight(.semibold))
|
||||
Text(
|
||||
"The Gateway is the WebSocket service that keeps Clawdis connected. " +
|
||||
"We’ll install/update the `clawdis` npm package and verify Node is available.")
|
||||
"Clawdis bundles it and runs it via launchd so it stays running.")
|
||||
.font(.body)
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
@@ -674,7 +676,7 @@ struct OnboardingView: View {
|
||||
if self.gatewayInstalling {
|
||||
ProgressView()
|
||||
} else {
|
||||
Text("Install or update gateway")
|
||||
Text("Enable Gateway daemon")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
@@ -692,7 +694,7 @@ struct OnboardingView: View {
|
||||
.lineLimit(2)
|
||||
} else {
|
||||
Text(
|
||||
"Runs `npm install -g clawdis@<version>` on your PATH. " +
|
||||
"Installs a per-user LaunchAgent (\(gatewayLaunchdLabel)). " +
|
||||
"The gateway listens on port 18789.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
@@ -1248,10 +1250,10 @@ struct OnboardingView: View {
|
||||
self.gatewayInstalling = true
|
||||
defer { self.gatewayInstalling = false }
|
||||
self.gatewayInstallMessage = nil
|
||||
let expected = GatewayEnvironment.expectedGatewayVersion()
|
||||
await GatewayEnvironment.installGlobal(version: expected) { message in
|
||||
Task { @MainActor in self.gatewayInstallMessage = message }
|
||||
}
|
||||
let port = GatewayEnvironment.gatewayPort()
|
||||
let bundlePath = Bundle.main.bundleURL.path
|
||||
let err = await GatewayLaunchAgentManager.set(enabled: true, bundlePath: bundlePath, port: port)
|
||||
self.gatewayInstallMessage = err ?? "Gateway enabled and started on port \(port)"
|
||||
self.refreshGatewayStatus()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user