import AppKit import AVFoundation /// A borderless window covering one screen, above the menu bar, that shows a video layer. @MainActor public final class OutputWindow: NSWindow { public let sink: VideoSink /// Called when the operator presses Esc on the output. public var onCancel: (() -> Void)? public init(screen: NSScreen) { let view = VideoView() sink = VideoSink(renderer: view.displayLayer.sampleBufferRenderer) super.init(contentRect: screen.frame, styleMask: .borderless, backing: .buffered, defer: false) contentView = view setFrame(screen.frame, display: false) level = .screenSaver collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle, .fullScreenNone] backgroundColor = .black isOpaque = true hasShadow = false isReleasedWhenClosed = false animationBehavior = .none } public override var canBecomeKey: Bool { true } public override func orderFrontRegardless() { super.orderFrontRegardless() (contentView as? VideoView)?.hideCursorIfInside() } public override func orderOut(_ sender: Any?) { (contentView as? VideoView)?.showCursor() super.orderOut(sender) } public override func cancelOperation(_ sender: Any?) { onCancel?() } } /// Layer-hosting view backed by `AVSampleBufferDisplayLayer`; hides the cursor while it is inside. @MainActor final class VideoView: NSView { let displayLayer = AVSampleBufferDisplayLayer() private var cursorHidden = false init() { super.init(frame: .zero) displayLayer.videoGravity = .resizeAspect displayLayer.backgroundColor = NSColor.black.cgColor layer = displayLayer wantsLayer = true } @available(*, unavailable) required init?(coder: NSCoder) { fatalError() } override func updateTrackingAreas() { super.updateTrackingAreas() trackingAreas.forEach(removeTrackingArea) addTrackingArea(NSTrackingArea(rect: .zero, options: [.mouseEnteredAndExited, .activeAlways, .inVisibleRect], owner: self)) } override func mouseEntered(with event: NSEvent) { hideCursor() } override func mouseExited(with event: NSEvent) { showCursor() } /// The cursor may already rest on the screen when the output opens; no enter event comes then. func hideCursorIfInside() { guard let window, window.frame.contains(NSEvent.mouseLocation) else { return } hideCursor() } // NSCursor.hide/unhide are counted, so keep them balanced. private func hideCursor() { guard !cursorHidden else { return } NSCursor.hide() cursorHidden = true } func showCursor() { guard cursorHidden else { return } NSCursor.unhide() cursorHidden = false } }