use std::ffi::{OsStr, c_void};
use std::os::windows::ffi::OsStrExt;
use std::{iter, ptr, slice};
use anyhow::{Context, bail};
use log::debug;
use sysinfo::{Pid, Process, ProcessRefreshKind, RefreshKind, System};
use wasapi::{DeviceEnumerator, Direction};
use windows::Win32::Storage::FileSystem::{
GetFileVersionInfoSizeW, GetFileVersionInfoW, VerQueryValueW,
};
use windows::core::PCWSTR;
use crate::common::result::Result;
use crate::common::source::{AudioSource, AudioSources};
pub fn list() -> Result<AudioSources> {
let mut sources = Vec::new();
let enumerator = DeviceEnumerator::new()
.context("Failed to get device enumerator")?;
let system = System::new_with_specifics(
RefreshKind::nothing().with_processes(ProcessRefreshKind::everything()),
);
for device in &enumerator.get_device_collection(&Direction::Render)
.context("Failed to get audio device collection")?
{
let device = device.context("Failed to get audio endpoint device")?;
let session_enum = device.get_iaudiosessionmanager()
.context("Failed to get audio session manager")?
.get_audiosessionenumerator()
.context("Failed to get audio session enumerator")?;
for i in 0..session_enum.get_count()
.context("Failed to get audio session count")?
{
let session = session_enum.get_session(i)
.context("Failed to get audio session control")?;
let id = session.get_process_id()
.context("Failed to get PID of audio session")?;
if id != 0 {
if let Some(process) = system.process(Pid::from(id as usize)) {
sources.push(AudioSource {
id: id.into(),
name: get_display_name(process).unwrap_or_else(|e| {
let proc = process.name().display();
debug!("failed to get display name for process {proc}, using '{e}'");
proc.to_string()
}).into(),
});
}
}
}
}
Ok(sources.into())
}
fn get_display_name(process: &Process) -> Result<String> {
unsafe {
let exe_path: Vec<u16> = process.exe().context("Failed to get path to process")?
.as_os_str().encode_wide().chain(iter::once(0)).collect();
let info_size = GetFileVersionInfoSizeW(PCWSTR(exe_path.as_ptr()), None);
if info_size == 0 {
bail!("No version information available for this process");
}
let mut info_buffer = vec![0u8; info_size as usize];
GetFileVersionInfoW(
PCWSTR(exe_path.as_ptr()),
None,
info_size,
info_buffer.as_mut_ptr().cast::<c_void>(),
).context("Failed to get file version information")?;
let translation_query = wide_string("\\VarFileInfo\\Translation");
let mut translation_ptr = ptr::null_mut();
let mut translation_size = 0u32;
let result = VerQueryValueW(
info_buffer.as_ptr().cast::<c_void>(),
PCWSTR(translation_query.as_ptr()),
&raw mut translation_ptr,
&raw mut translation_size,
);
if !result.as_bool() || translation_size == 0 {
bail!("Failed to get language and code-pair information");
}
let translation = *translation_ptr.cast::<u32>();
let locale = format!(
"{:04x}{:04x}",
translation as u16,
(translation >> 16) as u16
);
let query_path = wide_string(&format!("\\StringFileInfo\\{locale}\\FileDescription"));
let mut description_ptr = ptr::null_mut();
let mut description_size = 0;
let result = VerQueryValueW(
info_buffer.as_ptr().cast::<c_void>(),
PCWSTR(query_path.as_ptr()),
&raw mut description_ptr,
&raw mut description_size,
);
if !result.as_bool() || description_size == 0 {
bail!("Failed to get display name from FileDescription");
}
let friendly_name = String::from_utf16_lossy(slice::from_raw_parts(
description_ptr.cast::<u16>(),
description_size.saturating_sub(1) as usize,
));
Ok(friendly_name)
}
}
fn wide_string(s: &str) -> Vec<u16> {
OsStr::new(s).encode_wide().chain(iter::once(0)).collect()
}