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 #[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 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 unsafe { HostExtensions::new_unchecked(self) }
64 }
65}
66
67macro_rules! impl_host_get_str {
68 ($($description:tt),*) => {
69 impl Host {
70 $(
71 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 unsafe { callback(&raw const *self.clap_host()) }
98 }
99 }
100 )*
101 }
102 };
103}
104
105impl_host_request!(request_process, request_restart, request_callback);
106
107unsafe impl Send for Host {}
109unsafe impl Sync for Host {}
111
112pub struct HostExtensions<'a> {
113 host: &'a Host,
114}
115
116impl<'a> HostExtensions<'a> {
117 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 let callback = self.host.clap_host().get_extension.unwrap();
127 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}