Skip to content

Commit 8c4aec8

Browse files
committed
use polling, throttle cursor detection and device re-mute
1 parent 2e6bddf commit 8c4aec8

6 files changed

Lines changed: 84 additions & 33 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "mic-mute"
33
description = "System-wide mic mute for macOS"
4-
version = "0.1.0"
4+
version = "0.2.0"
55
edition = "2021"
66
authors = ["Brett Gardiner"]
77
license = "MIT"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Mute with <kbd>Cmd</kbd> <kbd>Shift</kbd> <kbd>A</kbd> or from the system tray d
1616
- [x] Mute input devices
1717
- Note: Some virtual devices may be unable to mute for now
1818
- [x] Provide global hotkey muting
19-
- [ ] Polls for new devices to mute while active
19+
- [x] Poll new devices to mute while microphones should be off
2020
- Visual confirmation of mute status
2121
- [x] Show microphone mute status in system tray
2222
- [x] Show microphone mute status in small popup window

src/event_loop.rs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
use crate::{mic::MicController, ui::UI};
1+
use crate::mic::MicController;
2+
use crate::ui::UI;
3+
use crate::utils::Throttle;
24
use async_std::task;
35
use global_hotkey::GlobalHotKeyEvent;
46
use log::trace;
57
use std::sync::{Arc, RwLock};
68
use std::time::Duration;
79
use tao::event::Event;
810
use tao::event_loop::{ControlFlow, EventLoop, EventLoopProxy};
9-
use tao::platform::macos::ActivationPolicy;
10-
use tao::platform::macos::EventLoopExtMacOS;
11+
use tao::platform::macos::{ActivationPolicy, EventLoopExtMacOS};
1112
use tray_icon::menu::MenuEvent;
1213

