mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 23:54:32 +00:00
Working stuff
This commit is contained in:
parent
fbcce0f587
commit
cbc6ad875e
1 changed files with 135 additions and 39 deletions
|
|
@ -50,6 +50,13 @@ final class SystemTimerBridge {
|
||||||
let paused: Bool
|
let paused: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct LogTimerEntry {
|
||||||
|
let identifier: String
|
||||||
|
let state: String?
|
||||||
|
let title: String?
|
||||||
|
let duration: Double?
|
||||||
|
}
|
||||||
|
|
||||||
private let domain = "com.apple.mobiletimerd" as CFString
|
private let domain = "com.apple.mobiletimerd" as CFString
|
||||||
private let preferencesPath = (NSHomeDirectory() as NSString)
|
private let preferencesPath = (NSHomeDirectory() as NSString)
|
||||||
.appendingPathComponent("Library/Preferences/com.apple.mobiletimerd.plist")
|
.appendingPathComponent("Library/Preferences/com.apple.mobiletimerd.plist")
|
||||||
|
|
@ -59,6 +66,7 @@ final class SystemTimerBridge {
|
||||||
private var initialTotalDuration: TimeInterval?
|
private var initialTotalDuration: TimeInterval?
|
||||||
private var latestRemaining: TimeInterval?
|
private var latestRemaining: TimeInterval?
|
||||||
private var latestPaused: Bool = false
|
private var latestPaused: Bool = false
|
||||||
|
private var latestUpdateTimestamp: Date?
|
||||||
|
|
||||||
private var logProcess: Process?
|
private var logProcess: Process?
|
||||||
private var logPipe: Pipe?
|
private var logPipe: Pipe?
|
||||||
|
|
@ -164,6 +172,7 @@ final class SystemTimerBridge {
|
||||||
self.metadata = nil
|
self.metadata = nil
|
||||||
self.latestRemaining = nil
|
self.latestRemaining = nil
|
||||||
self.latestPaused = false
|
self.latestPaused = false
|
||||||
|
self.latestUpdateTimestamp = nil
|
||||||
self.logPreferredName = nil
|
self.logPreferredName = nil
|
||||||
self.logPreferredDuration = nil
|
self.logPreferredDuration = nil
|
||||||
self.logIdentifier = nil
|
self.logIdentifier = nil
|
||||||
|
|
@ -415,6 +424,8 @@ final class SystemTimerBridge {
|
||||||
|
|
||||||
logDebug("Applying timer update (remaining: \(remaining), paused: \(paused))")
|
logDebug("Applying timer update (remaining: \(remaining), paused: \(paused))")
|
||||||
|
|
||||||
|
latestUpdateTimestamp = Date()
|
||||||
|
|
||||||
let durationFromMetadata = logPreferredDuration ?? metadata?.duration ?? 0
|
let durationFromMetadata = logPreferredDuration ?? metadata?.duration ?? 0
|
||||||
if let metadata, metadata.state == .running || metadata.state == .paused {
|
if let metadata, metadata.state == .running || metadata.state == .paused {
|
||||||
if durationFromMetadata > 0, (initialTotalDuration ?? 0) < durationFromMetadata {
|
if durationFromMetadata > 0, (initialTotalDuration ?? 0) < durationFromMetadata {
|
||||||
|
|
@ -515,16 +526,50 @@ final class SystemTimerBridge {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleScheduledTimersMessage(_ message: String) {
|
private func handleScheduledTimersMessage(_ message: String) {
|
||||||
guard let identifier = captureFirstMatch(pattern: "TimerID:\\s*([A-F0-9\\-]+)", in: message) else {
|
let entries = parseLogTimerEntries(from: message)
|
||||||
logDebug("Scheduled timers message without identifier; clearing state")
|
guard !entries.isEmpty else {
|
||||||
clearLogState(triggerSmoothClose: true)
|
logDebug("Scheduled timers event without timer entries; preserving state")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logDebug("Scheduled timers message for ID: \(identifier)")
|
let normalizedCurrentId = logIdentifier?.uppercased()
|
||||||
setLogIdentifier(identifier)
|
|
||||||
|
|
||||||
if let title = captureFirstMatch(pattern: "Title:\\s*([^,]+)", in: message) {
|
let selectedEntry: LogTimerEntry?
|
||||||
|
|
||||||
|
if let currentId = normalizedCurrentId,
|
||||||
|
let current = entries.first(where: { $0.identifier == currentId }) {
|
||||||
|
selectedEntry = current
|
||||||
|
} else if let active = entries.first(where: { ($0.state == "running" || $0.state == "paused") }) {
|
||||||
|
selectedEntry = active
|
||||||
|
} else if normalizedCurrentId == nil,
|
||||||
|
let fired = entries.first(where: { $0.state == "fired" }) {
|
||||||
|
selectedEntry = fired
|
||||||
|
} else {
|
||||||
|
selectedEntry = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let entry = selectedEntry else {
|
||||||
|
logDebug("Scheduled timers event did not reference an active timer; keeping existing state")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let state = entry.state {
|
||||||
|
logDebug("Scheduled timers entry for ID \(entry.identifier) with state \(state)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if normalizedCurrentId == nil {
|
||||||
|
if let state = entry.state, state == "running" || state == "paused" || state == "fired" {
|
||||||
|
setLogIdentifier(entry.identifier)
|
||||||
|
}
|
||||||
|
} else if let currentId = normalizedCurrentId, currentId != entry.identifier {
|
||||||
|
if let state = entry.state, state == "running" || state == "paused" {
|
||||||
|
setLogIdentifier(entry.identifier)
|
||||||
|
} else {
|
||||||
|
logDebug("Ignoring timer entry for ID \(entry.identifier) because state is \(entry.state ?? "unknown")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let title = entry.title {
|
||||||
let trimmed = title.trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmed = title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if !trimmed.isEmpty, trimmed != "(null)" {
|
if !trimmed.isEmpty, trimmed != "(null)" {
|
||||||
logPreferredName = trimmed
|
logPreferredName = trimmed
|
||||||
|
|
@ -532,8 +577,7 @@ final class SystemTimerBridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let durationString = captureFirstMatch(pattern: "duration:([0-9]+(?:\\.[0-9]+)?)", in: message),
|
if let duration = entry.duration {
|
||||||
let duration = Double(durationString) {
|
|
||||||
if let existing = logPreferredDuration {
|
if let existing = logPreferredDuration {
|
||||||
logPreferredDuration = max(existing, duration)
|
logPreferredDuration = max(existing, duration)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -547,31 +591,33 @@ final class SystemTimerBridge {
|
||||||
logDebug("Recorded duration from log: \(duration)")
|
logDebug("Recorded duration from log: \(duration)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if let stateString = captureFirstMatch(pattern: "state:([A-Za-z]+)", in: message) {
|
guard let state = entry.state else { return }
|
||||||
let normalized = stateString.lowercased()
|
|
||||||
logDebug("Timer state from log: \(normalized)")
|
switch state {
|
||||||
switch normalized {
|
case "paused":
|
||||||
case "paused":
|
if let remaining = latestRemaining {
|
||||||
|
applyLogDrivenUpdate(remaining: remaining, isPaused: true)
|
||||||
|
} else {
|
||||||
latestPaused = true
|
latestPaused = true
|
||||||
if let remaining = latestRemaining {
|
|
||||||
applyLogDrivenUpdate(remaining: remaining, isPaused: true)
|
|
||||||
}
|
|
||||||
case "running":
|
|
||||||
latestPaused = false
|
|
||||||
if let remaining = latestRemaining {
|
|
||||||
applyLogDrivenUpdate(remaining: remaining, isPaused: false)
|
|
||||||
}
|
|
||||||
case "fired":
|
|
||||||
latestPaused = false
|
|
||||||
applyLogDrivenUpdate(remaining: 0, isPaused: false)
|
|
||||||
return
|
|
||||||
case "stopped":
|
|
||||||
latestPaused = false
|
|
||||||
clearLogState(triggerSmoothClose: true)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
case "running":
|
||||||
|
if let remaining = latestRemaining {
|
||||||
|
applyLogDrivenUpdate(remaining: remaining, isPaused: false)
|
||||||
|
} else {
|
||||||
|
latestPaused = false
|
||||||
|
}
|
||||||
|
case "fired":
|
||||||
|
latestPaused = false
|
||||||
|
applyLogDrivenUpdate(remaining: 0, isPaused: false)
|
||||||
|
case "stopped":
|
||||||
|
latestPaused = false
|
||||||
|
if entry.identifier == normalizedCurrentId {
|
||||||
|
clearLogState(triggerSmoothClose: true)
|
||||||
|
} else {
|
||||||
|
logDebug("Ignoring stopped state for unrelated timer ID: \(entry.identifier)")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -601,14 +647,15 @@ final class SystemTimerBridge {
|
||||||
logDebug("Next timer changed token: \(trimmed)")
|
logDebug("Next timer changed token: \(trimmed)")
|
||||||
|
|
||||||
if trimmed.lowercased().contains("null") {
|
if trimmed.lowercased().contains("null") {
|
||||||
clearLogState(triggerSmoothClose: true)
|
logDebug("Next timer null marker received; retaining current identifier")
|
||||||
} else {
|
return
|
||||||
setLogIdentifier(trimmed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLogIdentifier(trimmed)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleTimerStartedMessage(_ message: String) {
|
private func handleTimerStartedMessage(_ message: String) {
|
||||||
guard let identifier = captureFirstMatch(pattern: "started timer:\\s*([A-F0-9\\-]+)", in: message) else { return }
|
guard let identifier = captureFirstMatch(pattern: "started timer:\\s*([A-Fa-f0-9\\-]+)", in: message) else { return }
|
||||||
logDebug("Timer started for ID: \(identifier)")
|
logDebug("Timer started for ID: \(identifier)")
|
||||||
setLogIdentifier(identifier)
|
setLogIdentifier(identifier)
|
||||||
}
|
}
|
||||||
|
|
@ -632,9 +679,22 @@ final class SystemTimerBridge {
|
||||||
logDebug("Applying duration override: \(durationOverride)")
|
logDebug("Applying duration override: \(durationOverride)")
|
||||||
}
|
}
|
||||||
|
|
||||||
latestPaused = isPaused
|
let previousRemaining = latestRemaining
|
||||||
|
var resolvedPaused = isPaused
|
||||||
|
|
||||||
|
if resolvedPaused {
|
||||||
|
if let previous = previousRemaining, remaining < previous - 0.75 {
|
||||||
|
logDebug("Remaining dropped from \(previous) to \(remaining) despite paused state; treating as running")
|
||||||
|
resolvedPaused = false
|
||||||
|
} else if metadata?.state == .running {
|
||||||
|
logDebug("Metadata reports running; overriding paused state from log")
|
||||||
|
resolvedPaused = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
latestPaused = resolvedPaused
|
||||||
latestRemaining = remaining
|
latestRemaining = remaining
|
||||||
logDebug("Log-driven update (remaining: \(remaining), paused: \(isPaused))")
|
logDebug("Log-driven update (remaining: \(remaining), paused: \(resolvedPaused), rawPaused: \(isPaused))")
|
||||||
|
|
||||||
if let duration = logPreferredDuration, (initialTotalDuration ?? 0) < duration {
|
if let duration = logPreferredDuration, (initialTotalDuration ?? 0) < duration {
|
||||||
initialTotalDuration = duration
|
initialTotalDuration = duration
|
||||||
|
|
@ -644,11 +704,13 @@ final class SystemTimerBridge {
|
||||||
initialTotalDuration = remaining
|
initialTotalDuration = remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
applyTimerUpdate(remaining: remaining, paused: isPaused)
|
applyTimerUpdate(remaining: remaining, paused: resolvedPaused)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setLogIdentifier(_ identifier: String) {
|
private func setLogIdentifier(_ identifier: String) {
|
||||||
let cleaned = identifier.trimmingCharacters(in: CharacterSet(charactersIn: " <>"))
|
let cleaned = identifier
|
||||||
|
.trimmingCharacters(in: CharacterSet(charactersIn: " <>"))
|
||||||
|
.uppercased()
|
||||||
guard !cleaned.isEmpty else { return }
|
guard !cleaned.isEmpty else { return }
|
||||||
|
|
||||||
if logIdentifier != cleaned {
|
if logIdentifier != cleaned {
|
||||||
|
|
@ -672,6 +734,7 @@ final class SystemTimerBridge {
|
||||||
initialTotalDuration = nil
|
initialTotalDuration = nil
|
||||||
latestRemaining = nil
|
latestRemaining = nil
|
||||||
latestPaused = false
|
latestPaused = false
|
||||||
|
latestUpdateTimestamp = nil
|
||||||
logDidCompleteActiveTimer = false
|
logDidCompleteActiveTimer = false
|
||||||
|
|
||||||
guard triggerSmoothClose else { return }
|
guard triggerSmoothClose else { return }
|
||||||
|
|
@ -693,8 +756,8 @@ final class SystemTimerBridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func captureFirstMatch(pattern: String, in text: String) -> String? {
|
private func captureFirstMatch(pattern: String, in text: String, options: NSRegularExpression.Options = []) -> String? {
|
||||||
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil }
|
guard let regex = try? NSRegularExpression(pattern: pattern, options: options) else { return nil }
|
||||||
let range = NSRange(text.startIndex..<text.endIndex, in: text)
|
let range = NSRange(text.startIndex..<text.endIndex, in: text)
|
||||||
guard let match = regex.firstMatch(in: text, options: [], range: range),
|
guard let match = regex.firstMatch(in: text, options: [], range: range),
|
||||||
match.numberOfRanges >= 2,
|
match.numberOfRanges >= 2,
|
||||||
|
|
@ -705,6 +768,38 @@ final class SystemTimerBridge {
|
||||||
return String(text[swiftRange])
|
return String(text[swiftRange])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func parseLogTimerEntries(from message: String) -> [LogTimerEntry] {
|
||||||
|
guard let regex = try? NSRegularExpression(pattern: "<MT(?:Mutable)?Timer:[^>]+>", options: []) else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
let range = NSRange(message.startIndex..<message.endIndex, in: message)
|
||||||
|
let matches = regex.matches(in: message, options: [], range: range)
|
||||||
|
|
||||||
|
var entries: [LogTimerEntry] = []
|
||||||
|
|
||||||
|
for match in matches {
|
||||||
|
guard let swiftRange = Range(match.range, in: message) else { continue }
|
||||||
|
let entryString = String(message[swiftRange])
|
||||||
|
guard let rawIdentifier = captureFirstMatch(pattern: "TimerID:\\s*([A-Fa-f0-9\\-]+)", in: entryString) else { continue }
|
||||||
|
|
||||||
|
let identifier = rawIdentifier.uppercased()
|
||||||
|
let state = captureFirstMatch(pattern: "state:([A-Za-z]+)", in: entryString)?.lowercased()
|
||||||
|
let title = captureFirstMatch(pattern: "Title:\\s*([^,]+)", in: entryString)
|
||||||
|
let durationString = captureFirstMatch(pattern: "duration:([0-9]+(?:\\.[0-9]+)?)", in: entryString)
|
||||||
|
let duration = durationString.flatMap(Double.init)
|
||||||
|
|
||||||
|
entries.append(LogTimerEntry(
|
||||||
|
identifier: identifier,
|
||||||
|
state: state,
|
||||||
|
title: title,
|
||||||
|
duration: duration
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
private func refreshMetadata() {
|
private func refreshMetadata() {
|
||||||
let previous = metadata
|
let previous = metadata
|
||||||
metadata = fetchMetadata()
|
metadata = fetchMetadata()
|
||||||
|
|
@ -935,4 +1030,5 @@ final class SystemTimerBridge {
|
||||||
debugPrint("[SystemTimerBridge] Accessibility permission is required to mirror Clock timers. Grant access in System Settings → Privacy & Security → Accessibility.")
|
debugPrint("[SystemTimerBridge] Accessibility permission is required to mirror Clock timers. Grant access in System Settings → Privacy & Security → Accessibility.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue