oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1import AppKit
2@_spi(Fixtures) import CallsKit
3import Foundation
4import LiveKitWebRTC
5import ScreenCaptureKit
6import SwiftUI
7
8// A separate test executable: synthetic video and fixture credentials are never
9// compiled into CallsMac. Input travels on stdin, not argv or a saved file.
10struct Input: Decodable {
11 let origin: String
12 let room: String
13 let cookies: String
14 let chatSnapshot: String?
15 let invitation: String?
16}
17@main struct Interop {
18 @MainActor static var screen: SyntheticScreen?
19 @MainActor static var display: DisplayCapture?
20 @MainActor static var webrtcLogger: LKRTCCallbackLogger?
21 @MainActor static let cameras = SyntheticCameras()
22 // A defaults domain of its own, so the gate's camera choices are never
23 // remembered into anyone's real preferences.
24 @MainActor static let camera = ManagedCamera(
25 backend: cameras, defaults: UserDefaults(suiteName: "calls-interop-\(UUID().uuidString)")!)
26 @MainActor static func main() async {
27 if CommandLine.arguments.count == 3, CommandLine.arguments[1] == "--lobby-snapshots" {
28 do { try await LobbySnapshots.save(to: CommandLine.arguments[2]) } catch { exit(1) }
29 return
30 }
31 if CommandLine.arguments.count == 3, CommandLine.arguments[1] == "--call-snapshots" {
32 do {
33 try await CallSnapshots.save(to: URL(fileURLWithPath: CommandLine.arguments[2]))
34 } catch { exit(1) }
35 return
36 }
37 if CommandLine.arguments.count == 3, CommandLine.arguments[1] == "--connecting-snapshots" {
38 do {
39 try await CallSnapshots.saveConnecting(to: URL(fileURLWithPath: CommandLine.arguments[2]))
40 } catch { exit(1) }
41 return
42 }
43 if CommandLine.arguments.count == 3, CommandLine.arguments[1] == "--glass-snapshots" {
44 do {
45 try await CallSnapshots.saveGlass(to: URL(fileURLWithPath: CommandLine.arguments[2]))
46 } catch { exit(1) }
47 return
48 }
49 if CommandLine.arguments.count == 3, CommandLine.arguments[1] == "--speaker-snapshots" {
50 do {
51 try await CallSnapshots.saveSpeaker(to: URL(fileURLWithPath: CommandLine.arguments[2]))
52 } catch { exit(1) }
53 return
54 }
55 if CommandLine.arguments.count == 2, CommandLine.arguments[1] == "--mini-clicks" {
56 // Under NSApplication's own run loop, as the app is: the surface
57 // tracks a press with a nested event loop, which Swift's async main
58 // alone does not survive.
59 NSApplication.shared.setActivationPolicy(.accessory)
60 Task { @MainActor in
61 let call = Call(playAudio: false)
62 CallFixture.grid.apply(to: call)
63 let results = await MiniWindowProbe.run(call: call)
64 for r in results { print((r.passed ? "ok " : "FAIL ") + r.name + (r.passed ? "" : " — " + r.detail)) }
65 print("mini-clicks: \(results.filter(\.passed).count) of \(results.count) presses did what they should")
66 fflush(stdout)
67 exit(results.allSatisfy(\.passed) ? 0 : 1)
68 }
69 NSApplication.shared.run()
70 }
71 if CommandLine.arguments.count == 3, CommandLine.arguments[1] == "--stage-snapshots" {
72 do {
73 try await StageSnapshots.save(to: URL(fileURLWithPath: CommandLine.arguments[2]))
74 } catch { exit(1) }
75 return
76 }
77 if CommandLine.arguments.count == 3, CommandLine.arguments[1] == "--bar-layout" {
78 exit(await BarLayoutHarness.run(to: URL(fileURLWithPath: CommandLine.arguments[2])) ? 0 : 1)
79 }
80 if CommandLine.arguments.count == 2, CommandLine.arguments[1] == "--audio-start-stall" {
81 exit(await AudioStartStallHarness.run() ? 0 : 1)
82 }
83 if CommandLine.arguments.count == 2, CommandLine.arguments[1] == "--audio-selftest" {
84 exit(await AudioSelfTestHarness.run() ? 0 : 1)
85 }
86 guard let line = readLine(),
87 let input = try? JSONDecoder().decode(Input.self, from: Data(line.utf8)),
88 let url = URL(string: input.origin), url.scheme == "http", url.host == "127.0.0.1"
89 else { exit(2) }
90 // Decoders the gate can wedge (spec §15.22), set before the call's
91 // factory is made; only for the scenarios that ask, so every other
92 // gate decodes exactly as the apps do.
93 if ProcessInfo.processInfo.environment["CALLS_DECODER_WEDGE"] == "1" { DecoderWedge.enabled = true }
94 let call = Call(playAudio: false, audioInput: SyntheticVoice())
95 if ProcessInfo.processInfo.environment["CALLS_SCREEN_WATCHDOG"] == "0" { Call.screenWatchdogEnabled = false }
96 // Quality reports every N seconds rather than thirty (spec §15.26), so
97 // the gate sees one inside its call. A number of seconds, at least one.
98 if let seconds = ProcessInfo.processInfo.environment["CALLS_QUALITY_INTERVAL"].flatMap(Double.init), seconds >= 1 {
99 Call.qualityInterval = seconds
100 }
101 // WebRTC's own log, filtered to the video encoder, into a file: for
102 // diagnosing the gate's screen scenarios by hand. Never credentials:
103 // the filter keeps encoder lines only.
104 if let base = ProcessInfo.processInfo.environment["CALLS_WEBRTC_LOG"] {
105 let path = "\(base).\(getpid())"
106 FileManager.default.createFile(atPath: path, contents: nil)
107 let handle = FileHandle(forWritingAtPath: path)
108 let logger = LKRTCCallbackLogger()
109 logger.severity = .info
110 let everything = ProcessInfo.processInfo.environment["CALLS_WEBRTC_LOG_ALL"] == "1"
111 logger.start { message in
112 let lower = message.lowercased()
113 guard everything || ["h264", "encod", "videotoolbox", "vtcompression", "resolution", "adapt", "quality"].contains(where: lower.contains)
114 else { return }
115 let stamp = String(format: "%.3f ", Date().timeIntervalSince1970)
116 handle?.write(Data((stamp + message).utf8))
117 }
118 Interop.webrtcLogger = logger
119 }
120 // 0.4.1's motion sizing, without the H.264 level cap, for measuring it.
121 if ProcessInfo.processInfo.environment["CALLS_SCREEN_UNCAPPED"] == "1" { ScreenSendPolicy.levelCapped = false }
122 let preview = input.chatSnapshot.map { ChatPreview(call: call, path: $0) }
123 do {
124 try await call.join(
125 origin: input.origin, room: input.room, cookies: input.cookies, capture: Interop.camera)
126 } catch {
127 print("FAIL native start")
128 exit(1)
129 }
130 Task.detached {
131 while let command = readLine() {
132 if command == "leave" {
133 await call.leave()
134 exit(0)
135 }
136 if command == "mute" {
137 await call.mute()
138 print("{\"muted\":\(await call.muted)}")
139 fflush(stdout)
140 }
141 if command == "chat" {
142 await call.sendChat("native chat probe")
143 print("{\"chatCommandHandled\":true}")
144 fflush(stdout)
145 }
146 if command == "snapshot" {
147 let saved = await preview?.save() ?? false
148 _ = await preview?.saveCall(call: call)
149 print("{\"chatSnapshotSaved\":\(saved)}")
150 fflush(stdout)
151 }
152 if command == "share" {
153 await call.loadInvitation()
154 let invitation = await call.invitationURL?.absoluteString
155 let refused = await !call.invitationError.isEmpty
156 let ready = invitation != nil && invitation == input.invitation
157 print("{\"invitationReady\":\(ready),\"invitationRefused\":\(refused)}")
158 fflush(stdout)
159 }
160 if command == "audio-default" || command == "audio-missing" {
161 let succeeded = await MainActor.run {
162 do {
163 try call.audio.use(input: command == "audio-default" ? "" : "no-such-device")
164 try call.audio.use(output: "")
165 return true
166 } catch { return false }
167 }
168 print("{\"audioSwitched\":\(succeeded)}")
169 fflush(stdout)
170 }
171 // The camera stops sending, and the watchdog has to notice and
172 // restart it: frames come back on the next open.
173 if command == "camera-stall" {
174 await MainActor.run { Interop.cameras.stalled = true }
175 print("{\"cameraStalled\":true}")
176 fflush(stdout)
177 }
178 if command.hasPrefix("camera ") {
179 let id = String(command.dropFirst("camera ".count))
180 await call.useCamera(id)
181 let now = await call.currentCamera ?? ""
182 let note = await call.deviceNote
183 print("{\"camera\":\"\(now)\",\"cameraRefused\":\(note.isEmpty ? "false" : "true")}")
184 fflush(stdout)
185 }
186 // §5.4, sending. `screen-end` is the capture ending on its own,
187 // which is the path the system's stop control takes.
188 if command == "screen-on" {
189 let screen = await SyntheticScreen()
190 await MainActor.run { Interop.screen = screen }
191 await call.shareScreen(screen)
192 let on = await call.sharingScreen
193 print("{\"screenSharing\":\(on)}")
194 fflush(stdout)
195 }
196 // "Optimise for", as the device panel sets it.
197 if command.hasPrefix("screen-mode ") {
198 let mode = ScreenShareMode(rawValue: String(command.dropFirst("screen-mode ".count)))
199 if let mode { await MainActor.run { call.screenMode = mode } }
200 print("{\"screenMode\":\"\(mode?.rawValue ?? "")\"}")
201 fflush(stdout)
202 }
203 // The motion gate: a 1080p page scrolling at 30 fps.
204 if command == "screen-motion" {
205 let screen = await SyntheticScreen(motion: true)
206 await MainActor.run { Interop.screen = screen }
207 await call.shareScreen(screen)
208 let on = await call.sharingScreen
209 print("{\"screenSharing\":\(on)}")
210 fflush(stdout)
211 }
212 // A thin uplink, for the same gate: `uplink 1200000`, or `uplink 0`
213 // to lift it.
214 if command.hasPrefix("uplink ") {
215 let bps = Int(command.dropFirst("uplink ".count)) ?? 0
216 await call.limitUplink(bps > 0 ? bps : nil)
217 print("{\"uplink\":\(bps)}")
218 fflush(stdout)
219 }
220 if command == "screen-off" {
221 await call.stopSharingScreen()
222 print("{\"screenSharing\":false}")
223 fflush(stdout)
224 }
225 // The synthetic screen stops producing anything, as a capture
226 // with every surface held does; the watchdog has to restart it.
227 if command == "screen-stall" {
228 await MainActor.run { Interop.screen?.stall() }
229 print("{\"screenStalled\":true}")
230 fflush(stdout)
231 }
232 // Every screen encoding switched off behind the watchdog's back:
233 // frames go in and nothing comes out.
234 if command == "screen-encoder-stall" {
235 await call.stallScreenEncoderForTest()
236 print("{\"encoderStalled\":true}")
237 fflush(stdout)
238 }
239 // The real thing: this Mac's main display through ScreenCaptureKit
240 // and the hardware encoder, at the given queue depth. Needs Screen
241 // Recording permission for the process running the gate.
242 if command.hasPrefix("screen-display") {
243 let depth = Int(command.split(separator: " ").dropFirst().first ?? "") ?? 6
244 await MainActor.run { MacScreenCapture.queueDepth = depth }
245 let display = await DisplayCapture()
246 await MainActor.run { Interop.display = display }
247 await call.shareScreen(display)
248 let on = await call.sharingScreen
249 print("{\"screenSharing\":\(on)}")
250 fflush(stdout)
251 }
252 // The receiving side's watch treats every screen as frozen.
253 if command.hasPrefix("screen-freeze-test ") {
254 let ms = Double(command.dropFirst("screen-freeze-test ".count)) ?? 0
255 await call.freezeScreenWatchForTest(seconds: ms / 1000)
256 print("{\"screenFrozenForTest\":true}")
257 fflush(stdout)
258 }
259 // §15.22: this app's screen decoders stop putting out pictures
260 // while packets keep arriving — a wedge no keyframe mends and a
261 // new transceiver does. `sticky` wedges the new ones too.
262 if command == "screen-decoder-wedge" || command == "screen-decoder-wedge sticky" {
263 let n = DecoderWedge.wedgeScreens(sticky: command.hasSuffix("sticky"))
264 print("{\"decoderWedged\":\(n)}")
265 fflush(stdout)
266 }
267 if command == "screen-decoder-release" {
268 DecoderWedge.release()
269 print("{\"decoderReleased\":true}")
270 fflush(stdout)
271 }
272 // "Copy call diagnostics", as the panel would copy it.
273 if command == "diagnostics" {
274 let text = await call.audioDiagnosticsText()
275 if let data = try? JSONSerialization.data(withJSONObject: ["diagnostics": text]),
276 let line = String(data: data, encoding: .utf8)
277 {
278 print(line)
279 fflush(stdout)
280 }
281 }
282 if command == "screen-reshare-test" {
283 await call.reshareScreenForTest()
284 print("{\"reshared\":true}")
285 fflush(stdout)
286 }
287 if command == "screen-end" {
288 await Interop.screen?.end()
289 let on = await call.sharingScreen
290 print("{\"screenSharing\":\(on)}")
291 fflush(stdout)
292 }
293 if command == "share-snapshot" {
294 let saved = await preview?.saveInvitation(call: call) ?? false
295 print("{\"invitationSnapshotSaved\":\(saved)}")
296 fflush(stdout)
297 }
298 }
299 }
300 // Long enough for the member scenario, screens and all, and for the
301 // motion gate's three modes: the gate always says `leave` long before
302 // this runs out.
303 // CALLS_INTEROP_SECONDS for the soaks, which outlast that (§15.22).
304 let life = Int(ProcessInfo.processInfo.environment["CALLS_INTEROP_SECONDS"] ?? "") ?? 400
305 for _ in 0..<life {
306 try? await Task.sleep(nanoseconds: 1_000_000_000)
307 let stats = await call.sampleStats()
308 if let data = try? JSONSerialization.data(withJSONObject: [
309 "phase": call.state?.phase ?? "", "peer": call.state?.peer ?? "", "stats": stats,
310 "receivedChatProbe": call.chat.contains { $0.hasSuffix(": browser chat probe") },
311 "sentChatProbe": call.chat.contains("You: native chat probe"),
312 "receivedChatCount": call.receivedChatCount,
313 // §15.19: remote peers this app has been told are muted.
314 "remoteMuted": (call.state?.peers ?? []).filter {
315 $0.peer != call.state?.peer && call.isMuted($0.peer)
316 }.count,
317 // §5.4: what the stage would caption each screen, in the order
318 // they started. The words, not a count: the caption is the only
319 // place a screen says whose it is.
320 "screenCaptions": call.screens.map { screenCaption(call.name(of: $0)) },
321 "sharingScreen": call.sharingScreen,
322 // §15.22: the sharer's notice, word for word.
323 "screenRecovery": call.screenRecovery,
324 // §15.23: "Reconnecting…" said for the call, and on how many tiles.
325 "reconnecting": call.reconnecting,
326 "reconnectingPeers": call.reconnectingPeers.count,
327 "syntheticScreenFrames": Interop.screen?.produced ?? 0,
328 "cameraOpens": Interop.camera.opens,
329 "cameraStatus": {
330 switch call.cameraStatus {
331 case .live?: return "live"
332 case .starting?: return "starting"
333 case .noSignal?: return "no-signal"
334 case .disconnected?: return "disconnected"
335 case .failed?: return "failed"
336 case .noCamera?: return "none"
337 case nil: return ""
338 }
339 }(),
340 ]), let text = String(data: data, encoding: .utf8) {
341 print(text)
342 fflush(stdout)
343 }
344 }
345 await call.leave()
346 exit(0)
347 }
348}
349
350/// The main display, captured by the shipping MacScreenCapture with no
351/// picker: the gate's way to put ScreenCaptureKit, the frame handling and
352/// the hardware encoder under test together.
353@MainActor final class DisplayCapture: ScreenCapture {
354 let mac = MacScreenCapture()
355 func start(source: RTCVideoSource, ended: @escaping @MainActor () -> Void) async throws {
356 let content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true)
357 guard let display = content.displays.first else { throw CancellationError() }
358 try await mac.start(display: display, source: source, ended: ended)
359 }
360 func stop() { mac.stop() }
361 var counts: ScreenCaptureCounts { mac.counts }
362 func restart() async -> Bool { await mac.restart() }
363 var frameSize: (width: Int, height: Int)? { mac.frameSize }
364}
365
366// Render the shipping chat view against real received messages. This fixture
367// window sits off-screen and never activates or interrupts the user's call.
368@MainActor private final class ChatPreview {
369 let window: NSWindow
370 let view: NSHostingView<AnyView>
371 let path: String
372 init(call: Call, path: String) {
373 NSApplication.shared.setActivationPolicy(.prohibited)
374 self.path = path
375 view = NSHostingView(
376 rootView: AnyView(
377 ChatView(call: call).padding(12).background(Color.white).environment(\.colorScheme, .light))
378 )
379 window = NSWindow(
380 contentRect: NSRect(x: -2000, y: -2000, width: 600, height: 200),
381 styleMask: [.borderless], backing: .buffered, defer: false)
382 window.isReleasedWhenClosed = false
383 window.contentView = view
384 window.orderFront(nil)
385 }
386 func save() -> Bool {
387 view.layoutSubtreeIfNeeded()
388 guard let bitmap = view.bitmapImageRepForCachingDisplay(in: view.bounds) else { return false }
389 view.cacheDisplay(in: view.bounds, to: bitmap)
390 guard let data = bitmap.representation(using: .png, properties: [:]) else { return false }
391 do {
392 try data.write(to: URL(fileURLWithPath: path))
393 return true
394 } catch { return false }
395 }
396 func saveCall(call: Call) async -> Bool {
397 var saved = true
398 for (name, grid, chat) in [
399 ("call-focus.png", false, false),
400 ("call-chat.png", false, true),
401 ("call-grid.png", true, false),
402 ] {
403 view.rootView = AnyView(
404 MacCallView(call: call, grid: grid, showingChat: chat, style: .classic, leave: {}).id(name)
405 .padding(20).tint(CallsAppearance.accent).background(Color.white)
406 .environment(\.colorScheme, .light))
407 window.setContentSize(NSSize(width: 900, height: 540))
408 try? await Task.sleep(for: .milliseconds(300))
409 saved = saveImage(named: name) && saved
410 }
411 view.rootView = AnyView(
412 VStack(alignment: .leading, spacing: 0) {
413 Text("Devices").font(.headline).padding(.horizontal, 16).padding(.top, 16)
414 DevicePanel(call: call)
415 Spacer(minLength: 0)
416 }.frame(width: 300)
417 .background(Color.white).environment(\.colorScheme, .light))
418 window.setContentSize(NSSize(width: 300, height: 340))
419 try? await Task.sleep(for: .milliseconds(300))
420 saved = saveImage(named: "call-devices.png") && saved
421 view.rootView = AnyView(
422 ChatView(call: call).padding(12)
423 .background(Color.white).environment(\.colorScheme, .light))
424 window.setContentSize(NSSize(width: 600, height: 200))
425 return saved
426 }
427 private func saveImage(named name: String) -> Bool {
428 view.layoutSubtreeIfNeeded()
429 guard let bitmap = view.bitmapImageRepForCachingDisplay(in: view.bounds) else { return false }
430 view.cacheDisplay(in: view.bounds, to: bitmap)
431 guard let data = bitmap.representation(using: .png, properties: [:]) else { return false }
432 let target = URL(fileURLWithPath: path).deletingLastPathComponent().appendingPathComponent(name)
433 do {
434 try data.write(to: target)
435 return true
436 } catch { return false }
437 }
438 func saveInvitation(call: Call) async -> Bool {
439 view.rootView = AnyView(
440 MeetingInvitationView(call: call, close: {})
441 .background(Color.white).environment(\.colorScheme, .light))
442 window.setContentSize(NSSize(width: 440, height: 340))
443 try? await Task.sleep(for: .milliseconds(250))
444 view.layoutSubtreeIfNeeded()
445 guard let bitmap = view.bitmapImageRepForCachingDisplay(in: view.bounds) else { return false }
446 view.cacheDisplay(in: view.bounds, to: bitmap)
447 guard let data = bitmap.representation(using: .png, properties: [:]) else { return false }
448 let target = URL(fileURLWithPath: path).deletingLastPathComponent().appendingPathComponent(
449 "share-meeting.png")
450 do {
451 try data.write(to: target)
452 return true
453 } catch { return false }
454 }
455}
456