clap_clap/
host.rs

1use std::{
2    ffi::{CStr, c_void},
3    fmt::{Display, Formatter},
4};
5
6use crate::{
7    ext::{
8        audio_ports::HostAudioPorts, latency::HostLatency, log::HostLog, note_ports::HostNotePorts,
9        params::HostParams, state::HostState, tail::HostTail,
10    },
11    ffi::{
12        CLAP_EXT_AUDIO_PORTS, CLAP_EXT_LATENCY, CLAP_EXT_LOG, CLAP_EXT_NOTE_PORTS, CLAP_EXT_PARAMS,
13        CLAP_EXT_STATE, CLAP_EXT_TAIL, clap_host, clap_host_audio_ports, clap_host_latency,
14        clap_host_log, clap_host_note_ports, clap_host_params, clap_host_state, clap_host_tail,
15    },
16    version::ClapVersion,
17};
18
19#[derive(Debug, PartialEq)]
20pub struct Host {
21    clap_host: *const clap_host,
22}
23
24impl Host {
25    /// # Safety
26    ///
27    /// The host argument must be a non-null pointer to a CLAP host, obtained as
28    /// the argument passed to factory create_plugin(). In particular, all
29    /// fields, except for host_data, of clap_host struct must be valid
30    /// pointers.
31    ///
32    /// # Panic
33    ///
34    /// The function will panic if host description strings aren't properly
35    /// validated UTF-8 strings.
36    #[doc(hidden)]
37    pub const unsafe fn new_unchecked(clap_host: *const clap_host) -> Self {
38        #[cfg(debug_assertions)]
39        {
40            assert!(!clap_host.is_null());
41            let clap_host = unsafe { &*clap_host };
42            assert!(clap_host.get_extension.is_some());
43            assert!(clap_host.request_callback.is_some());
44            assert!(clap_host.request_process.is_some());
45            assert!(clap_host.request_restart.is_some());
46        }
47
48        Self { clap_host }
49    }
50
51    pub const fn clap_host(&self) -> &clap_host {
52        // SAFETY: by construction, we can obtain a shared reference to clap_host for
53        // the lifetime of self.
54        unsafe { &*self.clap_host }
55    }
56
57    pub const fn clap_version(&self) -> ClapVersion {
58        self.clap_host().clap_version
59    }
60
61    pub const fn get_extension(&self) -> HostExtensions {
62        // SAFETY: By construction, the function pointer to `get_extension()` is valid.
63        unsafe { HostExtensions::new_unchecked(self) }
64    }
65}
66
67macro_rules! impl_host_get_str {
68    ($($description:tt),*) => {
69        impl Host {
70            $(
71                /// # Panic
72                ///
73                /// This method will panic if the host returns an invalid UTF-8 string.
74                pub fn $description(&self) -> &str {
75                    unsafe { CStr::from_ptr(self.clap_host().$description)
76                                .to_str()
77                                .expect("host description must be a valid UTF-8 string")
78                    }
79                }
80            )*
81        }
82    };
83}
84
85impl_host_get_str!(name, vendor, url, version);
86
87macro_rules! impl_host_request {
88    ($($request_method:tt),*) => {
89        impl Host {
90            $(
91                pub fn $request_method(&self) {
92                    let clap_host = self.clap_host();
93                    if let Some(callback) = clap_host.$request_method {
94                        // SAFETY: By the Host constructon, the callback is non-null.
95                        // The pointer is a valid function obtained from the CLAP host. It is
96                        // guaranteed be the host that the call is safe.
97                        unsafe { callback(&raw const *self.clap_host()) }
98                    }
99                }
100            )*
101        }
102    };
103}
104
105impl_host_request!(request_process, request_restart, request_callback);
106
107// SAFETY: !Send for raw pointers is not for safety, just as a lint.
108unsafe impl Send for Host {}
109// SAFETY: !Sync for raw pointers is not for safety, just as a lint.
110unsafe impl Sync for Host {}
111
112pub struct HostExtensions<'a> {
113    host: &'a Host,
114}
115
116impl<'a> HostExtensions<'a> {
117    /// # Safety
118    ///
119    /// The function pointer:  `host.clap_host().get_extension()` must be Some.
120    const unsafe fn new_unchecked(host: &'a Host) -> Self {
121        Self { host }
122    }
123
124    fn get_extension_ptr(&self, extension_id: &CStr) -> Option<*const c_void> {
125        // HostExtensions constructor guarantees that unwrap won't panic.
126        let callback = self.host.clap_host().get_extension.unwrap();
127        // SAFETY: Host constructor guarantees that the call is safe.
128        let ext_ptr = unsafe { callback(self.host.clap_host(), extension_id.as_ptr()) };
129        (!ext_ptr.is_null()).then_some(ext_ptr)
130    }
131
132    pub fn audio_ports(&self) -> Result<HostAudioPorts<'a>, Error> {
133        let clap_host_audio_ports = self
134            .get_extension_ptr(CLAP_EXT_AUDIO_PORTS)
135            .ok_or(Error::ExtensionNotFound("audio_ports"))?;
136
137        // SAFETY: We just checked if the pointer to clap_host_audio_ports
138        // is non-null. We return a reference to it for the lifetime of Host.
139        let clap_host_audio_ports: &clap_host_audio_ports =
140            unsafe { &*clap_host_audio_ports.cast() };
141
142        let _ = clap_host_audio_ports
143            .is_rescan_flag_supported
144            .ok_or(Error::Callback("is_rescan_flag_supported"))?;
145        let _ = clap_host_audio_ports
146            .rescan
147            .ok_or(Error::Callback("rescan"))?;
148
149        // SAFETY: We just checked if the methods are non-null (Some).
150        Ok(unsafe { HostAudioPorts::new_unchecked(self.host, clap_host_audio_ports) })
151    }
152
153    pub fn latency(&self) -> Result<HostLatency<'a>, Error> {
154        let clap_host_latency = self
155            .get_extension_ptr(CLAP_EXT_LATENCY)
156            .ok_or(Error::ExtensionNotFound("latency"))?;
157
158        // SAFETY: We just checked if the pointer to clap_log is non-null. We return a
159        // reference to it for the lifetime of Host.
160        let clap_host_latency: &clap_host_latency = unsafe { &*clap_host_latency.cast() };
161
162        let _ = clap_host_latency
163            .changed
164            .ok_or(Error::Callback("changed"))?;
165
166        // SAFETY: We just checked if the pointer to clap_host_latency, and all its
167        // methods, are non-null.
168        Ok(unsafe { HostLatency::new_unchecked(self.host, clap_host_latency) })
169    }
170
171    pub fn log(&self) -> Result<HostLog<'a>, Error> {
172        let clap_host_log = self
173            .get_extension_ptr(CLAP_EXT_LOG)
174            .ok_or(Error::ExtensionNotFound("log"))?;
175
176        // SAFETY: We just checked if the pointer to clap_log is non-null. We return a
177        // reference to it for the lifetime of Host.
178        let clap_host_log: &clap_host_log = unsafe { &*clap_host_log.cast() };
179
180        let _ = clap_host_log.log.ok_or(Error::Callback("log"))?;
181
182        // SAFETY: We just checked if the pointer to clap_host_log, and all its methods,
183        // are non-null.
184        Ok(unsafe { HostLog::new_unchecked(self.host, clap_host_log) })
185    }
186
187    pub fn note_ports(&self) -> Result<HostNotePorts<'a>, Error> {
188        let clap_host_note_ports = self
189            .get_extension_ptr(CLAP_EXT_NOTE_PORTS)
190            .ok_or(Error::ExtensionNotFound("note_ports"))?;
191
192        // SAFETY: We just checked if the pointer to clap_host_note_ports
193        // is non-null. We return a reference to it for the lifetime of Host.
194        let clap_host_note_ports: &clap_host_note_ports = unsafe { &*clap_host_note_ports.cast() };
195
196        let _ = clap_host_note_ports
197            .supported_dialects
198            .ok_or(Error::Callback("supported_dialects"))?;
199        let _ = clap_host_note_ports
200            .rescan
201            .ok_or(Error::Callback("rescan"))?;
202
203        // SAFETY: We just checked if the methods are non-null (Some).
204        Ok(unsafe { HostNotePorts::new_unchecked(self.host, clap_host_note_ports) })
205    }
206
207    pub fn params(&self) -> Result<HostParams<'a>, Error> {
208        let clap_host_params = self
209            .get_extension_ptr(CLAP_EXT_PARAMS)
210            .ok_or(Error::ExtensionNotFound("params"))?;
211
212        // SAFETY: We just checked if the pointer to clap_host_params is non-null. We
213        // return a reference to it for the lifetime of Host.
214        let clap_host_params: &clap_host_params = unsafe { &*clap_host_params.cast() };
215
216        let _ = clap_host_params.rescan.ok_or(Error::Callback("rescan"))?;
217        let _ = clap_host_params.clear.ok_or(Error::Callback("clear"))?;
218        let _ = clap_host_params
219            .request_flush
220            .ok_or(Error::Callback("request_flush"))?;
221
222        // SAFETY: We just checked if the pointer to clap_host_params, and all its
223        // methods are non-null.
224        Ok(unsafe { HostParams::new_unchecked(self.host, clap_host_params) })
225    }
226
227    pub fn state(&self) -> Result<HostState<'a>, Error> {
228        let clap_host_state = self
229            .get_extension_ptr(CLAP_EXT_STATE)
230            .ok_or(Error::ExtensionNotFound("state"))?;
231
232        // SAFETY: We just checked if the pointer to clap_host_state is non-null. We
233        // return a reference to it for the lifetime of Host.
234        let clap_host_state: &clap_host_state = unsafe { &*clap_host_state.cast() };
235
236        let _ = clap_host_state
237            .mark_dirty
238            .ok_or(Error::Callback("make_dirty"))?;
239
240        // SAFETY: We just checked if the pointer to clap_host_state, and all its
241        // methods are non-null.
242        Ok(unsafe { HostState::new_unchecked(self.host, clap_host_state) })
243    }
244
245    pub fn tail(&self) -> Result<HostTail<'a>, Error> {
246        let clap_host_tail = self
247            .get_extension_ptr(CLAP_EXT_TAIL)
248            .ok_or(Error::ExtensionNotFound("tail"))?;
249
250        // SAFETY: We just checked if the pointer to clap_host_tail is non-null. We
251        // return a reference to it for the lifetime of Host.
252        let clap_host_tail: &clap_host_tail = unsafe { &*clap_host_tail.cast() };
253
254        let _ = clap_host_tail.changed.ok_or(Error::Callback("changed"))?;
255
256        // SAFETY: We just checked if the pointer to clap_host_state, and all its
257        // methods are non-null.
258        Ok(unsafe { HostTail::new_unchecked(self.host, clap_host_tail) })
259    }
260}
261
262#[derive(Debug, Clone, PartialEq)]
263pub enum Error {
264    ExtensionNotFound(&'static str),
265    Callback(&'static str),
266}
267
268impl Display for Error {
269    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
270        match self {
271            Error::ExtensionNotFound(name) => write!(f, "extension not found: {name}"),
272            Error::Callback(name) => write!(f, "extension callback not found: {name}"),
273        }
274    }
275}
276
277impl std::error::Error for Error {}
278
279impl From<Error> for crate::Error {
280    fn from(value: Error) -> Self {
281        Self::Host(value)
282    }
283}