import CoreAudio import Foundation /// Lists the PIDs of processes currently using an audio **input** (the mic), via /// the CoreAudio process-object API (macOS 14+). /// /// This is how we attribute mic usage to a *specific* app — e.g. "is Signal in a /// call?" — which is far more robust than matching window titles, and it works /// uniformly for Zoom/Teams/Signal and browser calls (Meet). It also lets us /// ignore our own recording: we look at the *call app's* PID, not the global mic, /// so a call's end is detected even while we keep the mic open. /// /// Approach mirrors fastrepl/anarlog's `list_mic_using_apps`. @available(macOS 14.0, *) enum AudioInputProcesses { static func micUsingPIDs() -> Set { var listAddr = AudioObjectPropertyAddress( mSelector: kAudioHardwarePropertyProcessObjectList, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMain) var dataSize: UInt32 = 0 guard AudioObjectGetPropertyDataSize( AudioObjectID(kAudioObjectSystemObject), &listAddr, 0, nil, &dataSize) == noErr, dataSize > 0 else { return [] } let count = Int(dataSize) / MemoryLayout.size var processes = [AudioObjectID](repeating: 0, count: count) guard AudioObjectGetPropertyData( AudioObjectID(kAudioObjectSystemObject), &listAddr, 0, nil, &dataSize, &processes) == noErr else { return [] } var pids = Set() for process in processes where isRunningInput(process) { if let pid = pid(of: process) { pids.insert(pid) } } return pids } private static func isRunningInput(_ process: AudioObjectID) -> Bool { var addr = AudioObjectPropertyAddress( mSelector: kAudioProcessPropertyIsRunningInput, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMain) var value: UInt32 = 0 var size = UInt32(MemoryLayout.size) guard AudioObjectGetPropertyData(process, &addr, 0, nil, &size, &value) == noErr else { return false } return value != 0 } private static func pid(of process: AudioObjectID) -> pid_t? { var addr = AudioObjectPropertyAddress( mSelector: kAudioProcessPropertyPID, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMain) var value: pid_t = 0 var size = UInt32(MemoryLayout.size) guard AudioObjectGetPropertyData(process, &addr, 0, nil, &size, &value) == noErr else { return nil } return value } }