From 7aefcab8b023051d14707496a83a3af32d3fc3ee Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 9 Dec 2025 03:46:52 +0100 Subject: [PATCH] Health: clean degraded message; PTT hotkey monitors --- apps/macos/Sources/Clawdis/HealthStore.swift | 12 ++++++++- .../Sources/Clawdis/MenuContentView.swift | 3 ++- .../Sources/Clawdis/VoicePushToTalk.swift | 26 ++++++++++++++----- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/apps/macos/Sources/Clawdis/HealthStore.swift b/apps/macos/Sources/Clawdis/HealthStore.swift index d2492a930..f4dc83df4 100644 --- a/apps/macos/Sources/Clawdis/HealthStore.swift +++ b/apps/macos/Sources/Clawdis/HealthStore.swift @@ -164,7 +164,7 @@ final class HealthStore: ObservableObject { return nil } - private func describeFailure(from snap: HealthSnapshot, fallback: String?) -> String { + func describeFailure(from snap: HealthSnapshot, fallback: String?) -> String { if !snap.web.linked { return "Not linked — run clawdis login" } @@ -185,6 +185,16 @@ final class HealthStore: ObservableObject { } return "health probe failed" } + + var degradedSummary: String? { + guard case let .degraded(reason) = self.state else { return nil } + if reason == "[object Object]" || reason.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, + let snap = self.snapshot + { + return self.describeFailure(from: snap, fallback: reason) + } + return reason + } } func msToAge(_ ms: Double) -> String { diff --git a/apps/macos/Sources/Clawdis/MenuContentView.swift b/apps/macos/Sources/Clawdis/MenuContentView.swift index 94fcc73a4..f8431de6c 100644 --- a/apps/macos/Sources/Clawdis/MenuContentView.swift +++ b/apps/macos/Sources/Clawdis/MenuContentView.swift @@ -98,8 +98,9 @@ struct MenuContent: View { label = "Health: login required" color = .red case let .degraded(reason): + let detail = HealthStore.shared.degradedSummary ?? reason let ageText = lastAge.map { " · checked \($0)" } ?? "" - label = "Health degraded: \(reason)\(ageText)" + label = "Health degraded: \(detail)\(ageText)" color = .orange case .unknown: label = "Health pending" diff --git a/apps/macos/Sources/Clawdis/VoicePushToTalk.swift b/apps/macos/Sources/Clawdis/VoicePushToTalk.swift index 0a1d86100..5dc3123d7 100644 --- a/apps/macos/Sources/Clawdis/VoicePushToTalk.swift +++ b/apps/macos/Sources/Clawdis/VoicePushToTalk.swift @@ -8,7 +8,8 @@ import Speech final class VoicePushToTalkHotkey { static let shared = VoicePushToTalkHotkey() - private var monitor: Any? + private var globalMonitor: Any? + private var localMonitor: Any? private var optionDown = false // right option only private var active = false @@ -21,18 +22,27 @@ final class VoicePushToTalkHotkey { } private func startMonitoring() { - guard self.monitor == nil else { return } + guard self.globalMonitor == nil, self.localMonitor == nil else { return } // Listen-only global monitor; we rely on Input Monitoring permission to receive events. - self.monitor = NSEvent.addGlobalMonitorForEvents(matching: .flagsChanged) { [weak self] event in + self.globalMonitor = NSEvent.addGlobalMonitorForEvents(matching: .flagsChanged) { [weak self] event in guard let self else { return } self.updateModifierState(from: event) } + // Also listen locally so we still catch events when the app is active/focused. + self.localMonitor = NSEvent.addLocalMonitorForEvents(matching: .flagsChanged) { [weak self] event in + self?.updateModifierState(from: event) + return event + } } private func stopMonitoring() { - if let monitor { - NSEvent.removeMonitor(monitor) - self.monitor = nil + if let globalMonitor { + NSEvent.removeMonitor(globalMonitor) + self.globalMonitor = nil + } + if let localMonitor { + NSEvent.removeMonitor(localMonitor) + self.localMonitor = nil } self.optionDown = false self.active = false @@ -48,11 +58,15 @@ final class VoicePushToTalkHotkey { if chordActive && !self.active { self.active = true Task { + Logger(subsystem: "com.steipete.clawdis", category: "voicewake.ptt") + .info("ptt hotkey down") await VoicePushToTalk.shared.begin() } } else if !chordActive && self.active { self.active = false Task { + Logger(subsystem: "com.steipete.clawdis", category: "voicewake.ptt") + .info("ptt hotkey up") await VoicePushToTalk.shared.end() } }