import AppKit import SwiftUI /// Borderless, floating NSPanel that hosts the notch overlay. /// When the notch is open the window accepts key status so the /// terminal can receive keyboard input. On resignKey the /// `onResignKey` closure fires to close the notch. class NotchWindow: NSPanel { var isNotchOpen: Bool = false /// Called when the window loses key status while the notch is open. var onResignKey: (() -> Void)? override init( contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool ) { // Start as a plain borderless utility panel. // .nonactivatingPanel is NOT included so the window can // properly accept key status when the notch opens. super.init( contentRect: contentRect, styleMask: [.borderless, .utilityWindow, .nonactivatingPanel], backing: .buffered, defer: flag ) configureWindow() } private func configureWindow() { isOpaque = false backgroundColor = .clear isFloatingPanel = true level = .mainMenu + 3 titleVisibility = .hidden titlebarAppearsTransparent = true hasShadow = false isMovable = false isMovableByWindowBackground = false collectionBehavior = [ .canJoinAllSpaces, .stationary, .fullScreenAuxiliary, .ignoresCycle ] appearance = NSAppearance(named: .darkAqua) // Accepts mouse events when the app is NOT active so the // user can click the closed notch to open it. acceptsMouseMovedEvents = true } // MARK: - Key window management override var canBecomeKey: Bool { isNotchOpen } override var canBecomeMain: Bool { false } override func resignKey() { super.resignKey() if isNotchOpen { // Brief async dispatch so the new key window settles first — // avoids closing when we're just transferring focus between // our own windows (e.g. opening settings). DispatchQueue.main.async { [weak self] in guard let self, self.isNotchOpen else { return } self.onResignKey?() } } } }