#![allow(unsafe_code)]
use crate::{CaptureError, DeviceId, DeviceInfo, DeviceKind};
use windows::Win32::Media::Audio::{
DEVICE_STATE_ACTIVE, EDataFlow, IMMDeviceEnumerator, MMDeviceEnumerator, eCapture, eConsole,
eRender,
};
use windows::Win32::System::Com::{
CLSCTX_INPROC_SERVER, COINIT_MULTITHREADED, CoCreateInstance, CoInitializeEx,
};
use crate::windows_audio::{ComGuard, endpoint_friendly_name, endpoint_id};
use crate::windows_camera as camera;
use crate::windows_desktop as dxgi;
pub fn enumerate(kind: DeviceKind) -> Result<Vec<DeviceInfo>, CaptureError> {
match kind {
DeviceKind::Microphone => enumerate_audio_endpoints(eCapture, DeviceKind::Microphone),
DeviceKind::Loopback => enumerate_audio_endpoints(eRender, DeviceKind::Loopback),
DeviceKind::Camera => camera::enumerate_cameras(),
DeviceKind::Screen => dxgi::enumerate_outputs(),
_ => Err(CaptureError::Unsupported),
}
}
fn enumerate_audio_endpoints(
data_flow: EDataFlow,
kind: DeviceKind,
) -> Result<Vec<DeviceInfo>, CaptureError> {
let hr = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
if hr.is_err() {
return Err(CaptureError::Backend);
}
let _com = ComGuard;
let enumerator: IMMDeviceEnumerator =
unsafe { CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_INPROC_SERVER) }
.map_err(|_| CaptureError::Backend)?;
let default_id = unsafe { enumerator.GetDefaultAudioEndpoint(data_flow, eConsole) }
.ok()
.and_then(|device| endpoint_id(&device));
let collection = unsafe { enumerator.EnumAudioEndpoints(data_flow, DEVICE_STATE_ACTIVE) }
.map_err(|_| CaptureError::Backend)?;
let count = unsafe { collection.GetCount() }.map_err(|_| CaptureError::Backend)?;
let mut out = Vec::with_capacity(count as usize);
for ordinal in 0..count {
let Ok(device) = (unsafe { collection.Item(ordinal) }) else {
continue;
};
let Some(id) = endpoint_id(&device) else {
continue;
};
let name = endpoint_friendly_name(&device).unwrap_or_default();
let is_default = default_id.as_deref() == Some(id.as_str());
out.push(DeviceInfo {
id: DeviceId::from_wasapi_endpoint_id(id),
kind,
name,
is_default,
ordinal,
});
}
Ok(out)
}
#[cfg(test)]
#[path = "enumeration_tests.rs"]
mod tests;