use core::array;
use core::ffi::{CStr, c_void};
use core::mem::MaybeUninit;
use core::ptr::{NonNull, null};
use anyhow::Context;
use objc2_core_audio::{
AudioHardwareCreateAggregateDevice, AudioObjectGetPropertyData, AudioObjectGetPropertyDataSize,
AudioObjectID, AudioObjectPropertyAddress, AudioObjectSetPropertyData,
};
use objc2_core_foundation::{
CFArray, CFBoolean, CFDictionary, CFNumber, CFRetained, CFString,
kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks,
};
use crate::common::result::Result;
pub fn get_prop_size(
audio_object: AudioObjectID,
address: &AudioObjectPropertyAddress,
) -> Result<u32> {
let mut data_size = 0;
check_status!(unsafe {
AudioObjectGetPropertyDataSize(
audio_object,
NonNull::from(address),
0, null(), NonNull::from(&mut data_size),
)
})?;
Ok(data_size)
}
pub fn get_prop_data<T>(
audio_object: AudioObjectID,
property: &AudioObjectPropertyAddress,
qualifier: Option<(u32, *const c_void)>,
mut data_size: u32,
data: *mut T,
) -> Result<()> {
let (qualifier_size, qualifier_data) = match qualifier {
Some((size, qual_data)) => (size, qual_data),
None => (0, null()),
};
check_status!(unsafe {
AudioObjectGetPropertyData(
audio_object,
NonNull::from(property),
qualifier_size,
qualifier_data,
NonNull::from(&mut data_size),
NonNull::new(data.cast::<c_void>()).unwrap(),
)
})
}
pub fn set_prop_data<T>(
audio_object: AudioObjectID,
property: &AudioObjectPropertyAddress,
data_size: u32,
data: *const T,
) -> Result<()> {
check_status!(unsafe {
AudioObjectSetPropertyData(
audio_object,
NonNull::from(property),
0, null(), data_size,
NonNull::new(data.cast::<c_void>().cast_mut()).unwrap(),
)
})
}
pub fn create_aggregate(dict: &CFDictionary) -> Result<AudioObjectID> {
let mut aggregate_id: AudioObjectID = 0;
check_status!(unsafe {
AudioHardwareCreateAggregateDevice(dict, NonNull::from(&mut aggregate_id))
})?;
Ok(aggregate_id)
}
pub struct DictBuilder<const N: usize> {
keys: [MaybeUninit<CFRetained<CFString>>; N],
vals: [MaybeUninit<DictValue>; N],
count: usize
}
impl<const N: usize> DictBuilder<N> {
#[must_use]
pub const fn new() -> Self {
Self {
keys: [const { MaybeUninit::uninit() }; N],
vals: [const { MaybeUninit::uninit() }; N],
count: 0,
}
}
pub fn insert(mut self, key: &'static CStr, value: impl Into<DictValue>) -> Self {
self.keys[self.count] = MaybeUninit::new(CFString::from_static_str(
key.to_str().expect("Key should never be invalid UTF-8")
));
self.vals[self.count] = MaybeUninit::new(value.into());
self.count += 1;
self
}
pub fn build(self) -> Result<CFRetained<CFDictionary>> {
let mut key_ptrs: [*const c_void; N] = unsafe { *self.keys.as_ptr().cast() };
let mut val_ptrs: [*const c_void; N] = array::from_fn(|i| {
unsafe { self.vals[i].assume_init_ref().as_ptr() }
});
unsafe {
CFDictionary::new(
None,
key_ptrs.as_mut_ptr(),
val_ptrs.as_mut_ptr(),
N.cast_signed(),
&raw const kCFTypeDictionaryKeyCallBacks,
&raw const kCFTypeDictionaryValueCallBacks,
)
}.context("Failed to create dictionary")
}
}
pub enum DictValue {
String(CFRetained<CFString>),
Boolean(CFRetained<CFBoolean>),
Array(CFRetained<CFArray<CFDictionary>>),
Integer(CFRetained<CFNumber>),
}
impl DictValue {
fn as_ptr(&self) -> *const c_void {
match self {
Self::String(val) => Self::ptr(val),
Self::Boolean(val) => Self::ptr(val),
Self::Array(val) => Self::ptr(val),
Self::Integer(val) => Self::ptr(val),
}
}
fn ptr<T: objc2_core_foundation::Type>(val: &CFRetained<T>) -> *const c_void {
CFRetained::as_ptr(val).as_ptr().cast::<c_void>()
}
}
impl From<bool> for DictValue {
fn from(b: bool) -> Self {
Self::Boolean(CFRetained::from(CFBoolean::new(b)))
}
}
impl From<CFRetained<CFDictionary>> for DictValue {
fn from(dict: CFRetained<CFDictionary>) -> Self {
Self::Array(CFArray::from_retained_objects(&[dict]))
}
}
impl From<&str> for DictValue {
fn from(s: &str) -> Self {
Self::String(CFString::from_str(s))
}
}
impl From<u32> for DictValue {
fn from(t: u32) -> Self {
Self::Integer(CFNumber::new_i64(i64::from(t)))
}
}