Files
AutoMuteTool/src/main.rs
2023-02-19 22:32:27 -05:00

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);
}
}