import AppKit import MediaPlayer @_cdecl("terebi_app_main") @MainActor public func checkAppStartup() { if let configuration = Bundle.main.url(forResource: "cli-check", withExtension: "json") { CLIInstanceCheck.run(configuration) return } AppStartupCheck.run() } @MainActor private enum AppStartupCheck { static var model: PlayerModel? static var timer: Timer? static let deadline = ContinuousClock.now.advanced(by: .seconds(15)) static let historyDomain = Bundle.main.bundleIdentifier! + ".history" static func run() { _ = NSApplication.shared NSApp.setActivationPolicy(.regular) let defaults = UserDefaults(suiteName: historyDomain)! model = PlayerModel(defaults: defaults) model!.setVolume(0) model!.open(URL(fileURLWithPath: CommandLine.arguments[2]).appendingPathComponent("av.mp4")) timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in MainActor.assumeIsolated { finishWhenReady() } } NSApp.run() } static func finishWhenReady() { guard let model else { return } let ready = model.canTogglePlayback && model.state.status == PLAYER_PLAYING guard ready || model.state.status == PLAYER_ERROR || ContinuousClock.now >= deadline else { return } timer?.invalidate() let pid = ProcessInfo.processInfo.processIdentifier let application = NSRunningApplication.current let recognized = application.processIdentifier == pid && NSWorkspace.shared.runningApplications.contains { $0.processIdentifier == pid && $0.bundleURL == Bundle.main.bundleURL } let result: [String: Any] = [ "passed": ready && recognized && MPNowPlayingInfoCenter.default().playbackState == .playing, "pid": pid, "registeredPID": application.processIdentifier, "bundleIdentifier": application.bundleIdentifier ?? "nil", "playing": ready, ] do { let pipe = try FileHandle(forWritingTo: URL(fileURLWithPath: CommandLine.arguments[1])) try pipe.write(contentsOf: JSONSerialization.data(withJSONObject: result)) try pipe.write(contentsOf: Data([10])) try pipe.close() } catch { FileHandle.standardError.write(Data("Startup check could not report: \(error)\n".utf8)) } model.shutdown() UserDefaults(suiteName: historyDomain)!.removePersistentDomain(forName: historyDomain) self.model = nil NSApp.terminate(nil) } } @MainActor private enum CLIInstanceCheck { static var timer: Timer? static func run(_ configuration: URL) { let settings = try! JSONSerialization.jsonObject(with: Data(contentsOf: configuration)) as! [String: String] let directory = URL(fileURLWithPath: settings["directory"]!, isDirectory: true) setenv("PATH", settings["path"]!, 1) _ = NSApplication.shared PlayerModel.shared.setVolume(0) // Exercise the real SwiftUI scene and delegate without displaying test windows. _ = NotificationCenter.default.addObserver(forName: NSWindow.didUpdateNotification, object: nil, queue: .main) { _ in MainActor.assumeIsolated { for window in NSApp.windows { window.alphaValue = 0 } } } timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in MainActor.assumeIsolated { let model = PlayerModel.shared let pid = ProcessInfo.processInfo.processIdentifier let windows = NSApp.windows.filter { !($0 is NSPanel) && $0.contentView != nil } for window in windows { window.alphaValue = 0 } let applications = NSWorkspace.shared.runningApplications.filter { !$0.isTerminated && $0.bundleURL == Bundle.main.bundleURL } let snapshot: [String: Any] = [ "pid": pid, "pids": applications.map(\.processIdentifier).sorted(), "media": model.fileName ?? "", "status": model.state.status.rawValue, "position": model.state.position, "windows": windows.count, "hasAudio": model.state.has_audio, "hasVideo": model.state.has_video, "error": model.errorText ?? "", ] do { let data = try JSONSerialization.data(withJSONObject: snapshot) try data.write(to: directory.appendingPathComponent("state-\(pid).json"), options: .atomic) } catch { fatalError("Could not publish CLI instance check: \(error)") } } } TerebiApp.main() } }