#![allow(unsafe_code)]
use crate::audio::AudioCaptureConfig;
use crate::{CaptureError, DeviceKind, PermissionState, Support, Unavailable};
use mediaway_common::Rational;
use windows::Graphics::Capture::GraphicsCaptureSession;
use windows::Win32::Graphics::Dxgi::{CreateDXGIFactory1, IDXGIFactory1};
use windows::Win32::Media::Audio::{
DEVICE_STATE_ACTIVE, EDataFlow, IMMDeviceEnumerator, MMDeviceEnumerator, eCapture, eRender,
};
use windows::Win32::System::Com::{
CLSCTX_INPROC_SERVER, COINIT_MULTITHREADED, CoCreateInstance, CoInitializeEx,
};
use crate::windows_audio::{ComGuard, WindowsWasapiCapture, open_process_loopback_client};
#[must_use]
pub fn support(kind: DeviceKind) -> Support {
match kind {
DeviceKind::Window => window_capture_support(),
DeviceKind::Screen => screen_output_support(),
DeviceKind::Microphone => endpoint_support(eCapture),
DeviceKind::Loopback => endpoint_support(eRender),
DeviceKind::ProcessLoopback => process_loopback_support(),
_ => Support::Unavailable(Unavailable::NotImplemented),
}
}
fn window_capture_support() -> Support {
if GraphicsCaptureSession::IsSupported().unwrap_or(false) {
Support::Supported
} else {
Support::Unavailable(Unavailable::OsVersionTooOld)
}
}
fn screen_output_support() -> Support {
let Ok(factory) = (unsafe { CreateDXGIFactory1::<IDXGIFactory1>() }) else {
return Support::Unavailable(Unavailable::NoDeviceFound);
};
let mut index = 0u32;
loop {
let Ok(adapter) = (unsafe { factory.EnumAdapters1(index) }) else {
return Support::Unavailable(Unavailable::NoDeviceFound);
};
if unsafe { adapter.EnumOutputs(0) }.is_ok() {
return Support::Supported;
}
index += 1;
}
}
fn endpoint_support(data_flow: EDataFlow) -> Support {
let hr = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
if hr.is_err() {
return Support::Unavailable(Unavailable::NoDeviceFound);
}
let _com = ComGuard;
let result = (|| -> windows_core::Result<u32> {
let enumerator: IMMDeviceEnumerator =
unsafe { CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_INPROC_SERVER) }?;
let endpoints = unsafe { enumerator.EnumAudioEndpoints(data_flow, DEVICE_STATE_ACTIVE) }?;
unsafe { endpoints.GetCount() }
})();
match result {
Ok(count) if count > 0 => Support::Supported,
_ => Support::Unavailable(Unavailable::NoDeviceFound),
}
}
fn process_loopback_support() -> Support {
let hr = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
if hr.is_err() {
return Support::Unavailable(Unavailable::OsVersionTooOld);
}
let _com = ComGuard;
match open_process_loopback_client(std::process::id(), false) {
Ok((client, _capture, _rate, _channels)) => {
let _ = unsafe { client.Stop() };
Support::Supported
}
Err(_) => Support::Unavailable(Unavailable::OsVersionTooOld),
}
}
pub fn request_permission(kind: DeviceKind) -> Result<PermissionState, CaptureError> {
if matches!(support(kind), Support::Unavailable(_)) {
return Ok(PermissionState::NotSupported);
}
match kind {
DeviceKind::Loopback | DeviceKind::ProcessLoopback => Ok(PermissionState::Granted),
DeviceKind::Screen | DeviceKind::Window => Ok(PermissionState::Unknown),
DeviceKind::Microphone => probe_microphone(),
_ => Ok(PermissionState::NotSupported),
}
}
fn probe_microphone() -> Result<PermissionState, CaptureError> {
let cfg = AudioCaptureConfig::microphone(Rational::new(1, 48_000));
match WindowsWasapiCapture::open_microphone(&cfg) {
Ok(mut cap) => {
let _ = cap.close();
Ok(PermissionState::Granted)
}
Err(CaptureError::AccessDenied) => Ok(PermissionState::Denied),
Err(CaptureError::Unsupported | CaptureError::InvalidInput) => Ok(PermissionState::Unknown),
Err(e) => Err(e),
}
}
#[cfg(test)]
#[path = "capabilities_tests.rs"]
mod tests;