#![warn(missing_docs)]
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(rustdoc::broken_intra_doc_links)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[allow(missing_docs)]
#[allow(rustdoc::broken_intra_doc_links)]
pub mod bindings;
#[cfg(feature = "interface")]
pub mod interface;
#[cfg(miri)]
#[doc(hidden)]
pub mod miri;
#[cfg(feature = "interface")]
pub mod types;
#[cfg(feature = "interface")]
pub use types::Device;
use std::ffi::{OsStr, OsString};
use std::io;
use std::path::Path;
#[doc(hidden)]
pub static VOICEMEETER_REMOTE: std::sync::OnceLock<VoicemeeterRemoteRaw> =
std::sync::OnceLock::new();
#[doc(inline, hidden)]
pub use bindings::{VoicemeeterRemoteRaw, VBVMR_AUDIOCALLBACK as AudioCallbackMode};
#[doc(inline)]
#[cfg(feature = "interface")]
pub use interface::VoicemeeterRemote;
#[doc(inline)]
#[cfg(feature = "interface")]
pub use interface::callback::{commands::CallbackCommand, data::DeviceBuffer};
use winreg::enums::{KEY_READ, KEY_WOW64_32KEY};
static INSTALLER_UNINST_KEY: &str = "VB:Voicemeeter {17359A74-1236-5467}";
static UNINSTALLER_DIR: &str = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
static LIBRARY_NAME_64: &str = "VoicemeeterRemote64.dll";
#[doc(hidden)]
pub fn get_voicemeeter_raw() -> Result<&'static VoicemeeterRemoteRaw, LoadError> {
if let Some(remote) = VOICEMEETER_REMOTE.get() {
Ok(remote)
} else {
let path = find_voicemeeter_remote_with_registry()?;
load_voicemeeter_from_path(&path)
}
}
#[tracing::instrument]
fn load_voicemeeter_from_path(path: &OsStr) -> Result<&'static VoicemeeterRemoteRaw, LoadError> {
tracing::debug!("loading voicemeeter");
VOICEMEETER_REMOTE
.set(unsafe { VoicemeeterRemoteRaw::new(path)? })
.map_err(|_| LoadError::AlreadyLoaded)?;
Ok(VOICEMEETER_REMOTE.get().expect("lock was just set"))
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum LoadError {
#[error("library is already loaded")]
AlreadyLoaded,
#[error("library could not be loaded")]
LoadingError(#[from] libloading::Error),
#[error("library could not be located")]
RemoteFileError(#[from] RemoteFileError),
}
#[tracing::instrument]
pub(crate) fn find_voicemeeter_remote_with_registry() -> Result<OsString, RemoteFileError> {
tracing::debug!("finding voicemeeter dll");
let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE);
let voicemeeter_uninst = if let Ok(reg) = hklm
.open_subkey(UNINSTALLER_DIR)
.and_then(|s| s.open_subkey(INSTALLER_UNINST_KEY))
{
reg
} else {
hklm.open_subkey_with_flags(UNINSTALLER_DIR, KEY_READ | KEY_WOW64_32KEY)
.map_err(RegistryError::CouldNotFindUninstallReg)?
.open_subkey(INSTALLER_UNINST_KEY)
.map_err(RegistryError::CouldNotFindVM)?
};
let path: String = voicemeeter_uninst
.get_value("UninstallString")
.map_err(|_| RegistryError::CouldNotFindUninstallString)?;
let remote = Path::new(&path)
.parent()
.ok_or_else(|| RegistryError::UninstallStringInvalid(path.clone()))
.map(|p| p.join(LIBRARY_NAME_64))?;
if remote.exists() {
Ok(remote.into_os_string())
} else {
Err(RemoteFileError::NotFound(remote.display().to_string()))
}
}
#[test]
#[ignore]
fn registry_check() -> Result<(), RemoteFileError> {
dbg!(find_voicemeeter_remote_with_registry()?);
Ok(())
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum RemoteFileError {
#[error("could not find voicemeeter folder: {}", 0)]
NotFound(String),
#[error(transparent)]
RegistryError(#[from] RegistryError),
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum RegistryError {
#[error("could not find uninstall folder in hklm")]
CouldNotFindUninstallReg(#[source] io::Error),
#[error("could not find voicemeeter in registry. Is Voicemeeter installed?")]
CouldNotFindVM(#[source] io::Error),
#[error("could not find voicemeeter uninstall string")]
CouldNotFindUninstallString,
#[error("given uninstall exe is not a valid path: {:?}", 0)]
UninstallStringInvalid(String),
}
#[cfg(feature = "interface")]
pub(crate) fn opt_or_null<T>(mut option: Option<&mut T>) -> *mut T {
if let Some(p) = option.take() {
std::ptr::addr_of_mut!(*p)
} else {
std::ptr::null_mut()
}
}