diff --git a/DynamicIsland/managers/DoNotDisturbManager.swift b/DynamicIsland/managers/DoNotDisturbManager.swift index 9e3aa14..6afdec3 100644 --- a/DynamicIsland/managers/DoNotDisturbManager.swift +++ b/DynamicIsland/managers/DoNotDisturbManager.swift @@ -516,12 +516,59 @@ private extension DoNotDisturbManager { if mode == .useDevTools { stopAssertionsPolling() focusLogStream.start() + checkInitialFocusStateViaLog() } else { focusLogStream.stop() startAssertionsPolling() } } + /// When using the log-stream mode, `log stream` only delivers future events — it misses any + /// focus mode that was already active when Atoll launched. This one-shot `log show` reads + /// recent duetexpertd debug logs to find the most recent `semanticModeIdentifier` event and + /// seeds the initial focus state from it. Tries progressively larger windows so the common + /// case (focus toggled recently) resolves in ~1-2s without scanning a full day of logs. + private func checkInitialFocusStateViaLog() { + metadataExtractionQueue.async { [weak self] in + guard let self else { return } + + for window in ["5m", "1h", "24h"] { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/bin/log") + task.arguments = [ + "show", + "--last", window, + "--debug", + "--style", "compact", + "--predicate", "process == \"duetexpertd\" AND eventMessage CONTAINS \"semanticModeIdentifier\"" + ] + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = Pipe() + + guard (try? task.run()) != nil else { return } + task.waitUntilExit() + + let output = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? "" + let lines = output.components(separatedBy: "\n").filter { + $0.contains("semanticModeIdentifier") && !$0.hasPrefix("Filtering") + } + + guard let lastLine = lines.last(where: { !$0.isEmpty }) else { continue } + + // starting: 0 means focus ended — nothing to activate. + guard !lastLine.contains("starting: 0") else { return } + + let identifier = FocusMetadataDecoder.extractIdentifier(from: lastLine) + let name = FocusMetadataDecoder.extractName(from: lastLine) + guard identifier != nil || name != nil else { return } + + self.publishMetadata(identifier: identifier, name: name, isActive: true, source: "log-initial") + return + } + } + } + func startAssertionsPolling() { stopAssertionsPolling() lastAssertionsModificationDate = nil