// // DoNotDisturbManager.swift // DynamicIsland // // Replaces the legacy polling-based Focus detection with // NSDistributedNotificationCenter-backed monitoring. // import AppKit import Combine import Defaults import Foundation import SwiftUI final class DoNotDisturbManager: ObservableObject { static let shared = DoNotDisturbManager() @Published private(set) var isMonitoring = false @Published var isDoNotDisturbActive = false @Published var currentFocusModeName: String = "" @Published var currentFocusModeIdentifier: String = "" private let notificationCenter = DistributedNotificationCenter.default() private let metadataExtractionQueue = DispatchQueue(label: "com.dynamicisland.focus.metadata", qos: .userInitiated) private let focusStateResolver = FocusStateResolver() private let focusLogStream = FocusLogStream() private init() { focusLogStream.onMetadataUpdate = { [weak self] identifier, name in self?.handleLogMetadataUpdate(identifier: identifier, name: name) } } deinit { stopMonitoring() } func startMonitoring() { guard !isMonitoring else { return } notificationCenter.addObserver( self, selector: #selector(handleFocusEnabled(_:)), name: .focusModeEnabled, object: nil, suspensionBehavior: .deliverImmediately ) notificationCenter.addObserver( self, selector: #selector(handleFocusDisabled(_:)), name: .focusModeDisabled, object: nil, suspensionBehavior: .deliverImmediately ) focusLogStream.start() isMonitoring = true } func stopMonitoring() { guard isMonitoring else { return } notificationCenter.removeObserver(self, name: .focusModeEnabled, object: nil) notificationCenter.removeObserver(self, name: .focusModeDisabled, object: nil) focusLogStream.stop() isMonitoring = false DispatchQueue.main.async { self.isDoNotDisturbActive = false self.currentFocusModeIdentifier = "" self.currentFocusModeName = "" } } @objc private func handleFocusEnabled(_ notification: Notification) { apply(notification: notification, isActive: true) } @objc private func handleFocusDisabled(_ notification: Notification) { apply(notification: notification, isActive: false) } private func apply(notification: Notification, isActive: Bool) { metadataExtractionQueue.async { [weak self] in guard let self = self else { return } let metadata = self.extractMetadata(from: notification) self.publishMetadata(identifier: metadata.identifier, name: metadata.name, isActive: isActive, source: notification.name.rawValue) } } private func publishMetadata(identifier: String?, name: String?, isActive: Bool?, source: String) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } let trimmedIdentifier = identifier?.trimmingCharacters(in: .whitespacesAndNewlines) let trimmedName = name?.trimmingCharacters(in: .whitespacesAndNewlines) let resolvedMode = FocusModeType.resolve(identifier: trimmedIdentifier, name: trimmedName) let finalIdentifier: String if let identifier = trimmedIdentifier, !identifier.isEmpty { finalIdentifier = identifier } else { finalIdentifier = resolvedMode.rawValue } let finalName: String if let name = trimmedName, !name.isEmpty { finalName = name } else if !resolvedMode.displayName.isEmpty { finalName = resolvedMode.displayName } else if let identifier = trimmedIdentifier, !identifier.isEmpty { finalName = identifier } else { finalName = "Focus" } let previousIdentifier = self.currentFocusModeIdentifier let previousName = self.currentFocusModeName let previousActive = self.isDoNotDisturbActive let identifierChanged = finalIdentifier != previousIdentifier let nameChanged = finalName != previousName let shouldToggleActive = isActive.map { $0 != previousActive } ?? false if identifierChanged { self.currentFocusModeIdentifier = finalIdentifier } if nameChanged { self.currentFocusModeName = finalName } if identifierChanged || nameChanged || shouldToggleActive { debugPrint("[DoNotDisturbManager] Focus update -> source: \(source) | identifier: \(trimmedIdentifier ?? "") | name: \(trimmedName ?? "") | resolved: \(resolvedMode.rawValue)") } guard let isActive = isActive, shouldToggleActive else { return } withAnimation(.smooth(duration: 0.25)) { self.isDoNotDisturbActive = isActive } } } private func handleLogMetadataUpdate(identifier: String?, name: String?) { metadataExtractionQueue.async { [weak self] in guard let self = self else { return } let trimmedIdentifier = identifier?.trimmingCharacters(in: .whitespacesAndNewlines) let trimmedName = name?.trimmingCharacters(in: .whitespacesAndNewlines) let hasIdentifier = (trimmedIdentifier?.isEmpty == false) let hasName = (trimmedName?.isEmpty == false) guard hasIdentifier || hasName else { return } self.publishMetadata(identifier: trimmedIdentifier, name: trimmedName, isActive: nil, source: "log-stream") } } private func extractMetadata(from notification: Notification) -> (name: String?, identifier: String?) { var identifier: String? var name: String? let identifierKeys = [ "FocusModeIdentifier", "focusModeIdentifier", "FocusModeUUID", "focusModeUUID", "UUID", "uuid", "identifier", "Identifier" ] let nameKeys = [ "FocusModeName", "focusModeName", "FocusMode", "focusMode", "displayName", "display_name", "name", "Name" ] var candidates: [Any] = [] if let userInfo = notification.userInfo { candidates.append(userInfo) } if let object = notification.object { candidates.append(object) } debugPrint("[DoNotDisturbManager] raw focus payload -> name: \(notification.name.rawValue), object: \(String(describing: notification.object)), userInfo: \(String(describing: notification.userInfo))") for candidate in candidates { if identifier == nil { identifier = firstMatch(for: identifierKeys, in: candidate) } if name == nil { name = firstMatch(for: nameKeys, in: candidate) } if identifier != nil && name != nil { break } } if identifier == nil || name == nil { for candidate in candidates { if let decoded = decodeFocusPayloadIfNeeded(candidate) { if identifier == nil { identifier = firstMatch(for: identifierKeys, in: decoded) } if name == nil { name = firstMatch(for: nameKeys, in: decoded) } if identifier != nil && name != nil { break } } } } if identifier == nil || name == nil { for candidate in candidates { if let object = candidate as? NSObject { if identifier == nil, let extractedIdentifier = extractIdentifier(fromFocusObject: object) { identifier = extractedIdentifier } if name == nil, let extractedName = extractDisplayName(fromFocusObject: object) { name = extractedName } if identifier != nil && name != nil { break } } } } if identifier == nil || name == nil { var descriptionSources: [Any] = candidates for candidate in candidates { if let decoded = decodeFocusPayloadIfNeeded(candidate) { descriptionSources.append(decoded) } } for candidate in descriptionSources { let description = String(describing: candidate) if identifier == nil, let inferredIdentifier = FocusMetadataDecoder.extractIdentifier(from: description) { identifier = inferredIdentifier } if name == nil, let inferredName = FocusMetadataDecoder.extractName(from: description) { name = inferredName } if identifier != nil && name != nil { break } } } if identifier == nil || name == nil { if let logMetadata = focusLogStream.latestMetadata() { if identifier == nil { identifier = logMetadata.identifier } if name == nil { name = logMetadata.name } } } if identifier == nil || name == nil { if let fallback = focusStateResolver.loadActiveFocusState(identifierMissing: identifier == nil, nameMissing: name == nil) { if identifier == nil { identifier = fallback.identifier } if name == nil { name = fallback.name } } } return (name, identifier) } } private extension Notification.Name { static let focusModeEnabled = Notification.Name("_NSDoNotDisturbEnabledNotification") static let focusModeDisabled = Notification.Name("_NSDoNotDisturbDisabledNotification") } // MARK: - Focus Mode Types enum FocusModeType: String, CaseIterable { case doNotDisturb = "com.apple.donotdisturb.mode" case work = "com.apple.focus.work" case personal = "com.apple.focus.personal" case sleep = "com.apple.focus.sleep" case driving = "com.apple.focus.driving" case fitness = "com.apple.focus.fitness" case gaming = "com.apple.focus.gaming" case mindfulness = "com.apple.focus.mindfulness" case reading = "com.apple.focus.reading" case custom = "com.apple.focus.custom" case unknown = "" var displayName: String { switch self { case .doNotDisturb: return "Do Not Disturb" case .work: return "Work" case .personal: return "Personal" case .sleep: return "Sleep" case .driving: return "Driving" case .fitness: return "Fitness" case .gaming: return "Gaming" case .mindfulness: return "Mindfulness" case .reading: return "Reading" case .custom: return "Focus" case .unknown: return "Focus Mode" } } var sfSymbol: String { switch self { case .doNotDisturb: return "moon.fill" case .work: return "briefcase.fill" case .personal: return "person.fill" case .sleep: return "bed.double.fill" case .driving: return "car.fill" case .fitness: return "figure.run" case .gaming: return "gamecontroller.fill" case .mindfulness: return "brain.head.profile" case .reading: return "book.fill" case .custom: return "app.badge" case .unknown: return "moon.fill" } } var accentColor: Color { switch self { case .doNotDisturb: return Color(red: 0.370, green: 0.360, blue: 0.902) case .work: return Color(red: 0.133, green: 0.475, blue: 0.992) case .personal: return Color(red: 0.937, green: 0.282, blue: 0.624) case .sleep: return Color(red: 0.341, green: 0.384, blue: 0.980) case .driving: return Color(red: 0.988, green: 0.561, blue: 0.153) case .fitness: return Color(red: 0.176, green: 0.804, blue: 0.459) case .gaming: return Color(red: 0.639, green: 0.329, blue: 0.937) case .mindfulness: return Color(red: 0.239, green: 0.718, blue: 0.682) case .reading: return Color(red: 0.239, green: 0.596, blue: 0.965) case .custom: return Color(red: 0.513, green: 0.478, blue: 0.965) case .unknown: return Color(red: 0.370, green: 0.360, blue: 0.902) } } var inactiveSymbol: String { switch self { case .doNotDisturb: return "moon.circle.fill" default: return sfSymbol } } } extension FocusModeType { init(identifier: String) { let normalized = identifier.trimmingCharacters(in: .whitespacesAndNewlines) let normalizedLowercased = normalized.lowercased() guard !normalized.isEmpty else { self = .doNotDisturb return } if let direct = FocusModeType(rawValue: normalized) ?? FocusModeType(rawValue: normalizedLowercased) { self = direct return } if let resolved = FocusModeType.allCases.first(where: { guard !$0.rawValue.isEmpty else { return false } return normalized.hasPrefix($0.rawValue) || normalizedLowercased.hasPrefix($0.rawValue) }) { self = resolved return } if normalizedLowercased.hasPrefix("com.apple.focus.") { self = .custom return } self = .doNotDisturb } static func resolve(identifier: String?, name: String?) -> FocusModeType { let trimmedName = name?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" if !trimmedName.isEmpty { if let match = FocusModeType.allCases.first(where: { guard !$0.displayName.isEmpty else { return false } return $0.displayName.compare(trimmedName, options: [.caseInsensitive, .diacriticInsensitive]) == .orderedSame }) { return match } } let trimmedIdentifier = identifier?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" if !trimmedIdentifier.isEmpty { return FocusModeType(identifier: trimmedIdentifier) } return .doNotDisturb } } // MARK: - Metadata helpers private extension DoNotDisturbManager { func firstMatch(for keys: [String], in value: Any) -> String? { if let dictionary = value as? [AnyHashable: Any] { for key in keys { if let candidate = dictionary[key], let string = normalizedString(from: candidate) { return string } } for nestedValue in dictionary.values { if let nestedMatch = firstMatch(for: keys, in: nestedValue) { return nestedMatch } } } else if let array = value as? [Any] { for element in array { if let nestedMatch = firstMatch(for: keys, in: element) { return nestedMatch } } } return nil } func normalizedString(from value: Any) -> String? { switch value { case let string as String: let cleaned = FocusMetadataDecoder.cleanedString(string) return cleaned.isEmpty ? nil : cleaned case let number as NSNumber: return FocusMetadataDecoder.cleanedString(number.stringValue) case let uuid as UUID: return uuid.uuidString case let uuid as NSUUID: return uuid.uuidString case let data as Data: if let decoded = decodeFocusPayload(from: data) { if let nested = firstMatch(for: ["identifier", "Identifier", "uuid", "UUID"], in: decoded) { return nested } if let name = firstMatch(for: ["name", "Name", "displayName", "display_name"], in: decoded) { return name } } if let string = String(data: data, encoding: .utf8) { let cleaned = FocusMetadataDecoder.cleanedString(string) return cleaned.isEmpty ? nil : cleaned } return nil case let dict as [AnyHashable: Any]: // Attempt to pull common keys from nested dictionaries if let nested = firstMatch(for: ["identifier", "Identifier", "uuid", "UUID"], in: dict) { return nested } if let name = firstMatch(for: ["name", "Name", "displayName", "display_name"], in: dict) { return name } return nil default: return nil } } func decodeFocusPayloadIfNeeded(_ value: Any) -> Any? { switch value { case let data as Data: return decodeFocusPayload(from: data) case let data as NSData: return decodeFocusPayload(from: data as Data) default: return nil } } func decodeFocusPayload(from data: Data) -> Any? { guard !data.isEmpty else { return nil } if let propertyList = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) { return propertyList } if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]) { return jsonObject } if let string = String(data: data, encoding: .utf8) { let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines) return trimmed.isEmpty ? nil : trimmed } return nil } func extractIdentifier(fromFocusObject object: NSObject) -> String? { if let array = object as? [Any] { for element in array { if let nested = element as? NSObject, let identifier = extractIdentifier(fromFocusObject: nested) { return identifier } } return nil } if let identifier = focusString(object, selector: "modeIdentifier") { return identifier } if let identifier = focusString(object, selector: "identifier") { return identifier } if let details = focusObject(object, selector: "details"), let identifier = extractIdentifier(fromFocusObject: details) { return identifier } if let metadata = focusObject(object, selector: "activeModeAssertionMetadata"), let identifier = extractIdentifier(fromFocusObject: metadata) { return identifier } if let configuration = focusObject(object, selector: "activeModeConfiguration"), let identifier = extractIdentifier(fromFocusObject: configuration) { return identifier } if let modeConfiguration = focusObject(object, selector: "modeConfiguration"), let identifier = extractIdentifier(fromFocusObject: modeConfiguration) { return identifier } if let mode = focusObject(object, selector: "mode") { return extractIdentifier(fromFocusObject: mode) } if let identifiers = focusObject(object, selector: "activeModeIdentifiers") { if let stringArray = identifiers as? [String] { if let first = stringArray.compactMap({ FocusMetadataDecoder.cleanedString($0) }).first(where: { !$0.isEmpty }) { return first } } else if let array = identifiers as? NSArray { for case let string as String in array { let trimmed = FocusMetadataDecoder.cleanedString(string) if !trimmed.isEmpty { return trimmed } } } } return nil } func extractDisplayName(fromFocusObject object: NSObject) -> String? { if let array = object as? [Any] { for element in array { if let nested = element as? NSObject, let name = extractDisplayName(fromFocusObject: nested) { return name } } return nil } if let name = focusString(object, selector: "name") { return name } if let name = focusString(object, selector: "displayName") { return name } if let name = focusString(object, selector: "activityDisplayName") { return name } if let descriptor = focusObject(object, selector: "symbolDescriptor"), let name = focusString(descriptor, selector: "name") { return name } if let mode = focusObject(object, selector: "mode"), let name = extractDisplayName(fromFocusObject: mode) { return name } if let details = focusObject(object, selector: "details"), let name = extractDisplayName(fromFocusObject: details) { return name } if let configuration = focusObject(object, selector: "modeConfiguration"), let name = extractDisplayName(fromFocusObject: configuration) { return name } return nil } func focusObject(_ object: NSObject, selector selectorName: String) -> NSObject? { let selector = NSSelectorFromString(selectorName) guard object.responds(to: selector) else { return nil } guard let value = object.perform(selector)?.takeUnretainedValue() else { return nil } return value as? NSObject } func focusString(_ object: NSObject, selector selectorName: String) -> String? { let selector = NSSelectorFromString(selectorName) guard object.responds(to: selector) else { return nil } guard let value = object.perform(selector)?.takeUnretainedValue() else { return nil } switch value { case let string as String: return FocusMetadataDecoder.cleanedString(string) case let string as NSString: return FocusMetadataDecoder.cleanedString(string as String) case let number as NSNumber: return FocusMetadataDecoder.cleanedString(number.stringValue) default: return nil } } } // MARK: - Filesystem fallback for Focus metadata private final class FocusStateResolver { private struct CacheKey: Hashable { let url: URL } private struct FocusRecord { let identifier: String?; let name: String? } private let fileManager = FileManager.default private let identifierKeys = [ "FocusModeIdentifier", "focusModeIdentifier", "FocusModeUUID", "focusModeUUID", "UUID", "uuid", "identifier", "Identifier", "activeModeIdentifier", "activeModeUUID", "modeIdentifier", "modeUUID" ] private let nameKeys = [ "FocusModeName", "focusModeName", "FocusMode", "focusMode", "displayName", "display_name", "name", "Name", "modeName" ] private var cache: [CacheKey: (modified: Date, records: [FocusRecord])] = [:] private lazy var activeStateURLs: [URL] = { let home = fileManager.homeDirectoryForCurrentUser return [ "Library/DoNotDisturb/ActiveState.plist", "Library/DoNotDisturb/DB/ActiveState.plist", "Library/Focus/ActiveState.plist" ].map { home.appendingPathComponent($0) } }() private lazy var catalogURLs: [URL] = { let home = fileManager.homeDirectoryForCurrentUser return [ "Library/DoNotDisturb/DB/FocusPreferences.plist", "Library/DoNotDisturb/DB/FocusModeDefaults.plist", "Library/DoNotDisturb/DB/FocusModes.plist", "Library/Focus/FocusModeDefaults.plist", "Library/Preferences/com.apple.controlcenter.plist" ].map { home.appendingPathComponent($0) } }() func loadActiveFocusState(identifierMissing: Bool, nameMissing: Bool) -> (identifier: String?, name: String?)? { guard identifierMissing || nameMissing else { return nil } let activeRecords = loadRecords(from: activeStateURLs) let catalogRecords = loadRecords(from: catalogURLs) let allRecords = activeRecords + catalogRecords guard !allRecords.isEmpty else { return nil } var identifier: String? var name: String? if let record = activeRecords.first(where: { record in guard let id = record.identifier, !id.isEmpty else { return false } guard let name = record.name, !name.isEmpty else { return false } return true }) { identifier = record.identifier name = record.name } if identifierMissing && (identifier == nil || identifier?.isEmpty == true) { identifier = activeRecords.compactMap { $0.identifier?.trimmingCharacters(in: .whitespacesAndNewlines) } .first(where: { !$0.isEmpty }) if identifier == nil { identifier = catalogRecords.compactMap { $0.identifier?.trimmingCharacters(in: .whitespacesAndNewlines) } .first(where: { !$0.isEmpty }) } } if nameMissing && (name == nil || name?.isEmpty == true) { if let id = identifier { name = allRecords.first(where: { guard let candidateIdentifier = $0.identifier else { return false } return candidateIdentifier.compare(id, options: [.caseInsensitive, .diacriticInsensitive]) == .orderedSame && ($0.name?.isEmpty == false) })?.name } if name == nil { name = activeRecords.compactMap { $0.name?.trimmingCharacters(in: .whitespacesAndNewlines) } .first(where: { !$0.isEmpty }) } if name == nil { name = catalogRecords.compactMap { $0.name?.trimmingCharacters(in: .whitespacesAndNewlines) } .first(where: { !$0.isEmpty }) } } if identifier == nil && name == nil { return nil } return (identifierMissing ? identifier : nil, nameMissing ? name : nil) } private func loadRecords(from urls: [URL]) -> [FocusRecord] { var records: [FocusRecord] = [] for url in urls { guard fileManager.fileExists(atPath: url.path) else { continue } if let cached = cachedRecords(for: url) { records.append(contentsOf: cached) continue } guard let attributes = try? fileManager.attributesOfItem(atPath: url.path), let modified = attributes[.modificationDate] as? Date, let data = try? Data(contentsOf: url) else { continue } let decoded = decodeData(data) let extracted = extractRecords(from: decoded) cache[CacheKey(url: url)] = (modified, extracted) records.append(contentsOf: extracted) } return records } private func cachedRecords(for url: URL) -> [FocusRecord]? { let key = CacheKey(url: url) guard let cached = cache[key], let attributes = try? fileManager.attributesOfItem(atPath: url.path), let modified = attributes[.modificationDate] as? Date, cached.modified == modified else { cache.removeValue(forKey: key) return nil } return cached.records } private func decodeData(_ data: Data) -> Any { if let propertyList = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) { return propertyList } if let unarchived = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSDictionary.self, NSArray.self, NSString.self, NSNumber.self, NSData.self], from: data) { return unarchived as Any } if let unarchived = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) { return unarchived as Any } if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]) { return jsonObject } if let string = String(data: data, encoding: .utf8) { return string } return [:] } private func extractRecords(from value: Any) -> [FocusRecord] { var results: [FocusRecord] = [] if let dictionary = value as? [AnyHashable: Any] { let identifier = directMatch(in: dictionary, keys: identifierKeys) let name = directMatch(in: dictionary, keys: nameKeys) if identifier != nil || name != nil { results.append(FocusRecord(identifier: identifier, name: name)) } for nested in dictionary.values { results.append(contentsOf: extractRecords(from: nested)) } } else if let array = value as? [Any] { for element in array { results.append(contentsOf: extractRecords(from: element)) } } else if let data = value as? Data { let decoded = decodeData(data) results.append(contentsOf: extractRecords(from: decoded)) } else if let data = value as? NSData { let decoded = decodeData(data as Data) results.append(contentsOf: extractRecords(from: decoded)) } else if let string = value as? String { let cleaned = FocusMetadataDecoder.cleanedString(string) if !cleaned.isEmpty { let identifier = matchIdentifier(in: cleaned) let name = identifier == nil ? matchName(in: cleaned) : matchName(in: cleaned, matching: identifier) if identifier != nil || name != nil { results.append(FocusRecord(identifier: identifier, name: name)) } } } return results } private func directMatch(in dictionary: [AnyHashable: Any], keys: [String]) -> String? { for key in keys { if let candidate = dictionary[key], let string = normalizedString(from: candidate) { return string } } return nil } private func normalizedString(from value: Any) -> String? { switch value { case let string as String: return FocusMetadataDecoder.cleanedString(string) case let string as NSString: return FocusMetadataDecoder.cleanedString(string as String) case let number as NSNumber: return FocusMetadataDecoder.cleanedString(number.stringValue) case let data as Data: if let string = String(data: data, encoding: .utf8) { return FocusMetadataDecoder.cleanedString(string) } return nil case let data as NSData: return normalizedString(from: data as Data) default: return nil } } private func matchIdentifier(in string: String) -> String? { FocusMetadataDecoder.extractIdentifier(from: string) } private func matchName(in string: String, matching identifier: String? = nil) -> String? { if let name = FocusMetadataDecoder.extractName(from: string) { return name } if let identifier = identifier { let components = identifier.split(separator: ".") if let last = components.last { let derived = FocusMetadataDecoder.cleanedString(String(last).replacingOccurrences(of: "_", with: " ")) if !derived.isEmpty { return derived.capitalized } } } return nil } } private final class FocusLogStream { private let queue = DispatchQueue(label: "com.dynamicisland.focus.logstream", qos: .utility) private var process: Process? private var pipe: Pipe? private var buffer = Data() private var isRunning = false private let metadataLock = NSLock() private var lastIdentifier: String? private var lastName: String? var onMetadataUpdate: ((String?, String?) -> Void)? func start() { queue.async { [weak self] in guard let self = self else { return } guard !self.isRunning else { return } let process = Process() process.executableURL = URL(fileURLWithPath: "/usr/bin/log") process.arguments = [ "stream", "--style", "compact", "--level", "info", "--predicate", "subsystem == \"com.apple.focus\"" ] let pipe = Pipe() process.standardOutput = pipe process.standardError = pipe pipe.fileHandleForReading.readabilityHandler = { [weak self] handle in guard let self else { return } let data = handle.availableData if data.isEmpty { self.queue.async { [weak self] in self?.handleTermination() } return } self.queue.async { [weak self] in self?.handleIncomingData(data) } } process.terminationHandler = { [weak self] _ in self?.queue.async { self?.handleTermination() } } do { try process.run() self.process = process self.pipe = pipe self.isRunning = true debugPrint("[FocusLogStream] Started unified log tail for com.apple.focus") } catch { debugPrint("[FocusLogStream] Failed to start log stream: \(error)") pipe.fileHandleForReading.readabilityHandler = nil self.process = nil self.pipe = nil } } } func stop() { queue.async { [weak self] in guard let self else { return } guard self.isRunning else { return } self.handleTermination(terminateProcess: true) } } func latestMetadata() -> (identifier: String?, name: String?)? { metadataLock.lock() let identifier = lastIdentifier?.trimmingCharacters(in: .whitespacesAndNewlines) let name = lastName?.trimmingCharacters(in: .whitespacesAndNewlines) metadataLock.unlock() let normalizedIdentifier = (identifier?.isEmpty == false) ? identifier : nil let normalizedName = (name?.isEmpty == false) ? name : nil if normalizedIdentifier == nil && normalizedName == nil { return nil } return (normalizedIdentifier, normalizedName) } private func handleIncomingData(_ data: Data) { buffer.append(data) let newline: UInt8 = 0x0A while let newlineIndex = buffer.firstIndex(of: newline) { let lineData = buffer.prefix(upTo: newlineIndex) buffer.removeSubrange(buffer.startIndex...newlineIndex) let trimmedLineData: Data if let lastByte = lineData.last, lastByte == 0x0D { trimmedLineData = lineData.dropLast() } else { trimmedLineData = lineData } guard !trimmedLineData.isEmpty, let line = String(data: trimmedLineData, encoding: .utf8) else { continue } processLine(line) } } private func processLine(_ line: String) { let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return } if trimmed.hasPrefix("Filtering the log data") || trimmed.hasPrefix("Timestamp") { return } if trimmed.contains("active mode assertion: (null)") || trimmed.contains("active activity: (null)") { clearMetadata() return } var updatedIdentifier: String? var updatedName: String? if let identifier = FocusMetadataDecoder.extractIdentifier(from: trimmed), !identifier.isEmpty { updatedIdentifier = identifier } if let name = FocusMetadataDecoder.extractName(from: trimmed), !name.isEmpty { updatedName = name } guard updatedIdentifier != nil || updatedName != nil else { return } var identifierToSend: String? var nameToSend: String? metadataLock.lock() if let identifier = updatedIdentifier, !identifier.isEmpty { lastIdentifier = identifier } if let name = updatedName, !name.isEmpty { lastName = name } identifierToSend = lastIdentifier nameToSend = lastName metadataLock.unlock() notifyMetadataUpdate(identifier: identifierToSend, name: nameToSend) } private func clearMetadata() { metadataLock.lock() lastIdentifier = nil lastName = nil metadataLock.unlock() notifyMetadataUpdate(identifier: nil, name: nil) } private func handleTermination(terminateProcess: Bool = false) { if terminateProcess, let process, process.isRunning { process.terminate() } pipe?.fileHandleForReading.readabilityHandler = nil pipe?.fileHandleForReading.closeFile() pipe = nil process = nil buffer.removeAll(keepingCapacity: false) isRunning = false clearMetadata() debugPrint("[FocusLogStream] Stopped unified log tail for com.apple.focus") } private func notifyMetadataUpdate(identifier: String?, name: String?) { guard let handler = onMetadataUpdate else { return } handler(identifier, name) } } private enum FocusNotificationParsing { static let identifierPattern: NSRegularExpression? = { let pattern = "com\\.apple\\.(?:focus|donotdisturb)[A-Za-z0-9_.-]*" return try? NSRegularExpression(pattern: pattern, options: []) }() static let identifierDetailPatterns: [NSRegularExpression] = { let patterns = [ "modeIdentifier:\\s*'([^'\\s]+)'", "activityIdentifier:\\s*([A-Za-z0-9._-]+)" ] return patterns.compactMap { try? NSRegularExpression(pattern: $0, options: []) } }() static let namePatterns: [NSRegularExpression] = { let patterns = [ "(?i)(?:focusModeName|focusMode|displayName|name)\\s*=\\s*\"([^\"]+)\"", "(?i)(?:focusModeName|focusMode|displayName|name)\\s*=\\s*([^;\\n]+)", "activityDisplayName:\\s*([^;>\\n]+)", "modeIdentifier:\\s*'com\\.apple\\.focus\\.([A-Za-z0-9._-]+)'" ] return patterns.compactMap { try? NSRegularExpression(pattern: $0, options: []) } }() } private enum FocusMetadataDecoder { static func cleanedString(_ string: String) -> String { var trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines) trimmed = trimmed.trimmingCharacters(in: CharacterSet(charactersIn: "'\"")) return trimmed } static func extractIdentifier(from description: String) -> String? { let fullRange = NSRange(description.startIndex.. 0, let identifierRange = Range(match.range(at: 0), in: description) { let candidate = cleanedString(String(description[identifierRange])) if !candidate.isEmpty { return candidate } } for regex in FocusNotificationParsing.identifierDetailPatterns { if let match = regex.firstMatch(in: description, options: [], range: fullRange), match.numberOfRanges > 1, let identifierRange = Range(match.range(at: 1), in: description) { let candidate = cleanedString(String(description[identifierRange])) if !candidate.isEmpty { return candidate } } } return nil } static func extractName(from description: String) -> String? { let fullRange = NSRange(description.startIndex.. 1, let nameRange = Range(match.range(at: 1), in: description) { let candidate = cleanedString(String(description[nameRange])) if !candidate.isEmpty { return candidate } } } return nil } }