use std::ffi::{OsStr, c_void};
use std::os::windows::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::{iter, ptr, slice};
use anyhow::{Context, bail};
use log::{debug, warn};
use wasapi::{DeviceEnumerator, Direction};
use windows::Win32::Foundation::{CloseHandle, MAX_PATH};
use windows::Win32::Storage::FileSystem::{
GetFileVersionInfoSizeW, GetFileVersionInfoW, VerQueryValueW,
};
use windows::Win32::System::Threading::{
OpenProcess, PROCESS_NAME_WIN32, PROCESS_QUERY_LIMITED_INFORMATION, QueryFullProcessImageNameW,
};
use windows::core::{PCWSTR, PWSTR};
use crate::SourceName;
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")?;
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 {
match exe_path(id) {
Ok(path) => {
sources.push(AudioSource {
id: id.into(),
name: friendly_name(&path).or_else(|e| -> Result<SourceName> {
let name = path.file_name()
.context("Process path should never end in '..'")?
.display().to_string();
debug!("using name={name} for source id={id}: {e}");
Ok(name.into())
})?
});
}
Err(e) => {
warn!("skipping process id={id}: {e}");
break;
}
}
}
}
}
Ok(sources.into())
}
fn exe_path(pid: u32) -> Result<PathBuf> {
unsafe {
let handle = OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION,
false,
pid
).context("Failed to access process object")?;
let mut path_buffer = vec![0u16; MAX_PATH as usize];
let mut buffer_size = MAX_PATH;
QueryFullProcessImageNameW(
handle,
PROCESS_NAME_WIN32,
PWSTR(path_buffer.as_mut_ptr()),
&raw mut buffer_size,
).context("Failed to query process image name")?;
CloseHandle(handle).context("Handle should never be invalid")?;
let path = String::from_utf16_lossy(&path_buffer[..buffer_size as usize]);
Ok(path.into())
}
}
fn friendly_name(path: &Path) -> Result<SourceName> {
unsafe {
let exe_path: Vec<u16> = path.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 locale 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.into())
}
}
fn wide_string(s: &str) -> Vec<u16> {
OsStr::new(s).encode_wide().chain(iter::once(0)).collect()
}