Last active
October 29, 2018 01:08
-
-
Save ChunMinChang/f0f4a71f78d1e1c6390493ab1c9d10d3 to your computer and use it in GitHub Desktop.
Rust wrappers for AudioObjectAddPropertyListener on OSX
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
all: | |
rustc property_listener_native_apis.rs | |
rustc property_listener_generic_type.rs | |
rustc property_listener_within_context.rs | |
clean: | |
rm property_listener_native_apis | |
rm property_listener_generic_type | |
rm property_listener_within_context |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TODO: | |
// 1. Merge this with https://gist.github.com/ChunMinChang/07b806cb6a9ea1136cb3cbd8cda6c806 | |
// 2. If we have a way to set property data, then we don't need to plug or | |
// unplug audio devices manually. We can have a process like: | |
// a) add property listener | |
// b) set(change) the property | |
// c) check if property listener is fired | |
mod sys; | |
use std::os::raw::c_void; | |
use std::slice; | |
use std::thread; | |
const DEVICES_PROPERTY_ADDRESS: sys::AudioObjectPropertyAddress = | |
sys::AudioObjectPropertyAddress { | |
mSelector: sys::kAudioHardwarePropertyDevices, | |
mScope: sys::kAudioObjectPropertyScopeGlobal, | |
mElement: sys::kAudioObjectPropertyElementMaster, | |
}; | |
fn test_property_listener() { | |
let mut context: f64 = 123.456; | |
fn listener( | |
id: sys::AudioObjectID, | |
number_of_addresses: u32, | |
addresses: *const sys::AudioObjectPropertyAddress, | |
data: *mut f64 | |
) -> sys::OSStatus { | |
let addrs = unsafe { | |
slice::from_raw_parts(addresses, number_of_addresses as usize) | |
}; | |
// TODO: Find a way to test the case for number_of_addresses > 1. | |
for (i, addr) in addrs.iter().enumerate() { | |
println!("device {} > address {}: selector {}, scope {}, element {}", | |
id, i, addr.mSelector, addr.mScope, addr.mElement); | |
} | |
let ctx = unsafe { | |
&mut (*data) | |
}; | |
println!("context is {}", ctx); | |
assert_eq!(ctx, &123.456_f64); | |
0 // noErr. | |
} | |
let _ = add_property_listener( | |
sys::kAudioObjectSystemObject, | |
&DEVICES_PROPERTY_ADDRESS, | |
listener, | |
&mut context, | |
); | |
println!("Plug or unplug audio devices to see if the listener is fired..."); | |
loop {}; | |
// Since this function never ends, we can make sure `context` exists | |
// when listener is called! | |
} | |
type PropertyListener<T> = fn( | |
sys::AudioObjectID, | |
u32, | |
*const sys::AudioObjectPropertyAddress, | |
*mut T | |
) -> sys::OSStatus; | |
struct DataWithListener<T> { | |
listener: PropertyListener<T>, | |
data: *mut T, | |
} | |
fn add_property_listener<T>( | |
id: sys::AudioObjectID, | |
address: &sys::AudioObjectPropertyAddress, | |
listener: PropertyListener<T>, | |
data: *mut T, | |
) -> sys::OSStatus { | |
let data_with_listener = Box::new(DataWithListener { | |
listener, | |
data | |
}); | |
audio_object_add_property_listener( | |
id, | |
address, | |
Some(property_changed_callback::<T>), | |
Box::into_raw(data_with_listener) as *mut c_void | |
) | |
} | |
extern "C" fn property_changed_callback<T>( | |
id: sys::AudioObjectID, | |
number_of_addresses: u32, | |
addresses: *const sys::AudioObjectPropertyAddress, | |
data: *mut c_void | |
) -> sys::OSStatus { | |
let data_with_listener = unsafe { | |
Box::from_raw(data as *mut DataWithListener<T>) | |
}; | |
(data_with_listener.listener)( | |
id, | |
number_of_addresses, | |
addresses, | |
data_with_listener.data, | |
) | |
} | |
fn audio_object_add_property_listener( | |
id: sys::AudioObjectID, | |
address: &sys::AudioObjectPropertyAddress, | |
listener: sys::AudioObjectPropertyListenerProc, | |
data: *mut c_void, | |
) -> sys::OSStatus { | |
unsafe { | |
sys::AudioObjectAddPropertyListener( | |
id, | |
address, | |
listener, | |
data | |
) | |
} | |
} | |
fn main() { | |
// Make a vector to hold the children which are spawned. | |
let mut children = vec![]; | |
children.push(thread::spawn(|| { | |
test_property_listener(); | |
})); | |
// You can add another listeners or do other things here ... | |
for child in children { | |
// Wait for the threads to finish. | |
let _ = child.join(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TODO: | |
// 1. Merge this with https://gist.github.com/ChunMinChang/07b806cb6a9ea1136cb3cbd8cda6c806 | |
// 2. If we have a way to set property data, then we don't need to plug or | |
// unplug audio devices manually. We can have a process like: | |
// a) add property listener | |
// b) set(change) the property | |
// c) check if property listener is fired | |
mod sys; | |
use std::os::raw::c_void; | |
use std::slice; | |
use std::thread; | |
const DEVICES_PROPERTY_ADDRESS: sys::AudioObjectPropertyAddress = | |
sys::AudioObjectPropertyAddress { | |
mSelector: sys::kAudioHardwarePropertyDevices, | |
mScope: sys::kAudioObjectPropertyScopeGlobal, | |
mElement: sys::kAudioObjectPropertyElementMaster, | |
}; | |
// Call AudioObjectAddPropertyListener by using native APIs | |
// ============================================================================ | |
fn test_property_listener() { | |
let mut context: i32 = 100; | |
// TODO: is `listener` a static function? | |
extern fn listener( | |
id: sys::AudioObjectID, | |
number_of_addresses: u32, | |
addresses: *const sys::AudioObjectPropertyAddress, | |
data: *mut c_void | |
) -> sys::OSStatus { | |
let addrs = unsafe { | |
slice::from_raw_parts(addresses, number_of_addresses as usize) | |
}; | |
// TODO: Find a way to test the case for number_of_addresses > 1. | |
for (i, addr) in addrs.iter().enumerate() { | |
println!("device {} > address {}: selector {}, scope {}, element {}", | |
id, i, addr.mSelector, addr.mScope, addr.mElement); | |
} | |
let ctx = unsafe { | |
&mut (*(data as *mut i32)) | |
}; | |
println!("context is {}", ctx); | |
assert_eq!(ctx, &100); | |
0 // noErr. | |
} | |
let _ = audio_object_add_property_listener( | |
sys::kAudioObjectSystemObject, | |
&DEVICES_PROPERTY_ADDRESS, | |
Some(listener), | |
&mut context as *mut i32 as *mut c_void, | |
); | |
println!("Plug or unplug audio devices to see if the listener is fired..."); | |
loop {}; | |
// Since this function never ends, we can make sure `context` exists | |
// when listener is called! | |
} | |
fn audio_object_add_property_listener( | |
id: sys::AudioObjectID, | |
address: &sys::AudioObjectPropertyAddress, | |
listener: sys::AudioObjectPropertyListenerProc, | |
data: *mut c_void, | |
) -> sys::OSStatus { | |
unsafe { | |
sys::AudioObjectAddPropertyListener( | |
id, | |
address, | |
listener, | |
data | |
) | |
} | |
} | |
fn main() { | |
// Make a vector to hold the children which are spawned. | |
let mut children = vec![]; | |
children.push(thread::spawn(|| { | |
test_property_listener(); | |
})); | |
// You can add another listeners or do other things here ... | |
for child in children { | |
// Wait for the threads to finish. | |
let _ = child.join(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TODO: | |
// 1. Merge this with https://gist.github.com/ChunMinChang/07b806cb6a9ea1136cb3cbd8cda6c806 | |
// 2. If we have a way to set property data, then we don't need to plug or | |
// unplug audio devices manually. We can have a process like: | |
// a) add property listener | |
// b) set(change) the property | |
// c) check if property listener is fired | |
mod sys; | |
use std::os::raw::c_void; | |
use std::slice; | |
use std::thread; | |
const DEVICES_PROPERTY_ADDRESS: sys::AudioObjectPropertyAddress = | |
sys::AudioObjectPropertyAddress { | |
mSelector: sys::kAudioHardwarePropertyDevices, | |
mScope: sys::kAudioObjectPropertyScopeGlobal, | |
mElement: sys::kAudioObjectPropertyElementMaster, | |
}; | |
fn test_property_listener() { | |
let mut listener = PropertyListener::new(); | |
let _ = listener.listen_property_changed(); | |
println!("Plug or unplug audio devices to see if the listener is fired..."); | |
loop {}; | |
// Since this function never ends, we can make sure `context` exists | |
// when on_property_changed is called! | |
} | |
struct PropertyListener {} | |
impl PropertyListener { | |
fn new() -> Self { | |
PropertyListener {} | |
} | |
fn listen_property_changed(&mut self) -> sys::OSStatus { | |
audio_object_add_property_listener( | |
sys::kAudioObjectSystemObject, | |
&DEVICES_PROPERTY_ADDRESS, | |
Some(Self::property_changed_callback), | |
self as *mut Self as *mut c_void | |
) | |
} | |
fn on_property_changed<'a>( | |
&self, | |
id: sys::AudioObjectID, | |
addresses: &'a [sys::AudioObjectPropertyAddress], | |
) -> sys::OSStatus { | |
// TODO: Find a way to test the case for number_of_addresses > 1. | |
for (i, addr) in addresses.iter().enumerate() { | |
println!("device {} > address {}: selector {}, scope {}, element {}", | |
id, i, addr.mSelector, addr.mScope, addr.mElement); | |
} | |
0 // noErr. | |
} | |
// The *static* callback function that will be registered into | |
// the `AudioObjectAddPropertyListener`. | |
extern "C" fn property_changed_callback( | |
id: sys::AudioObjectID, | |
number_of_addresses: u32, | |
addresses: *const sys::AudioObjectPropertyAddress, | |
data: *mut c_void | |
) -> sys::OSStatus { | |
let addrs = unsafe { | |
slice::from_raw_parts(addresses, number_of_addresses as usize) | |
}; | |
let property_listener = data as *mut Self; | |
unsafe { | |
(*property_listener).on_property_changed( | |
id, | |
addrs, | |
) | |
} | |
} | |
} | |
fn audio_object_add_property_listener( | |
id: sys::AudioObjectID, | |
address: &sys::AudioObjectPropertyAddress, | |
listener: sys::AudioObjectPropertyListenerProc, | |
data: *mut c_void, | |
) -> sys::OSStatus { | |
unsafe { | |
sys::AudioObjectAddPropertyListener( | |
id, | |
address, | |
listener, | |
data | |
) | |
} | |
} | |
fn main() { | |
// Make a vector to hold the children which are spawned. | |
let mut children = vec![]; | |
children.push(thread::spawn(|| { | |
test_property_listener(); | |
})); | |
// You can add another listeners or do other things here ... | |
for child in children { | |
// Wait for the threads to finish. | |
let _ = child.join(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![allow(non_snake_case, non_upper_case_globals)] | |
use std::os::raw::c_void; | |
pub type OSStatus = i32; | |
pub type AudioObjectID = u32; | |
// pub const kAudioObjectUnknown: AudioObjectID = 0; | |
pub const kAudioObjectSystemObject: AudioObjectID = 1; | |
pub type AudioObjectPropertySelector = u32; | |
// https://developer.apple.com/documentation/coreaudio/1545886-anonymous/kaudiohardwarepropertydevices?language=objc | |
// https://github.com/phracker/MacOSX-SDKs/blob/9fc3ed0ad0345950ac25c28695b0427846eea966/MacOSX10.13.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers/AudioHardware.h#L585 | |
// 0x'dev#' = 0x64657623 = 1684370979 | |
pub const kAudioHardwarePropertyDevices: AudioObjectPropertySelector = 1684370979; | |
pub type AudioObjectPropertyScope = u32; | |
// https://developer.apple.com/documentation/coreaudio/1494464-anonymous/kaudioobjectpropertyscopeglobal | |
// https://github.com/phracker/MacOSX-SDKs/blob/9fc3ed0ad0345950ac25c28695b0427846eea966/MacOSX10.13.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers/AudioHardwareBase.h#L198 | |
// 0x'glob' = 0x676C6F62 = 1735159650 | |
pub const kAudioObjectPropertyScopeGlobal: AudioObjectPropertyScope = 1735159650; | |
pub type AudioObjectPropertyElement = u32; | |
// https://developer.apple.com/documentation/coreaudio/1494464-anonymous/kaudioobjectpropertyelementmaster | |
// https://github.com/phracker/MacOSX-SDKs/blob/9fc3ed0ad0345950ac25c28695b0427846eea966/MacOSX10.13.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers/AudioHardwareBase.h#L202 | |
pub const kAudioObjectPropertyElementMaster: AudioObjectPropertyElement = 0; | |
#[repr(C)] | |
pub struct AudioObjectPropertyAddress { | |
pub mSelector: AudioObjectPropertySelector, | |
pub mScope: AudioObjectPropertyScope, | |
pub mElement: AudioObjectPropertyElement, | |
} | |
// https://developer.apple.com/documentation/coreaudio/audioobjectpropertylistenerproc?language=objc | |
// https://github.com/phracker/MacOSX-SDKs/blob/9fc3ed0ad0345950ac25c28695b0427846eea966/MacOSX10.13.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers/AudioHardware.h#L117-L143 | |
pub type AudioObjectPropertyListenerProc = Option<unsafe extern "C" fn( | |
AudioObjectID, | |
u32, | |
*const AudioObjectPropertyAddress, | |
*mut c_void | |
) -> OSStatus>; | |
#[link(name = "CoreAudio", kind = "framework")] // Link to a dynamic library in CoreAudio framework. | |
extern "C" { | |
// https://developer.apple.com/documentation/coreaudio/1422472-audioobjectaddpropertylistener?language=objc | |
// https://github.com/phracker/MacOSX-SDKs/blob/9fc3ed0ad0345950ac25c28695b0427846eea966/MacOSX10.13.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers/AudioHardware.h#L331-L350 | |
pub fn AudioObjectAddPropertyListener( | |
id: AudioObjectID, | |
address: *const AudioObjectPropertyAddress, | |
listener: AudioObjectPropertyListenerProc, | |
data: *mut c_void, | |
) -> OSStatus; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment