import AppKit @_spi(Fixtures) import CallsKit import SwiftUI /// Offline visual fixtures of the Mac call window, full-window and /// classic, from described calls (see CallFixture): controls shown and /// hidden, Focus and Grid, a shared screen, the Devices panel open, and /// the notices that stay when the controls go. The tiles show their /// placeholders — an off-screen capture draws no Metal video anyway. @MainActor enum CallSnapshots { static func save(to folder: URL) async throws { NSApplication.shared.setActivationPolicy(.prohibited) try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) let size = CGSize(width: 1000, height: 640) for scheme in [ColorScheme.light, .dark] { let suffix = LobbySnapshots.name(scheme) func shot( _ name: String, _ fixture: CallFixture, grid: Bool = false, shown: Bool = true, devices: Bool = false, chat: Bool = false, style: CallLayoutStyle = .fullWindow, backdrop: FixtureBackdrop? = nil, me: FixtureSelf? = nil ) async throws { let call = Call(playAudio: false) fixture.apply(to: call) me?.apply(to: call) try await LobbySnapshots.render( MacCallView( call: call, grid: grid, showingChat: chat, showingDevices: devices, style: style, controlsShown: shown, leave: {}, rejoin: {} ).tint(CallsAppearance.accent).environment(\.fixtureBackdrop, backdrop), scheme: scheme, size: size, to: folder.appendingPathComponent("mac-\(name)-\(suffix).png")) } try await shot("focus-controls", .focus) try await shot("focus-hidden", .focus, shown: false) try await shot("grid-controls", .grid, grid: true) try await shot("grid-hidden", .grid, grid: true, shown: false) try await shot("screen-controls", .screen) try await shot("screen-hidden", .screen, shown: false) try await shot("devices", .focus, devices: true) try await shot("chat", .focus, chat: true) try await shot("notices-hidden", .notices, shown: false) // 2026-10-02: no title pill. A named meeting's title is centred in // the title bar's strip and fades with the controls; an automatic // one ("Call, 24 Sep 10:21", the focus fixture) is not shown. try await shot("named-controls", .named) try await shot("named-hidden", .named, shown: false) try await shot("named-white", .named, backdrop: .white) try await shot("named-busy", .named, backdrop: .busy) try await shot("classic-focus", .focus, style: .classic) try await shot("classic-grid", .grid, grid: true, style: .classic) // The self view in the top-left corner: below the title bar's strip // (there is no pill to keep clear of since 2026-10-02). let corner = UserDefaults.standard.string(forKey: PiPCorner.key) UserDefaults.standard.set(PiPCorner.topLeading.rawValue, forKey: PiPCorner.key) try await shot("pip-top-leading-controls", .focus) try await shot("pip-top-leading-hidden", .focus, shown: false) UserDefaults.standard.set(corner, forKey: PiPCorner.key) // The overlay over the worst pictures there are for it, in each of // your own states: it should look the same in light and dark. for backdrop in FixtureBackdrop.allCases { for me in FixtureSelf.allCases { try await shot("contrast-\(backdrop.rawValue)-\(me.rawValue)", .focus, backdrop: backdrop, me: me) } try await shot("contrast-\(backdrop.rawValue)-notices", .notices, backdrop: backdrop) } try await shot("contrast-busy-hidden", .focus, shown: false, backdrop: .busy) } } /// Spec §15.24: two people who have just joined — Samuel still /// connecting and unable to hear you yet, Bea's browser blocking sound in /// place of that — in Grid, Speaker and Classic, light and dark, over a /// busy picture, and the mini window and its tab with the small ear for /// someone it is not showing. static func saveConnecting(to folder: URL) async throws { NSApplication.shared.setActivationPolicy(.prohibited) try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) let size = CGSize(width: 1000, height: 640) for scheme in [ColorScheme.light, .dark] { let suffix = LobbySnapshots.name(scheme) func shot( _ name: String, grid: Bool = false, style: CallLayoutStyle = .fullWindow, backdrop: FixtureBackdrop? = nil ) async throws { let call = Call(playAudio: false) CallFixture.connecting.apply(to: call) try await LobbySnapshots.render( MacCallView( call: call, grid: grid, showingChat: false, showingDevices: false, style: style, controlsShown: true, leave: {}, rejoin: {} ).tint(CallsAppearance.accent).environment(\.fixtureBackdrop, backdrop), scheme: scheme, size: size, to: folder.appendingPathComponent("mac-connecting-\(name)-\(suffix).png")) } try await shot("grid", grid: true) try await shot("speaker") try await shot("classic-grid", grid: true, style: .classic) try await shot("busy-grid", grid: true, backdrop: .busy) for hovering in [false, true] { let call = Call(playAudio: false) CallFixture.connecting.apply(to: call) let width = MiniWindowRules.size.width try await LobbySnapshots.render( MiniWindowSnapshot.window(call: call, hovering: hovering), scheme: scheme, size: CGSize(width: width, height: width / MiniWindowRules.aspect), to: folder.appendingPathComponent("mac-connecting-mini\(hovering ? "-hover" : "")-\(suffix).png")) } let call = Call(playAudio: false) CallFixture.connecting.apply(to: call) try await LobbySnapshots.render( MiniWindowSnapshot.tab(call: call), scheme: scheme, size: MiniWindowTab.size, to: folder.appendingPathComponent("mac-connecting-tab-\(suffix).png")) } } /// The glass and the glow as the window server draws them (0.3.7). An /// off-screen bitmap has no Liquid Glass and no backdrop to blur, so /// these are real windows, parked below the desktop where nobody sees /// them, and captured by `screencapture -l`: the control bar over black, /// white and a busy frame, in glass and in the material older systems /// draw; the Devices panel; the names glowing at four levels; the tab. static func saveGlass(to folder: URL) async throws { NSApplication.shared.setActivationPolicy(.prohibited) try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) let size = CGSize(width: 1000, height: 640) func shot( _ name: String, _ fixture: CallFixture, grid: Bool = false, devices: Bool = false, glass: Bool = true, backdrop: FixtureBackdrop? = nil, me: FixtureSelf? = nil, speaking: FixtureSpeaking? = nil ) async throws { let call = Call(playAudio: false) fixture.apply(to: call) me?.apply(to: call) speaking?.apply(to: call) try await renderLive( MacCallView( call: call, grid: grid, showingDevices: devices, style: .fullWindow, controlsShown: true, leave: {}, rejoin: {} ) .tint(CallsAppearance.accent) .environment(\.fixtureBackdrop, backdrop) .environment(\.callOverlayGlass, glass), size: size, to: folder.appendingPathComponent("mac-\(name).png")) } for glass in [true, false] { let kind = glass ? "glass" : "fallback" for backdrop in FixtureBackdrop.allCases { try await shot("bar-\(kind)-\(backdrop.rawValue)", .focus, glass: glass, backdrop: backdrop) } try await shot("bar-\(kind)-busy-muted", .focus, glass: glass, backdrop: .busy, me: .muted) try await shot("bar-\(kind)-busy-sharing", .focus, glass: glass, backdrop: .busy, me: .sharing) try await shot("devices-\(kind)", .focus, devices: true, glass: glass, backdrop: .busy) } for level in FixtureSpeaking.allCases { try await shot("glow-\(level.rawValue)", .grid, grid: true, speaking: level) try await shot("glow-focus-\(level.rawValue)", .focus, speaking: level) } // The warm rim on a name at each level over the pictures it has to // read on, for comparing with the mock-up (0.4.3). for level in FixtureSpeaking.allCases { try await renderLive( WarmRimGallery(level: level).background(Color.black).environment(\.colorScheme, .dark), size: CGSize(width: 380, height: 670), to: folder.appendingPathComponent("mac-rim-\(level.rawValue).png")) } for level in [FixtureSpeaking.silent, .high] { let call = Call(playAudio: false) CallFixture.focus.apply(to: call) level.apply(to: call) try await renderLive( ZStack { FixtureBackdrop.busy.view MiniWindowSnapshot.tab(call: call) }, size: CGSize(width: MiniWindowTab.size.width + 40, height: MiniWindowTab.size.height + 40), to: folder.appendingPathComponent("mac-tab-\(level.rawValue).png")) } } /// Speaker view and the viewer's order (0.4.0), as the window server /// draws them: a call of four in Speaker view with Tom speaking and the /// other three in the filmstrip (controls up and down, and at the /// smallest window, 760 × 540), Li pinned, a shared screen with the /// sharer's camera in the filmstrip, a drag half way in Grid, and Grid /// after a reorder. static func saveSpeaker(to folder: URL) async throws { NSApplication.shared.setActivationPolicy(.prohibited) try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) func shot( _ name: String, _ fixture: CallFixture = .grid, grid: Bool = false, shown: Bool = true, size: CGSize = CGSize(width: 1000, height: 640), speaker: String? = "tom", pinned: StageItem? = nil, lift: String? = nil, order: [String] = [] ) async throws { let call = Call(playAudio: false) fixture.apply(to: call) call.levels.speaker.showFixture(speaker) call.arrangement.showPinned(pinned) call.arrangement.showLift(lift) call.arrangement.showOrder(order) try await renderLive( MacCallView( call: call, grid: grid, style: .fullWindow, controlsShown: shown, leave: {}, rejoin: {} ) .tint(CallsAppearance.accent), size: size, to: folder.appendingPathComponent("mac-\(name).png")) } try await shot("speaker-filmstrip-controls") try await shot("speaker-filmstrip-hidden", shown: false) try await shot("speaker-filmstrip-760x540", size: CGSize(width: 760, height: 540)) try await shot("speaker-pinned", pinned: .camera("li")) try await shot("speaker-screen", .screen, shown: false) try await shot("speaker-drag", lift: "me") try await shot("grid-drag", grid: true, lift: "li") try await shot("grid-reordered", grid: true, order: ["li", "tom", "me", "ana"]) try await shot("speaker-two", .focus, shown: false, speaker: nil) } /// A real window below the desktop, captured by the window server. static func renderLive(_ content: some View, size: CGSize, to file: URL) async throws { let view = NSHostingView(rootView: content.environment(\.colorScheme, .dark)) view.frame.size = size let window = NSWindow( contentRect: NSRect(origin: CGPoint(x: 0, y: 0), size: size), styleMask: [.borderless], backing: .buffered, defer: false) window.isReleasedWhenClosed = false window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.desktopWindow)) - 1) window.appearance = NSAppearance(named: .darkAqua) window.contentView = view window.orderBack(nil) try await Task.sleep(for: .milliseconds(600)) view.layoutSubtreeIfNeeded() let capture = Process() capture.executableURL = URL(fileURLWithPath: "/usr/sbin/screencapture") capture.arguments = ["-x", "-o", "-l", String(window.windowNumber), file.path] try capture.run() capture.waitUntilExit() window.close() guard capture.terminationStatus == 0 else { throw CocoaError(.fileWriteUnknown) } } }