Compare commits

..

22 Commits

Author SHA1 Message Date
Your Name
a14bc78b3b Use windows subsystem for this to avoid console window 2025-02-16 14:51:13 -05:00
Your Name
e81900fa79 Finally have basic UI shit going 2025-02-16 14:47:24 -05:00
Your Name
16f5f49c99 Update windows-rs 2025-02-16 12:15:16 -05:00
Your Name
989c5c05a5 Add mute change notification, remove nwg trash, package updates 2025-02-16 11:19:29 -05:00
Your Name
5f69fc664a Reduce required permissions on OpenProcess 2024-08-26 23:41:46 -04:00
Your Name
82153d31c9 Update to current winapi, split project 2024-08-25 22:24:06 -04:00
Your Name
a647fc5186 Free some additional structures from winapi properly 2023-02-23 22:47:33 -05:00
Your Name
847631d94e Fix all clippy warnings 2023-02-23 19:32:54 -05:00
Your Name
5d9c8b4d0c Remove extraneous logging
Version bump
2023-02-23 19:06:34 -05:00
Your Name
60b7b34708 Big refactor, file change notifications added 2023-02-22 22:44:11 -05:00
Your Name
45f8d9bd26 Allow dropping old muters. 2023-02-21 23:12:46 -05:00
Your Name
671814fc81 Better isolation of window change event handling code 2023-02-20 20:39:02 -05:00
Your Name
f7fde32897 Cleanup part 3: Strip old stuff, unnecessary library 2023-02-20 18:39:08 -05:00
Your Name
3bb1ca9b1c Remove unnecessary threading/channel 2023-02-19 23:27:49 -05:00
Your Name
f2ac3d5fa6 Clean up unnecessary use of RefCell 2023-02-19 22:32:27 -05:00
Your Name
a5d0729bba No more once_cell
Fix system audio check that didn't work because the API returns ()
2023-02-19 22:27:19 -05:00
Your Name
9eb15a4a5f Code cleanup part 2: Huge ass refactor edition 2023-02-19 18:14:37 -05:00
Your Name
f2d5263244 Improve session filename error handling 2023-02-19 11:20:21 -05:00
Your Name
270a256444 Use friendly names for audio devices, clean up logs 2023-02-19 11:08:43 -05:00
Your Name
23e9f624f4 Code cleanup part 1 2023-02-19 10:10:12 -05:00
Your Name
739dd6613b Add mute.txt file reading 2023-02-18 22:59:22 -05:00
Your Name
532d404815 Upgrade windows-rs, get the basics working 2023-02-18 21:59:02 -05:00
22 changed files with 1196 additions and 328 deletions

274
Cargo.lock generated
View File

@@ -3,43 +3,111 @@
version = 3 version = 3
[[package]] [[package]]
name = "auto_mute_tool" name = "auto_mute_cli"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"once_cell", "auto_mute_lib",
"widestring",
"windows", "windows",
] ]
[[package]] [[package]]
name = "once_cell" name = "auto_mute_gui"
version = "1.12.0" version = "0.2.0"
dependencies = [
"auto_mute_lib",
"windows",
"windows-core",
"winresource",
]
[[package]]
name = "auto_mute_lib"
version = "0.2.0"
dependencies = [
"windows",
"windows-core",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "indexmap"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.39" version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.18" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]] [[package]]
name = "syn" name = "serde"
version = "1.0.96" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_spanned"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -47,73 +115,193 @@ dependencies = [
] ]
[[package]] [[package]]
name = "unicode-ident" name = "toml"
version = "1.0.0" version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]] [[package]]
name = "widestring" name = "toml_datetime"
version = "1.0.0" version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5babd2d3fcd28bcf9712ef93e437684156f5c46173a9718829aa57aa4aa37a8" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "unicode-ident"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]] [[package]]
name = "windows" name = "windows"
version = "0.37.0" version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1"
dependencies = [
"windows-core",
"windows-targets",
]
[[package]]
name = "windows-core"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce"
dependencies = [ dependencies = [
"windows-implement", "windows-implement",
"windows_aarch64_msvc", "windows-interface",
"windows_i686_gnu", "windows-result",
"windows_i686_msvc", "windows-strings",
"windows_x86_64_gnu", "windows-targets",
"windows_x86_64_msvc",
] ]
[[package]] [[package]]
name = "windows-implement" name = "windows-implement"
version = "0.37.0" version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a1062e555f7d9d66fd1130ed4f7c6ec41a47529ee0850cd0e926d95b26bb14" checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1"
dependencies = [ dependencies = [
"proc-macro2",
"quote",
"syn", "syn",
"windows-tokens",
] ]
[[package]] [[package]]
name = "windows-tokens" name = "windows-interface"
version = "0.37.0" version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3263d25f1170419995b78ff10c06b949e8a986c35c208dc24333c64753a87169" checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-result"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d08106ce80268c4067c0571ca55a9b4e9516518eaa1a1fe9b37ca403ae1d1a34"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-strings"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b888f919960b42ea4e11c2f408fadb55f78a9f236d5eef084103c8ce52893491"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.37.0" version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.37.0" version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.37.0" version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.37.0" version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.37.0" version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603"
dependencies = [
"memchr",
]
[[package]]
name = "winresource"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7276691b353ad4547af8c3268488d1311f4be791ffdc0c65b8cfa8f41eed693b"
dependencies = [
"toml",
"version_check",
]

View File

