windows_volume_control/
lib.rs1use session::{ApplicationSession, EndPointSession, Session};
2use windows::{
3 core::Interface,
4 Win32::{
5 Media::Audio::{
6 eMultimedia, eRender, Endpoints::IAudioEndpointVolume, IAudioSessionControl,
7 IAudioSessionControl2, IAudioSessionEnumerator, IAudioSessionManager2, IMMDevice,
8 IMMDeviceEnumerator, ISimpleAudioVolume, MMDeviceEnumerator,
9 },
10 System::{
11 Com::{CoCreateInstance, CoInitializeEx, CLSCTX_INPROC_SERVER, COINIT_MULTITHREADED, CLSCTX_ALL, COINIT_APARTMENTTHREADED},
12 ProcessStatus::K32GetProcessImageFileNameA,
13 Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ},
14 },
15 },
16};
17use std::process::exit;
18
19mod session;
20
21pub struct AudioController {
22 default_device: Option<IMMDevice>,
23 imm_device_enumerator: Option<IMMDeviceEnumerator>,
24 sessions: Vec<Box<dyn Session>>,
25}
26
27pub enum CoinitMode {
28 MultiTreaded,
29 ApartmentThreaded
30}
31
32impl AudioController {
33 pub unsafe fn init(coinit_mode: Option<CoinitMode>) -> Self {
34 let mut coinit: windows::Win32::System::Com::COINIT = COINIT_MULTITHREADED;
35 if let Some(x) = coinit_mode {
36 match x {
37 CoinitMode::ApartmentThreaded => {coinit = COINIT_APARTMENTTHREADED},
38 CoinitMode::MultiTreaded => {coinit = COINIT_MULTITHREADED}
39 }
40 }
41 CoInitializeEx(None, coinit).unwrap_or_else(|err| {
42 eprintln!("ERROR: Couldn't initialize windows connection: {err}");
43 exit(1);
44 });
45
46 Self {
47 default_device: None,
48 imm_device_enumerator: None,
49 sessions: vec![],
50 }
51 }
52 pub unsafe fn GetSessions(&mut self) {
53 self.imm_device_enumerator = Some(
54 CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_INPROC_SERVER).unwrap_or_else(
55 |err| {
56 eprintln!("ERROR: Couldn't get Media device enumerator: {err}");
57 exit(1);
58 },
59 ),
60 );
61 }
62
63 pub unsafe fn GetDefaultAudioEnpointVolumeControl(&mut self) {
64 if self.imm_device_enumerator.is_none() {
65 eprintln!("ERROR: Function called before creating enumerator");
66 return;
67 }
68
69 self.default_device = Some(
70 self.imm_device_enumerator
71 .clone()
72 .unwrap()
73 .GetDefaultAudioEndpoint(eRender, eMultimedia)
74 .unwrap_or_else(|err| {
75 eprintln!("ERROR: Couldn't get Default audio endpoint {err}");
76 exit(1);
77 }),
78 );
79 let simple_audio_volume: IAudioEndpointVolume = self
80 .default_device
81 .clone()
82 .unwrap()
83 .Activate(CLSCTX_ALL, None)
84 .unwrap_or_else(|err| {
85 eprintln!("ERROR: Couldn't get Endpoint volume control: {err}");
86 exit(1);
87 });
88
89 self.sessions.push(Box::new(EndPointSession::new(
90 simple_audio_volume,
91 "master".to_string(),
92 )));
93 }
94
95 pub unsafe fn GetAllProcessSessions(&mut self) {
96 if self.default_device.is_none() {
97 eprintln!("ERROR: Default device hasn't been initialized so the cant find the audio processes...");
98 return;
99 }
100
101 let session_manager2: IAudioSessionManager2 = self.default_device.as_ref().unwrap().Activate(CLSCTX_INPROC_SERVER, None).unwrap_or_else(|err| {
102 eprintln!("ERROR: Couldnt get AudioSessionManager for enumerating over processes... {err}");
103 exit(1);
104 });
105
106 let session_enumerator: IAudioSessionEnumerator = session_manager2
107 .GetSessionEnumerator()
108 .unwrap_or_else(|err| {
109 eprintln!("ERROR: Couldnt get session enumerator... {err}");
110 exit(1);
111 });
112
113 for i in 0..session_enumerator.GetCount().unwrap() {
114 let normal_session_control: Option<IAudioSessionControl> =
115 session_enumerator.GetSession(i).ok();
116 if normal_session_control.is_none() {
117 eprintln!("ERROR: Couldn't get session control of audio session...");
118 continue;
119 }
120
121 let session_control: Option<IAudioSessionControl2> =
122 normal_session_control.unwrap().cast().ok();
123 if session_control.is_none() {
124 eprintln!(
125 "ERROR: Couldn't convert from normal session control to session control 2"
126 );
127 continue;
128 }
129
130 let pid = session_control.as_ref().unwrap().GetProcessId().unwrap();
131 if pid == 0 {
132 continue;
133 }
134 let process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok();
135 if process.is_none() {
136 eprintln!("ERROR: Couldn't get process information of process id {pid}");
137 continue;
138 }
139 let mut filename: [u8; 128] = [0; 128];
140 K32GetProcessImageFileNameA(process, &mut filename);
141 let mut new_filename: Vec<u8> = vec![];
142 for i in filename.iter() {
143 if i == &(0 as u8) {
144 continue;
145 }
146 new_filename.push(i.clone());
147 }
148 let mut str_filename = match String::from_utf8(new_filename) {
149 Ok(data) => data,
150 Err(err) => {
151 eprintln!("ERROR: Filename couldn't be converted to string, {err}");
152 continue;
153 }
154 };
155 str_filename = match str_filename.split("\\").last() {
156 Some(data) => data.to_string().replace(".exe", ""),
157 None => {
158 continue;
159 }
160 };
161 let audio_control: ISimpleAudioVolume = match session_control.unwrap().cast() {
162 Ok(data) => data,
163 Err(err) => {
164 eprintln!(
165 "ERROR: Couldn't get the simpleaudiovolume from session controller: {err}"
166 );
167 continue;
168 }
169 };
170 let application_session = ApplicationSession::new(audio_control, str_filename);
171 self.sessions.push(Box::new(application_session));
172 }
173 }
174
175 pub unsafe fn get_all_session_names(&self) -> Vec<String> {
176 self.sessions.iter().map(|i| i.getName()).collect()
177 }
178
179 pub unsafe fn get_session_by_name(&self, name: String) -> Option<&Box<dyn Session>> {
180 self.sessions.iter().find(|i| i.getName() == name)
181 }
182}