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            Some(unsafe { CStr::from_ptr(self.0) })
436        } else {
437            None
438        }
439    }
440
441    #[inline]
442    pub fn set(&mut self, new: Option<CString>) {
443        // We do this just in case *something* panics, in which case this instance remains valid and worst case we just leak some memory.
444        let old_ptr = core::mem::replace(&mut self.0, EMPTY.as_ptr());
445
446        // SAFETY: per our own invariants, this pointer is either null, EMPTY, or from to_raw.
447        unsafe { Self::deallocate(old_ptr) };
448
449        // If the string is not empty, we overwrite our EMPTY pointer with an owned one
450        if let Some(new) = new {
451            if !new.is_empty() {
452                // Note: this allocates, which implies it may panic. In that case we're good, because
453                // our previous inner value of EMPTY is completely valid.
454                self.0 = new.into_raw();
455            }
456        }
457
458        // If the string is empty, then we are already set to EMPTY, so we have nothing to do.
459    }
460
461    #[inline]
462    pub fn set_str(&mut self, string: &str) {
463        if string.is_empty() {
464            self.set(None);
465            return;
466        }
467
468        let string = CString::new(string).expect("Invalid plugin descriptor string.");
469        self.set(Some(string));
470    }
471
472    #[inline]
473    fn is_allocated(ptr: *const c_char) -> bool {
474        !ptr.is_null() && ptr != EMPTY.as_ptr()
475    }
476
477    /// # Safety
478    ///
479    /// This must ONLY be called on pointers that are either null, EMPTY, or from [`Self::allocate`].
480    #[inline]
481    unsafe fn deallocate(old_ptr: *const c_char) {
482        if Self::is_allocated(old_ptr) {
483            // SAFETY: From our own invariants, if it's not null or EMPTY, then it's from into_raw.
484            let _ = unsafe { CString::from_raw(old_ptr.cast_mut()) };
485        }
486    }
487}
488
489impl Clone for OwnedCString {
490    #[inline]
491    fn clone(&self) -> Self {
492        Self::new(self.get().map(|s| s.to_owned()))
493    }
494}
495
496impl Drop for OwnedCString {
497    #[inline]
498    fn drop(&mut self) {
499        // Just in case.
500        let old_ptr = core::mem::replace(&mut self.0, EMPTY.as_ptr());
501
502        // SAFETY: per our own invariants, this pointer is either null, EMPTY, or from to_raw.
503        unsafe { Self::deallocate(old_ptr) };
504    }
505}
506
507/// The same invariants as for [`OwnedCString`], but for an array.
508#[repr(C)]
509struct OwnedCStringArray(*const *const c_char);
510
511// SAFETY: OwnedCStringArray is fully self-contained, the pointers refer to data owned by it.
512unsafe impl Send for OwnedCStringArray {}
513
514// SAFETY: OwnedCStringArray does not have any interior mutability.
515unsafe impl Sync for OwnedCStringArray {}
516
517// Technically this doesn't need to be OwnedCString, it just needs to be (ABI-compatible with)
518// any NULL pointer that's Send + Sync.
519// I could have made a dedicated wrapper but OwnedCString::null() fits the bill just as well :)
520static EMPTY_FEATURES: &[OwnedCString; 1] = &[OwnedCString::null()];
521
522impl OwnedCStringArray {
523    #[inline]
524    fn is_allocated(ptr: *const *const c_char) -> bool {
525        !ptr.is_null() && ptr != empty_features()
526    }
527
528    #[inline]
529    pub const fn empty() -> Self {
530        Self(empty_features())
531    }
532
533    #[inline]
534    pub const fn get(&self) -> FeaturesIter<'_> {
535        FeaturesIter {
536            ptr: CArrayIter(self.0),
537            _marker: PhantomData,
538        }
539    }
540
541    #[inline]
542    pub fn set<'a>(&mut self, features: impl IntoIterator<Item = &'a CStr>) {
543        // We do this just in case *something* panics, in which case this instance remains valid and worst case we just leak some memory.
544        let old_ptr = core::mem::replace(&mut self.0, empty_features());
545
546        // SAFETY: per our own invariants, this pointer is either null, EMPTY, or from to_raw.
547        unsafe { Self::deallocate(old_ptr) };
548
549        // It is safe to transmute OwnedCString to *const c_char.
550        self.0 = Self::allocate(features).cast::<*const c_char>();
551    }
552
553    fn allocate<'a>(features: impl IntoIterator<Item = &'a CStr>) -> *const OwnedCString {
554        let mut strings = Vec::with_capacity(1);
555
556        for feature in features {
557            strings.push(OwnedCString::new(Some(feature.to_owned())).into_raw());
558        }
559
560        if strings.is_empty() {
561            return EMPTY_FEATURES.as_ptr().cast_mut();
562        }
563
564        strings.push(core::ptr::null());
565
566        let strings = strings.into_boxed_slice();
567        let strings = Box::into_raw(strings);
568
569        strings.cast()
570    }
571
572    /// # Safety
573    ///
574    /// This must ONLY be called on pointers that are either null, EMPTY_FEATURES, or from [`Self::allocate`].
575    unsafe fn deallocate(ptr: *const *const c_char) {
576        if !Self::is_allocated(ptr) {
577            return;
578        }
579
580        // First: deallocate *everything* in here, while tracking the length of the array.
581        let mut len = 1; // Include the null terminator.
582
583        // Note: if any of these calls panics, then we simply leak the vec.
584        // SAFETY: per our own invariants, this is a null-terminated array that we own (see allocate).
585        for str in unsafe { CArrayIter::new(ptr) } {
586            // SAFETY: made by OwnedCString::into_raw
587            let _ = unsafe { OwnedCString::from_raw(str) };
588            len += 1;
589        }
590
591        // Then, reconstruct the Boxed slice and deallocate that.
592        // SAFETY: this was made from Box::into_raw in allocate.
593        let _ = unsafe { Box::from_raw(core::ptr::slice_from_raw_parts_mut(ptr.cast_mut(), len)) };
594    }
595}
596
597impl Clone for OwnedCStringArray {
598    fn clone(&self) -> Self {
599        let mut new = Self::empty();
600        new.set(self.get());
601        new
602    }
603}
604
605impl Drop for OwnedCStringArray {
606    #[inline]
607    fn drop(&mut self) {
608        // Just in case.
609        let old_ptr = core::mem::replace(&mut self.0, empty_features());
610
611        // SAFETY: per our own invariants, this pointer is either null, EMPTY, or from to_raw.
612        unsafe { Self::deallocate(old_ptr) };
613    }
614}
615
616const fn empty_features() -> *const *const c_char {
617    EMPTY_FEATURES.as_ptr().cast()
618}
619
620/// An iterator over a [`PluginDescriptor`]'s features list.
621///
622/// This type is obtained from the [`PluginDescriptor::features`] method.
623pub struct FeaturesIter<'a> {
624    ptr: CArrayIter,
625    _marker: PhantomData<&'a OwnedCStringArray>,
626}
627
628impl<'a> Iterator for FeaturesIter<'a> {
629    type Item = &'a CStr;
630
631    #[inline]
632    fn next(&mut self) -> Option<Self::Item> {
633        let next = self.ptr.next()?;
634
635        if next.is_null() {
636            return None;
637        }
638
639        // SAFETY: upheld by caller to be a valid C string for 'a
640        Some(unsafe { CStr::from_ptr(next) })
641    }
642}
643
644struct CArrayIter(*const *const c_char);
645
646impl CArrayIter {
647    /// # Safety
648    ///
649    /// This must either be NULL, point to a null-terminated C array
650    #[inline]
651    unsafe fn new(ptr: *const *const c_char) -> Self {
652        Self(ptr)
653    }
654}
655
656impl Iterator for CArrayIter {
657    type Item = *const c_char;
658
659    #[inline]
660    fn next(&mut self) -> Option<Self::Item> {
661        // SAFETY: Upheld by caller that the array this points to is null-terminated
662        let current = *unsafe { self.0.as_ref() }?;
663
664        if current.is_null() {
665            return None;
666        }
667
668        self.0 = self.0.wrapping_add(1);
669        Some(current)
670    }
671}