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
//! The interface for Voicemeeter remote.
//!
//! See the methods on [`VoicemeeterRemote`] for how to use the interface
use crate::{types::VoicemeeterApplication, LoadError};

use self::{
    communication_login_logout::LoginError, general_information::GetVoicemeeterInformationError,
};

pub mod callback;
pub mod communication_login_logout;
pub mod device;
pub mod general_information;
pub mod get_levels;
pub mod macro_buttons;
pub mod parameters;

/// Interface for voicemeeter.
#[derive(Clone)]
#[cfg(feature = "interface")] // for doc_cfg
pub struct VoicemeeterRemote {
    raw: &'static crate::bindings::VoicemeeterRemoteRaw,
    logout_handle: Option<std::sync::Arc<bool>>,
    /// The type of the running Voicemeeter instance.
    pub program: VoicemeeterApplication,
}

pub(crate) static LOGOUT_HANDLE: std::sync::OnceLock<
    std::sync::Mutex<Option<std::sync::Arc<bool>>>,
> = std::sync::OnceLock::new();

impl std::fmt::Debug for VoicemeeterRemote {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.program {
            VoicemeeterApplication::None => write!(f, "[no voicemeeter program running]"),
            p => write!(f, "[{}]", p),
        }
    }
}

impl VoicemeeterRemote {
    /// Creates a new [`VoicemeeterRemote`] instance that is logged in with the client.
    ///
    /// # Notes
    ///
    /// If you create [`VoicemeeterRemote`] instances, when the last instance remaining is dropped,
    /// you will automatically be logged out of the API, meaning you cannot login again.
    ///
    /// This is done to prevent leaving the API logged in when your program is closed,
    /// causing a visual bug in the voicemeeter application.
    #[tracing::instrument]
    pub fn new() -> Result<Self, InitializationError> {
        let raw = crate::get_voicemeeter_raw()?;
        let mut s = VoicemeeterRemote::from_raw(raw);
        if s.logout_handle.is_none() {
            return Err(InitializationError::AlreadyLoggedOut);
        }
        match s.login() {
            Ok(_) => {}
            Err(LoginError::AlreadyLoggedIn(_)) => {}
            e => {
                e?;
            }
        };
        s.update_program()?;
        Ok(s)
    }

    fn from_raw(raw: &'static crate::VoicemeeterRemoteRaw) -> VoicemeeterRemote {
        Self {
            raw,
            program: VoicemeeterApplication::Other,
            logout_handle: LOGOUT_HANDLE
                .get_or_init(|| std::sync::Mutex::new(Some(std::sync::Arc::new(false))))
                .lock()
                .unwrap()
                .clone(),
        }
    }

    /// Update the current program type.
    pub fn update_program(&mut self) -> Result<(), GetVoicemeeterInformationError> {
        match self.get_voicemeeter_type() {
            Ok(t) => self.program = t,
            Err(GetVoicemeeterInformationError::NoServer) => {
                self.program = VoicemeeterApplication::None
            }
            Err(e) => return Err(e),
        }
        Ok(())
    }
}

impl Drop for VoicemeeterRemote {
    fn drop(&mut self) {
        // This logout only happens if this is the only voicemeeter handle that exists.
        let _ = self._logout();
    }
}

/// Errors that can occur when initializing the Voicemeeter remote DLL.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum InitializationError {
    /// Error while loading the DLL.
    #[error("could not load the client")]
    LoadError(#[from] LoadError),
    /// Error when logging in.
    #[error("could not login")]
    LoginError(#[from] LoginError),
    /// Application has already logged out.
    #[error("application has already logged out, so cannot login again")]
    AlreadyLoggedOut,
    /// Error when getting the Voicemeeter application type.
    #[error("could not get voicemeeter type")]
    InformationError(#[from] GetVoicemeeterInformationError),
}