Skip to content

Add mic-detection #751

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions crates/detect/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
mod app;
mod browser;
mod mic;
mod utils;

pub use app::*;
pub use browser::*;
pub use mic::*;

use utils::*;

pub type DetectCallback = std::sync::Arc<dyn Fn(String) + Send + Sync + 'static>;
Expand All @@ -22,8 +25,9 @@ trait Observer: Send + Sync {

#[derive(Default)]
pub struct Detector {
app_detector: AppDetector,
browser_detector: BrowserDetector,
// app_detector: AppDetector,
// browser_detector: BrowserDetector,
mic_detector: MicDetector,
}

impl Detector {
Expand Down Expand Up @@ -59,13 +63,15 @@ impl Detector {
// }

pub fn start(&mut self, f: DetectCallback) {
self.app_detector.start(f.clone());
self.browser_detector.start(f);
// self.app_detector.start(f.clone());
// self.browser_detector.start(f.clone());
self.mic_detector.start(f);
}

pub fn stop(&mut self) {
self.app_detector.stop();
self.browser_detector.stop();
// self.app_detector.stop();
// self.browser_detector.stop();
self.mic_detector.stop();
}
}

Expand Down
217 changes: 217 additions & 0 deletions crates/detect/src/mic/macos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
use cidre::{core_audio as ca, os};

use crate::BackgroundTask;

pub struct Detector {
background: BackgroundTask,
}

impl Default for Detector {
fn default() -> Self {
Self {
background: BackgroundTask::default(),
}
}
}

const DEVICE_IS_RUNNING_SOMEWHERE: ca::PropAddr = ca::PropAddr {
selector: ca::PropSelector::DEVICE_IS_RUNNING_SOMEWHERE,
scope: ca::PropScope::GLOBAL,
element: ca::PropElement::MAIN,
};

impl crate::Observer for Detector {
fn start(&mut self, f: crate::DetectCallback) {
self.background.start(|running, mut rx| async move {
let (tx, mut notify_rx) = tokio::sync::mpsc::channel(1);

std::thread::spawn(move || {
let callback = std::sync::Arc::new(std::sync::Mutex::new(f));
let current_device = std::sync::Arc::new(std::sync::Mutex::new(None::<ca::Device>));

let callback_for_device = callback.clone();
let current_device_for_device = current_device.clone();

extern "C-unwind" fn device_listener(
_obj_id: ca::Obj,
number_addresses: u32,
addresses: *const ca::PropAddr,
client_data: *mut (),
) -> os::Status {
let data = unsafe {
&*(client_data
as *const (
std::sync::Arc<std::sync::Mutex<crate::DetectCallback>>,
std::sync::Arc<std::sync::Mutex<Option<ca::Device>>>,
))
};
let callback = &data.0;

let addresses =
unsafe { std::slice::from_raw_parts(addresses, number_addresses as usize) };

for addr in addresses {
if addr.selector == ca::PropSelector::DEVICE_IS_RUNNING_SOMEWHERE {
if let Ok(device) = ca::System::default_input_device() {
if let Ok(is_running) =
device.prop::<u32>(&DEVICE_IS_RUNNING_SOMEWHERE)
{
if is_running != 0 {
if let Ok(guard) = callback.lock() {
(*guard)("microphone_in_use".to_string());
}
}
}
}
}
}

os::Status::NO_ERR
}

extern "C-unwind" fn system_listener(
_obj_id: ca::Obj,
number_addresses: u32,
addresses: *const ca::PropAddr,
client_data: *mut (),
) -> os::Status {
let data = unsafe {
&*(client_data
as *const (
std::sync::Arc<std::sync::Mutex<crate::DetectCallback>>,
std::sync::Arc<std::sync::Mutex<Option<ca::Device>>>,
*mut (),
))
};
let current_device = &data.1;
let device_listener_data = data.2;

let addresses =
unsafe { std::slice::from_raw_parts(addresses, number_addresses as usize) };

for addr in addresses {
if addr.selector == ca::PropSelector::HW_DEFAULT_INPUT_DEVICE {
if let Ok(mut device_guard) = current_device.lock() {
if let Some(old_device) = device_guard.take() {
let _ = old_device.remove_prop_listener(
&DEVICE_IS_RUNNING_SOMEWHERE,
device_listener,
device_listener_data,
);
}

if let Ok(new_device) = ca::System::default_input_device() {
if new_device
.add_prop_listener(
&DEVICE_IS_RUNNING_SOMEWHERE,
device_listener,
device_listener_data,
)
.is_ok()
{
*device_guard = Some(new_device);
}
}
}
}
}

os::Status::NO_ERR
}

let device_listener_data = Box::new((
callback_for_device.clone(),
current_device_for_device.clone(),
));
let device_listener_ptr = Box::into_raw(device_listener_data) as *mut ();

let system_listener_data = Box::new((
callback.clone(),
current_device.clone(),
device_listener_ptr,
));
let system_listener_ptr = Box::into_raw(system_listener_data) as *mut ();

ca::System::OBJ
.add_prop_listener(
&ca::PropSelector::HW_DEFAULT_INPUT_DEVICE.global_addr(),
system_listener,
system_listener_ptr,
)
.unwrap();

if let Ok(device) = ca::System::default_input_device() {
if device
.add_prop_listener(
&DEVICE_IS_RUNNING_SOMEWHERE,
device_listener,
device_listener_ptr,
)
.is_ok()
{
if let Ok(mut device_guard) = current_device.lock() {
*device_guard = Some(device);
}
}
}

let _ = tx.blocking_send(());

ca::System::OBJ
.remove_prop_listener(
&ca::PropSelector::HW_DEFAULT_INPUT_DEVICE.global_addr(),
system_listener,
system_listener_ptr,
)
.unwrap();

if let Ok(device_guard) = current_device.lock() {
if let Some(device) = &*device_guard {
let _ = device.remove_prop_listener(
&DEVICE_IS_RUNNING_SOMEWHERE,
device_listener,
device_listener_ptr,
);
}
}

unsafe {
let _ = Box::from_raw(
system_listener_ptr
as *mut (
std::sync::Arc<std::sync::Mutex<crate::DetectCallback>>,
std::sync::Arc<std::sync::Mutex<Option<ca::Device>>>,
*mut (),
),
);
let _ = Box::from_raw(
device_listener_ptr
as *mut (
std::sync::Arc<std::sync::Mutex<crate::DetectCallback>>,
std::sync::Arc<std::sync::Mutex<Option<ca::Device>>>,
),
);
}
});

let _ = notify_rx.recv().await;

loop {
tokio::select! {
_ = &mut rx => {
break;
}
_ = tokio::time::sleep(tokio::time::Duration::from_millis(500)) => {
if !running.load(std::sync::atomic::Ordering::SeqCst) {
break;
}
}
}
}
});
}

fn stop(&mut self) {
self.background.stop();
}
}
23 changes: 23 additions & 0 deletions crates/detect/src/mic/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
type PlatformDetector = macos::Detector;

#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "windows")]
type PlatformDetector = windows::Detector;

#[derive(Default)]
pub struct MicDetector {
inner: PlatformDetector,
}

impl crate::Observer for MicDetector {
fn start(&mut self, f: crate::DetectCallback) {
self.inner.start(f);
}
fn stop(&mut self) {
self.inner.stop();
}
}
7 changes: 7 additions & 0 deletions crates/detect/src/mic/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[derive(Default)]
pub struct Detector {}

impl crate::Observer for Detector {
fn start(&mut self, f: crate::DetectCallback) {}
fn stop(&mut self) {}
}
Loading