clack-plugin 0.2.0

Low-level, safe Rust wrappers to create audio plugins using the CLAP audio plugin API.
Documentation
use crate::extensions::wrapper::PluginWrapperError;
use crate::plugin::{Plugin, PluginBoxInner};
use clap_sys::ext::log::{CLAP_EXT_LOG, clap_host_log, clap_log_severity};
use clap_sys::host::clap_host;
use clap_sys::plugin::clap_plugin;
use std::os::raw::c_char;
use std::{error::Error, ffi::CString, fmt::Display, fmt::Write};

pub type ClapLoggingFn =
    unsafe extern "C" fn(host: *const clap_host, severity: clap_log_severity, msg: *const c_char);

/// # Safety
///
/// Plugin pointer must be non-dangling (but can be NULL).
/// It *must* point to a plugin instance created by Clack.
unsafe fn get_logger<P: Plugin>(
    plugin: *const clap_plugin,
) -> Option<(*const clap_host, ClapLoggingFn)> {
    let host = plugin
        .as_ref()?
        .plugin_data
        .cast::<PluginBoxInner<P>>()
        .as_ref()?
        .host()
        .as_raw();

    let log = host.get_extension?(host, CLAP_EXT_LOG.as_ptr()) as *mut clap_host_log;
    Some((host, log.as_ref()?.log?))
}

fn log_display<D: Display>(message: &D) -> Result<CString, Box<dyn Error>> {
    let mut buf = String::new();
    write!(buf, "{message}")?;
    Ok(CString::new(buf)?)
}

/// # Safety
///
/// Plugin pointer must be non-dangling (but can be NULL).
/// It *must* point to a plugin instance created by Clack.
pub unsafe fn plugin_log<P: Plugin>(plugin: *const clap_plugin, e: &PluginWrapperError) {
    if let Some((host, logger)) = get_logger::<P>(plugin) {
        match log_display(e) {
            Ok(cstr) => {
                logger(host, e.severity(), cstr.as_ptr());
                return;
            }
            Err(e) => {
                eprintln!("[CLAP_PLUGIN_ERROR] Failed to serialize error message for host: {e}")
            }
        };
    }

    eprintln!("[CLAP_PLUGIN_ERROR] {e}");
}