diff --git a/cidre/Cargo.toml b/cidre/Cargo.toml index aff42e9e..1b0088f6 100644 --- a/cidre/Cargo.toml +++ b/cidre/Cargo.toml @@ -177,6 +177,7 @@ ios_16_0 = ["ios_15_0"] ios_16_4 = ["ios_16_0"] ios_17_0 = ["ios_16_4"] ios_18_0 = ["ios_17_0"] +ios_18_2 = ["ios_18_0"] tvos_7_0 = [] tvos_8_0 = ["tvos_7_0"] @@ -206,6 +207,7 @@ visionos_1_0 = [] visionos_1_1 = ["visionos_1_0"] visionos_1_2 = ["visionos_1_1"] visionos_2_0 = ["visionos_1_2"] +visionos_2_2 = ["visionos_2_0"] # end of deployment targets diff --git a/cidre/src/av/audio.rs b/cidre/src/av/audio.rs index 34d7f682..f853c67f 100644 --- a/cidre/src/av/audio.rs +++ b/cidre/src/av/audio.rs @@ -74,6 +74,8 @@ pub use session::InterruptionType as SessionInterruptionType; #[cfg(any(target_os = "ios", target_os = "watchos", target_os = "tvos"))] pub use session::IoType as SessionIoType; #[cfg(any(target_os = "ios", target_os = "watchos", target_os = "tvos"))] +pub use session::MicInjectionMode as SessionMicInjectionMode; +#[cfg(any(target_os = "ios", target_os = "watchos", target_os = "tvos"))] pub use session::Mode as SessionMode; #[cfg(any(target_os = "ios", target_os = "watchos", target_os = "tvos"))] pub use session::Port as SessionPort; diff --git a/cidre/src/av/audio/application.rs b/cidre/src/av/audio/application.rs index 7f2cf5e8..89479a32 100644 --- a/cidre/src/av/audio/application.rs +++ b/cidre/src/av/audio/application.rs @@ -1,10 +1,40 @@ -use crate::{av, blocks, define_cls, define_obj_type, ns, objc}; +use crate::{arc, av, blocks, define_cls, define_obj_type, ns, objc}; +#[doc(alias = "AVAudioApplicationRecordPermission")] #[derive(Debug, Eq, PartialEq, Copy, Clone)] #[repr(isize)] pub enum RecordPermission { + /// The user has not yet been asked for permission. + #[doc(alias = "AVAudioApplicationRecordPermissionUndetermined")] Undetermined = i32::from_be_bytes(*b"undt") as _, + + /// The user has been asked and has denied permission. + #[doc(alias = "AVAudioApplicationRecordPermissionDenied")] + Denied = i32::from_be_bytes(*b"deny") as _, + + /// The user has been asked and has granted permission. + #[doc(alias = "AVAudioApplicationRecordPermissionGranted")] + Granted = i32::from_be_bytes(*b"grnt") as _, +} + +#[doc(alias = "AVAudioApplicationMicrophoneInjectionPermission")] +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[repr(isize)] +pub enum MicInjectionPermission { + /// The user has disabled this service for all apps. + #[doc(alias = "AVAudioApplicationMicrophoneInjectionPermissionServiceDisabled")] + ServiceDisabled = i32::from_be_bytes(*b"srds") as _, + + /// The user has not yet been asked for permission. + #[doc(alias = "AVAudioApplicationMicrophoneInjectionPermissionUndetermined")] + Undetermined = i32::from_be_bytes(*b"undt") as _, + + /// The user has been asked and has denied permission. + #[doc(alias = "AVAudioApplicationMicrophoneInjectionPermissionDenied")] Denied = i32::from_be_bytes(*b"deny") as _, + + /// The user has been asked and has granted permission. + #[doc(alias = "AVAudioApplicationMicrophoneInjectionPermissionGranted")] Granted = i32::from_be_bytes(*b"grnt") as _, } @@ -35,7 +65,7 @@ impl App { } #[objc::msg_send(sharedInstance)] - pub fn shared() -> &'static mut Self; + pub fn shared() -> arc::R; #[objc::msg_send(isInputMuted)] pub fn is_input_mutted(&self) -> bool; @@ -76,6 +106,34 @@ impl App { Self::request_record_permission_ch_block(&mut block); future.await } + + /// Returns an enum indicating whether the user has granted or denied permission to inject audio into input, + /// or has not been asked + #[objc::msg_send(microphoneInjectionPermission)] + #[objc::available(ios = 18.2, maccatalyst = 18.2, visionos = 2.2)] + pub fn mic_injection_permission(&self) -> MicInjectionPermission; + + #[objc::msg_send(requestMicrophoneInjectionPermissionWithCompletionHandler:)] + #[objc::available(ios = 18.2, maccatalyst = 18.2, visionos = 2.2)] + pub fn request_mic_injection_permission_ch_block( + handler: &mut blocks::SendBlock, + ); + + #[objc::available(ios = 18.2, maccatalyst = 18.2, visionos = 2.2)] + pub fn request_mic_injection_permission_ch( + handler: impl FnMut(MicInjectionPermission) + 'static + std::marker::Send, + ) { + let mut handler = blocks::SendBlock::new1(handler); + Self::request_mic_injection_permission_ch_block(&mut handler) + } + + #[cfg(feature = "async")] + #[objc::available(ios = 18.2, maccatalyst = 18.2, visionos = 2.2)] + pub async fn request_mic_injection_permission() -> MicInjectionPermission { + let (future, mut block) = blocks::comp1(); + Self::request_mic_injection_permission_ch_block(&mut block); + future.await + } } #[link(name = "AVFAudio", kind = "framework")] diff --git a/cidre/src/av/audio/session.rs b/cidre/src/av/audio/session.rs index 19da9371..08881e72 100644 --- a/cidre/src/av/audio/session.rs +++ b/cidre/src/av/audio/session.rs @@ -1,3 +1,4 @@ +use crate::api; use crate::{arc, define_cls, define_obj_type, ns, objc}; mod types; @@ -8,6 +9,7 @@ pub use types::InterruptionOpts; pub use types::InterruptionReason; pub use types::InterruptionType; pub use types::IoType; +pub use types::MicInjectionMode; pub use types::Mode; pub use types::Port; pub use types::PortOverride; @@ -438,8 +440,33 @@ impl Session { pub fn prefers_interruption_on_route_disconnect(&self) -> bool; } +/// MicrophoneInjection +impl Session { + #[objc::msg_send(setPreferredMicrophoneInjectionMode:error:)] + #[api::available(ios = 18.2, maccatalyst = 18.2, visionos = 2.2)] + pub unsafe fn set_preferred_mic_injection_mode_err<'ear>( + &mut self, + val: MicInjectionMode, + err: *mut Option<&'ear ns::Error>, + ) -> bool; + + /// Set the preferred form of audio injection into another app's input stream + #[objc::available(ios = 18.2, maccatalyst = 18.2, visionos = 2.2)] + pub fn set_preferred_mic_injection_mode<'ear>(&mut self, val: MicInjectionMode) -> ns::Result { + ns::if_false(|err| unsafe { self.set_preferred_mic_injection_mode_err(val, err) }) + } + + #[objc::msg_send(preferredMicrophoneInjectionMode)] + #[api::available(ios = 18.2, maccatalyst = 18.2, visionos = 2.2)] + pub fn preferred_mic_injection_mode(&self) -> MicInjectionMode; + + #[objc::msg_send(isMicrophoneInjectionAvailable)] + #[api::available(ios = 18.2, maccatalyst = 18.2, visionos = 2.2)] + pub fn is_mic_injection_available(&self) -> bool; +} + #[cfg(any(target_os = "ios", target_os = "watchos", target_os = "tvos"))] -#[link(name = "AVFAudio", kind = "framework")] +#[link(name = "av", kind = "static")] extern "C" { static AV_AUDIO_SESSION: &'static objc::Class; } @@ -492,8 +519,17 @@ impl Session { pub fn rendering_capabilities_change_notification() -> &'static ns::NotificationName { unsafe { AVAudioSessionRenderingCapabilitiesChangeNotification } } + + #[doc(alias = "AVAudioSessionMicrophoneInjectionCapabilitiesChangeNotification")] + #[api::available(ios = 18.2, maccatalyst = 18.2, visionos = 2.2)] + #[inline] + pub fn mic_injection_capabilities_change_notification() -> &'static ns::NotificationName { + unsafe { AVAudioSessionMicrophoneInjectionCapabilitiesChangeNotification } + } } +#[link(name = "AVAudio", kind = "framework")] +#[api::weak] extern "C" { static AVAudioSessionInterruptionNotification: &'static ns::NotificationName; static AVAudioSessionRouteChangeNotification: &'static ns::NotificationName; @@ -504,4 +540,8 @@ extern "C" { &'static ns::NotificationName; static AVAudioSessionRenderingModeChangeNotification: &'static ns::NotificationName; static AVAudioSessionRenderingCapabilitiesChangeNotification: &'static ns::NotificationName; + + #[api::available(ios = 18.2, maccatalyst = 18.2, visionos = 2.2)] + static AVAudioSessionMicrophoneInjectionCapabilitiesChangeNotification: + &'static ns::NotificationName; } diff --git a/cidre/src/av/audio/session/types.rs b/cidre/src/av/audio/session/types.rs index b697057b..00d3dc4b 100644 --- a/cidre/src/av/audio/session/types.rs +++ b/cidre/src/av/audio/session/types.rs @@ -917,3 +917,22 @@ pub enum RenderingMode { #[doc(alias = "AVAudioSessionRenderingModeDolbyAtmos")] DolbyAtmos = 5, } + +/// Various modes to inject audio coming from a session to another app’s input stream +/// +/// Applications can state their intent to mix locally generated audio, which should consist primarily of +/// synthesized speech, to another app's input stream. This feature is intended to be used by accessibility apps +/// implementing augmentative and alternative communication systems that enable users with disabilities to +/// communicate with synthesized speech. When input is muted, microphone injection will also be muted. +#[doc(alias = "AVAudioSessionMicrophoneInjectionMode")] +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[repr(isize)] +pub enum MicInjectionMode { + /// Default state, microphone injection is not preferred + #[doc(alias = "AVAudioSessionMicrophoneInjectionModeNone")] + None = 0, + + /// Inject Spoken Audio, like synthesized speech, with microphone audio + #[doc(alias = "AVAudioSessionMicrophoneInjectionModeSpokenAudio")] + SpokenAudio = 1, +} diff --git a/cidre/src/mtl/library.rs b/cidre/src/mtl/library.rs index 31eb72a6..7464778b 100644 --- a/cidre/src/mtl/library.rs +++ b/cidre/src/mtl/library.rs @@ -355,7 +355,7 @@ impl Lib { #[objc::msg_send(type)] pub fn type_(&self) -> mtl::LibType; - /// The installName provided when this mtl::Lib was created. + /// The install_name provided when this mtl::Lib was created. /// /// Always nil if the type of the library is not mtl::LibType::Dynamic. /// [read more](https://developer.apple.com/documentation/metal/mtllibrary/3554039-installname?language=objc)