summaryrefslogtreecommitdiffstats
path: root/third_party/rust/coreaudio-sys-utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/coreaudio-sys-utils
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/coreaudio-sys-utils')
-rw-r--r--third_party/rust/coreaudio-sys-utils/.cargo-checksum.json1
-rw-r--r--third_party/rust/coreaudio-sys-utils/Cargo.toml14
-rw-r--r--third_party/rust/coreaudio-sys-utils/src/aggregate_device.rs17
-rw-r--r--third_party/rust/coreaudio-sys-utils/src/audio_object.rs147
-rw-r--r--third_party/rust/coreaudio-sys-utils/src/audio_unit.rs171
-rw-r--r--third_party/rust/coreaudio-sys-utils/src/cf_mutable_dict.rs37
-rw-r--r--third_party/rust/coreaudio-sys-utils/src/dispatch.rs206
-rw-r--r--third_party/rust/coreaudio-sys-utils/src/lib.rs13
-rw-r--r--third_party/rust/coreaudio-sys-utils/src/string.rs178
9 files changed, 784 insertions, 0 deletions
diff --git a/third_party/rust/coreaudio-sys-utils/.cargo-checksum.json b/third_party/rust/coreaudio-sys-utils/.cargo-checksum.json
new file mode 100644
index 0000000000..7c62883f2a
--- /dev/null
+++ b/third_party/rust/coreaudio-sys-utils/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"561897801935609ec7bb9936e40ed969072067c7c9102303e65e27f664b38596","src/aggregate_device.rs":"7d2bd5f5fd7f3d008ebb69ad81f522ca0cb73db6d7b3e50ed1a63ea26ff721f4","src/audio_object.rs":"5447179330a862659a25bceedfdc5d29a1296f63490908d1c868c6b21c5f95a1","src/audio_unit.rs":"d783878930df4923b57ad230138c0f3fd6b0b9bb80a39725092ff4c6615162d8","src/cf_mutable_dict.rs":"fc42edd270c6dfb02f123214d2d8e487bbd62b5bd923b71eec13190fd0104d2a","src/dispatch.rs":"82ca429be8f930db730c7c571d6f2246e59e82ecb220b5290a3cf4a53e997053","src/lib.rs":"bcc559d69ef6ed0cbea5b2a36fec89d8c011eb9da70e2f26c00f881ad97a2546","src/string.rs":"28f88b816c768bcfcc674a60d962b93f1c94e5e0f4cc8ed2a1301138b91039e7"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/coreaudio-sys-utils/Cargo.toml b/third_party/rust/coreaudio-sys-utils/Cargo.toml
new file mode 100644
index 0000000000..6da2c2e808
--- /dev/null
+++ b/third_party/rust/coreaudio-sys-utils/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "coreaudio-sys-utils"
+version = "0.1.0"
+authors = ["Chun-Min Chang <chun.m.chang@gmail.com>"]
+edition = "2018"
+license = "ISC"
+
+[dependencies]
+core-foundation-sys = { version = "0.8" }
+
+[dependencies.coreaudio-sys]
+default-features = false
+features = ["audio_unit", "core_audio"]
+version = "0.2"
diff --git a/third_party/rust/coreaudio-sys-utils/src/aggregate_device.rs b/third_party/rust/coreaudio-sys-utils/src/aggregate_device.rs
new file mode 100644
index 0000000000..5c0d230967
--- /dev/null
+++ b/third_party/rust/coreaudio-sys-utils/src/aggregate_device.rs
@@ -0,0 +1,17 @@
+// A compile-time static string mapped to kAudioAggregateDeviceNameKey
+pub const AGGREGATE_DEVICE_NAME_KEY: &str = "name";
+
+// A compile-time static string mapped to kAudioAggregateDeviceUIDKey
+pub const AGGREGATE_DEVICE_UID_KEY: &str = "uid";
+
+// A compile-time static string mapped to kAudioAggregateDeviceIsPrivateKey
+pub const AGGREGATE_DEVICE_PRIVATE_KEY: &str = "private";
+
+// A compile-time static string mapped to kAudioAggregateDeviceIsStackedKey
+pub const AGGREGATE_DEVICE_STACKED_KEY: &str = "stacked";
+
+// A compile-time static string mapped to kAudioAggregateDeviceSubDeviceListKey
+pub const AGGREGATE_DEVICE_SUB_DEVICE_LIST_KEY: &str = "subdevices";
+
+// A compile-time static string mapped to kAudioSubDeviceUIDKey
+pub const SUB_DEVICE_UID_KEY: &str = "uid";
diff --git a/third_party/rust/coreaudio-sys-utils/src/audio_object.rs b/third_party/rust/coreaudio-sys-utils/src/audio_object.rs
new file mode 100644
index 0000000000..368d6caadc
--- /dev/null
+++ b/third_party/rust/coreaudio-sys-utils/src/audio_object.rs
@@ -0,0 +1,147 @@
+use coreaudio_sys::*;
+use std::fmt;
+use std::os::raw::c_void;
+use std::ptr;
+
+pub fn audio_object_has_property(id: AudioObjectID, address: &AudioObjectPropertyAddress) -> bool {
+ unsafe { AudioObjectHasProperty(id, address) != 0 }
+}
+
+pub fn audio_object_get_property_data<T>(
+ id: AudioObjectID,
+ address: &AudioObjectPropertyAddress,
+ size: *mut usize,
+ data: *mut T,
+) -> OSStatus {
+ unsafe {
+ AudioObjectGetPropertyData(
+ id,
+ address,
+ 0,
+ ptr::null(),
+ size as *mut UInt32,
+ data as *mut c_void,
+ )
+ }
+}
+
+pub fn audio_object_get_property_data_with_qualifier<T, Q>(
+ id: AudioObjectID,
+ address: &AudioObjectPropertyAddress,
+ qualifier_size: usize,
+ qualifier_data: *const Q,
+ size: *mut usize,
+ data: *mut T,
+) -> OSStatus {
+ unsafe {
+ AudioObjectGetPropertyData(
+ id,
+ address,
+ qualifier_size as UInt32,
+ qualifier_data as *const c_void,
+ size as *mut UInt32,
+ data as *mut c_void,
+ )
+ }
+}
+
+pub fn audio_object_get_property_data_size(
+ id: AudioObjectID,
+ address: &AudioObjectPropertyAddress,
+ size: *mut usize,
+) -> OSStatus {
+ unsafe { AudioObjectGetPropertyDataSize(id, address, 0, ptr::null(), size as *mut UInt32) }
+}
+
+pub fn audio_object_get_property_data_size_with_qualifier<Q>(
+ id: AudioObjectID,
+ address: &AudioObjectPropertyAddress,
+ qualifier_size: usize,
+ qualifier_data: *const Q,
+ size: *mut usize,
+) -> OSStatus {
+ unsafe {
+ AudioObjectGetPropertyDataSize(
+ id,
+ address,
+ qualifier_size as UInt32,
+ qualifier_data as *const c_void,
+ size as *mut UInt32,
+ )
+ }
+}
+
+pub fn audio_object_set_property_data<T>(
+ id: AudioObjectID,
+ address: &AudioObjectPropertyAddress,
+ size: usize,
+ data: *const T,
+) -> OSStatus {
+ unsafe {
+ AudioObjectSetPropertyData(
+ id,
+ address,
+ 0,
+ ptr::null(),
+ size as UInt32,
+ data as *const c_void,
+ )
+ }
+}
+
+#[allow(non_camel_case_types)]
+pub type audio_object_property_listener_proc =
+ extern "C" fn(AudioObjectID, u32, *const AudioObjectPropertyAddress, *mut c_void) -> OSStatus;
+
+pub fn audio_object_add_property_listener<T>(
+ id: AudioObjectID,
+ address: &AudioObjectPropertyAddress,
+ listener: audio_object_property_listener_proc,
+ data: *mut T,
+) -> OSStatus {
+ unsafe { AudioObjectAddPropertyListener(id, address, Some(listener), data as *mut c_void) }
+}
+
+pub fn audio_object_remove_property_listener<T>(
+ id: AudioObjectID,
+ address: &AudioObjectPropertyAddress,
+ listener: audio_object_property_listener_proc,
+ data: *mut T,
+) -> OSStatus {
+ unsafe { AudioObjectRemovePropertyListener(id, address, Some(listener), data as *mut c_void) }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum PropertySelector {
+ DefaultOutputDevice,
+ DefaultInputDevice,
+ DeviceIsAlive,
+ DataSource,
+ Unknown,
+}
+
+impl From<AudioObjectPropertySelector> for PropertySelector {
+ fn from(p: AudioObjectPropertySelector) -> Self {
+ use coreaudio_sys as sys;
+ match p {
+ sys::kAudioHardwarePropertyDefaultOutputDevice => Self::DefaultOutputDevice,
+ sys::kAudioHardwarePropertyDefaultInputDevice => Self::DefaultInputDevice,
+ sys::kAudioDevicePropertyDeviceIsAlive => Self::DeviceIsAlive,
+ sys::kAudioDevicePropertyDataSource => Self::DataSource,
+ _ => Self::Unknown,
+ }
+ }
+}
+
+impl fmt::Display for PropertySelector {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let s = match self {
+ Self::DefaultOutputDevice => "kAudioHardwarePropertyDefaultOutputDevice",
+ Self::DefaultInputDevice => "kAudioHardwarePropertyDefaultInputDevice",
+ Self::DeviceIsAlive => "kAudioDevicePropertyDeviceIsAlive",
+ Self::DataSource => "kAudioDevicePropertyDataSource",
+ _ => "Unknown",
+ };
+ write!(f, "{}", s)
+ }
+}
diff --git a/third_party/rust/coreaudio-sys-utils/src/audio_unit.rs b/third_party/rust/coreaudio-sys-utils/src/audio_unit.rs
new file mode 100644
index 0000000000..059a58f26b
--- /dev/null
+++ b/third_party/rust/coreaudio-sys-utils/src/audio_unit.rs
@@ -0,0 +1,171 @@
+use coreaudio_sys::*;
+use std::convert::TryFrom;
+use std::os::raw::c_void;
+use std::ptr;
+
+pub fn audio_unit_get_property_info(
+ unit: AudioUnit,
+ property: AudioUnitPropertyID,
+ scope: AudioUnitScope,
+ element: AudioUnitElement,
+ size: &mut usize,
+ writable: Option<&mut bool>, // Use `Option` since `writable` is nullable.
+) -> OSStatus {
+ assert!(!unit.is_null());
+ assert!(UInt32::try_from(*size).is_ok()); // Check if `size` can be converted to a UInt32.
+ unsafe {
+ AudioUnitGetPropertyInfo(
+ unit,
+ property,
+ scope,
+ element,
+ size as *mut usize as *mut UInt32,
+ writable.map_or(ptr::null_mut(), |v| v as *mut bool as *mut Boolean),
+ )
+ }
+}
+
+pub fn audio_unit_get_property<T>(
+ unit: AudioUnit,
+ property: AudioUnitPropertyID,
+ scope: AudioUnitScope,
+ element: AudioUnitElement,
+ data: &mut T,
+ size: &mut usize,
+) -> OSStatus {
+ assert!(!unit.is_null());
+ assert!(UInt32::try_from(*size).is_ok()); // Check if `size` can be converted to a UInt32.
+ unsafe {
+ AudioUnitGetProperty(
+ unit,
+ property,
+ scope,
+ element,
+ data as *mut T as *mut c_void,
+ size as *mut usize as *mut UInt32,
+ )
+ }
+}
+
+pub fn audio_unit_set_property<T>(
+ unit: AudioUnit,
+ property: AudioUnitPropertyID,
+ scope: AudioUnitScope,
+ element: AudioUnitElement,
+ data: &T,
+ size: usize,
+) -> OSStatus {
+ assert!(!unit.is_null());
+ unsafe {
+ AudioUnitSetProperty(
+ unit,
+ property,
+ scope,
+ element,
+ data as *const T as *const c_void,
+ size as UInt32,
+ )
+ }
+}
+
+pub fn audio_unit_get_parameter(
+ unit: AudioUnit,
+ id: AudioUnitParameterID,
+ scope: AudioUnitScope,
+ element: AudioUnitElement,
+ value: &mut AudioUnitParameterValue,
+) -> OSStatus {
+ assert!(!unit.is_null());
+ unsafe {
+ AudioUnitGetParameter(
+ unit,
+ id,
+ scope,
+ element,
+ value as *mut AudioUnitParameterValue,
+ )
+ }
+}
+
+pub fn audio_unit_set_parameter(
+ unit: AudioUnit,
+ id: AudioUnitParameterID,
+ scope: AudioUnitScope,
+ element: AudioUnitElement,
+ value: AudioUnitParameterValue,
+ buffer_offset_in_frames: UInt32,
+) -> OSStatus {
+ assert!(!unit.is_null());
+ unsafe { AudioUnitSetParameter(unit, id, scope, element, value, buffer_offset_in_frames) }
+}
+
+pub fn audio_unit_initialize(unit: AudioUnit) -> OSStatus {
+ assert!(!unit.is_null());
+ unsafe { AudioUnitInitialize(unit) }
+}
+
+pub fn audio_unit_uninitialize(unit: AudioUnit) -> OSStatus {
+ assert!(!unit.is_null());
+ unsafe { AudioUnitUninitialize(unit) }
+}
+
+pub fn dispose_audio_unit(unit: AudioUnit) -> OSStatus {
+ unsafe { AudioComponentInstanceDispose(unit) }
+}
+
+pub fn audio_output_unit_start(unit: AudioUnit) -> OSStatus {
+ assert!(!unit.is_null());
+ unsafe { AudioOutputUnitStart(unit) }
+}
+
+pub fn audio_output_unit_stop(unit: AudioUnit) -> OSStatus {
+ assert!(!unit.is_null());
+ unsafe { AudioOutputUnitStop(unit) }
+}
+
+pub fn audio_unit_render(
+ in_unit: AudioUnit,
+ io_action_flags: &mut AudioUnitRenderActionFlags,
+ in_time_stamp: &AudioTimeStamp,
+ in_output_bus_number: u32,
+ in_number_frames: u32,
+ io_data: &mut AudioBufferList,
+) -> OSStatus {
+ assert!(!in_unit.is_null());
+ unsafe {
+ AudioUnitRender(
+ in_unit,
+ io_action_flags,
+ in_time_stamp,
+ in_output_bus_number,
+ in_number_frames,
+ io_data,
+ )
+ }
+}
+
+#[allow(non_camel_case_types)]
+pub type audio_unit_property_listener_proc =
+ extern "C" fn(*mut c_void, AudioUnit, AudioUnitPropertyID, AudioUnitScope, AudioUnitElement);
+
+pub fn audio_unit_add_property_listener<T>(
+ unit: AudioUnit,
+ id: AudioUnitPropertyID,
+ listener: audio_unit_property_listener_proc,
+ data: *mut T,
+) -> OSStatus {
+ assert!(!unit.is_null());
+ unsafe { AudioUnitAddPropertyListener(unit, id, Some(listener), data as *mut c_void) }
+}
+
+pub fn audio_unit_remove_property_listener_with_user_data<T>(
+ unit: AudioUnit,
+ id: AudioUnitPropertyID,
+ listener: audio_unit_property_listener_proc,
+ data: *mut T,
+) -> OSStatus {
+ assert!(!unit.is_null());
+ unsafe {
+ AudioUnitRemovePropertyListenerWithUserData(unit, id, Some(listener), data as *mut c_void)
+ }
+}
diff --git a/third_party/rust/coreaudio-sys-utils/src/cf_mutable_dict.rs b/third_party/rust/coreaudio-sys-utils/src/cf_mutable_dict.rs
new file mode 100644
index 0000000000..86f585fa69
--- /dev/null
+++ b/third_party/rust/coreaudio-sys-utils/src/cf_mutable_dict.rs
@@ -0,0 +1,37 @@
+use coreaudio_sys::*;
+use std::os::raw::c_void;
+
+pub struct CFMutableDictRef(CFMutableDictionaryRef);
+
+impl CFMutableDictRef {
+ pub fn add_value<K, V>(&self, key: *const K, value: *const V) {
+ assert!(!self.0.is_null());
+ unsafe {
+ CFDictionaryAddValue(self.0, key as *const c_void, value as *const c_void);
+ }
+ }
+}
+
+impl Default for CFMutableDictRef {
+ fn default() -> Self {
+ let dict = unsafe {
+ CFDictionaryCreateMutable(
+ kCFAllocatorDefault,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks,
+ )
+ };
+ assert!(!dict.is_null());
+ Self(dict)
+ }
+}
+
+impl Drop for CFMutableDictRef {
+ fn drop(&mut self) {
+ assert!(!self.0.is_null());
+ unsafe {
+ CFRelease(self.0 as *const c_void);
+ }
+ }
+}
diff --git a/third_party/rust/coreaudio-sys-utils/src/dispatch.rs b/third_party/rust/coreaudio-sys-utils/src/dispatch.rs
new file mode 100644
index 0000000000..ee115f9676
--- /dev/null
+++ b/third_party/rust/coreaudio-sys-utils/src/dispatch.rs
@@ -0,0 +1,206 @@
+use coreaudio_sys::*;
+
+use std::ffi::CString;
+use std::mem;
+use std::os::raw::c_void;
+use std::ptr;
+use std::sync::atomic::{AtomicBool, Ordering};
+
+// Queue: A wrapper around `dispatch_queue_t`.
+// ------------------------------------------------------------------------------------------------
+#[derive(Debug)]
+pub struct Queue(dispatch_queue_t);
+
+impl Queue {
+ pub fn new(label: &str) -> Self {
+ const DISPATCH_QUEUE_SERIAL: dispatch_queue_attr_t =
+ ptr::null_mut::<dispatch_queue_attr_s>();
+ let label = CString::new(label).unwrap();
+ let c_string = label.as_ptr();
+ let queue = Self(unsafe { dispatch_queue_create(c_string, DISPATCH_QUEUE_SERIAL) });
+ queue.set_should_cancel(Box::new(AtomicBool::new(false)));
+ queue
+ }
+
+ pub fn run_async<F>(&self, work: F)
+ where
+ F: Send + FnOnce(),
+ {
+ let should_cancel = self.get_should_cancel();
+ let (closure, executor) = Self::create_closure_and_executor(|| {
+ if should_cancel.map_or(false, |v| v.load(Ordering::SeqCst)) {
+ return;
+ }
+ work();
+ });
+ unsafe {
+ dispatch_async_f(self.0, closure, executor);
+ }
+ }
+
+ pub fn run_sync<F>(&self, work: F)
+ where
+ F: Send + FnOnce(),
+ {
+ let should_cancel = self.get_should_cancel();
+ let (closure, executor) = Self::create_closure_and_executor(|| {
+ if should_cancel.map_or(false, |v| v.load(Ordering::SeqCst)) {
+ return;
+ }
+ work();
+ });
+ unsafe {
+ dispatch_sync_f(self.0, closure, executor);
+ }
+ }
+
+ pub fn run_final<F>(&self, work: F)
+ where
+ F: Send + FnOnce(),
+ {
+ let should_cancel = self.get_should_cancel();
+ let (closure, executor) = Self::create_closure_and_executor(|| {
+ work();
+ should_cancel
+ .expect("dispatch context should be allocated!")
+ .store(true, Ordering::SeqCst);
+ });
+ unsafe {
+ dispatch_sync_f(self.0, closure, executor);
+ }
+ }
+
+ fn get_should_cancel(&self) -> Option<&mut AtomicBool> {
+ unsafe {
+ let context = dispatch_get_context(
+ mem::transmute::<dispatch_queue_t, dispatch_object_t>(self.0),
+ ) as *mut AtomicBool;
+ context.as_mut()
+ }
+ }
+
+ fn set_should_cancel(&self, context: Box<AtomicBool>) {
+ unsafe {
+ let queue = mem::transmute::<dispatch_queue_t, dispatch_object_t>(self.0);
+ // Leak the context from Box.
+ dispatch_set_context(queue, Box::into_raw(context) as *mut c_void);
+
+ extern "C" fn finalizer(context: *mut c_void) {
+ // Retake the leaked context into box and then drop it.
+ let _ = unsafe { Box::from_raw(context as *mut AtomicBool) };
+ }
+
+ // The `finalizer` is only run if the `context` in `queue` is set by `dispatch_set_context`.
+ dispatch_set_finalizer_f(queue, Some(finalizer));
+ }
+ }
+
+ fn release(&self) {
+ unsafe {
+ // This will release the inner `dispatch_queue_t` asynchronously.
+ // TODO: It's incredibly unsafe to call `transmute` directly.
+ // Find another way to release the queue.
+ dispatch_release(mem::transmute::<dispatch_queue_t, dispatch_object_t>(
+ self.0,
+ ));
+ }
+ }
+
+ fn create_closure_and_executor<F>(closure: F) -> (*mut c_void, dispatch_function_t)
+ where
+ F: FnOnce(),
+ {
+ extern "C" fn closure_executer<F>(unboxed_closure: *mut c_void)
+ where
+ F: FnOnce(),
+ {
+ // Retake the leaked closure.
+ let closure = unsafe { Box::from_raw(unboxed_closure as *mut F) };
+ // Execute the closure.
+ (*closure)();
+ // closure is released after finishing this function call.
+ }
+
+ let closure = Box::new(closure); // Allocate closure on heap.
+ let executor: dispatch_function_t = Some(closure_executer::<F>);
+
+ (
+ Box::into_raw(closure) as *mut c_void, // Leak the closure.
+ executor,
+ )
+ }
+}
+
+impl Drop for Queue {
+ fn drop(&mut self) {
+ self.release();
+ }
+}
+
+impl Clone for Queue {
+ fn clone(&self) -> Self {
+ // TODO: It's incredibly unsafe to call `transmute` directly.
+ // Find another way to release the queue.
+ unsafe {
+ dispatch_retain(mem::transmute::<dispatch_queue_t, dispatch_object_t>(
+ self.0,
+ ));
+ }
+ Self(self.0)
+ }
+}
+
+#[test]
+fn run_tasks_in_order() {
+ let mut visited = Vec::<u32>::new();
+
+ // Rust compilter doesn't allow a pointer to be passed across threads.
+ // A hacky way to do that is to cast the pointer into a value, then
+ // the value, which is actually an address, can be copied into threads.
+ let ptr = &mut visited as *mut Vec<u32> as usize;
+
+ fn visit(v: u32, visited_ptr: usize) {
+ let visited = unsafe { &mut *(visited_ptr as *mut Vec<u32>) };
+ visited.push(v);
+ }
+
+ let queue = Queue::new("Run tasks in order");
+
+ queue.run_sync(move || visit(1, ptr));
+ queue.run_sync(move || visit(2, ptr));
+ queue.run_async(move || visit(3, ptr));
+ queue.run_async(move || visit(4, ptr));
+ // Call sync here to block the current thread and make sure all the tasks are done.
+ queue.run_sync(move || visit(5, ptr));
+
+ assert_eq!(visited, vec![1, 2, 3, 4, 5]);
+}
+
+#[test]
+fn run_final_task() {
+ let mut visited = Vec::<u32>::new();
+
+ {
+ // Rust compilter doesn't allow a pointer to be passed across threads.
+ // A hacky way to do that is to cast the pointer into a value, then
+ // the value, which is actually an address, can be copied into threads.
+ let ptr = &mut visited as *mut Vec<u32> as usize;
+
+ fn visit(v: u32, visited_ptr: usize) {
+ let visited = unsafe { &mut *(visited_ptr as *mut Vec<u32>) };
+ visited.push(v);
+ }
+
+ let queue = Queue::new("Task after run_final will be cancelled");
+
+ queue.run_sync(move || visit(1, ptr));
+ queue.run_async(move || visit(2, ptr));
+ queue.run_final(move || visit(3, ptr));
+ queue.run_async(move || visit(4, ptr));
+ queue.run_sync(move || visit(5, ptr));
+ }
+ // `queue` will be dropped asynchronously and then the `finalizer` of the `queue`
+ // should be fired to clean up the `context` set in the `queue`.
+
+ assert_eq!(visited, vec![1, 2, 3]);
+}
diff --git a/third_party/rust/coreaudio-sys-utils/src/lib.rs b/third_party/rust/coreaudio-sys-utils/src/lib.rs
new file mode 100644
index 0000000000..04efde0bbd
--- /dev/null
+++ b/third_party/rust/coreaudio-sys-utils/src/lib.rs
@@ -0,0 +1,13 @@
+extern crate core_foundation_sys;
+extern crate coreaudio_sys;
+
+pub mod aggregate_device;
+pub mod audio_object;
+pub mod audio_unit;
+pub mod cf_mutable_dict;
+pub mod dispatch;
+pub mod string;
+
+pub mod sys {
+ pub use coreaudio_sys::*;
+}
diff --git a/third_party/rust/coreaudio-sys-utils/src/string.rs b/third_party/rust/coreaudio-sys-utils/src/string.rs
new file mode 100644
index 0000000000..81517a1ed0
--- /dev/null
+++ b/third_party/rust/coreaudio-sys-utils/src/string.rs
@@ -0,0 +1,178 @@
+use core_foundation_sys::base::{
+ kCFAllocatorDefault, kCFAllocatorNull, Boolean, CFIndex, CFRange, CFRelease,
+};
+use core_foundation_sys::string::{
+ kCFStringEncodingUTF8, CFStringCreateWithBytes, CFStringCreateWithBytesNoCopy,
+ CFStringGetBytes, CFStringGetLength, CFStringRef,
+};
+use std::ffi::CString;
+
+pub fn cfstringref_from_static_string(string: &'static str) -> coreaudio_sys::CFStringRef {
+ // Set deallocator to kCFAllocatorNull to prevent the the memory of the parameter `string`
+ // from being released by CFRelease. We manage the string memory by ourselves.
+ let cfstringref = unsafe {
+ CFStringCreateWithBytesNoCopy(
+ kCFAllocatorDefault,
+ string.as_ptr(),
+ string.len() as CFIndex,
+ kCFStringEncodingUTF8,
+ false as Boolean,
+ kCFAllocatorNull,
+ )
+ };
+ cfstringref as coreaudio_sys::CFStringRef
+}
+
+pub fn cfstringref_from_string(string: &str) -> coreaudio_sys::CFStringRef {
+ let cfstringref = unsafe {
+ CFStringCreateWithBytes(
+ kCFAllocatorDefault,
+ string.as_ptr(),
+ string.len() as CFIndex,
+ kCFStringEncodingUTF8,
+ false as Boolean,
+ )
+ };
+ cfstringref as coreaudio_sys::CFStringRef
+}
+
+#[derive(Debug)]
+pub struct StringRef(CFStringRef);
+impl StringRef {
+ pub fn new(string_ref: CFStringRef) -> Self {
+ assert!(!string_ref.is_null());
+ Self(string_ref)
+ }
+
+ pub fn into_string(self) -> String {
+ self.to_string()
+ }
+
+ pub fn to_cstring(&self) -> CString {
+ unsafe {
+ // Assume that bytes doesn't contain `0` in the middle.
+ CString::from_vec_unchecked(utf8_from_cfstringref(self.0))
+ }
+ }
+
+ pub fn into_cstring(self) -> CString {
+ self.to_cstring()
+ }
+
+ pub fn get_raw(&self) -> CFStringRef {
+ self.0
+ }
+}
+
+impl Drop for StringRef {
+ fn drop(&mut self) {
+ use std::os::raw::c_void;
+ unsafe { CFRelease(self.0 as *mut c_void) };
+ }
+}
+
+impl std::fmt::Display for StringRef {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let string =
+ String::from_utf8(utf8_from_cfstringref(self.0)).expect("convert bytes to a String");
+ write!(f, "{}", string)
+ }
+}
+
+fn utf8_from_cfstringref(string_ref: CFStringRef) -> Vec<u8> {
+ use std::ptr;
+
+ assert!(!string_ref.is_null());
+
+ let length: CFIndex = unsafe { CFStringGetLength(string_ref) };
+ if length == 0 {
+ return Vec::new();
+ }
+
+ // Get the buffer size of the string.
+ let range: CFRange = CFRange {
+ location: 0,
+ length,
+ };
+ let mut size: CFIndex = 0;
+ let mut converted_chars: CFIndex = unsafe {
+ CFStringGetBytes(
+ string_ref,
+ range,
+ kCFStringEncodingUTF8,
+ 0,
+ false as Boolean,
+ ptr::null_mut() as *mut u8,
+ 0,
+ &mut size,
+ )
+ };
+ assert!(converted_chars > 0 && size > 0);
+
+ // Then, allocate the buffer with the required size and actually copy data into it.
+ let mut buffer = vec![b'\x00'; size as usize];
+ converted_chars = unsafe {
+ CFStringGetBytes(
+ string_ref,
+ range,
+ kCFStringEncodingUTF8,
+ 0,
+ false as Boolean,
+ buffer.as_mut_ptr(),
+ size,
+ ptr::null_mut() as *mut CFIndex,
+ )
+ };
+ assert!(converted_chars > 0);
+
+ buffer
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_create_static_cfstring_ref() {
+ const STATIC_STRING: &str = "static string for testing";
+ let stringref =
+ StringRef::new(cfstringref_from_static_string(STATIC_STRING) as CFStringRef);
+ assert_eq!(STATIC_STRING, stringref.to_string());
+ assert_eq!(
+ CString::new(STATIC_STRING).unwrap(),
+ stringref.into_cstring()
+ );
+ // TODO: Find a way to check the string's inner pointer is same.
+ }
+
+ #[test]
+ fn test_create_static_empty_cfstring_ref() {
+ const STATIC_EMPTY_STRING: &str = "";
+ let stringref =
+ StringRef::new(cfstringref_from_static_string(STATIC_EMPTY_STRING) as CFStringRef);
+ assert_eq!(STATIC_EMPTY_STRING, stringref.to_string());
+ assert_eq!(
+ CString::new(STATIC_EMPTY_STRING).unwrap(),
+ stringref.into_cstring()
+ );
+ // TODO: Find a way to check the string's inner pointer is same.
+ }
+
+ #[test]
+ fn test_create_cfstring_ref() {
+ let expected = "Rustaceans 🦀";
+ let stringref = StringRef::new(cfstringref_from_string(expected) as CFStringRef);
+ assert_eq!(expected, stringref.to_string());
+ assert_eq!(CString::new(expected).unwrap(), stringref.into_cstring());
+ // TODO: Find a way to check the string's inner pointer is different.
+ }
+
+ #[test]
+ fn test_create_empty_cfstring_ref() {
+ let expected = "";
+ let stringref = StringRef::new(cfstringref_from_string(expected) as CFStringRef);
+ assert_eq!(expected, stringref.to_string());
+ assert_eq!(CString::new(expected).unwrap(), stringref.into_cstring());
+ // TODO: Find a way to check the string's inner pointer is different.
+ }
+}