diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/coreaudio-sys-utils | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/coreaudio-sys-utils')
9 files changed, 798 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..978440f867 --- /dev/null +++ b/third_party/rust/coreaudio-sys-utils/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"d15a17e76eddf5088ad0b09544704479b3269ef1bc9060118bda7e87ed499eab","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..7afdb69775 --- /dev/null +++ b/third_party/rust/coreaudio-sys-utils/Cargo.toml @@ -0,0 +1,28 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "coreaudio-sys-utils" +version = "0.1.0" +authors = ["Chun-Min Chang <chun.m.chang@gmail.com>"] +license = "ISC" + +[dependencies.core-foundation-sys] +version = "0.8" + +[dependencies.coreaudio-sys] +version = "0.2" +features = [ + "audio_unit", + "core_audio", +] +default-features = false 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. + } +} |