//! Setting the default audio endpoint. //! //! Windows exposes no public API for this. `IPolicyConfig` is undocumented, but it is //! what every audio switcher has used since Vista. If a Windows update ever breaks this //! tool, this is the file it breaks in. #![allow(non_snake_case)] use core::ffi::c_void; use windows::core::{interface, IUnknown, IUnknown_Vtbl, Result, GUID, HRESULT, PCWSTR}; use windows::Win32::Media::Audio::ERole; use windows::Win32::System::Com::{CoCreateInstance, CLSCTX_ALL}; /// `CPolicyConfigClient`. const CLSID_POLICY_CONFIG_CLIENT: GUID = GUID::from_u128(0x870af99c_171d_4f9e_af0d_e63df40c2bc9); /// Only `SetDefaultEndpoint` is ever called, but every method preceding it in the vtable /// has to be declared so the slot indices line up. The signatures of the unused ones are /// irrelevant; their *order* is not — get it wrong and we call the wrong method. #[interface("f8679f50-850a-41cf-9c72-430f290290c8")] unsafe trait IPolicyConfig: IUnknown { unsafe fn GetMixFormat(&self, id: PCWSTR, format: *mut *mut c_void) -> HRESULT; unsafe fn GetDeviceFormat(&self, id: PCWSTR, def: i32, format: *mut *mut c_void) -> HRESULT; unsafe fn ResetDeviceFormat(&self, id: PCWSTR) -> HRESULT; unsafe fn SetDeviceFormat(&self, id: PCWSTR, end: *mut c_void, mix: *mut c_void) -> HRESULT; unsafe fn GetProcessingPeriod( &self, id: PCWSTR, def: i32, default_period: *mut i64, min_period: *mut i64, ) -> HRESULT; unsafe fn SetProcessingPeriod(&self, id: PCWSTR, period: *mut i64) -> HRESULT; unsafe fn GetShareMode(&self, id: PCWSTR, mode: *mut c_void) -> HRESULT; unsafe fn SetShareMode(&self, id: PCWSTR, mode: *mut c_void) -> HRESULT; unsafe fn GetPropertyValue(&self, id: PCWSTR, key: *const c_void, value: *mut c_void) -> HRESULT; unsafe fn SetPropertyValue(&self, id: PCWSTR, key: *const c_void, value: *mut c_void) -> HRESULT; unsafe fn SetDefaultEndpoint(&self, id: PCWSTR, role: ERole) -> HRESULT; unsafe fn SetEndpointVisibility(&self, id: PCWSTR, visible: i32) -> HRESULT; } /// Make `device_id` the default render endpoint for each of `roles`. /// /// Roles are applied in order and the first failure aborts — a half-applied switch is /// more confusing than a clean error. pub fn set_default(device_id: &str, roles: &[ERole]) -> Result<()> { let wide: Vec = device_id.encode_utf16().chain(core::iter::once(0)).collect(); unsafe { let policy: IPolicyConfig = CoCreateInstance(&CLSID_POLICY_CONFIG_CLIENT, None, CLSCTX_ALL)?; for role in roles { policy.SetDefaultEndpoint(PCWSTR(wide.as_ptr()), *role).ok()?; } } Ok(()) }