@@ -1,29 +1,3 @@
[package] [workspace]
name = "auto_mute_tool" members = ["crates/*"]
version = "0.1.0" resolver = "2"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
widestring = "1.0.0"
once_cell = "1.12.0"
[dependencies.windows]
version = "0.37.0"
features = [
"alloc",
"implement",
"Win32_Media_Audio",
"Win32_UI_Shell_PropertiesSystem",
"Data_Xml_Dom",
"Win32_UI",
"Win32_UI_Accessibility",
"Win32_Foundation",
"Win32_Security",
"Win32_System_Threading",
"Win32_UI_WindowsAndMessaging",
"Win32_System_Com",
"Win32_System_Com_StructuredStorage"
]

BIN
auto_mute_gui.exe Normal file

Binary file not shown.

View File

@@ -0,0 +1,26 @@
[package]
name = "auto_mute_cli"
version = "0.2.0"
edition = "2021"
[dependencies.windows]
version = "0.59.0"
features = [
"Win32_Media_Audio",
"Win32_UI_Shell_PropertiesSystem",
"Data_Xml_Dom",
"Win32_UI",
"Win32_UI_Accessibility",
"Win32_Foundation",
"Win32_Security",
"Win32_System_Threading",
"Win32_UI_WindowsAndMessaging",
"Win32_System_Com",
"Win32_System_Com_StructuredStorage",
"Win32_Devices_FunctionDiscovery",
"Win32_Storage_FileSystem",
"Win32_System_WindowsProgramming",
]
[dependencies]
auto_mute_lib = { path = "../auto_mute_lib" }

View File

@@ -0,0 +1,48 @@
use std::{
collections::HashSet,
env,
error::Error,
fs::{self, File},
io::{BufRead, BufReader}
};
use auto_mute_lib::muter::MuterThread;
use windows::{
Win32::{
Foundation::HANDLE,
Storage::FileSystem::{
FindCloseChangeNotification, FindFirstChangeNotificationW, FindNextChangeNotification,
FILE_NOTIFY_CHANGE_LAST_WRITE,
},
System::Threading::{WaitForSingleObject, INFINITE},
}, core::w,
};
pub fn await_file_change(file_name: &str) -> Result<(), Box<dyn Error>> {
unsafe {
let md = fs::metadata(file_name)?.modified()?;
let handle = FindFirstChangeNotificationW(w!("."), false, FILE_NOTIFY_CHANGE_LAST_WRITE)?;
while md == fs::metadata(file_name)?.modified()? {
WaitForSingleObject(HANDLE(handle.0), INFINITE);
FindNextChangeNotification(handle)?;
}
println!("File change detected, restarting");
FindCloseChangeNotification(handle)?;
Ok(())
}
}
pub fn load_mute_txt(file_name: &str) -> HashSet<String> {
let file = File::open(file_name).unwrap();
HashSet::from_iter(BufReader::new(file).lines().map(|line| line.unwrap()))
}
fn main() {
MuterThread::com_init();
let mute_file: String = env::args().nth(1).unwrap_or("mute.txt".to_string());
loop {
let mut _mt = MuterThread::new(load_mute_txt(&mute_file), None);
await_file_change(&mute_file).unwrap();
}
}

View File

@@ -0,0 +1,25 @@
[package]
name = "auto_mute_gui"
version = "0.2.0"
edition = "2021"
[build-dependencies]
winresource = "0.1.17"
[dependencies]
auto_mute_lib = { path = "../auto_mute_lib" }
windows-core = "0.59.0"
[dependencies.windows]
version = "0.59.0"
features = [
"Win32_UI",
"Win32_UI_Accessibility",
"Win32_UI_WindowsAndMessaging",
"Win32_UI_Shell",
"Win32_Foundation",
"Win32_System_LibraryLoader",
"Win32_Graphics_Gdi",
"Win32_System_LibraryLoader",
"Win32_UI_WindowsAndMessaging",
]

View File

@@ -0,0 +1,11 @@
extern crate winresource;
fn main() {
if cfg!(target_os = "windows") {
winresource::WindowsResource::new()
.set_icon(r"C:\Users\ultra\ownCloud\ShitMuter\ShitMuter\icon1.ico")
.set_icon_with_id(r"C:\Users\ultra\ownCloud\ShitMuter\ShitMuter\icon1.ico", "ICO_UNMUTE")
.set_icon_with_id(r"C:\Users\ultra\ownCloud\ShitMuter\ShitMuter\icon2.ico", "ICO_MUTE")
.compile().unwrap();
}
}

View File

