import Foundation /// Keeps the Mac's display awake through a call (2026-10-19, for 0.4.10): /// a call is often watched rather than typed into, and the display going /// to sleep under it reads as the call having ended. Held from the moment /// the call is joined until it is left or ends, by a process activity /// that disables idle display sleep only. It does not stop a person /// locking the Mac, or the lid closing, and it goes with the process: the /// system ends an activity whose process quits or crashes. The iPhone /// keeps its own screen on with isIdleTimerDisabled. @MainActor public final class CallWake { public static let reason = "In a Calls call" /// What begins and ends the activity: ProcessInfo's, or a test's. public protocol Activities { func begin(reason: String) -> NSObjectProtocol func end(_ activity: NSObjectProtocol) } struct ProcessActivities: Activities { func begin(reason: String) -> NSObjectProtocol { ProcessInfo.processInfo.beginActivity( options: [.idleDisplaySleepDisabled, .userInitiated], reason: reason) } func end(_ activity: NSObjectProtocol) { ProcessInfo.processInfo.endActivity(activity) } } private let activities: Activities private var activity: NSObjectProtocol? /// Whether the display is being kept awake now. public var holding: Bool { activity != nil } public init(activities: Activities? = nil) { self.activities = activities ?? ProcessActivities() } /// In a call or not: begins the activity on the way in, ends it on the /// way out, and does nothing when nothing changed, so it can be told /// as often as the state is published. public func update(inCall: Bool) { if inCall, activity == nil { activity = activities.begin(reason: Self.reason) } else if !inCall, let held = activity { activities.end(held) activity = nil } } /// Whether a call in `phase` (the room's: "admitted", "ended", …) keeps /// the display awake while `joining`. public static func inCall(joining: Bool, phase: String?) -> Bool { joining && phase != "ended" } }