import AppKit import Foundation import OSLog enum VoiceWakeChime: Codable, Equatable, Sendable { case none case system(name: String) case custom(displayName: String, bookmark: Data) var systemName: String? { if case let .system(name) = self { return name } return nil } var displayLabel: String { switch self { case .none: return "No Sound" case let .system(name): return VoiceWakeChimeCatalog.displayName(for: name) case let .custom(displayName, _): return displayName } } } struct VoiceWakeChimeCatalog { /// Options shown in the picker. static let systemOptions: [String] = [ "Glass", // default "Ping", "Pop", "Frog", "Submarine", "Funk", "Tink", ] static func displayName(for raw: String) -> String { return raw } } @MainActor enum VoiceWakeChimePlayer { private static let logger = Logger(subsystem: "com.steipete.clawdis", category: "voicewake.chime") private static var lastSound: NSSound? @MainActor static func play(_ chime: VoiceWakeChime) { guard let sound = self.sound(for: chime) else { return } self.logger.log(level: .info, "chime play type=\(String(describing: chime), privacy: .public) name=\(sound.name ?? "", privacy: .public)") self.lastSound = sound sound.stop() sound.play() } private static func sound(for chime: VoiceWakeChime) -> NSSound? { switch chime { case .none: return nil case let .system(name): return NSSound(named: NSSound.Name(name)) case let .custom(_, bookmark): var stale = false guard let url = try? URL( resolvingBookmarkData: bookmark, options: [.withoutUI, .withSecurityScope], bookmarkDataIsStale: &stale) else { return nil } let scoped = url.startAccessingSecurityScopedResource() defer { if scoped { url.stopAccessingSecurityScopedResource() } } return NSSound(contentsOf: url, byReference: false) } } }