Expert guidance for developing VolumeGuard, a macOS menu bar app that prevents dangerous volume spikes using CoreAudio HAL APIs.
Expert guidance for developing VolumeGuard, a lightweight macOS menu bar application that prevents dangerous volume spikes when Bluetooth audio devices connect.
VolumeGuard is a macOS menu bar application that monitors audio device changes and enforces per-device volume limits within ~15-30ms using Apple's CoreAudio HAL APIs. The app targets macOS 13.0+ (Ventura), uses Swift 5.9+, and SwiftUI for the interface.
The project uses xcodegen to generate the Xcode project from `project.yml`. When making project-level changes:
The codebase follows a clear separation of concerns:
**Core/** - Low-level audio system interaction:
**Models/** - Data structures:
**Views/** - SwiftUI interface:
When working with audio events:
1. CoreAudio fires property notifications (`kAudioHardwarePropertyDefaultOutputDevice` or `kAudioDevicePropertyVolumeScalar`)
2. CoreAudioInterface callback invokes AudioManager handler on main thread
3. AudioManager compares current volume to limit, calls `setVolume()` if exceeded
4. Debouncing logic ignores callbacks triggered by our own volume changes
**Volume property fallback:** Some devices don't expose volume on `kAudioObjectPropertyElementMain`. Always try element 0 first, then fall back to element 1.
**CFString handling:** Use `Unmanaged<CFString>` with `takeUnretainedValue()` for device name/UID properties:
```swift
var deviceName: CFString?
AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, nil, &propertySize, &deviceName)
let name = (deviceName as? Unmanaged<CFString>)?.takeUnretainedValue() as String?
```
**Debouncing pattern:** Prevent infinite loops from volume change callbacks:
```swift
if let lastSet = lastSetVolume, abs(newVolume - lastSet) < 0.02 {
lastSetVolume = nil
return // Ignore our own change
}
```
Target is macOS 13.0 minimum. Be aware of API differences:
**onChange modifier:**
**Settings window:**
Forms apply their own styling that can interfere with layouts. To keep controls grouped (e.g., icon + slider + label together):
```swift
LabeledContent {
HStack(spacing: 4) {
Image(systemName: "lock.fill")
Slider(value: $value, in: 0...1)
Text("\(Int(value * 100))%")
}
} label: {
Toggle("", isOn: $enabled).labelsHidden()
}
```
This properly separates the label (left) from the content group (right) within a Form.
**Registering a volume change listener:**
```swift
AudioObjectAddPropertyListenerBlock(
deviceID,
&propertyAddress,
DispatchQueue.main,
{ [weak self] _, _ in
self?.handleVolumeChange(for: deviceID)
}
)
```
**Safe volume enforcement with debouncing:**
```swift
func enforceVolumeLimit(for device: DeviceInfo, currentVolume: Float) {
guard let config = settingsManager.getConfiguration(for: device.uid),
config.enabled,
currentVolume > config.maxVolume else { return }
// Check debounce
if let lastSet = lastSetVolume, abs(currentVolume - lastSet) < 0.02 {
lastSetVolume = nil
return
}
// Enforce limit
coreAudio.setVolume(device.id, to: config.maxVolume)
lastSetVolume = config.maxVolume
}
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/macos-volume-guard-development/raw