@@ -0,0 +1,181 @@
#![windows_subsystem = "windows"]
use std::{
collections::HashSet,
env,
error::Error,
ffi::c_void,
fs::File,
io::{BufRead, BufReader},
sync::mpsc,
thread,
};
use auto_mute_lib::muter::{MuteChangeNotification, MuterThread};
use windows::Win32::{
Foundation::HWND,
System::LibraryLoader::GetModuleHandleW,
UI::{
Shell::{Shell_NotifyIconW, NIF_ICON, NIF_MESSAGE, NIM_ADD, NIM_MODIFY, NOTIFYICONDATAW},
WindowsAndMessaging::{LoadIconW, WM_USER},
},
};
use windows::{
core::*, Win32::{Foundation::*, Graphics::Gdi::ValidateRect, System::LibraryLoader::GetModuleHandleA, UI::WindowsAndMessaging::*},
};
use windows_core::w;
const WM_TRAY_MENU: u32 = WM_USER;
pub struct MagicTray {
pub notifdata: NOTIFYICONDATAW,
pub hwnd: HWND,
}
impl MagicTray {
fn new() -> MagicTray {
unsafe {
MagicTray {
notifdata: NOTIFYICONDATAW {
cbSize: size_of::<NOTIFYICONDATAW>().try_into().unwrap(),
hWnd: HWND::default(),
uID: 1,
uFlags: NIF_ICON | NIF_MESSAGE,
uCallbackMessage: WM_TRAY_MENU,
hIcon: LoadIconW(
Some(GetModuleHandleW(None).unwrap().into()),
w!("ICO_UNMUTE"),
)
.unwrap(),
..Default::default()
},
hwnd: MagicTray::main_window().unwrap(),
}
}
}
fn main_window() -> std::result::Result<HWND, Box<dyn Error>> {
unsafe {
let instance = GetModuleHandleA(None)?;
let window_class = s!("window");
let wc = WNDCLASSA {
hCursor: LoadCursorW(None, IDC_ARROW)?,
hInstance: instance.into(),
lpszClassName: window_class,
style: CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(wndproc),
..Default::default()
};
let atom = RegisterClassA(&wc);
debug_assert!(atom != 0);
Ok(CreateWindowExA(
WINDOW_EX_STYLE::default(),
window_class,
s!("Tray Host"),
WS_OVERLAPPEDWINDOW,
0,
0,
0,
0,
None,
None,
None,
None,
)?)
}
}
fn run_loop(&mut self) {
unsafe {
SetWindowLongPtrW(self.hwnd, GWLP_USERDATA, (&mut* self as *mut _) as isize);
self.notifdata.hWnd = self.hwnd;
Shell_NotifyIconW(NIM_ADD, &self.notifdata).unwrap();
let mut message = MSG::default();
while GetMessageA(&mut message, None, 0, 0).into() {
DispatchMessageA(&message);
}
}
}
fn set_mute(&mut self, lparam: LPARAM) {
unsafe {
self.notifdata.hIcon = LoadIconW(
Some(GetModuleHandleW(None).unwrap().into()),
if lparam.0 == 0 {w!("ICO_UNMUTE")} else {w!("ICO_MUTE")},
).unwrap();
Shell_NotifyIconW(NIM_MODIFY, &self.notifdata).unwrap();
}
}
}
impl Default for MagicTray {
fn default() -> Self {
Self::new()
}
}
pub fn load_mute_txt(file_name: &str) -> HashSet<String> {
let file = File::open(file_name).unwrap();
HashSet::from_iter(BufReader::new(file).lines().map(|line| line.unwrap()))
}
const WM_APP_CUSTOM: u32 = WM_APP + 1;
fn main() {
let mut tray = MagicTray::default();
let (tx, rx) = mpsc::channel::<MuteChangeNotification>();
MuterThread::com_init();
let mute_file: String = env::args().nth(1).unwrap_or("mute.txt".to_string());
let _mt = MuterThread::new(load_mute_txt(&mute_file), Some(tx));
let hwnd = tray.hwnd.0 as usize;
thread::spawn(move || loop {
if let Ok(mpsc_msg) = rx.recv() {
match mpsc_msg {
MuteChangeNotification::MuteChanged(val) => unsafe {
PostMessageA(
Some(HWND(hwnd as *mut c_void)),
WM_APP_CUSTOM,
WPARAM(0),
LPARAM(if val { 1 } else { 0 }),
)
.unwrap()
},
}
}
});
tray.run_loop()
}
extern "system" fn wndproc(hwnd: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
unsafe {
let mt = (GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut MagicTray).as_mut();
match message {
WM_PAINT => {
println!("WM_PAINT");
_ = ValidateRect(Some(hwnd), None);
LRESULT(0)
}
WM_DESTROY => {
println!("WM_DESTROY");
PostQuitMessage(0);
LRESULT(0)
}
WM_APP_CUSTOM => {
mt.unwrap().set_mute(lparam);
LRESULT(0)
}
_ => DefWindowProcA(hwnd, message, wparam, lparam),
}
}
}

View File

@@ -0,0 +1,27 @@
[package]
name = "auto_mute_lib"
version = "0.2.0"
edition = "2021"
[dependencies]
windows-core = "0.59.0"
[dependencies.windows]
version = "0.59.0"
features = [
"Win32_Media_Audio",
"Win32_UI_Shell_PropertiesSystem",
"Data_Xml_Dom",
"Win32_UI",
"Win32_UI_Accessibility",
"Win32_Foundation",
"Win32_Security",
"Win32_System_Threading",
"Win32_UI_WindowsAndMessaging",
"Win32_System_Com",
"Win32_System_Com_StructuredStorage",
"Win32_Devices_FunctionDiscovery",
"Win32_Storage_FileSystem",
"Win32_System_WindowsProgramming",
"Win32_System_Variant"
]

View File

