1#![warn(missing_docs)]
2#![deny(unsafe_op_in_unsafe_fn)]
3#![deny(rustdoc::broken_intra_doc_links)]
4#![cfg_attr(docsrs, feature(doc_auto_cfg))]
5#[allow(missing_docs)]
22#[allow(rustdoc::broken_intra_doc_links)]
24pub mod bindings;
25#[cfg(feature = "interface")]
26pub mod interface;
27#[cfg(miri)]
28#[doc(hidden)]
29pub mod miri;
30#[cfg(feature = "interface")]
31pub mod types;
32#[cfg(feature = "interface")]
33pub use types::Device;
34
35use std::ffi::{OsStr, OsString};
36
37use std::io;
38use std::path::Path;
39
40#[doc(hidden)]
41pub static VOICEMEETER_REMOTE: std::sync::OnceLock<VoicemeeterRemoteRaw> =
42 std::sync::OnceLock::new();
43
44#[doc(inline, hidden)]
45pub use bindings::{VoicemeeterRemoteRaw, VBVMR_AUDIOCALLBACK as AudioCallbackMode};
46#[doc(inline)]
47#[cfg(feature = "interface")]
48pub use interface::VoicemeeterRemote;
49
50#[doc(inline)]
51#[cfg(feature = "interface")]
52pub use interface::callback::{commands::CallbackCommand, data::DeviceBuffer};
53
54use winreg::enums::{KEY_READ, KEY_WOW64_32KEY};
55
56static INSTALLER_UNINST_KEY: &str = "VB:Voicemeeter {17359A74-1236-5467}";
57static UNINSTALLER_DIR: &str = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
58static LIBRARY_NAME_64: &str = "VoicemeeterRemote64.dll";
59
60#[doc(hidden)]
61pub fn get_voicemeeter_raw() -> Result<&'static VoicemeeterRemoteRaw, LoadError> {
63 if let Some(remote) = VOICEMEETER_REMOTE.get() {
64 Ok(remote)
65 } else {
66 let path = find_voicemeeter_remote_with_registry()?;
67 load_voicemeeter_from_path(&path)
68 }
69}
70
71#[tracing::instrument]
75fn load_voicemeeter_from_path(path: &OsStr) -> Result<&'static VoicemeeterRemoteRaw, LoadError> {
76 tracing::debug!("loading voicemeeter");
77 VOICEMEETER_REMOTE
78 .set(unsafe { VoicemeeterRemoteRaw::new(path)? })
79 .map_err(|_| LoadError::AlreadyLoaded)?;
80 Ok(VOICEMEETER_REMOTE.get().expect("lock was just set"))
81}
82
83#[derive(Debug, thiserror::Error)]
85#[non_exhaustive]
86pub enum LoadError {
87 #[error("library is already loaded")]
89 AlreadyLoaded,
90 #[error("library could not be loaded")]
92 LoadingError(#[from] libloading::Error),
93 #[error("library could not be located")]
95 RemoteFileError(#[from] RemoteFileError),
96}
97
98#[tracing::instrument]
100pub(crate) fn find_voicemeeter_remote_with_registry() -> Result<OsString, RemoteFileError> {
101 tracing::debug!("finding voicemeeter dll");
102 let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE);
103 let voicemeeter_uninst = if let Ok(reg) = hklm
104 .open_subkey(UNINSTALLER_DIR)
105 .and_then(|s| s.open_subkey(INSTALLER_UNINST_KEY))
106 {
107 reg
109 } else {
110 hklm.open_subkey_with_flags(UNINSTALLER_DIR, KEY_READ | KEY_WOW64_32KEY)
111 .map_err(RegistryError::CouldNotFindUninstallReg)?
112 .open_subkey(INSTALLER_UNINST_KEY)
113 .map_err(RegistryError::CouldNotFindVM)?
114 };
115 let path: String = voicemeeter_uninst
116 .get_value("UninstallString")
117 .map_err(|_| RegistryError::CouldNotFindUninstallString)?;
118 let remote = Path::new(&path)
119 .parent()
120 .ok_or_else(|| RegistryError::UninstallStringInvalid(path.clone()))
121 .map(|p| p.join(LIBRARY_NAME_64))?;
122
123 if remote.exists() {
124 Ok(remote.into_os_string())
125 } else {
126 Err(RemoteFileError::NotFound(remote.display().to_string()))
127 }
128}
129
130#[test]
131#[ignore]
132fn registry_check() -> Result<(), RemoteFileError> {
133 dbg!(find_voicemeeter_remote_with_registry()?);
134 Ok(())
135}
136
137#[derive(Debug, thiserror::Error)]
139#[non_exhaustive]
140pub enum RemoteFileError {
141 #[error("could not find voicemeeter folder: {}", 0)]
143 NotFound(String),
144 #[error(transparent)]
146 RegistryError(#[from] RegistryError),
147}
148
149#[derive(Debug, thiserror::Error)]
151#[non_exhaustive]
152pub enum RegistryError {
153 #[error("could not find uninstall folder in hklm")]
155 CouldNotFindUninstallReg(#[source] io::Error),
156 #[error("could not find voicemeeter in registry. Is Voicemeeter installed?")]
158 CouldNotFindVM(#[source] io::Error),
159 #[error("could not find voicemeeter uninstall string")]
161 CouldNotFindUninstallString,
162 #[error("given uninstall exe is not a valid path: {:?}", 0)]
164 UninstallStringInvalid(String),
165}
166
167#[cfg(feature = "interface")]
169pub(crate) fn opt_or_null<T>(mut option: Option<&mut T>) -> *mut T {
170 if let Some(p) = option.take() {
171 std::ptr::addr_of_mut!(*p)
172 } else {
173 std::ptr::null_mut()
174 }
175}