Skip to main content

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}