@@ -0,0 +1,47 @@
use windows::Win32::Media::Audio::{
IMMNotificationClient, IMMNotificationClient_Impl, DEVICE_STATE,
};
pub trait DeviceNotificationObserver {
fn add_device(&self, device_id: &windows::core::PCWSTR);
}
#[windows::core::implement(IMMNotificationClient)]
pub(crate) struct DeviceNotificationClient {
pub observer: Box<dyn DeviceNotificationObserver>,
}
impl IMMNotificationClient_Impl for DeviceNotificationClient_Impl {
fn OnDeviceStateChanged(
&self,
_pwstrdeviceid: &windows::core::PCWSTR,
_dwnewstate: DEVICE_STATE,
) -> windows::core::Result<()> {
Ok(())
}
fn OnDeviceAdded(&self, pwstrdeviceid: &windows::core::PCWSTR) -> windows::core::Result<()> {
self.observer.add_device(pwstrdeviceid);
Ok(())
}
fn OnDeviceRemoved(&self, _pwstrdeviceid: &windows::core::PCWSTR) -> windows::core::Result<()> {
Ok(())
}
fn OnDefaultDeviceChanged(
&self,
_flow: windows::Win32::Media::Audio::EDataFlow,
_role: windows::Win32::Media::Audio::ERole,
_pwstrdefaultdeviceid: &windows::core::PCWSTR,
) -> windows::core::Result<()> {
Ok(())
}
fn OnPropertyValueChanged(
&self,
_pwstrdeviceid: &windows_core::PCWSTR,
_key: &windows::Win32::Foundation::PROPERTYKEY,
) -> windows_core::Result<()> {
Ok(())
}
}

View File

@@ -0,0 +1,6 @@
mod device_notification_client;
mod pid_to_exe;
mod session_notification;
mod sm_session_notifier;
mod window_change;
pub mod muter;

View File

@@ -0,0 +1,182 @@
use std::{
collections::HashSet,
error::Error,
ffi::OsString,
path::Path,
ptr::null_mut,
sync::mpsc::{self, Receiver, Sender},
thread::{self, JoinHandle},
};
use crate::sm_session_notifier::SMSessionNotifierThread;
use crate::window_change::WindowChangeMonitor;
use windows::{
core::Interface,
Win32::{
Media::Audio::{IAudioSessionControl2, ISimpleAudioVolume},
System::Com::{CoInitializeEx, COINIT_MULTITHREADED},
},
};
use crate::pid_to_exe::pid_to_exe_path;
enum MuterMessage {
WindowChange(String),
AddSession(IAudioSessionControl2),
Exit(),
}
pub enum MuteChangeNotification {
MuteChanged(bool),
}
unsafe impl Send for MuterMessage {}
struct SessionMuter {
sessions: Vec<IAudioSessionControl2>,
mute_executables: HashSet<String>,
mute_flag: bool,
_session_notifier: SMSessionNotifierThread,
_win_change_mon: WindowChangeMonitor,
rx: Receiver<MuterMessage>,
notify_tx: Option<Sender<MuteChangeNotification>>,
}
impl SessionMuter {
fn new(
mute_executables: HashSet<String>,
rx: Receiver<MuterMessage>,
tx: Sender<MuterMessage>,
notify_tx: Option<Sender<MuteChangeNotification>>,
) -> SessionMuter {
SessionMuter {
sessions: Vec::new(),
mute_executables,
mute_flag: true,
_session_notifier: {
let tx = tx.clone();
SMSessionNotifierThread::new(Box::new(move |session| {
tx.send(MuterMessage::AddSession(session)).unwrap();
}))
},
_win_change_mon: {
WindowChangeMonitor::start(Box::new(move |s| {
tx.send(MuterMessage::WindowChange(s.to_owned())).unwrap();
}))
},
rx,
notify_tx,
}
}
fn run(&mut self) {
loop {
let msg = self.rx.recv().unwrap();
match msg {
MuterMessage::WindowChange(win) => self.notify_window_changed(&win),
MuterMessage::AddSession(session) => self.add_session(session).unwrap(),
MuterMessage::Exit() => break,
}
}
}
fn add_session(
self: &mut SessionMuter,
session: IAudioSessionControl2,
) -> Result<(), Box<dyn Error>> {
if let Ok(file_name) = self.session_to_filename(&session) {
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);
} else {
println!("Skipping session from: {:?}", fn_str);
}
}
Ok(())
}
fn set_mute_all(self: &mut SessionMuter, mute: bool) {
unsafe {
self.notify_tx
.as_ref()
.and_then(|x| x.send(MuteChangeNotification::MuteChanged(mute)).ok());
let results = self
.sessions
.iter()
.map(|session_control2| session_control2.cast::<ISimpleAudioVolume>())
.map(|vol_result| vol_result?.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) {
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")?;
Ok(file_name.to_os_string())
}
}
}
unsafe impl Send for SessionMuter {}
pub struct MuterThread {
handle: Option<JoinHandle<()>>,
sender: Sender<MuterMessage>,
}
impl MuterThread {
pub fn com_init() {
unsafe { CoInitializeEx(None, COINIT_MULTITHREADED).unwrap() };
}
pub fn new(
s: HashSet<String>,
notify_tx: Option<Sender<MuteChangeNotification>>,
) -> MuterThread {
let (sender, receiver) = mpsc::channel::<MuterMessage>();
MuterThread {
sender: sender.clone(),
handle: Some(thread::spawn(move || {
let mut muter = SessionMuter::new(s, receiver, sender, notify_tx);
muter.run();
})),
}
}
}
impl Drop for MuterThread {
fn drop(&mut self) {
if let Some(handle) = self.handle.take() {
self.sender.send(MuterMessage::Exit()).unwrap();
handle.join().unwrap()
}
}
}

View File

