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, mute_executables: HashSet, mute_flag: bool, } fn load_mute_txt() -> HashSet { 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> { 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::()) .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> { 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, Receiver) = 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); } }