148 lines
4.2 KiB
Rust
148 lines
4.2 KiB
Rust
mod device_notification_client;
|
|
mod pid_to_exe;
|
|
mod session_notification;
|
|
mod sm_session_notifier;
|
|
mod window_change;
|
|
|
|
use std::{
|
|
collections::HashSet,
|
|
error::Error,
|
|
ffi::OsString,
|
|
fs::File,
|
|
io::{BufRead, BufReader},
|
|
path::Path,
|
|
ptr::null_mut,
|
|
sync::{
|
|
mpsc::{self, Receiver, Sender},
|
|
Arc, Mutex,
|
|
},
|
|
thread,
|
|
};
|
|
|
|
use sm_session_notifier::SMSessionNotifier;
|
|
use window_change::{win_event_hook_loop, WIN_CHANGE_CHANNEL_TX};
|
|
use windows::{
|
|
core::Interface,
|
|
Win32::{
|
|
Media::Audio::{IAudioSessionControl2, ISimpleAudioVolume},
|
|
System::Com::{CoInitializeEx, COINIT_MULTITHREADED},
|
|
},
|
|
};
|
|
|
|
use crate::pid_to_exe::pid_to_exe_path;
|
|
|
|
struct SessionMuter {
|
|
sessions: Vec<IAudioSessionControl2>,
|
|
mute_executables: HashSet<String>,
|
|
mute_flag: bool,
|
|
}
|
|
|
|
fn load_mute_txt() -> HashSet<String> {
|
|
let file = File::open("mute.txt").unwrap();
|
|
return HashSet::from_iter(BufReader::new(file).lines().map(|line| line.unwrap()));
|
|
}
|
|
|
|
impl SessionMuter {
|
|
fn new() -> SessionMuter {
|
|
SessionMuter {
|
|
sessions: Vec::new(),
|
|
mute_executables: load_mute_txt(),
|
|
mute_flag: true,
|
|
}
|
|
}
|
|
|
|
fn add_session(
|
|
self: &mut SessionMuter,
|
|
session: IAudioSessionControl2,
|
|
) -> Result<(), Box<dyn Error>> {
|
|
match self.session_to_filename(&session) {
|
|
Ok(file_name) => {
|
|
let fn_str = file_name.to_string_lossy().to_string();
|
|
if self.mute_executables.contains(&fn_str) {
|
|
println!("Adding session from: {:?}", fn_str);
|
|
unsafe {
|
|
let volume: ISimpleAudioVolume = session.cast()?;
|
|
volume.SetMute(self.mute_flag, null_mut())?;
|
|
}
|
|
self.sessions.push(session);
|
|
}
|
|
}
|
|
Err(_) => {}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn set_mute_all(self: &mut SessionMuter, mute: bool) {
|
|
let results = self
|
|
.sessions
|
|
.iter()
|
|
.map(|session_control2| session_control2.cast::<ISimpleAudioVolume>())
|
|
.map(|vol_result| vol_result.map(|volume| volume.SetMute(mute, null_mut())));
|
|
for err in results.filter_map(|x| x.err()) {
|
|
println!("Error muting a session: {:?}", err);
|
|
}
|
|
}
|
|
|
|
fn notify_window_changed(self: &mut SessionMuter, path: &str) {
|
|
unsafe {
|
|
let binding = Path::new(path)
|
|
.file_name()
|
|
.expect("failed to extract filename from path");
|
|
let file_name = binding.to_os_string().to_string_lossy().to_string();
|
|
let mute_flag = !self.mute_executables.contains(&file_name);
|
|
if mute_flag != self.mute_flag {
|
|
self.mute_flag = mute_flag;
|
|
self.set_mute_all(self.mute_flag);
|
|
println!(
|
|
"Mute set to {} due to foreground window: {}",
|
|
self.mute_flag, file_name
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn session_to_filename(
|
|
self: &mut SessionMuter,
|
|
session: &IAudioSessionControl2,
|
|
) -> Result<OsString, Box<dyn Error>> {
|
|
unsafe {
|
|
let pid = session.GetProcessId()?;
|
|
let path = pid_to_exe_path(pid)?;
|
|
let file_name = Path::new(&path)
|
|
.file_name()
|
|
.ok_or("Failed to extract filename from path")?;
|
|
return Ok(file_name.to_os_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
unsafe {
|
|
CoInitializeEx(None, COINIT_MULTITHREADED).unwrap();
|
|
}
|
|
let muter = Arc::new(Mutex::new(SessionMuter::new()));
|
|
let muter2 = muter.clone();
|
|
let sn = SMSessionNotifier::new(Box::new(move |session| {
|
|
muter2.lock().unwrap().add_session(session).unwrap()
|
|
}));
|
|
|
|
sn.lock()
|
|
.unwrap()
|
|
.as_mut()
|
|
.unwrap()
|
|
.boot_devices()
|
|
.expect("failed to get initial audio devices and sessions");
|
|
|
|
let (tx, rx): (Sender<String>, Receiver<String>) = mpsc::channel();
|
|
*WIN_CHANGE_CHANNEL_TX.lock().unwrap() = Some(tx);
|
|
|
|
thread::spawn(move || {
|
|
win_event_hook_loop();
|
|
});
|
|
|
|
loop {
|
|
let path = rx.recv().unwrap();
|
|
muter.lock().unwrap().notify_window_changed(&path);
|
|
}
|
|
}
|