import AppKit import CoreGraphics import EventKit import Foundation import ImageIO import UniformTypeIdentifiers private struct CalendarEventOutput: Encodable { let id: String let subject: String let start: Date let end: Date let location: String let notes: String let url: String let attendees: [String] let isCanceled: Bool } private struct PromptRequest: Decodable { let subject: String let start: Date } private struct PromptOutput: Encodable { let response: String } private struct ScreenshotOutput: Encodable { let windowTitle: String? } private struct SelfTestOutput: Encodable { let status: String let features: [String] } private enum IntegrationError: LocalizedError { case usage case invalidDate(String) case calendarPermission case screenRecordingPermission case noFrontmostApplication case noActiveWindow case screenshotCapture case screenshotWrite var errorDescription: String? { switch self { case .usage: return "Usage: macos-meeting-integrations self-test | calendar | prompt | screenshot | crop " case .invalidDate(let value): return "Invalid ISO-8601 date: \(value)" case .calendarPermission: return "Calendar Full Access permission is required. Enable Meeting Assistant under System Settings > Privacy & Security > Calendars." case .screenRecordingPermission: return "Screen & System Audio Recording permission is required. Enable Meeting Assistant under System Settings > Privacy & Security." case .noFrontmostApplication: return "Could not determine the frontmost macOS application." case .noActiveWindow: return "Could not find an on-screen window for the frontmost macOS application." case .screenshotCapture: return "macOS could not capture the active application window." case .screenshotWrite: return "macOS could not write the active-window screenshot as PNG." } } } @main private enum MacOsMeetingIntegrations { @MainActor static func main() async { do { let arguments = Array(CommandLine.arguments.dropFirst()) guard let command = arguments.first else { throw IntegrationError.usage } switch command { case "self-test": try writeJson(SelfTestOutput( status: "ok", features: ["calendar", "prompt", "screenshot", "crop"])) case "calendar": guard arguments.count == 3 else { throw IntegrationError.usage } let from = try parseDate(arguments[1]) let to = try parseDate(arguments[2]) try await writeCalendar(from: from, to: to) case "prompt": guard arguments.count == 2 else { throw IntegrationError.usage } try showPrompt(requestPath: arguments[1]) case "screenshot": guard arguments.count == 2 else { throw IntegrationError.usage } try captureScreenshot(outputPath: arguments[1]) case "crop": guard arguments.count == 7, let x = Int(arguments[3]), let y = Int(arguments[4]), let width = Int(arguments[5]), let height = Int(arguments[6]) else { throw IntegrationError.usage } try cropScreenshot( inputPath: arguments[1], outputPath: arguments[2], x: x, y: y, width: width, height: height) default: throw IntegrationError.usage } } catch { fputs("\(error.localizedDescription)\n", stderr) if let integrationError = error as? IntegrationError, case .calendarPermission = integrationError { exit(77) } exit(1) } } private static func parseDate(_ value: String) throws -> Date { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] if let date = formatter.date(from: value) { return date } formatter.formatOptions = [.withInternetDateTime] guard let date = formatter.date(from: value) else { throw IntegrationError.invalidDate(value) } return date } private static func writeCalendar(from: Date, to: Date) async throws { let store = EKEventStore() guard try await requestCalendarAccess(store) else { throw IntegrationError.calendarPermission } let predicate = store.predicateForEvents(withStart: from, end: to, calendars: nil) let events = store.events(matching: predicate) .map { event in CalendarEventOutput( id: event.eventIdentifier ?? "\(event.startDate.timeIntervalSince1970)|\(event.title ?? "")", subject: event.title ?? "", start: event.startDate, end: event.endDate, location: event.location ?? "", notes: event.notes ?? "", url: event.url?.absoluteString ?? "", attendees: attendees(from: event), isCanceled: event.status == .canceled) } .sorted { $0.start < $1.start } try writeJson(events) } private static func requestCalendarAccess(_ store: EKEventStore) async throws -> Bool { if #available(macOS 14.0, *) { return try await store.requestFullAccessToEvents() } return try await withCheckedThrowingContinuation { continuation in store.requestAccess(to: .event) { granted, error in if let error { continuation.resume(throwing: error) } else { continuation.resume(returning: granted) } } } } private static func attendees(from event: EKEvent) -> [String] { var values: [String] = [] if let organizer = event.organizer, let value = participantText(organizer) { values.append(value) } for participant in event.attendees ?? [] { if let value = participantText(participant) { values.append(value) } } return Array(NSOrderedSet(array: values)) as? [String] ?? values } private static func participantText(_ participant: EKParticipant) -> String? { let name = participant.name?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" let email = participant.url .absoluteString .replacingOccurrences(of: "mailto:", with: "", options: .caseInsensitive) if name.isEmpty { return email.isEmpty ? nil : email } if email.isEmpty || name.caseInsensitiveCompare(email) == .orderedSame { return name } return "\(name) <\(email)>" } @MainActor private static func showPrompt(requestPath: String) throws { let data = try Data(contentsOf: URL(fileURLWithPath: requestPath)) let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 let request = try decoder.decode(PromptRequest.self, from: data) let application = NSApplication.shared application.setActivationPolicy(.accessory) application.activate(ignoringOtherApps: true) let alert = NSAlert() alert.messageText = "Record meeting?" alert.informativeText = "\(request.subject) starts at \(request.start.formatted(date: .omitted, time: .shortened))." alert.alertStyle = .informational alert.addButton(withTitle: "Record") alert.addButton(withTitle: "Skip") let response = alert.runModal() == .alertFirstButtonReturn ? "record" : "skip" try writeJson(PromptOutput(response: response)) } private static func captureScreenshot(outputPath: String) throws { guard CGPreflightScreenCaptureAccess() || CGRequestScreenCaptureAccess() else { throw IntegrationError.screenRecordingPermission } guard let frontmost = NSWorkspace.shared.frontmostApplication else { throw IntegrationError.noFrontmostApplication } guard let windows = CGWindowListCopyWindowInfo( [.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID) as? [[String: Any]] else { throw IntegrationError.noActiveWindow } let candidate = windows.first { window in let ownerPid = window[kCGWindowOwnerPID as String] as? Int32 let layer = window[kCGWindowLayer as String] as? Int let alpha = window[kCGWindowAlpha as String] as? Double guard ownerPid == frontmost.processIdentifier, layer == 0, (alpha ?? 1) > 0, let bounds = window[kCGWindowBounds as String] as? [String: Any], let width = bounds["Width"] as? Double, let height = bounds["Height"] as? Double else { return false } return width > 1 && height > 1 } guard let candidate, let windowNumber = candidate[kCGWindowNumber as String] as? UInt32 else { throw IntegrationError.noActiveWindow } guard let image = CGWindowListCreateImage( .null, .optionIncludingWindow, CGWindowID(windowNumber), [.boundsIgnoreFraming, .bestResolution]) else { throw IntegrationError.screenshotCapture } try writePng(image, to: outputPath) try writeJson(ScreenshotOutput( windowTitle: candidate[kCGWindowName as String] as? String)) } private static func cropScreenshot( inputPath: String, outputPath: String, x: Int, y: Int, width: Int, height: Int ) throws { guard x >= 0, y >= 0, width > 0, height > 0, let source = CGImageSourceCreateWithURL( URL(fileURLWithPath: inputPath) as CFURL, nil), let image = CGImageSourceCreateImageAtIndex(source, 0, nil), x + width <= image.width, y + height <= image.height, let cropped = image.cropping(to: CGRect( x: x, y: y, width: width, height: height)) else { throw IntegrationError.screenshotCapture } try writePng(cropped, to: outputPath) try writeJson(["status": "ok"]) } private static func writePng(_ image: CGImage, to outputPath: String) throws { let outputUrl = URL(fileURLWithPath: outputPath) as CFURL guard let destination = CGImageDestinationCreateWithURL( outputUrl, UTType.png.identifier as CFString, 1, nil) else { throw IntegrationError.screenshotWrite } CGImageDestinationAddImage(destination, image, nil) guard CGImageDestinationFinalize(destination) else { throw IntegrationError.screenshotWrite } } private static func writeJson(_ value: T) throws { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 encoder.outputFormatting = [.sortedKeys] FileHandle.standardOutput.write(try encoder.encode(value)) FileHandle.standardOutput.write(Data([0x0A])) } }