You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Musubi/Packages/MusubiKit/Sources/VideoOutput/OutputWindow.swift

89 lines
2.8 KiB

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
}
}

Powered by TurnKey Linux.