voicemeeter/
lib.rs

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//! Voicemeeter sdk
6//!
7//! Create a new instance of the Voicemeeter SDK. The instance is automatically logged in.
8//!
9//! ```rust,no_run
10//! use voicemeeter::{types::Device, VoicemeeterRemote};
11//!
12//! let remote = VoicemeeterRemote::new()?;
13//! println!("{}", remote.get_voicemeeter_version()?);
14//! println!(
15//!     "Strip 1: {}",
16//!     remote.parameters().strip(Device::Strip1)?.label().get()?
17//! );
18//! # Ok::<(), Box<dyn std::error::Error>>(())
19//! ```
20
21#[allow(missing_docs)]
22/// Raw FFI Bindings
23#[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)]
61/// Get a reference to voicemeeter remote
62pub 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/// Load voicemeeter
72///
73/// Errors if it's already loaded
74#[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/// Load error while loading the Voicemeeter remote DLL
84#[derive(Debug, thiserror::Error)]
85#[non_exhaustive]
86pub enum LoadError {
87    /// Remote is already loaded. Not a hard error.
88    #[error("library is already loaded")]
89    AlreadyLoaded,
90    /// Error while loading the DLL.
91    #[error("library could not be loaded")]
92    LoadingError(#[from] libloading::Error),
93    /// Could not locate the dll
94    #[error("library could not be located")]
95    RemoteFileError(#[from] RemoteFileError),
96}
97
98/// Get VoiceMeeterRemote via registry key
99#[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        // TODO: This will almost always fail, esp. on 64bit systems.
108        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/// Error while trying to get Voicemeeter location
138#[derive(Debug, thiserror::Error)]
139#[non_exhaustive]
140pub enum RemoteFileError {
141    /// Voicemeeter dll not found at path
142    #[error("could not find voicemeeter folder: {}", 0)]
143    NotFound(String),
144    /// Registry error
145    #[error(transparent)]
146    RegistryError(#[from] RegistryError),
147}
148
149/// Registry errors
150#[derive(Debug, thiserror::Error)]
151#[non_exhaustive]
152pub enum RegistryError {
153    /// Could not find the uninstall folder in HKLM for 32-bit apps
154    #[error("could not find uninstall folder in hklm")]
155    CouldNotFindUninstallReg(#[source] io::Error),
156    /// Could not find voicemeeter in uninstall registry
157    #[error("could not find voicemeeter in registry. Is Voicemeeter installed?")]
158    CouldNotFindVM(#[source] io::Error),
159    /// Could not find voicemeeter uninstall string in registry
160    #[error("could not find voicemeeter uninstall string")]
161    CouldNotFindUninstallString,
162    /// Given uninstall exe is not a valid path
163    #[error("given uninstall exe is not a valid path: {:?}", 0)]
164    UninstallStringInvalid(String),
165}
166
167/// Get a pointer to a `T` if option is [`Some`](Option::Some) or a null ptr if it's [`None`](Option::None)
168#[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}