import AppKit import CoreFoundation import Foundation @main @MainActor struct CompactWindowChecks { struct Failure: Error, CustomStringConvertible { let description: String } static func require(_ condition: @autoclosure () -> Bool, _ message: String) throws { if !condition() { throw Failure(description: "FAIL: \(message)") } } static func close(_ actual: CGFloat, _ expected: CGFloat) -> Bool { abs(actual - expected) < 0.001 } static func sameFrame(_ actual: NSRect, _ expected: NSRect) -> Bool { close(actual.minX, expected.minX) && close(actual.minY, expected.minY) && close(actual.width, expected.width) && close(actual.height, expected.height) } static func geometry() throws { let screen = NSRect(x: 100, y: 200, width: 1_000, height: 700) func snap(_ x: CGFloat, _ y: CGFloat, to expectedX: CGFloat, _ expectedY: CGFloat) throws { let frame = NSRect(x: x, y: y, width: 320, height: 180) let expected = NSRect(x: expectedX, y: expectedY, width: 320, height: 180) try require(sameFrame(CompactWindowController.snappedFrame(frame, visibleFrame: screen), expected), "snap (\(x), \(y)) to (\(expectedX), \(expectedY)), preserving size") } // Inset edges: x = 116 / 764, y = 216 / 704. Each axis acts independently. try snap(130, 460, to: 116, 460) try snap(750, 460, to: 764, 460) try snap(440, 230, to: 440, 216) try snap(440, 690, to: 440, 704) try snap(130, 230, to: 116, 216) try snap(750, 230, to: 764, 216) try snap(130, 690, to: 116, 704) try snap(750, 690, to: 764, 704) try snap(140, 460, to: 116, 460) try snap(740, 460, to: 764, 460) try snap(440, 240, to: 440, 216) try snap(440, 680, to: 440, 704) try snap(92, 460, to: 116, 460) try snap(788, 460, to: 764, 460) try snap(141, 460, to: 141, 460) try snap(739, 460, to: 739, 460) try snap(440, 241, to: 440, 241) try snap(440, 679, to: 440, 679) try snap(440, 192, to: 440, 216) try snap(440, 728, to: 440, 704) try snap(91, 460, to: 91, 460) try snap(789, 460, to: 789, 460) try snap(440, 191, to: 440, 191) try snap(440, 729, to: 440, 729) try snap(440, 460, to: 440, 460) let negativeScreen = NSRect(x: -1_440, y: -900, width: 1_440, height: 900) let negativeFrame = NSRect(x: -1_410, y: -870, width: 320, height: 180) try require(sameFrame(CompactWindowController.snappedFrame(negativeFrame, visibleFrame: negativeScreen), NSRect(x: -1_424, y: -884, width: 320, height: 180)), "negative-origin screen uses its own inset edges") let narrowScreen = NSRect(x: 0, y: 0, width: 100, height: 200) try require(sameFrame(CompactWindowController.snappedFrame( NSRect(x: 35, y: 80, width: 40, height: 20), visibleFrame: narrowScreen), NSRect(x: 44, y: 80, width: 40, height: 20)), "overlapping magnetic zones choose the closest edge, not first edge") let usableScreen = NSRect(x: -1_200, y: 50, width: 1_200, height: 800) let landscape = CompactWindowController.initialFrame(aspect: 16.0 / 9.0, visibleFrame: usableScreen, width: 320) try require(sameFrame(landscape, NSRect(x: -336, y: 654, width: 320, height: 180)), "16:9 enters at top right with 320pt width and 16pt inset") let portrait = CompactWindowController.initialFrame(aspect: 9.0 / 16.0, visibleFrame: usableScreen, width: 320) try require(sameFrame(portrait, NSRect(x: -336, y: 834 - 5_120.0 / 9.0, width: 320, height: 5_120.0 / 9.0)), "portrait preserves exact aspect when it fits") for aspect in [16.0 / 9.0, 9.0 / 16.0, 100.0, 0.01] { let frame = CompactWindowController.initialFrame(aspect: aspect, visibleFrame: usableScreen, width: 320) try require(frame.origin.x.isFinite && frame.origin.y.isFinite && frame.width.isFinite && frame.height.isFinite, "initial geometry is finite for aspect \(aspect)") try require(frame.width >= 160 && frame.height >= 90, "extreme aspect \(aspect) never defeats minimum size") try require(frame.minX >= usableScreen.minX + 16 && frame.minY >= usableScreen.minY + 16 && frame.maxX <= usableScreen.maxX - 16 && frame.maxY <= usableScreen.maxY - 16, "aspect \(aspect) stays inside inset usable screen") try require(close(frame.maxX, usableScreen.maxX - 16) && close(frame.maxY, usableScreen.maxY - 16), "aspect \(aspect) stays anchored top right") } let smallScreen = NSRect(x: 0, y: 0, width: 300, height: 400) let bounded = CompactWindowController.initialFrame(aspect: 16.0 / 9.0, visibleFrame: smallScreen, width: 320) try require(sameFrame(bounded, NSRect(x: 16, y: 233.25, width: 268, height: 150.75)), "target width shrinks to usable bounds without distorting ordinary aspect") } // Run the real AppKit completion callbacks. The monotonic deadline is only a hang watchdog. static func settled(_ controller: CompactWindowController, compact: Bool, checking: () throws -> Void = {}) throws { let deadline = ContinuousClock.now.advanced(by: .seconds(5)) while controller.isTransitioning { try checking() try require(ContinuousClock.now < deadline, "native compact transition completes") CFRunLoopRunInMode(.defaultMode, 0.01, false) } try checking() try require(controller.isCompact == compact, "settled mode matches the latest destination") } @MainActor struct WindowState { let frame: NSRect let minSize: NSSize let contentMinSize: NSSize let aspect: NSSize let style: NSWindow.StyleMask let titleVisibility: NSWindow.TitleVisibility let transparentTitlebar: Bool let collection: NSWindow.CollectionBehavior let level: NSWindow.Level let hides: Bool let drag: Bool init(_ window: NSWindow) { frame = window.frame minSize = window.minSize contentMinSize = window.contentMinSize aspect = window.aspectRatio style = window.styleMask titleVisibility = window.titleVisibility transparentTitlebar = window.titlebarAppearsTransparent collection = window.collectionBehavior level = window.level hides = window.hidesOnDeactivate drag = window.isMovableByWindowBackground } func requireMatches(_ window: NSWindow) throws { try CompactWindowChecks.require(CompactWindowChecks.sameFrame(window.frame, frame), "restore recovers original normal geometry") try CompactWindowChecks.require(window.minSize == minSize && window.contentMinSize == contentMinSize && window.aspectRatio == aspect, "restore recovers aspect and both minima") try CompactWindowChecks.require(window.styleMask == style && window.collectionBehavior == collection && window.level == level, "restore recovers style, Spaces policy, and level") try CompactWindowChecks.require(window.titleVisibility == titleVisibility && window.titlebarAppearsTransparent == transparentTitlebar, "restore recovers original native chrome") try CompactWindowChecks.require(window.hidesOnDeactivate == hides && window.isMovableByWindowBackground == drag, "restore recovers deactivate and background drag behavior") } } @MainActor final class WindowFixture { let window: NSWindow let content: NSView let sentinel: NSView let sentinelFrame: NSRect let original: WindowState let identity: ObjectIdentifier init() throws { // Never orderFront, makeKey, or create an actual native fullscreen Space. window = NSWindow(contentRect: NSRect(x: -10_000, y: -10_000, width: 640, height: 360), styleMask: [.titled, .closable, .miniaturizable, .resizable], backing: .buffered, defer: false) window.isReleasedWhenClosed = false guard let content = window.contentView else { throw Failure(description: "FAIL: missing content view") } self.content = content sentinel = NSView(frame: NSRect(x: 5, y: 7, width: 12, height: 14)) content.addSubview(sentinel) sentinelFrame = sentinel.frame window.minSize = NSSize(width: 300, height: 180) window.contentMinSize = NSSize(width: 220, height: 120) window.aspectRatio = NSSize(width: 4, height: 3) window.collectionBehavior = [.managed, .fullScreenPrimary] window.level = .normal window.hidesOnDeactivate = true window.isMovableByWindowBackground = false original = WindowState(window) identity = ObjectIdentifier(window) } func preservedContent() throws { try CompactWindowChecks.require(ObjectIdentifier(window) == identity && sentinel.window === window, "compact mode keeps the actual rendering window") try CompactWindowChecks.require(window.contentView === content && sentinel.superview === content && content.subviews.count == 1 && CompactWindowChecks.sameFrame(sentinel.frame, sentinelFrame), "compact mode preserves content and attached sentinel without reparenting") try CompactWindowChecks.require(!window.isVisible && !window.isKeyWindow, "checks never display or key a window") } func restored() throws { try original.requireMatches(window) try preservedContent() } func compact(aspect: Double) throws { try CompactWindowChecks.require(window.level == .floating && !window.hidesOnDeactivate, "compact floats and remains visible on deactivate") try CompactWindowChecks.require(window.collectionBehavior.contains( [.canJoinAllSpaces, .canJoinAllApplications, .fullScreenAuxiliary]), "compact can join Spaces, other-app fullscreen, and Stage Manager") try CompactWindowChecks.require(window.collectionBehavior.intersection( [.primary, .auxiliary, .fullScreenPrimary, .fullScreenNone]).isEmpty, "compact removes incompatible primary and auxiliary policies") try CompactWindowChecks.require(window.contentMinSize == NSSize(width: 160, height: 90), "compact uses 160x90 content minimum") try CompactWindowChecks.require(window.isMovableByWindowBackground, "compact allows background dragging") try CompactWindowChecks.require(window.canBecomeKey, "compact accepts keyboard playback controls") try CompactWindowChecks.require(window.titleVisibility == .hidden && window.titlebarAppearsTransparent, "compact hides native title chrome") let visible = (window.screen ?? NSScreen.main!).visibleFrame try CompactWindowChecks.require(CompactWindowChecks.close(window.frame.maxX, visible.maxX - 16) && CompactWindowChecks.close(window.frame.maxY, visible.maxY - 16) && CompactWindowChecks.close(window.frame.width / window.frame.height, CGFloat(aspect)), "compact enters at inset top right with playback aspect") try preservedContent() } } // A later real native round-trip gives canceled animations a completion opportunity without // guessing elapsed animation time. Watch the interrupted window throughout and after it. static func nativeCompletionOpportunity(checking: () throws -> Void) throws { let fixture = try WindowFixture() let controller = CompactWindowController(reduceMotion: { false }) defer { controller.restore(resumeFullScreen: false, animated: false) fixture.window.close() } controller.attach(fixture.window) controller.toggle(aspect: 16.0 / 9.0) try require(controller.isTransitioning, "native completion barrier is an actual animated entry") try settled(controller, compact: true, checking: checking) try fixture.compact(aspect: 16.0 / 9.0) controller.restore(resumeFullScreen: false) try settled(controller, compact: false, checking: checking) try fixture.restored() } static func windowBehavior() throws { _ = NSApplication.shared guard NSScreen.main != nil else { throw Failure(description: "FAIL: native window checks require a macOS GUI session with a screen") } let fixture = try WindowFixture() let window = fixture.window let controller = CompactWindowController(reduceMotion: { false }) defer { controller.restore(resumeFullScreen: false, animated: false) window.close() } controller.attach(window) try require(!controller.isCompact && !controller.isTransitioning, "attached window starts ordinary") try require(controller.snappingEnabled, "isolated controller defaults snapping on") controller.toggle(aspect: 16.0 / 9.0) try require(controller.isCompact && controller.isTransitioning, "entry presents the compact destination while native geometry settles") try fixture.preservedContent() try settled(controller, compact: true) try fixture.compact(aspect: 16.0 / 9.0) let visible = (window.screen ?? NSScreen.main!).visibleFrame let nearEdge = NSRect(x: visible.minX + 26, y: visible.minY + 27, width: window.frame.width, height: window.frame.height) window.setFrame(nearEdge, display: false) try require(sameFrame(window.frame, nearEdge), "native move installs unsnapped drag position") controller.snapAfterDrag() try require(close(window.frame.minX, visible.minX + 16) && close(window.frame.minY, visible.minY + 16) && window.frame.size == nearEdge.size, "enabled preference snaps the actual moved window") controller.snappingEnabled = false let second = CompactWindowController(snappingEnabled: false, reduceMotion: { false }) try require(!second.snappingEnabled, "configuration can disable snapping for a new controller") window.setFrame(nearEdge, display: false) controller.snapAfterDrag() try require(sameFrame(window.frame, nearEdge), "disabled preference leaves an actual moved window untouched") second.snappingEnabled = true let third = CompactWindowController(snappingEnabled: false, reduceMotion: { false }) try require(!third.snappingEnabled, "a session override does not change the next controller's settings") controller.snappingEnabled = true let resized = NSRect(x: nearEdge.minX, y: nearEdge.minY, width: 240, height: 135) window.setFrame(resized, display: false) controller.updateAspect(16.0 / 9.0) try require(sameFrame(window.frame, resized), "repeated aspect updates preserve a user's compact resize and placement") controller.updateAspect(4.0 / 3.0) try require(controller.isCompact && !controller.isTransitioning, "aspect update retains compact mode") try require(close(window.frame.width / window.frame.height, 4.0 / 3.0), "actual compact window follows changed playback aspect") try fixture.preservedContent() controller.toggle(aspect: 4.0 / 3.0) try settled(controller, compact: false) try fixture.restored() // Enter -> exit before entry completes: the original normal snapshot must survive. controller.toggle(aspect: 16.0 / 9.0) try require(controller.isTransitioning, "entry is in flight before its destination changes") controller.toggle(aspect: 16.0 / 9.0) try settled(controller, compact: false) try fixture.restored() // Exit -> enter keeps the newest aspect and must not save an intermediate restore frame. controller.toggle(aspect: 16.0 / 9.0) try settled(controller, compact: true) controller.toggle(aspect: 16.0 / 9.0) try require(!controller.isCompact && controller.isTransitioning, "exit presents the ordinary destination while native geometry settles") controller.toggle(aspect: 4.0 / 3.0) try settled(controller, compact: true) try fixture.compact(aspect: 4.0 / 3.0) controller.restore(resumeFullScreen: false) try settled(controller, compact: false) try fixture.restored() // Alternating requests within one native animation settle only at the latest destination. for compactDestination in [false, true] { controller.toggle(aspect: 16.0 / 9.0) try require(controller.isTransitioning, "alternating requests interrupt native entry") for _ in 0..<(compactDestination ? 6 : 5) { controller.toggle(aspect: 16.0 / 9.0) } try settled(controller, compact: compactDestination) if compactDestination { try fixture.compact(aspect: 16.0 / 9.0) controller.restore(resumeFullScreen: false) try settled(controller, compact: false) } try fixture.restored() } controller.toggle(aspect: 16.0 / 9.0) controller.restore(resumeFullScreen: false) try settled(controller, compact: false) try fixture.restored() controller.restore(resumeFullScreen: false) try settled(controller, compact: false) try fixture.restored() // Urgent teardown is immediate; old native completion cannot touch its restored snapshot. controller.toggle(aspect: 16.0 / 9.0) try require(controller.isTransitioning, "urgent restore interrupts native entry") controller.restore(resumeFullScreen: false, animated: false) try require(!controller.isCompact && !controller.isTransitioning, "urgent restore settles immediately") try fixture.restored() try nativeCompletionOpportunity { try require(!controller.isCompact && !controller.isTransitioning, "stale completion cannot revive an urgently restored session") try fixture.restored() } let replacement = try WindowFixture() defer { replacement.window.close() } controller.toggle(aspect: 16.0 / 9.0) try require(controller.isTransitioning, "attachment interrupts native entry") controller.attach(replacement.window) try require(!controller.isCompact && !controller.isTransitioning, "replacement starts ordinary") try fixture.restored() controller.toggle(aspect: 4.0 / 3.0) try settled(controller, compact: true) { try fixture.restored() } try replacement.compact(aspect: 4.0 / 3.0) controller.restore(resumeFullScreen: false) try settled(controller, compact: false) { try fixture.restored() } try replacement.restored() controller.attach(window) // Replay AppKit's failure lifecycle without creating a visible fullscreen Space. NotificationCenter.default.post(name: NSWindow.willEnterFullScreenNotification, object: window) controller.toggle(aspect: 16.0 / 9.0) try require(controller.isTransitioning && !controller.isCompact, "compact entry waits for an in-flight native fullscreen transition") window.delegate?.windowDidFailToEnterFullScreen?(window) try settled(controller, compact: false) try fixture.restored() NotificationCenter.default.post(name: NSWindow.willExitFullScreenNotification, object: window) controller.toggle(aspect: 16.0 / 9.0) window.delegate?.windowDidFailToExitFullScreen?(window) try settled(controller, compact: false) try fixture.restored() controller.toggle(aspect: 16.0 / 9.0) try require(controller.isTransitioning, "close interrupts native entry") window.close() try require(!controller.isCompact && !controller.isTransitioning, "closed window ends its session") let closedState = WindowState(window) controller.attach(replacement.window) controller.toggle(aspect: 16.0 / 9.0) func closedWindowUnchanged() throws { try closedState.requireMatches(window) try fixture.preservedContent() } try settled(controller, compact: true, checking: closedWindowUnchanged) try replacement.compact(aspect: 16.0 / 9.0) controller.restore(resumeFullScreen: false) try settled(controller, compact: false, checking: closedWindowUnchanged) try replacement.restored() } static func rememberedSize() throws { let suite = "terebi.compact-size-check.\(UUID().uuidString)" let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } let fixture = try WindowFixture() let controller = CompactWindowController(defaults: defaults, reduceMotion: { true }) defer { controller.restore(resumeFullScreen: false, animated: false) fixture.window.close() } controller.attach(fixture.window) controller.toggle(aspect: 16.0 / 9.0) var resized = fixture.window.frame resized.size = NSSize(width: 240, height: 135) fixture.window.setFrame(resized, display: false) NotificationCenter.default.post(name: NSWindow.didEndLiveResizeNotification, object: fixture.window) controller.restore(resumeFullScreen: false) try fixture.restored() controller.toggle(aspect: 16.0 / 9.0) try require(close(fixture.window.frame.width, 240) && close(fixture.window.frame.height, 135), "reentering compact restores the manually chosen size") // Programmatic fitting and ordinary-window resizing must not replace the preference. resized.size = NSSize(width: 320, height: 180) fixture.window.setFrame(resized, display: false) controller.updateAspect(4.0 / 3.0) controller.restore(resumeFullScreen: false) try fixture.restored() NotificationCenter.default.post(name: NSWindow.didEndLiveResizeNotification, object: fixture.window) let replacement = try WindowFixture() let reloaded = CompactWindowController(defaults: UserDefaults(suiteName: suite)!, reduceMotion: { true }) defer { reloaded.restore(resumeFullScreen: false, animated: false) replacement.window.close() } reloaded.attach(replacement.window) reloaded.toggle(aspect: 4.0 / 3.0) try require(close(replacement.window.frame.width, 240) && close(replacement.window.frame.height, 180), "a recreated controller restores preferred width with the new video's aspect") try replacement.compact(aspect: 4.0 / 3.0) reloaded.restore(resumeFullScreen: false) try replacement.restored() } static func reducedMotion() throws { let fixture = try WindowFixture() let controller = CompactWindowController(reduceMotion: { true }) defer { controller.restore(resumeFullScreen: false, animated: false) fixture.window.close() } controller.attach(fixture.window) controller.toggle(aspect: 16.0 / 9.0) try require(controller.isCompact && !controller.isTransitioning, "Reduce Motion enters with geometry and controls already settled") try fixture.compact(aspect: 16.0 / 9.0) controller.toggle(aspect: 16.0 / 9.0) try require(!controller.isCompact && !controller.isTransitioning, "Reduce Motion restores without a pending frame animation") try fixture.restored() try nativeCompletionOpportunity { try require(!controller.isCompact && !controller.isTransitioning, "Reduce Motion leaves no completion that can revive compact state") try fixture.restored() } } static func main() throws { try geometry() try windowBehavior() try reducedMotion() try rememberedSize() print("Compact window checks passed") } }