1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
//! Communication with and to Voicemeeter
//!
//! # Functions
//! * [`initial_status`](VoicemeeterRemote::initial_status)
//! * `login` is implicitly called when creating your first [`VoicemeeterRemote`] instance.
//! * [`logout`](VoicemeeterRemote::logout)
//! * [`run_voicemeeter`](VoicemeeterRemote::run_voicemeeter)
use crate::types::VoicemeeterApplication;
use super::VoicemeeterRemote;
static HAS_LOGGED_IN: std::sync::OnceLock<VoicemeeterStatus> = std::sync::OnceLock::new();
impl VoicemeeterRemote {
/// Get the status of the running Voicemeeter instance when we first logged in
pub fn initial_status() -> VoicemeeterStatus {
HAS_LOGGED_IN.get().unwrap().clone()
}
pub(crate) fn login(&mut self) -> Result<VoicemeeterStatus, LoginError> {
if let Some(res) = HAS_LOGGED_IN.get() {
return Err(LoginError::AlreadyLoggedIn(res.clone()));
}
let res = unsafe { self.raw.VBVMR_Login() };
let res = match res {
0 => Ok(VoicemeeterStatus::Launched),
1 => Ok(VoicemeeterStatus::NotLaunched),
-2 => Err(LoginError::LoginFailed),
s => Err(LoginError::Unexpected(s)),
}?;
tracing::debug!("logged in with status {:?}", res);
Ok(HAS_LOGGED_IN.get_or_init(|| res).clone())
}
/// Logout from the voicemeeter instance. This should only be called when you never need another VoiceMeeter remote again.
///
/// # Notes
///
/// [`VoicemeeterRemote::new`] will automatically login if needed.
pub fn logout(self) -> Result<(), LogoutError> {
drop(self);
if super::LOGOUT_HANDLE
.get()
.unwrap()
.lock()
.unwrap()
.is_some()
{
Err(LogoutError::OtherRemotesExists)
} else {
Ok(())
}
}
pub(crate) fn _logout(&mut self) -> Result<(), LogoutError> {
let _ = self.logout_handle.take();
// TODO: use Option::take_if ?
let Some(mut a) = super::LOGOUT_HANDLE.get().unwrap().lock().unwrap().take() else {
return Ok(());
};
if let Some(logged_out) = std::sync::Arc::get_mut(&mut a) {
if *logged_out {
return Ok(());
}
tracing::debug!("logging out");
let res = unsafe { self.raw.VBVMR_Logout() };
match res {
0 => {
*logged_out = true;
Ok(())
}
s => Err(LogoutError::Unexpected(s)),
}
} else {
super::LOGOUT_HANDLE
.get()
.unwrap()
.lock()
.unwrap()
.replace(a);
Err(LogoutError::OtherRemotesExists)
}
}
/// Invoke Voicemeeter to open and be visible on previous location.
pub fn run_voicemeeter(
&self,
r#type: VoicemeeterApplication,
) -> Result<(), RunVoicemeeterError> {
let res = unsafe { self.raw.VBVMR_RunVoicemeeter(r#type as i32) };
match res {
0 => Ok(()),
-1 => Err(RunVoicemeeterError::NotInstalled),
-2 => Err(RunVoicemeeterError::UnknownType),
s => Err(RunVoicemeeterError::Other(s)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
/// The status of the Voicemeeter instance.
pub enum VoicemeeterStatus {
/// Voicemeeter is launched.
Launched,
/// Voicemeeter is not launched.
NotLaunched,
}
/// Errors that can happen when logging in.
#[derive(Debug, thiserror::Error, Clone)]
#[non_exhaustive]
pub enum LoginError {
/// Application has already logged in
#[error("application has already logged in")]
AlreadyLoggedIn(VoicemeeterStatus),
/// The login failed.
#[error("unexpected login (logout was expected before)")]
LoginFailed,
/// An unexpected error occured.
#[error("cannot get client (unexpected): {0}")]
Unexpected(i32),
}
/// Errors that can happen when loging out.
#[derive(Debug, thiserror::Error, Clone)]
#[non_exhaustive]
pub enum LogoutError {
/// Couldn't logout due to other [VoicemeeterRemote]s existing in this program
#[error("couldn't logout due to other `VoicemeeterRemote`s existing in this program")]
OtherRemotesExists,
/// An unexpected error occured.
#[error("cannot get client (unexpected): {0}")]
Unexpected(i32),
}
/// Errors that can happen when [opening](VoicemeeterRemote::run_voicemeeter) voicemeeter.
#[derive(Debug, thiserror::Error, Clone)]
#[non_exhaustive]
pub enum RunVoicemeeterError {
/// Voicemeeter is not installed.
#[error("voicemeeter is not installed")]
NotInstalled,
/// Unknown voicemeeter type.
#[error("unknown type")]
UnknownType,
/// An unexpected error occured.
#[error("unexpected error occurred: error code {0}")]
Other(i32),
}
impl LoginError {
/// Returns `true` if the login error is [`LoginFailed`].
///
/// [`LoginFailed`]: LoginError::LoginFailed
pub fn is_login_failed(&self) -> bool {
matches!(self, Self::LoginFailed)
}
/// Returns `true` if the login error is [`Unexpected`].
///
/// [`Unexpected`]: LoginError::Unexpected
pub fn is_unexpected(&self) -> bool {
matches!(self, Self::Unexpected(..))
}
}