@@ -0,0 +1,29 @@
use std::error::Error;
use windows::Win32::{
Foundation::{CloseHandle, MAX_PATH},
System::Threading::{
OpenProcess, QueryFullProcessImageNameW, PROCESS_QUERY_LIMITED_INFORMATION
},
};
pub fn pid_to_exe_path(pid: u32) -> Result<String, Box<dyn Error>> {
let mut exe_name: Vec<u16> = Vec::with_capacity(MAX_PATH as usize);
let mut size: u32 = exe_name.capacity().try_into().unwrap();
unsafe {
let process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid)?;
QueryFullProcessImageNameW(
process,
Default::default(),
windows::core::PWSTR(exe_name.as_mut_ptr()),
&mut size,
)
.ok();
CloseHandle(process)?;
exe_name.set_len(size.try_into().unwrap());
}
let process_name = String::from_utf16_lossy(&exe_name);
Ok(process_name)
}

View File

@@ -0,0 +1,23 @@
use windows::{
core::{implement, Interface},
Win32::Media::Audio::{
IAudioSessionControl, IAudioSessionControl2, IAudioSessionNotification, IAudioSessionNotification_Impl,
},
};
#[implement(IAudioSessionNotification)]
pub(crate) struct SessionNotification {
pub(crate) observer: Box<dyn SessionObserver>,
}
pub trait SessionObserver {
fn add_session(&self, session: IAudioSessionControl2);
}
impl IAudioSessionNotification_Impl for SessionNotification_Impl {
fn OnSessionCreated(&self,newsession:windows_core::Ref<'_, IAudioSessionControl>) -> windows::core::Result<()> {
let ses: IAudioSessionControl2 = newsession.as_ref().unwrap().cast().unwrap();
self.observer.add_session(ses);
Ok(())
}
}

View File

@@ -0,0 +1,205 @@
use std::{
error::Error,
sync::mpsc::{self, Receiver, Sender},
thread::{self, JoinHandle},
};
use crate::{
device_notification_client::{DeviceNotificationClient, DeviceNotificationObserver},
session_notification::{SessionNotification, SessionObserver},
};
use windows::{
core::{Interface, PCWSTR},
Win32::{
Devices::FunctionDiscovery::PKEY_Device_FriendlyName,
Media::Audio::{
eRender, IAudioSessionControl2, IAudioSessionManager2, IAudioSessionNotification,
IMMDeviceEnumerator, IMMNotificationClient, MMDeviceEnumerator, DEVICE_STATE_ACTIVE, IMMDevice,
},
System::Com::{
CoCreateInstance, StructuredStorage::PropVariantClear, CLSCTX_ALL, STGM_READ,
},
},
};
pub enum SMMessage {
Session(IAudioSessionControl2),
Device(PCWSTR),
Exit(),
}
unsafe impl Send for SMMessage {}
struct SessionToMessage {
sender: Sender<SMMessage>,
}
impl SessionObserver for SessionToMessage {
fn add_session(&self, session: IAudioSessionControl2) {
self.sender
.send(SMMessage::Session(session))
.unwrap_or_else(|_| println!("Failed to add new session"));
}
}
struct DeviceToMessage {
sender: Sender<SMMessage>,
}
impl DeviceNotificationObserver for DeviceToMessage {
fn add_device(&self, device_id: &windows::core::PCWSTR) {
self.sender
.send(SMMessage::Device(*device_id))
.unwrap_or_else(|_| println!("Failed to add new device"));
}
}
pub(crate) struct SMSessionNotifier {
device_enumerator: IMMDeviceEnumerator,
device_notification_client: IMMNotificationClient,
session_notification: IAudioSessionNotification,
session_managers: Vec<IAudioSessionManager2>,
notification_function: Box<dyn Fn(IAudioSessionControl2)>,
receiver: Receiver<SMMessage>,
}
impl SMSessionNotifier {
pub(crate) fn new(
callback: Box<dyn Fn(IAudioSessionControl2)>,
sender: mpsc::Sender<SMMessage>,
receiver: mpsc::Receiver<SMMessage>,
) -> SMSessionNotifier {
SMSessionNotifier {
session_managers: Vec::new(),
device_enumerator: unsafe {
CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL).unwrap()
},
device_notification_client: IMMNotificationClient::from(DeviceNotificationClient {
observer: Box::new(DeviceToMessage {
sender: sender.clone(),
}),
}),
session_notification: IAudioSessionNotification::from(SessionNotification {
observer: Box::new(SessionToMessage { sender }),
}),
notification_function: callback,
receiver,
}
}
pub(crate) fn boot_devices(self: &mut SMSessionNotifier) -> Result<(), Box<dyn Error>> {
unsafe {
self.device_enumerator
.RegisterEndpointNotificationCallback(&self.device_notification_client)?;
let device_collection = self
.device_enumerator
.EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE)?;
for device in (0..device_collection.GetCount()?).map(|x| device_collection.Item(x)) {
let mmdevice = device?;
self.add_device(mmdevice)?;
}
Ok(())
}
}
fn add_device_by_id(self: &mut SMSessionNotifier, id: &PCWSTR) -> Result<(), Box<dyn Error>> {
unsafe {
let device_enumerator: IMMDeviceEnumerator =
CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)?;
let device = device_enumerator.GetDevice(*id)?;
self.add_device(device)?;
Ok(())
}
}
unsafe fn add_device(
self: &mut SMSessionNotifier,
device: IMMDevice,
) -> Result<(), Box<dyn Error>> {
let session_manager: IAudioSessionManager2 = device.Activate(CLSCTX_ALL, None)?;
session_manager.RegisterSessionNotification(&self.session_notification)?;
let session_enumerator = session_manager.GetSessionEnumerator()?;
let session_count = session_enumerator.GetCount()?;
let device_sessions = (0..session_count)
.map(|idx| {
session_enumerator
.GetSession(idx)
.and_then(|session| session.cast())
})
.collect::<Result<Vec<IAudioSessionControl2>, _>>()?;
for session in device_sessions {
(self.notification_function)(session);
}
let prop_store = device.OpenPropertyStore(STGM_READ)?;
let mut prop_var = prop_store.GetValue(&PKEY_Device_FriendlyName)?;
println!(
"Device Added: {} Existing Sessions: {}",
prop_var.to_string(),
session_count
);
PropVariantClear(&mut prop_var)?;
self.session_managers.push(session_manager);
Ok(())
}
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
self.boot_devices()?;
loop {
let msg = self.receiver.recv()?;
match msg {
SMMessage::Session(session) => {
(self.notification_function)(session);
}
SMMessage::Device(id) => {
self.add_device_by_id(&id)?;
}
SMMessage::Exit() => break,
};
}
Ok(())
}
}
impl Drop for SMSessionNotifier {
fn drop(&mut self) {
unsafe {
self.session_managers
.drain(..)
.map(|x| x.UnregisterSessionNotification(&self.session_notification))
.collect::<Result<(), _>>()
.unwrap();
self.device_enumerator
.UnregisterEndpointNotificationCallback(&self.device_notification_client)
.unwrap();
}
}
}
pub struct SMSessionNotifierThread {
handle: Option<JoinHandle<()>>,
sender: Sender<SMMessage>,
}
impl SMSessionNotifierThread {
pub fn new(s: Box<dyn Fn(IAudioSessionControl2) + Send>) -> SMSessionNotifierThread {
let (sender, receiver) = mpsc::channel::<SMMessage>();
SMSessionNotifierThread {
sender: sender.clone(),
handle: Some(thread::spawn(move || {
let mut session_notifier = SMSessionNotifier::new(s, sender, receiver);
session_notifier.run().unwrap();
})),
}
}
}
impl Drop for SMSessionNotifierThread {
fn drop(&mut self) {
if let Some(handle) = self.handle.take() {
self.sender.send(SMMessage::Exit()).unwrap();
handle.join().unwrap()
}
}
}