14+
// Timeout for mouse detect and device re-mute
15+
const THROTTLE_TIMEOUT_MILLIS: u64 = 200;
16+
1317
#[derive(Debug)]
1418
pub enum Message {
1519
HidePopup,
@@ -28,16 +32,20 @@ pub struct EventIds {
2832
pub shortcut_shift_meta_a: u32,
2933
}
3034

31-
fn toggle_mic(
35+
fn update_mic(
3236
ui: Arc<RwLock<UI>>,
3337
controller: Arc<RwLock<MicController>>,
3438
proxy: EventLoopProxyMessage,
39+
toggle: bool,
3540
) {
3641
let mut controller = controller.write().unwrap();
37-
controller.toggle(None).unwrap();
38-
let mut ui = ui.write().unwrap();
39-
ui.update(controller.muted).unwrap();
40-
if !controller.muted {
42+
if toggle || controller.muted {
43+
let state = if toggle { None } else { Some(controller.muted) };
44+
controller.toggle(state).unwrap();
45+
let mut ui = ui.write().unwrap();
46+
ui.update(controller.muted).unwrap();
47+
}
48+
if toggle && !controller.muted {
4149
task::spawn(async move {
4250
task::sleep(Duration::from_secs(1)).await;
4351
proxy.send_event(Message::HidePopup).unwrap();
@@ -57,11 +65,13 @@ pub fn start(
5765
shortcut_shift_meta_a,
5866
} = event_ids;
5967

68+
let mut throttle = Throttle::new(Duration::from_millis(THROTTLE_TIMEOUT_MILLIS));
69+
6070
trace!("Starting event loop");
6171
let proxy = event_loop.create_proxy();
6272
event_loop.set_activation_policy(ActivationPolicy::Accessory);
6373
event_loop.run(move |event, _, control_flow| {
64-
*control_flow = ControlFlow::Wait;
74+
*control_flow = ControlFlow::Poll;
6575

6676
match event {
6777
Event::UserEvent(Message::HidePopup) => {
@@ -71,13 +81,13 @@ pub fn start(
7181
ui.hide_popup().unwrap();
7282
}
7383
}
74-
Event::WindowEvent { .. } => {
75-
// println!("event: {:?}", event);
76-
}
7784
_ => {
78-
// trace!("event: {:?}", event);
79-
let mut ui = ui.write().unwrap();
80-
ui.detect().unwrap();
85+
if throttle.available() {
86+
update_mic(ui.clone(), controller.clone(), proxy.clone(), false);
87+
let mut ui = ui.write().unwrap();
88+
ui.detect().unwrap();
89+
throttle.accept().unwrap_or(());
90+
}
8191
}
8292
};
8393

@@ -92,7 +102,7 @@ pub fn start(
92102
}
93103
MenuEvent { id } if id == button_toggle_mute => {
94104
trace!("Toggle mic tray menu item selected");
95-
toggle_mic(ui.clone(), controller.clone(), proxy.clone());
105+
update_mic(ui.clone(), controller.clone(), proxy.clone(), true);
96106
}
97107
_ => {}
98108
}
@@ -101,7 +111,7 @@ pub fn start(
101111
if let Ok(event) = GlobalHotKeyEvent::receiver().try_recv() {
102112
if shortcut_shift_meta_a == event.id {
103113
trace!("Toggle mic shortcut activated");
104-
toggle_mic(ui.clone(), controller.clone(), proxy.clone());
114+
update_mic(ui.clone(), controller.clone(), proxy.clone(), true);
105115
}
106116
}
107117
});

src/main.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@ mod utils;
1212
#[macro_use]
1313
extern crate objc;
1414

15+
use crate::config::AppVars;
16+
use crate::event_loop::start;
17+
use crate::mic::MicController;
18+
use crate::ui::UI;
19+
use crate::utils::arc_lock;
1520
use env_logger::{Builder, Env};
16-
use event_loop::start;
1721
use log::{info, trace};
18-
use mic::MicController;
19-
use ui::UI;
20-
use utils::arc_lock;
21-
22-
use crate::config::AppVars;
2322

2423
fn main() {
2524
Builder::from_env(Env::default().default_filter_or("trace")).init();

src/utils.rs

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use libc::c_void;
2-
use std::sync::{Arc, RwLock};
2+
use std::{
3+
collections::VecDeque,
4+
sync::{Arc, RwLock},
5+
time::{Duration, Instant},
6+
};
37

48
type CGFloat = f64;
59

@@ -22,15 +26,53 @@ pub fn get_cursor_pos() -> Option<(i32, i32)> {
2226
CFRelease(e);
2327
Some((point.x as _, point.y as _))
2428
}
25-
// let mut pt: NSPoint = unsafe { msg_send![class!(NSEvent), mouseLocation] };
26-
// let screen: id = unsafe { msg_send![class!(NSScreen), currentScreenForMouseLocation] };
27-
// let frame: NSRect = unsafe { msg_send![screen, frame] };
28-
// pt.x -= frame.origin.x;
29-
// pt.y -= frame.origin.y;
30-
// Some((pt.x as _, pt.y as _))
3129
}
3230

3331
pub fn arc_lock<T>(value: T) -> Arc<RwLock<T>> {
3432
let rwlock = RwLock::new(value);
3533
Arc::new(rwlock)
3634
}
35+
36+
// Modified from https://github.com/SOF3/throttle
37+
pub struct Throttle {
38+
timeout: Duration,
39+
deque: VecDeque<Instant>,
40+
}
41+
42+
impl Throttle {
43+
pub fn new(timeout: Duration) -> Throttle {
44+
Throttle {
45+
timeout,
46+
deque: Default::default(),
47+
}
48+
}
49+
50+
fn flush(&mut self) {
51+
while let Some(first) = self.deque.front() {
52+
if first.elapsed() >= self.timeout.clone() {
53+
self.deque.pop_front();
54+
} else {
55+
break;
56+
}
57+
}
58+
}
59+
60+
pub fn size(&mut self) -> usize {
61+
self.flush();
62+
self.deque.len()
63+
}
64+
65+
pub fn available(&mut self) -> bool {
66+
self.size() < 1
67+
}
68+
69+
pub fn accept(&mut self) -> Result<(), Instant> {
70+
self.flush();
71+
if self.deque.len() >= 1 {
72+
return Err(self.deque.front().unwrap().clone() + self.timeout.clone());
73+
}
74+
75+
self.deque.push_back(Instant::now());
76+
Ok(())
77+
}
78+
}

0 commit comments

Comments
 (0)