diff --git a/Sources/bar/Models/AppDelegate.swift b/Sources/bar/Models/AppDelegate.swift index cefdfc0..5db4946 100644 --- a/Sources/bar/Models/AppDelegate.swift +++ b/Sources/bar/Models/AppDelegate.swift @@ -6,15 +6,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate { private var windows: [BarWindow] = [] private var blocks: [Block] = Config.blocks.map(\.resolved) private var tokens: [UnsafeMutableRawPointer] = [] + private var screenObserver: NSObjectProtocol? func applicationDidFinishLaunching(_ notification: Notification) { - for screen in NSScreen.screens { - let frame = Screen.barFrame(screen, position: Config.position, height: Config.height) - let window = BarWindow(contentRect: frame) - window.contentView = NSHostingView(rootView: BarView(blocks: blocks)) - window.orderFrontRegardless() - windows.append(window) - } + buildWindows() // Register a listener to hide the bar tokens.append( @@ -41,11 +36,24 @@ final class AppDelegate: NSObject, NSApplicationDelegate { // Start the block's auto refresh for block in blocks { block.start() } + + // Observe screen changes — recreate windows when displays + // are added or removed. + screenObserver = NotificationCenter.default.addObserver( + forName: NSApplication.didChangeScreenParametersNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.rebuildWindows() + } } func applicationWillTerminate(_ notification: Notification) { for block in blocks { block.stop() } for token in tokens { Notifier.cancel(token) } + if let observer = screenObserver { + NotificationCenter.default.removeObserver(observer) + } } /// Order out all bar windows @@ -68,6 +76,25 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } } + /// Create a bar window for each connected screen. + @MainActor private func buildWindows() { + for screen in NSScreen.screens { + let frame = Screen.barFrame(screen, position: Config.position, height: Config.height) + let window = BarWindow(contentRect: frame) + window.contentView = NSHostingView(rootView: BarView(blocks: blocks)) + window.orderFrontRegardless() + windows.append(window) + } + } + + /// Remove all existing windows and create new ones for the current + /// set of screens — called when a display is connected or disconnected. + @MainActor private func rebuildWindows() { + for window in windows { window.orderOut(nil) } + windows.removeAll() + buildWindows() + } + /// Called from a Darwin notification handler. Marked `nonisolated` because /// libnotify's delivery contract isn't statically visible to Swift, so we /// hop back to the main actor explicitly.