View File

@@ -0,0 +1,127 @@
use std::{
sync::{atomic::AtomicU32, Arc, Mutex},
thread::{self, JoinHandle},
};
use windows::Win32::{
Foundation::{HWND, LPARAM, WPARAM},
System::Threading::GetCurrentThreadId,
UI::{
Accessibility::{SetWinEventHook, UnhookWinEvent, HWINEVENTHOOK},
WindowsAndMessaging::{
DispatchMessageW, GetForegroundWindow, GetMessageW, GetWindowThreadProcessId,
PostThreadMessageW, TranslateMessage, EVENT_SYSTEM_FOREGROUND,
EVENT_SYSTEM_MINIMIZEEND, MSG, WINEVENT_OUTOFCONTEXT, WINEVENT_SKIPOWNPROCESS, WM_QUIT,
},
},
};
use crate::pid_to_exe::pid_to_exe_path;
type WinCallback = Box<dyn Fn(&str) + Send>;
static WIN_CHANGE_CALLBACK: Mutex<Option<WinCallback>> = Mutex::new(None);
unsafe extern "system" fn win_event_proc(
_hook: HWINEVENTHOOK,
event: u32,
_hwnd: HWND,
_id_object: i32,
_id_child: i32,
_dw_event_thread: u32,
_dwms_event_time: u32,
) {
if event == EVENT_SYSTEM_FOREGROUND || event == EVENT_SYSTEM_MINIMIZEEND {
let mut pid: u32 = 0;
// Instead of using the hwnd passed to us in the wineventproc, call GetForegroundWindow again because Hollow Knight does something weird
// and immediately fires again with the hwnd for explorer?
let hwnd = GetForegroundWindow();
GetWindowThreadProcessId(hwnd, Some(&mut pid));
pid_to_exe_path(pid)
.map(|path| WIN_CHANGE_CALLBACK.lock().unwrap().as_ref().unwrap()(&path))
.unwrap_or_else(|err| {
println!(
"Error finding process with pid {} for hwnd: {:?}: {:?}",
pid, hwnd, err
)
});
}
}
pub fn await_win_change_events(callback: WinCallback) {
*WIN_CHANGE_CALLBACK.lock().unwrap() = Some(callback);
unsafe {
let fg_event = SetWinEventHook(
EVENT_SYSTEM_FOREGROUND,
EVENT_SYSTEM_FOREGROUND,
None,
Some(win_event_proc),
0,
0,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS,
);
let min_event = SetWinEventHook(
EVENT_SYSTEM_MINIMIZEEND,
EVENT_SYSTEM_MINIMIZEEND,
None,
Some(win_event_proc),
0,
0,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS,
);
let mut msg: MSG = MSG::default();
while GetMessageW(&mut msg, None, 0, 0).0 > 0 {
TranslateMessage(&msg).unwrap();
DispatchMessageW(&msg);
}
UnhookWinEvent(fg_event).unwrap();
UnhookWinEvent(min_event).unwrap();
}
}
pub struct WindowChangeMonitor {
join_handle: Option<JoinHandle<()>>,
win_thread_id: Arc<AtomicU32>,
}
impl Drop for WindowChangeMonitor {
fn drop(&mut self) {
if let Some(join_handle) = self.join_handle.take() {
let tid: u32 = self
.win_thread_id
.load(std::sync::atomic::Ordering::Relaxed);
if tid != 0 {
unsafe {
PostThreadMessageW(tid, WM_QUIT, WPARAM(0), LPARAM(0))
.ok()
.unwrap();
}
}
join_handle
.join()
.expect("Unable to terminate window change thread");
}
}
}
impl WindowChangeMonitor {
pub(crate) fn start(f: WinCallback) -> WindowChangeMonitor {
let win_thread_id = Arc::new(AtomicU32::new(0));
let join_handle = {
let win_thread_id = win_thread_id.clone();
thread::spawn(move || {
win_thread_id.store(
unsafe { GetCurrentThreadId() },
std::sync::atomic::Ordering::Relaxed,
);
await_win_change_events(f);
})
};
WindowChangeMonitor {
join_handle: Some(join_handle),
win_thread_id,
}
}
}

