extern crate alloc;
use alloc::format;
use alloc::string::String;
use crate::clsid::Clsid;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum EndpointKind {
Capture,
Render,
}
impl EndpointKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Capture => "Capture",
Self::Render => "Render",
}
}
}
pub const FMTID_FX: Clsid = Clsid::from_u128(0xD04E05A6_594B_4FB6_A80D_01AF5EED7D1D);
pub mod pid {
pub const PRE_MIX: u32 = 0;
pub const POST_MIX: u32 = 1;
pub const ENDPOINT: u32 = 2;
}
#[must_use]
pub const fn pid_for_category(category: crate::ApoCategory) -> u32 {
match category {
crate::ApoCategory::Sfx => pid::PRE_MIX,
crate::ApoCategory::Mfx => pid::POST_MIX,
crate::ApoCategory::Efx => pid::ENDPOINT,
}
}
#[must_use]
pub fn fx_properties_key_path(kind: EndpointKind, endpoint_id: &Clsid) -> String {
format!(
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\MMDevices\\Audio\\{}\\{}\\FxProperties",
kind.as_str(),
endpoint_id
)
}
#[must_use]
pub fn fx_property_value_name(fmtid: &Clsid, pid: u32) -> String {
format!("{fmtid},{pid}")
}
#[must_use]
pub fn serialize_clsid_property(clsid: &Clsid) -> [u8; 24] {
const VT_CLSID: u32 = 72;
let mut buf = [0u8; 24];
buf[0..4].copy_from_slice(&VT_CLSID.to_le_bytes());
buf[8..12].copy_from_slice(&clsid.data1.to_le_bytes());
buf[12..14].copy_from_slice(&clsid.data2.to_le_bytes());
buf[14..16].copy_from_slice(&clsid.data3.to_le_bytes());
buf[16..24].copy_from_slice(&clsid.data4);
buf
}
#[cfg(windows)]
pub use windows_impl::{clear_endpoint_binding, write_endpoint_binding};
#[cfg(windows)]
mod windows_impl {
extern crate alloc;
use alloc::vec::Vec;
use windows::Win32::Foundation::ERROR_FILE_NOT_FOUND;
use windows::Win32::System::Registry::{
RegCloseKey, RegCreateKeyExW, RegDeleteValueW, RegSetValueExW, HKEY, HKEY_LOCAL_MACHINE,
KEY_WRITE, REG_BINARY, REG_OPTION_NON_VOLATILE,
};
use windows_core::PCWSTR;
use crate::clsid::Clsid;
use crate::error::HResult;
use super::{
fx_properties_key_path, fx_property_value_name, serialize_clsid_property, EndpointKind,
};
pub fn write_endpoint_binding(
kind: EndpointKind,
endpoint_id: &Clsid,
fmtid: &Clsid,
pid: u32,
apo_clsid: &Clsid,
) -> windows_core::Result<()> {
let path = fx_properties_key_path(kind, endpoint_id);
let key = create_subkey(&path)?;
let value_name = fx_property_value_name(fmtid, pid);
let value_wide: Vec<u16> = value_name
.encode_utf16()
.chain(core::iter::once(0))
.collect();
let payload = serialize_clsid_property(apo_clsid);
let err = unsafe {
RegSetValueExW(
key,
PCWSTR(value_wide.as_ptr()),
None,
REG_BINARY,
Some(&payload),
)
};
close(key);
err.ok()
}
pub fn clear_endpoint_binding(
kind: EndpointKind,
endpoint_id: &Clsid,
fmtid: &Clsid,
pid: u32,
) -> windows_core::Result<()> {
let path = fx_properties_key_path(kind, endpoint_id);
let key = match create_subkey(&path) {
Ok(k) => k,
Err(e) if e.code() == HResult::E_INVALIDARG.into() => return Ok(()),
Err(e) => return Err(e),
};
let value_name = fx_property_value_name(fmtid, pid);
let value_wide: Vec<u16> = value_name
.encode_utf16()
.chain(core::iter::once(0))
.collect();
let err = unsafe { RegDeleteValueW(key, PCWSTR(value_wide.as_ptr())) };
close(key);
if err.is_ok() || err == ERROR_FILE_NOT_FOUND {
Ok(())
} else {
Err(err.into())
}
}
fn create_subkey(path: &str) -> windows_core::Result<HKEY> {
let path_wide: Vec<u16> = path.encode_utf16().chain(core::iter::once(0)).collect();
let mut key = HKEY::default();
let err = unsafe {
RegCreateKeyExW(
HKEY_LOCAL_MACHINE,
PCWSTR(path_wide.as_ptr()),
None,
PCWSTR::null(),
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
None,
&mut key,
None,
)
};
err.ok().map(|()| key)
}
fn close(key: HKEY) {
let _ = unsafe { RegCloseKey(key) };
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ApoCategory;
#[test]
fn endpoint_kind_as_str_matches_microsoft_naming() {
assert_eq!(EndpointKind::Capture.as_str(), "Capture");
assert_eq!(EndpointKind::Render.as_str(), "Render");
}
#[test]
fn fx_properties_key_path_is_canonical() {
let endpoint = Clsid::from_u128(0x12345678_1234_5678_1234_567812345678);
let path = fx_properties_key_path(EndpointKind::Capture, &endpoint);
assert_eq!(
path,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\MMDevices\\Audio\\Capture\\\
{12345678-1234-5678-1234-567812345678}\\FxProperties"
);
}
#[test]
fn fx_property_value_name_uses_brace_comma_format() {
let fmtid = FMTID_FX;
let name = fx_property_value_name(&fmtid, pid::POST_MIX);
assert_eq!(name, "{D04E05A6-594B-4FB6-A80D-01AF5EED7D1D},1");
}
#[test]
fn pid_for_category_maps_to_legacy_triple() {
assert_eq!(pid_for_category(ApoCategory::Sfx), pid::PRE_MIX);
assert_eq!(pid_for_category(ApoCategory::Mfx), pid::POST_MIX);
assert_eq!(pid_for_category(ApoCategory::Efx), pid::ENDPOINT);
}
#[test]
fn serialize_clsid_property_emits_vt_clsid_layout() {
let c = Clsid::from_u128(0x01020304_0506_0708_090A_0B0C0D0E0F10);
let bytes = serialize_clsid_property(&c);
assert_eq!(&bytes[0..4], &[72, 0, 0, 0]);
assert_eq!(&bytes[4..8], &[0, 0, 0, 0]);
assert_eq!(&bytes[8..12], &0x01020304u32.to_le_bytes());
assert_eq!(&bytes[12..14], &0x0506u16.to_le_bytes());
assert_eq!(&bytes[14..16], &0x0708u16.to_le_bytes());
assert_eq!(
&bytes[16..24],
&[0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10]
);
}
#[test]
fn serialize_clsid_property_total_length_is_24_bytes() {
let c = Clsid::from_u128(0);
let bytes = serialize_clsid_property(&c);
assert_eq!(bytes.len(), 24);
}
}