Files
bot/apps/macos/Sources/Bot/SoundEffects.swift
zeekay ca704d3e6a chore: rebrand openclaw → hanzo-bot for bridge merge
Comprehensive rename of all openclaw references to hanzo-bot equivalents:
- Package: openclaw → @hanzo/bot
- Binary: openclaw → hanzo-bot
- Types: OpenClaw* → Bot*
- Env vars: OPENCLAW_* → BOT_*
- Android: ai.openclaw.android → ai.hanzo.bot.android
- iOS/macOS: ai.openclaw.* → ai.hanzo.*
- Swift modules: OpenClaw* → Bot*
- Plugins: openclaw.plugin.json → bot.plugin.json
- Paths: .openclaw/ → .hanzo/bot/
- URLs: github.com/openclaw → github.com/hanzoai
2026-02-26 22:36:01 -08:00

110 lines
3.2 KiB
Swift

import AppKit
import Foundation
enum SoundEffectCatalog {
/// All discoverable system sound names, with "Glass" pinned first.
static var systemOptions: [String] {
var names = Set(Self.discoveredSoundMap.keys).union(Self.fallbackNames)
names.remove("Glass")
let sorted = names.sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending }
return ["Glass"] + sorted
}
static func displayName(for raw: String) -> String {
raw
}
static func url(for name: String) -> URL? {
self.discoveredSoundMap[name]
}
// MARK: - Internals
private static let allowedExtensions: Set<String> = [
"aif", "aiff", "caf", "wav", "m4a", "mp3",
]
private static let fallbackNames: [String] = [
"Glass", // default
"Ping",
"Pop",
"Frog",
"Submarine",
"Funk",
"Tink",
"Basso",
"Blow",
"Bottle",
"Hero",
"Morse",
"Purr",
"Sosumi",
"Mail Sent",
"New Mail",
"Mail Scheduled",
"Mail Fetch Error",
]
private static let searchRoots: [URL] = [
FileManager().homeDirectoryForCurrentUser.appendingPathComponent("Library/Sounds"),
URL(fileURLWithPath: "/Library/Sounds"),
URL(fileURLWithPath: "/System/Applications/Mail.app/Contents/Resources"), // Mail “swoosh”
URL(fileURLWithPath: "/System/Library/Sounds"),
]
private static let discoveredSoundMap: [String: URL] = {
var map: [String: URL] = [:]
for root in Self.searchRoots {
guard let contents = try? FileManager().contentsOfDirectory(
at: root,
includingPropertiesForKeys: nil,
options: [.skipsHiddenFiles])
else { continue }
for url in contents where Self.allowedExtensions.contains(url.pathExtension.lowercased()) {
let name = url.deletingPathExtension().lastPathComponent
// Preserve the first match in priority order.
if map[name] == nil {
map[name] = url
}
}
}
return map
}()
}
@MainActor
enum SoundEffectPlayer {
private static var lastSound: NSSound?
static func sound(named name: String) -> NSSound? {
if let named = NSSound(named: NSSound.Name(name)) {
return named
}
if let url = SoundEffectCatalog.url(for: name) {
return NSSound(contentsOf: url, byReference: false)
}
return nil
}
static func sound(from bookmark: Data) -> NSSound? {
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)
}
static func play(_ sound: NSSound?) {
guard let sound else { return }
self.lastSound = sound
sound.stop()
sound.play()
}
}