23
mute.txt Normal file
View File

@@ -0,0 +1,23 @@
Baba is You.exe
Celeste.exe
Cemu.exe
DarkSoulsII.exe
GRIS.exe
Hades.exe
LWIW.exe
Super Mario 64 Plus.exe
Trials of Mana-Win64-Shipping.exe
VampireSurvivors.exe
cod.exe
hollow_knight.exe
sm64.us.f3dex2e.exe
teardown.exe
underrail.exe
valheim.exe
Balatro.exe
Tactical Breach Wizards.exe
eldenring.exe
ACValhalla.exe
DQMonsters3.exe
Patrick's Parabox.exe
Signal.exe

BIN
res/muted.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
res/unmuted.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,230 +0,0 @@
mod pid_to_exe;
use once_cell::sync::Lazy;
use std::{
error::Error,
ffi::c_void,
mem,
ptr::{self, null_mut}, sync::{Arc, Mutex}
};
use widestring::U16CStr;
use windows::{
Win32::{
Foundation::{HINSTANCE, HWND},
Media::Audio::{
eRender, IAudioSessionManager2, IMMDeviceEnumerator, MMDeviceEnumerator,
DEVICE_STATE_ACTIVE, IAudioSessionNotification, IAudioSessionControl, IAudioSessionNotification_Impl, IAudioSessionControl2, IMMNotificationClient, IMMNotificationClient_Impl,
},
System::Com::{CoCreateInstance, CoInitializeEx, CLSCTX_ALL, COINIT_MULTITHREADED},
UI::{
Accessibility::{SetWinEventHook, HWINEVENTHOOK},
WindowsAndMessaging::{
DispatchMessageW, GetMessageW, GetWindowThreadProcessId, TranslateMessage,
EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_MINIMIZEEND, MSG, WINEVENT_OUTOFCONTEXT,
WINEVENT_SKIPOWNPROCESS,
},
},
}, core::{Interface, PCWSTR},
};
use crate::pid_to_exe::pid_to_exe_path;
unsafe extern "system" fn win_event_proc(
_hook: HWINEVENTHOOK,
event: u32,
hwnd: HWND,
_id_object: i32,
_id_child: i32,
_dw_event_thread: u32,
_dwms_event_time: u32,
) {
if event == EVENT_SYSTEM_FOREGROUND || event == EVENT_SYSTEM_MINIMIZEEND {
let mut pid: u32 = 0;
GetWindowThreadProcessId(hwnd, &mut pid);
println!("{:?}", pid_to_exe_path(pid));
}
}
fn win_event_hook_loop() {
unsafe {
SetWinEventHook(
EVENT_SYSTEM_FOREGROUND,
EVENT_SYSTEM_FOREGROUND,
HINSTANCE::default(),
Some(win_event_proc),
0,
0,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS,
);
SetWinEventHook(
EVENT_SYSTEM_MINIMIZEEND,
EVENT_SYSTEM_MINIMIZEEND,
HINSTANCE::default(),
Some(win_event_proc),
0,
0,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS,
);
let mut msg: MSG = MSG::default();
while GetMessageW(&mut msg, HWND::default(), 0, 0).as_bool() {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
}
/*
Okay, the main way this will function:
- MMDeviceEnumerator - get all current devices
- RegisterEndpointNotificationCallback - get updates on device add/remove
- For each new device found
- Activate Session Manager on Device
- Register Session Notification on Session Manager
- Get session enumerator from session manager
- Detect sessions from applications we care about by getting session PID and mapping to process names using existing code
*/
#[windows::core::implement(IAudioSessionNotification)]
struct SessionNotification {
sessions: Arc<Mutex<Vec<IAudioSessionControl>>>
}
impl IAudioSessionNotification_Impl for SessionNotification {
fn OnSessionCreated(self: &SessionNotification,newsession: &core::option::Option<IAudioSessionControl>) -> windows::core::Result<()> {
println!("Got OSC call");
let ses = newsession.as_ref().unwrap().clone();
let s2: IAudioSessionControl2 = ses.cast().unwrap();
println!("Holy shit, new session created! {}", unsafe { pid_to_exe_path(s2.GetProcessId().unwrap()).unwrap() });
self.sessions.lock().unwrap().push(ses);
Ok(())
}
}
impl Drop for SessionNotification {
fn drop(&mut self) {
println!("SN drop");
}
}
#[windows::core::implement(IMMNotificationClient)]
struct DeviceNotificationClient {}
impl Drop for DeviceNotificationClient {
fn drop(&mut self) {
println!("DNC drop");
}
}
impl IMMNotificationClient_Impl for DeviceNotificationClient {
fn OnDeviceStateChanged(&self,_pwstrdeviceid: &windows::core::PCWSTR,_dwnewstate:u32) -> windows::core::Result<()> {
println!("OnDeviceStateChanged");
Ok(())
}
fn OnDeviceAdded(&self,_pwstrdeviceid: &windows::core::PCWSTR) -> windows::core::Result<()> {
// EUGH!
println!("OnDeviceAdded");
/*
KING.lock().unwrap().add_device_by_id(pwstrdeviceid).map_err(|error| {
println!("Error adding device: {:?}", error);
}).unwrap_or(());
*/
Ok(())
}
fn OnDeviceRemoved(&self,_pwstrdeviceid: &windows::core::PCWSTR) -> windows::core::Result<()> {
println!("OnDeviceRemoved");
Ok(())
}
fn OnDefaultDeviceChanged(&self,_flow:windows::Win32::Media::Audio::EDataFlow,_role:windows::Win32::Media::Audio::ERole,_pwstrdefaultdeviceid: &windows::core::PCWSTR) -> windows::core::Result<()> {
println!("OnDefaultDeviceChanged");
Ok(())
}
fn OnPropertyValueChanged(&self,_pwstrdeviceid: &windows::core::PCWSTR,_key: &windows::Win32::UI::Shell::PropertiesSystem::PROPERTYKEY) -> windows::core::Result<()> {
println!("OnPropertyValueChanged");
Ok(())
}
}
struct SessionKing {
sessions: Arc<Mutex<Vec<IAudioSessionControl>>>,
device_enumerator : IMMDeviceEnumerator,
dnc : IMMNotificationClient
}
impl SessionKing {
fn new() -> SessionKing {
SessionKing {
sessions: Arc::new(Mutex::new(Vec::new())),
device_enumerator: unsafe { CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL).unwrap() },
dnc: IMMNotificationClient::from(DeviceNotificationClient {})
}
}
fn boot_devices(self: &mut SessionKing) -> Result<String, Box<dyn Error>> {
unsafe {
(Interface::vtable(&self.device_enumerator).RegisterEndpointNotificationCallback)(Interface::as_raw(&self.device_enumerator), self.dnc.as_raw()).ok()?;
let device_collection =
self.device_enumerator.EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE)?;
let dc = 0..device_collection.GetCount()?;
let devices = dc.map(|x| device_collection.Item(x));
for x in devices {
let mmdevice = x?;
self.add_device(mmdevice)?;
}
return Ok("Done".to_string());
}
}
fn add_device_by_id(self: &mut SessionKing, id: &PCWSTR) -> Result<(), Box<dyn Error>> {
unsafe {
let device_enumerator: IMMDeviceEnumerator =
CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)?;
let device = device_enumerator.GetDevice(id)?;
self.add_device(device)?;
Ok(())
}
}
unsafe fn add_device(self: &mut SessionKing, mmdevice: windows::Win32::Media::Audio::IMMDevice) -> Result<(), Box<dyn Error>> {
let mut sm_ptr: *mut c_void = null_mut();
mmdevice.Activate(
&<IAudioSessionManager2 as ::windows::core::Interface>::IID,
CLSCTX_ALL,
null_mut(),
ptr::addr_of_mut!(sm_ptr),
)?;
let sm: IAudioSessionManager2 = mem::transmute(sm_ptr);
let sesenum = sm.GetSessionEnumerator()?;
let sn_count = sesenum.GetCount()?;
let mut cdses = (0..sn_count).map(|idx| sesenum.GetSession(idx)).collect::<Result<Vec<IAudioSessionControl>,_>>()?;
self.sessions.lock().unwrap().append(&mut cdses);
sm.RegisterSessionNotification(IAudioSessionNotification::from( SessionNotification {
sessions: self.sessions.clone()
}))?;
let str = U16CStr::from_ptr_str(mmdevice.GetId()?.0).to_string_lossy();
println!("Device Added: {} {}", str, sn_count);
Ok(())
}
}
unsafe impl Send for SessionKing {}
unsafe impl Sync for SessionKing {}
static KING : Lazy<Arc<Mutex<SessionKing>>> = Lazy::new(|| {
Arc::new(Mutex::new(SessionKing::new()))
});
fn main() {
unsafe {
CoInitializeEx(null_mut(), COINIT_MULTITHREADED).unwrap();
}
KING.lock().unwrap().boot_devices().unwrap();
win_event_hook_loop();
}

View File

@@ -1,26 +0,0 @@
use std::error::Error;
use windows::Win32::{
Foundation::MAX_PATH,
System::Threading::{
OpenProcess, QueryFullProcessImageNameW, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ,
},
};
pub unsafe fn pid_to_exe_path(pid: u32) -> Result<String, Box<dyn Error>> {
let mut exe_name: Vec<u16> = Vec::with_capacity(MAX_PATH as usize);
let mut size: u32 = exe_name.capacity().try_into().unwrap();
let process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
if !QueryFullProcessImageNameW(
process?,
Default::default(),
windows::core::PWSTR(exe_name.as_mut_ptr()),
&mut size,
).as_bool()
{
return Err(Box::new(windows::core::Error::from_win32()));
}
exe_name.set_len(size.try_into().unwrap());
let process_name = String::from_utf16_lossy(&exe_name);
return Ok(process_name);
}

2
todo.txt Normal file
View File

@@ -0,0 +1,2 @@
- Tray icon
- Simple GUI to select from open windows and add to the list