windows_volume_control/
lib.rs

1use 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}