voicemeeter\interface/
communication_login_logout.rs

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