clack_common/plugin/descriptor.rs
1use clap_sys::plugin::clap_plugin_descriptor;
2use clap_sys::version::{CLAP_VERSION, clap_version};
3use std::ffi::{CStr, CString};
4use std::marker::PhantomData;
5use std::mem::ManuallyDrop;
6use std::os::raw::c_char;
7
8/// Provides metadata about a given Plugin, such as its ID, name, version, and more.
9///
10/// All fields of this type as exposed as optional, however the CLAP specification requires the
11/// [`id`](PluginDescriptor::id) and [`name`](PluginDescriptor::name) fields to be present. It is
12/// acceptable for a host to refuse loading a plugin that returns [`None`] for these fields.
13///
14/// As such, when constructing this type, the [`id`](PluginDescriptor::id) and [`name`](PluginDescriptor::name) fields are
15/// mandatory, and should not be blank. All the other fields are completely optional.
16///
17/// Note that all the read accessors of this type are exposed as the CLAP-native [`CStr`], as they are not
18/// required by the CLAP spec to be UTF-8 compliant, only exposing byte slices. Hosts are left to
19/// interpret non-UTF-8 data best they can.
20///
21/// The write accessors on this type take [string](str) references for convenience reasons, but they
22/// will still internally convert them into null-terminated C strings, and panic if that conversion fails.
23///
24/// See the documentation each accessor method to learn about the available metadata.
25///
26/// # Example
27///
28/// ```
29/// use clack_common::plugin::PluginDescriptor;
30///
31/// fn get_descriptor() -> PluginDescriptor {
32/// use clack_common::plugin::features::*;
33///
34/// PluginDescriptor::new("org.rust-audio.clack.gain", "Clack Gain Example")
35/// .with_description("A simple gain plugin example!")
36/// .with_version("0.1.0")
37/// .with_features([AUDIO_EFFECT, STEREO])
38/// }
39/// ```
40#[repr(C)]
41#[derive(Clone)]
42pub struct PluginDescriptor {
43 clap_version: clap_version,
44 id: OwnedCString,
45 name: OwnedCString,
46 vendor: OwnedCString,
47 url: OwnedCString,
48 manual_url: OwnedCString,
49 support_url: OwnedCString,
50 version: OwnedCString,
51 description: OwnedCString,
52 features: OwnedCStringArray,
53}
54
55impl PluginDescriptor {
56 /// Creates a new plugin descriptor, initializing it with the given Plugin ID and name.
57 ///
58 /// See the documentation of the [`id`](PluginDescriptor::id) and
59 /// [`name`](PluginDescriptor::name) methods for more information about the `id` and `name`
60 /// parameters.
61 ///
62 /// # Panics
63 ///
64 /// This function will panic if either the given ID or name are empty strings.
65 ///
66 /// This function will also panic if either the given ID or name contain invalid NULL-byte
67 /// characters, which are invalid.
68 #[inline]
69 pub fn new(id: &str, name: &str) -> Self {
70 Self {
71 clap_version: CLAP_VERSION,
72 id: OwnedCString::empty(),
73 name: OwnedCString::empty(),
74 vendor: OwnedCString::empty(),
75 url: OwnedCString::empty(),
76 manual_url: OwnedCString::empty(),
77 support_url: OwnedCString::empty(),
78 version: OwnedCString::empty(),
79 description: OwnedCString::empty(),
80 features: OwnedCStringArray::empty(),
81 }
82 .with_id(id)
83 .with_name(name)
84 }
85
86 /// Creates a [`PluginDescriptor`] reference from a pointer to a raw, C-FFI compatible CLAP
87 /// descriptor structure.
88 ///
89 /// # Safety
90 ///
91 /// All fields must either be null, or point to a valid for reads, null-terminated C
92 /// string (or for `features`, a null-terminated array of null-terminated C strings), which all
93 /// must also be valid for reads for the lifetime of the resulting [`PluginDescriptor`] reference.
94 pub const unsafe fn from_raw(raw: &clap_plugin_descriptor) -> &Self {
95 // SAFETY: WARNING WARNING WARNING!!!
96 // Even when the caller abides to the above safety rules, we MUST be CERTAIN that neither
97 // `set` or `Drop` can be called on any of the transmuted fields, as that would be instant UB.
98 // Returning a shared reference here is what makes this sound, as both of those operations
99 // require mutable references.
100 unsafe { &*(raw as *const clap_plugin_descriptor as *const Self) }
101 }
102
103 /// Returns the plugin descriptor as a reference to the C-FFI compatible CLAP struct.
104 #[inline]
105 pub fn as_raw(&self) -> &clap_plugin_descriptor {
106 // SAFETY: This type is ABI-compatible with clap_plugin_descriptor
107 unsafe { &*(self as *const Self as *const clap_plugin_descriptor) }
108 }
109
110 /// The unique identifier of a plugin.
111 ///
112 /// This field is **mandatory**, and should not be blank.
113 /// If it is found to be [`None`], it is acceptable for the host to refuse to load this plugin.
114 ///
115 /// This identifier should be as globally-unique as possible to any users that might load this
116 /// plugin, as this is the key hosts will use to differentiate between different plugins.
117 ///
118 /// Example: `com.u-he.diva`.
119 ///
120 /// This method will return [`None`] if this is either missing or a blank (i.e. empty) string.
121 #[inline]
122 pub fn id(&self) -> Option<&CStr> {
123 self.id.get()
124 }
125
126 /// Sets the plugin's unique ID.
127 ///
128 /// See the [`id`](PluginDescriptor::id) method documentation for more information.
129 ///
130 /// # Panics
131 ///
132 /// This function will panic if the given ID is an empty string.
133 ///
134 /// This function will also panic if the given ID contains NULL-byte characters, which are invalid.
135 pub fn with_id(mut self, id: &str) -> Self {
136 if id.is_empty() {
137 panic!("Plugin ID must not be blank!");
138 }
139
140 let id = CString::new(id).expect("Invalid Plugin ID");
141 self.id.set(Some(id));
142
143 self
144 }
145
146 /// The user-facing display name of this plugin.
147 ///
148 /// This field is **mandatory**, and should not be blank.
149 /// If it is found to be [`None`], it is acceptable for the host to refuse to load this plugin.
150 ///
151 /// This name will be displayed in plugin lists and selectors, and will be the main way users
152 /// will find and differentiate the plugin.
153 ///
154 /// Example: `Diva`.
155 ///
156 /// This method will return [`None`] if this is either missing or a blank (i.e. empty) string.
157 #[inline]
158 pub fn name(&self) -> Option<&CStr> {
159 self.name.get()
160 }
161
162 /// Sets the plugin's name.
163 ///
164 /// See the [`name`](PluginDescriptor::name) method documentation for more information.
165 ///
166 /// # Panics
167 ///
168 /// This function will panic if the given name is an empty string.
169 ///
170 /// This function will also panic if the given name contains NULL-byte characters, which are invalid.
171 pub fn with_name(mut self, name: &str) -> Self {
172 if name.is_empty() {
173 panic!("Plugin name must not be blank!");
174 }
175
176 let name = CString::new(name).expect("Invalid Plugin name");
177 self.name.set(Some(name));
178
179 self
180 }
181
182 /// The vendor of the plugin.
183 ///
184 /// Example: `u-he`.
185 ///
186 /// This method will return [`None`] if this is either missing or a blank (i.e. empty) string.
187 #[inline]
188 pub fn vendor(&self) -> Option<&CStr> {
189 self.vendor.get()
190 }
191
192 /// Sets the plugin's vendor name.
193 ///
194 /// See the [`vendor`](PluginDescriptor::vendor) method documentation for more information.
195 ///
196 /// Passing an empty string as the `vendor` parameter will mark it as unset, making
197 /// [`vendor`](PluginDescriptor::vendor) then return `None`.
198 ///
199 /// # Panics
200 ///
201 /// This function will also panic if the given vendor name contains NULL-byte characters,
202 /// which are invalid.
203 pub fn with_vendor(mut self, vendor: &str) -> Self {
204 self.vendor.set_str(vendor);
205 self
206 }
207
208 /// The URL of this plugin's homepage.
209 ///
210 /// Example: `https://u-he.com/products/diva/`.
211 ///
212 /// This method will return [`None`] if this is either missing or a blank (i.e. empty) string.
213 #[inline]
214 pub fn url(&self) -> Option<&CStr> {
215 self.url.get()
216 }
217
218 /// Sets the plugin's homepage's URL.
219 ///
220 /// See the [`url`](PluginDescriptor::url) method documentation for more information.
221 ///
222 /// Passing an empty string as the `url` parameter will mark it as unset, making
223 /// [`url`](PluginDescriptor::url) then return `None`.
224 ///
225 /// # Panics
226 ///
227 /// This function will also panic if the given URL contains NULL-byte characters,
228 /// which are invalid.
229 pub fn with_url(mut self, url: &str) -> Self {
230 self.url.set_str(url);
231 self
232 }
233
234 /// The URL of this plugin's user's manual.
235 ///
236 /// Example: `https://dl.u-he.com/manuals/plugins/diva/Diva-user-guide.pdf`.
237 ///
238 /// This method will return [`None`] if this is either missing or a blank (i.e. empty) string.
239 #[inline]
240 pub fn manual_url(&self) -> Option<&CStr> {
241 self.manual_url.get()
242 }
243
244 /// Sets the plugin's manual's URL.
245 ///
246 /// See the [`url`](PluginDescriptor::url) method documentation for more information.
247 ///
248 /// Passing an empty string as the `url` parameter will mark it as unset, making
249 /// [`url`](PluginDescriptor::url) then return `None`.
250 ///
251 /// # Panics
252 ///
253 /// This function will also panic if the given URL contains NULL-byte characters,
254 /// which are invalid.
255 pub fn with_manual_url(mut self, manual_url: &str) -> Self {
256 self.manual_url.set_str(manual_url);
257 self
258 }
259
260 /// The URL of this plugin's support page.
261 ///
262 /// Example: `https://u-he.com/support/`.
263 ///
264 /// This method will return [`None`] if this is either missing or a blank (i.e. empty) string.
265 #[inline]
266 pub fn support_url(&self) -> Option<&CStr> {
267 self.support_url.get()
268 }
269
270 /// Sets the plugin's support URL.
271 ///
272 /// See the [`support_url`](PluginDescriptor::support_url) method documentation for more information.
273 ///
274 /// Passing an empty string as the `support_url` parameter will mark it as unset, making
275 /// [`support_url`](PluginDescriptor::support_url) then return `None`.
276 ///
277 /// # Panics
278 ///
279 /// This function will also panic if the given URL contains NULL-byte characters,
280 /// which are invalid.
281 pub fn with_support_url(mut self, support_url: &str) -> Self {
282 self.support_url.set_str(support_url);
283 self
284 }
285
286 /// The version string of this plugin.
287 ///
288 /// While Semver-compatible version strings are preferred, this field can contain any arbitrary
289 /// string.
290 ///
291 /// Example: `1.4.4`.
292 ///
293 /// This method will return [`None`] if this is either missing or a blank (i.e. empty) string.
294 #[inline]
295 pub fn version(&self) -> Option<&CStr> {
296 self.version.get()
297 }
298
299 /// Sets the plugin's version string.
300 ///
301 /// See the [`version`](PluginDescriptor::version) method documentation for more information.
302 ///
303 /// Passing an empty string as the `version` parameter will mark it as unset, making
304 /// [`version`](PluginDescriptor::version) then return `None`.
305 ///
306 /// # Panics
307 ///
308 /// This function will also panic if the given version string contains NULL-byte characters,
309 /// which are invalid.
310 pub fn with_version(mut self, version: &str) -> Self {
311 self.version.set_str(version);
312 self
313 }
314
315 /// A short description of this plugin.
316 ///
317 /// Example: `The spirit of analogue`.
318 ///
319 /// This method will return [`None`] if this is either missing or a blank (i.e. empty) string.
320 #[inline]
321 pub fn description(&self) -> Option<&CStr> {
322 self.description.get()
323 }
324
325 /// Sets the plugin's description.
326 ///
327 /// See the [`description`](PluginDescriptor::description) method documentation for more information.
328 ///
329 /// Passing an empty string as the `description` parameter will mark it as unset, making
330 /// [`description`](PluginDescriptor::description) then return `None`.
331 ///
332 /// # Panics
333 ///
334 /// This function will also panic if the given description contains NULL-byte characters,
335 /// which are invalid.
336 pub fn with_description(mut self, description: &str) -> Self {
337 self.description.set_str(description);
338 self
339 }
340
341 /// An arbitrary list of tags that can be used by hosts to classify this plugin.
342 ///
343 /// For some standard features, see the constants in the [`features`](super::features) module.
344 ///
345 /// Example: `"instrument", "synthesizer", "stereo"`.
346 #[inline]
347 pub fn features(&self) -> FeaturesIter<'_> {
348 self.features.get()
349 }
350
351 /// Sets the plugin's feature list.
352 ///
353 /// See the [`features`](PluginDescriptor::features) method documentation for more information.
354 pub fn with_features<'a>(mut self, features: impl IntoIterator<Item = &'a CStr>) -> Self {
355 self.features.set(features);
356
357 self
358 }
359}
360
361const _: () = {
362 assert!(align_of::<clap_plugin_descriptor>() == align_of::<PluginDescriptor>());
363 assert!(size_of::<clap_plugin_descriptor>() == size_of::<PluginDescriptor>());
364};
365
366static EMPTY: &CStr = c"";
367
368/// # Safety Invariants
369///
370/// This type's inner pointer can either be :
371///
372/// * null;
373/// * pointing to `EMPTY`;
374/// * pointing to a string created by CString::into_raw.
375///
376/// This type alone cannot set its pointer to any other kind of value.
377///
378/// # Transmuting
379/// This type is `#[repr(C)]` and so *can* be transmuted from a raw `*const c_char`, in which case
380/// it may hold an arbitrary pointer.
381///
382/// In this case, you **MUST NOT** drop it, or to call `set`, which would cause immediate UB.
383///
384/// `get` may be called, but **only** if the pointer points to a valid C String (nul-terminated), and
385/// the pointer's value is not changed for the lifetime of this type.
386///
387/// In any case, this type can always safely be transmuted *to* a raw `*const c_char`.
388#[repr(C)]
389struct OwnedCString(*const c_char);
390
391// SAFETY: OwnedCString is fully self-contained, the pointers refer to data owned by it.
392unsafe impl Send for OwnedCString {}
393
394// SAFETY: OwnedCString does not have any interior mutability.
395unsafe impl Sync for OwnedCString {}
396
397impl OwnedCString {
398 #[inline]
399 pub fn new(string: Option<CString>) -> Self {
400 let mut new = Self::empty();
401 new.set(string);
402 new
403 }
404
405 #[inline]
406 fn into_raw(self) -> *const c_char {
407 let s = ManuallyDrop::new(self);
408 s.0
409 }
410
411 /// # Safety
412 ///
413 /// This pointer MUST follow this type's safety invariants.
414 ///
415 /// See [`OwnedCString`].
416 #[inline]
417 unsafe fn from_raw(ptr: *const c_char) -> Self {
418 Self(ptr)
419 }
420
421 #[inline]
422 pub const fn null() -> Self {
423 Self(std::ptr::null())
424 }
425
426 #[inline]
427 pub const fn empty() -> Self {
428 Self(EMPTY.as_ptr())
429 }
430
431 #[inline]
432 pub fn get(&self) -> Option<&CStr> {
433 if Self::is_allocated(self.0) {
434 // SAFETY: From our own invariants
435 let str = unsafe { CStr::from_ptr(self.0) };
436 if str.is_empty() {
437 return None;
438 }
439
440 Some(str)
441 } else {
442 None
443 }
444 }
445
446 #[inline]
447 pub fn set(&mut self, new: Option<CString>) {
448 // We do this just in case *something* panics, in which case this instance remains valid and worst case we just leak some memory.
449 let old_ptr = core::mem::replace(&mut self.0, EMPTY.as_ptr());
450
451 // SAFETY: per our own invariants, this pointer is either null, EMPTY, or from to_raw.
452 unsafe { Self::deallocate(old_ptr) };
453
454 // If the string is not empty, we overwrite our EMPTY pointer with an owned one
455 if let Some(new) = new {
456 if !new.is_empty() {
457 // Note: this allocates, which implies it may panic. In that case we're good, because
458 // our previous inner value of EMPTY is completely valid.
459 self.0 = new.into_raw();
460 }
461 }
462
463 // If the string is empty, then we are already set to EMPTY, so we have nothing to do.
464 }
465
466 #[inline]
467 pub fn set_str(&mut self, string: &str) {
468 if string.is_empty() {
469 self.set(None);
470 return;
471 }
472
473 let string = CString::new(string).expect("Invalid plugin descriptor string.");
474 self.set(Some(string));
475 }
476
477 #[inline]
478 fn is_allocated(ptr: *const c_char) -> bool {
479 !ptr.is_null() && ptr != EMPTY.as_ptr()
480 }
481
482 /// # Safety
483 ///
484 /// This must ONLY be called on pointers that are either null, EMPTY, or from [`Self::allocate`].
485 #[inline]
486 unsafe fn deallocate(old_ptr: *const c_char) {
487 if Self::is_allocated(old_ptr) {
488 // SAFETY: From our own invariants, if it's not null or EMPTY, then it's from into_raw.
489 let _ = unsafe { CString::from_raw(old_ptr.cast_mut()) };
490 }
491 }
492}
493
494impl Clone for OwnedCString {
495 #[inline]
496 fn clone(&self) -> Self {
497 Self::new(self.get().map(|s| s.to_owned()))
498 }
499}
500
501impl Drop for OwnedCString {
502 #[inline]
503 fn drop(&mut self) {
504 // Just in case.
505 let old_ptr = core::mem::replace(&mut self.0, EMPTY.as_ptr());
506
507 // SAFETY: per our own invariants, this pointer is either null, EMPTY, or from to_raw.
508 unsafe { Self::deallocate(old_ptr) };
509 }
510}
511
512/// The same invariants as for [`OwnedCString`], but for an array.
513#[repr(C)]
514struct OwnedCStringArray(*const *const c_char);
515
516// SAFETY: OwnedCStringArray is fully self-contained, the pointers refer to data owned by it.
517unsafe impl Send for OwnedCStringArray {}
518
519// SAFETY: OwnedCStringArray does not have any interior mutability.
520unsafe impl Sync for OwnedCStringArray {}
521
522// Technically this doesn't need to be OwnedCString, it just needs to be (ABI-compatible with)
523// any NULL pointer that's Send + Sync.
524// I could have made a dedicated wrapper but OwnedCString::null() fits the bill just as well :)
525static EMPTY_FEATURES: &[OwnedCString; 1] = &[OwnedCString::null()];
526
527impl OwnedCStringArray {
528 #[inline]
529 fn is_allocated(ptr: *const *const c_char) -> bool {
530 !ptr.is_null() && ptr != empty_features()
531 }
532
533 #[inline]
534 pub const fn empty() -> Self {
535 Self(empty_features())
536 }
537
538 #[inline]
539 pub const fn get(&self) -> FeaturesIter<'_> {
540 FeaturesIter {
541 ptr: CArrayIter(self.0),
542 _marker: PhantomData,
543 }
544 }
545
546 #[inline]
547 pub fn set<'a>(&mut self, features: impl IntoIterator<Item = &'a CStr>) {
548 // We do this just in case *something* panics, in which case this instance remains valid and worst case we just leak some memory.
549 let old_ptr = core::mem::replace(&mut self.0, empty_features());
550
551 // SAFETY: per our own invariants, this pointer is either null, EMPTY, or from to_raw.
552 unsafe { Self::deallocate(old_ptr) };
553
554 // It is safe to transmute OwnedCString to *const c_char.
555 self.0 = Self::allocate(features).cast::<*const c_char>();
556 }
557
558 fn allocate<'a>(features: impl IntoIterator<Item = &'a CStr>) -> *const OwnedCString {
559 let mut strings = Vec::with_capacity(1);
560
561 for feature in features {
562 strings.push(OwnedCString::new(Some(feature.to_owned())).into_raw());
563 }
564
565 if strings.is_empty() {
566 return EMPTY_FEATURES.as_ptr().cast_mut();
567 }
568
569 strings.push(core::ptr::null());
570
571 let strings = strings.into_boxed_slice();
572 let strings = Box::into_raw(strings);
573
574 strings.cast()
575 }
576
577 /// # Safety
578 ///
579 /// This must ONLY be called on pointers that are either null, EMPTY_FEATURES, or from [`Self::allocate`].
580 unsafe fn deallocate(ptr: *const *const c_char) {
581 if !Self::is_allocated(ptr) {
582 return;
583 }
584
585 // First: deallocate *everything* in here, while tracking the length of the array.
586 let mut len = 1; // Include the null terminator.
587
588 // Note: if any of these calls panics, then we simply leak the vec.
589 // SAFETY: per our own invariants, this is a null-terminated array that we own (see allocate).
590 for str in unsafe { CArrayIter::new(ptr) } {
591 // SAFETY: made by OwnedCString::into_raw
592 let _ = unsafe { OwnedCString::from_raw(str) };
593 len += 1;
594 }
595
596 // Then, reconstruct the Boxed slice and deallocate that.
597 // SAFETY: this was made from Box::into_raw in allocate.
598 let _ = unsafe { Box::from_raw(core::ptr::slice_from_raw_parts_mut(ptr.cast_mut(), len)) };
599 }
600}
601
602impl Clone for OwnedCStringArray {
603 fn clone(&self) -> Self {
604 let mut new = Self::empty();
605 new.set(self.get());
606 new
607 }
608}
609
610impl Drop for OwnedCStringArray {
611 #[inline]
612 fn drop(&mut self) {
613 // Just in case.
614 let old_ptr = core::mem::replace(&mut self.0, empty_features());
615
616 // SAFETY: per our own invariants, this pointer is either null, EMPTY, or from to_raw.
617 unsafe { Self::deallocate(old_ptr) };
618 }
619}
620
621const fn empty_features() -> *const *const c_char {
622 EMPTY_FEATURES.as_ptr().cast()
623}
624
625/// An iterator over a [`PluginDescriptor`]'s features list.
626///
627/// This type is obtained from the [`PluginDescriptor::features`] method.
628pub struct FeaturesIter<'a> {
629 ptr: CArrayIter,
630 _marker: PhantomData<&'a OwnedCStringArray>,
631}
632
633impl<'a> Iterator for FeaturesIter<'a> {
634 type Item = &'a CStr;
635
636 #[inline]
637 fn next(&mut self) -> Option<Self::Item> {
638 let next = self.ptr.next()?;
639
640 if next.is_null() {
641 return None;
642 }
643
644 // SAFETY: upheld by caller to be a valid C string for 'a
645 Some(unsafe { CStr::from_ptr(next) })
646 }
647}
648
649struct CArrayIter(*const *const c_char);
650
651impl CArrayIter {
652 /// # Safety
653 ///
654 /// This must either be NULL, point to a null-terminated C array
655 #[inline]
656 unsafe fn new(ptr: *const *const c_char) -> Self {
657 Self(ptr)
658 }
659}
660
661impl Iterator for CArrayIter {
662 type Item = *const c_char;
663
664 #[inline]
665 fn next(&mut self) -> Option<Self::Item> {
666 // SAFETY: Upheld by caller that the array this points to is null-terminated
667 let current = *unsafe { self.0.as_ref() }?;
668
669 if current.is_null() {
670 return None;
671 }
672
673 self.0 = self.0.wrapping_add(1);
674 Some(current)
675 }
676}