Skip to main content

maolan_plugin_host/
clap.rs

1use std::cell::Cell;
2use std::ffi::{CStr, CString, c_char, c_ulong, c_void};
3use std::path::Path;
4use std::ptr;
5use std::sync::{Mutex, OnceLock};
6use std::time::{Duration, Instant};
7
8// ─── Thread identity for clap_host_thread_check ───
9
10#[derive(Clone, Copy, PartialEq)]
11pub enum ThreadType {
12    MainThread,
13    AudioThread,
14    AudioThreadPool,
15}
16
17thread_local! {
18    static CURRENT_THREAD: Cell<ThreadType> = const { Cell::new(ThreadType::MainThread) };
19}
20
21pub fn set_thread_type(ty: ThreadType) {
22    CURRENT_THREAD.with(|t| t.set(ty));
23}
24
25pub fn current_thread_type() -> ThreadType {
26    CURRENT_THREAD.with(|t| t.get())
27}
28
29// ─── Global timer/FD state for host extensions ───
30
31pub struct HostTimer {
32    pub id: u32,
33    pub period_ms: u32,
34    pub deadline: Instant,
35}
36
37pub struct HostFd {
38    pub fd: i32,
39    pub flags: u32,
40}
41
42pub fn host_timers() -> &'static Mutex<Vec<HostTimer>> {
43    static TIMERS: OnceLock<Mutex<Vec<HostTimer>>> = OnceLock::new();
44    TIMERS.get_or_init(|| Mutex::new(Vec::new()))
45}
46
47pub fn host_fds() -> &'static Mutex<Vec<HostFd>> {
48    static FDS: OnceLock<Mutex<Vec<HostFd>>> = OnceLock::new();
49    FDS.get_or_init(|| Mutex::new(Vec::new()))
50}
51
52pub fn next_timer_id() -> u32 {
53    use std::sync::atomic::{AtomicU32, Ordering};
54    static NEXT: AtomicU32 = AtomicU32::new(1);
55    NEXT.fetch_add(1, Ordering::Relaxed)
56}
57
58// ─── Version ───
59
60#[repr(C)]
61#[derive(Clone, Copy)]
62pub struct ClapVersion {
63    pub major: u32,
64    pub minor: u32,
65    pub revision: u32,
66}
67
68pub const CLAP_VERSION: ClapVersion = ClapVersion {
69    major: 1,
70    minor: 2,
71    revision: 5,
72};
73
74// ─── Core structs ───
75
76#[repr(C)]
77pub struct ClapHost {
78    pub clap_version: ClapVersion,
79    pub host_data: *mut c_void,
80    pub name: *const c_char,
81    pub vendor: *const c_char,
82    pub url: *const c_char,
83    pub version: *const c_char,
84    pub get_extension:
85        Option<unsafe extern "C" fn(*const ClapHost, *const c_char) -> *const c_void>,
86    pub request_restart: Option<unsafe extern "C" fn(*const ClapHost)>,
87    pub request_process: Option<unsafe extern "C" fn(*const ClapHost)>,
88    pub request_callback: Option<unsafe extern "C" fn(*const ClapHost)>,
89}
90
91#[repr(C)]
92pub struct ClapPluginEntry {
93    pub clap_version: ClapVersion,
94    pub init: Option<unsafe extern "C" fn(*const c_char) -> bool>,
95    pub deinit: Option<unsafe extern "C" fn()>,
96    pub get_factory: Option<unsafe extern "C" fn(*const c_char) -> *const c_void>,
97}
98
99#[repr(C)]
100pub struct ClapPluginFactory {
101    pub get_plugin_count: Option<unsafe extern "C" fn(*const ClapPluginFactory) -> u32>,
102    pub get_plugin_descriptor:
103        Option<unsafe extern "C" fn(*const ClapPluginFactory, u32) -> *const ClapPluginDescriptor>,
104    pub create_plugin: Option<
105        unsafe extern "C" fn(
106            *const ClapPluginFactory,
107            *const ClapHost,
108            *const c_char,
109        ) -> *const ClapPlugin,
110    >,
111}
112
113#[repr(C)]
114pub struct ClapPluginDescriptor {
115    pub clap_version: ClapVersion,
116    pub id: *const c_char,
117    pub name: *const c_char,
118    pub vendor: *const c_char,
119    pub url: *const c_char,
120    pub manual_url: *const c_char,
121    pub support_url: *const c_char,
122    pub version: *const c_char,
123    pub description: *const c_char,
124    pub features: *const *const c_char,
125}
126
127#[repr(C)]
128pub struct ClapPlugin {
129    pub desc: *const ClapPluginDescriptor,
130    pub plugin_data: *mut c_void,
131    pub init: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
132    pub destroy: Option<unsafe extern "C" fn(*const ClapPlugin)>,
133    pub activate: Option<unsafe extern "C" fn(*const ClapPlugin, f64, u32, u32) -> bool>,
134    pub deactivate: Option<unsafe extern "C" fn(*const ClapPlugin)>,
135    pub start_processing: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
136    pub stop_processing: Option<unsafe extern "C" fn(*const ClapPlugin)>,
137    pub reset: Option<unsafe extern "C" fn(*const ClapPlugin)>,
138    pub process: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapProcess) -> i32>,
139    pub get_extension:
140        Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char) -> *const c_void>,
141    pub on_main_thread: Option<unsafe extern "C" fn(*const ClapPlugin)>,
142}
143
144// ─── Process / audio ───
145
146#[repr(C)]
147pub struct ClapProcess {
148    pub steady_time: i64,
149    pub frames_count: u32,
150    pub transport: *const c_void,
151    pub audio_inputs: *const ClapAudioBuffer,
152    pub audio_outputs: *mut ClapAudioBuffer,
153    pub audio_inputs_count: u32,
154    pub audio_outputs_count: u32,
155    pub in_events: *const ClapInputEvents,
156    pub out_events: *const ClapOutputEvents,
157}
158
159#[repr(C)]
160pub struct ClapAudioBuffer {
161    pub data32: *mut *mut f32,
162    pub data64: *mut *mut f64,
163    pub channel_count: u32,
164    pub latency: u32,
165    pub constant_mask: u64,
166}
167
168#[repr(C)]
169pub struct ClapInputEvents {
170    pub ctx: *const c_void,
171    pub size: Option<unsafe extern "C" fn(*const ClapInputEvents) -> u32>,
172    pub get: Option<unsafe extern "C" fn(*const ClapInputEvents, u32) -> *const ClapEventHeader>,
173}
174
175#[repr(C)]
176pub struct ClapOutputEvents {
177    pub ctx: *mut c_void,
178    pub try_push:
179        Option<unsafe extern "C" fn(*const ClapOutputEvents, *const ClapEventHeader) -> bool>,
180}
181
182#[repr(C)]
183#[derive(Clone, Copy)]
184pub struct ClapEventHeader {
185    pub size: u32,
186    pub time: u32,
187    pub space_id: u16,
188    pub type_: u16,
189    pub flags: u32,
190}
191
192#[repr(C)]
193#[derive(Clone, Copy)]
194pub struct ClapEventParamValue {
195    pub header: ClapEventHeader,
196    pub param_id: u32,
197    pub cookie: *mut c_void,
198    pub note_id: i32,
199    pub port_index: i16,
200    pub channel: i16,
201    pub key: i16,
202    pub value: f64,
203}
204
205#[repr(C)]
206#[derive(Clone, Copy)]
207pub struct ClapEventParamMod {
208    pub header: ClapEventHeader,
209    pub param_id: u32,
210    pub cookie: *mut c_void,
211    pub note_id: i32,
212    pub port_index: i16,
213    pub channel: i16,
214    pub key: i16,
215    pub amount: f64,
216}
217
218#[repr(C)]
219#[derive(Clone, Copy)]
220pub struct ClapEventParamGesture {
221    pub header: ClapEventHeader,
222    pub param_id: u32,
223}
224
225#[repr(C)]
226#[derive(Clone, Copy)]
227pub struct ClapEventMidi {
228    pub header: ClapEventHeader,
229    pub port_index: u16,
230    pub data: [u8; 3],
231}
232
233#[repr(C)]
234#[derive(Clone, Copy)]
235pub struct ClapEventNote {
236    pub header: ClapEventHeader,
237    pub note_id: i32,
238    pub port_index: i16,
239    pub channel: i16,
240    pub key: i16,
241    pub velocity: f64,
242}
243
244pub const CLAP_CORE_EVENT_SPACE_ID: u16 = 0;
245
246pub const CLAP_EVENT_PARAM_VALUE: u16 = 0;
247pub const CLAP_EVENT_PARAM_MOD: u16 = 1;
248pub const CLAP_EVENT_PARAM_GESTURE_BEGIN: u16 = 4;
249pub const CLAP_EVENT_PARAM_GESTURE_END: u16 = 5;
250pub const CLAP_EVENT_NOTE_ON: u16 = 2;
251pub const CLAP_EVENT_NOTE_OFF: u16 = 3;
252pub const CLAP_EVENT_NOTE_CHOKE: u16 = 6;
253pub const CLAP_EVENT_NOTE_END: u16 = 7;
254pub const CLAP_EVENT_MIDI: u16 = 8;
255pub const CLAP_EVENT_MIDI_SYSEX: u16 = 9;
256pub const CLAP_EVENT_MIDI2: u16 = 10;
257
258// ─── Extension structs ───
259
260#[repr(C)]
261pub struct ClapHostParams {
262    pub resize: Option<unsafe extern "C" fn(*const ClapHost, u32) -> bool>,
263    pub clear: Option<unsafe extern "C" fn(*const ClapHost, u32, u32)>,
264    pub request_flush: Option<unsafe extern "C" fn(*const ClapHost)>,
265}
266
267#[repr(C)]
268pub struct ClapHostAudioPorts {
269    pub is_rescan_flag_supported: Option<unsafe extern "C" fn(*const ClapHost, u32) -> bool>,
270    pub rescan: Option<unsafe extern "C" fn(*const ClapHost, u32)>,
271}
272
273#[repr(C)]
274pub struct ClapHostLatency {
275    pub changed: Option<unsafe extern "C" fn(*const ClapHost)>,
276}
277
278#[repr(C)]
279pub struct ClapHostThreadPool {
280    pub request_exec: Option<unsafe extern "C" fn(*const ClapHost, u32) -> bool>,
281}
282
283#[repr(C)]
284pub struct ClapHostGui {
285    pub resize_hints_changed: Option<unsafe extern "C" fn(*const ClapHost)>,
286    pub request_resize: Option<unsafe extern "C" fn(*const ClapHost, u32, u32) -> bool>,
287    pub request_show: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
288    pub request_hide: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
289    pub closed: Option<unsafe extern "C" fn(*const ClapHost, bool)>,
290}
291
292#[repr(C)]
293pub struct ClapHostThreadCheck {
294    pub is_main_thread: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
295    pub is_audio_thread: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
296}
297
298#[repr(C)]
299pub struct ClapHostLog {
300    pub log: Option<unsafe extern "C" fn(*const ClapHost, u32, *const c_char)>,
301}
302
303#[repr(C)]
304pub struct ClapHostTimerSupport {
305    pub register_timer: Option<unsafe extern "C" fn(*const ClapHost, u32, *mut u32) -> bool>,
306    pub unregister_timer: Option<unsafe extern "C" fn(*const ClapHost, u32) -> bool>,
307}
308
309#[repr(C)]
310pub struct ClapHostPosixFdSupport {
311    pub register_fd: Option<unsafe extern "C" fn(*const ClapHost, i32, u32) -> bool>,
312    pub modify_fd: Option<unsafe extern "C" fn(*const ClapHost, i32, u32) -> bool>,
313    pub unregister_fd: Option<unsafe extern "C" fn(*const ClapHost, i32) -> bool>,
314}
315
316#[repr(C)]
317pub struct ClapPluginParams {
318    pub count: Option<unsafe extern "C" fn(*const ClapPlugin) -> u32>,
319    pub get_info: Option<unsafe extern "C" fn(*const ClapPlugin, u32, *mut ClapParamInfo) -> bool>,
320    pub get_value: Option<unsafe extern "C" fn(*const ClapPlugin, u32, *mut f64) -> bool>,
321    pub value_to_text:
322        Option<unsafe extern "C" fn(*const ClapPlugin, u32, f64, *mut c_char, u32) -> bool>,
323    pub text_to_value:
324        Option<unsafe extern "C" fn(*const ClapPlugin, u32, *const c_char, *mut f64) -> bool>,
325    pub flush: Option<
326        unsafe extern "C" fn(*const ClapPlugin, *const ClapInputEvents, *const ClapOutputEvents),
327    >,
328}
329
330#[repr(C)]
331pub struct ClapParamInfo {
332    pub id: u32,
333    pub flags: u32,
334    pub cookie: *mut c_void,
335    pub name: [c_char; 256],
336    pub module: [c_char; 1024],
337    pub min_value: f64,
338    pub max_value: f64,
339    pub default_value: f64,
340}
341
342#[repr(C)]
343pub struct ClapPluginAudioPorts {
344    pub count: Option<unsafe extern "C" fn(*const ClapPlugin, bool) -> u32>,
345    pub get:
346        Option<unsafe extern "C" fn(*const ClapPlugin, u32, bool, *mut ClapAudioPortInfo) -> bool>,
347}
348
349#[repr(C)]
350pub struct ClapAudioPortInfo {
351    pub id: u32,
352    pub name: [c_char; 256],
353    pub flags: u32,
354    pub channel_count: u32,
355    pub port_type: *const c_char,
356    pub in_place_pair: u32,
357}
358
359#[repr(C)]
360pub struct ClapPluginGui {
361    pub is_api_supported:
362        Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char, bool) -> bool>,
363    pub get_preferred_api:
364        Option<unsafe extern "C" fn(*const ClapPlugin, *mut *const c_char, *mut bool) -> bool>,
365    pub create: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char, bool) -> bool>,
366    pub destroy: Option<unsafe extern "C" fn(*const ClapPlugin)>,
367    pub set_scale: Option<unsafe extern "C" fn(*const ClapPlugin, f64) -> bool>,
368    pub get_size: Option<unsafe extern "C" fn(*const ClapPlugin, *mut u32, *mut u32) -> bool>,
369    pub can_resize: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
370    pub get_resize_hints:
371        Option<unsafe extern "C" fn(*const ClapPlugin, *mut ClapGuiResizeHints) -> bool>,
372    pub adjust_size: Option<unsafe extern "C" fn(*const ClapPlugin, *mut u32, *mut u32) -> bool>,
373    pub set_size: Option<unsafe extern "C" fn(*const ClapPlugin, u32, u32) -> bool>,
374    pub set_parent: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapWindow) -> bool>,
375    pub set_transient: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapWindow) -> bool>,
376    pub suggest_title: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char)>,
377    pub show: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
378    pub hide: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
379}
380
381#[repr(C)]
382pub struct ClapGuiResizeHints {
383    pub can_resize_horizontally: bool,
384    pub can_resize_vertically: bool,
385    pub preserve_aspect_ratio: bool,
386    pub aspect_ratio_width: u32,
387    pub aspect_ratio_height: u32,
388}
389
390#[repr(C)]
391pub struct ClapWindow {
392    pub api: *const c_char,
393    pub clap_window__: ClapWindowUnion,
394}
395
396#[repr(C)]
397pub union ClapWindowUnion {
398    pub x11: c_ulong,
399    pub cocoa: *mut c_void,
400    pub win32: *mut c_void,
401}
402
403// ─── Extension IDs ───
404
405pub const CLAP_EXT_PARAMS: &CStr = c"clap.params";
406pub const CLAP_EXT_AUDIO_PORTS: &CStr = c"clap.audio-ports";
407pub const CLAP_EXT_NOTE_PORTS: &CStr = c"clap.note-ports";
408pub const CLAP_EXT_GUI: &CStr = c"clap.gui";
409pub const CLAP_EXT_STATE: &CStr = c"clap.state";
410pub const CLAP_EXT_THREAD_POOL: &CStr = c"clap.thread-pool";
411pub const CLAP_EXT_LATENCY: &CStr = c"clap.latency";
412pub const CLAP_EXT_TAIL: &CStr = c"clap.tail";
413pub const CLAP_EXT_TIMER_SUPPORT: &CStr = c"clap.timer-support";
414pub const CLAP_EXT_THREAD_CHECK: &CStr = c"clap.thread-check";
415pub const CLAP_EXT_LOG: &CStr = c"clap.log";
416pub const CLAP_EXT_POSIX_FD_SUPPORT: &CStr = c"clap.posix-fd-support";
417pub const CLAP_PORT_MONO: &str = "clap.mono";
418pub const CLAP_PORT_STEREO: &str = "clap.stereo";
419
420// ─── Thread-pool extension struct ───
421
422#[repr(C)]
423pub struct ClapPluginThreadPool {
424    pub exec: Option<unsafe extern "C" fn(*const ClapPlugin, u32)>,
425}
426
427#[repr(C)]
428pub struct ClapPluginTimerSupport {
429    pub on_timer: Option<unsafe extern "C" fn(*const ClapPlugin, u32)>,
430}
431
432#[repr(C)]
433pub struct ClapPluginPosixFdSupport {
434    pub on_fd: Option<unsafe extern "C" fn(*const ClapPlugin, i32, u32)>,
435}
436
437#[repr(C)]
438pub struct ClapPluginNotePorts {
439    pub count: Option<unsafe extern "C" fn(*const ClapPlugin, bool) -> u32>,
440    pub get:
441        Option<unsafe extern "C" fn(*const ClapPlugin, u32, bool, *mut ClapNotePortInfo) -> bool>,
442}
443
444#[repr(C)]
445pub struct ClapNotePortInfo {
446    pub id: u32,
447    pub name: [c_char; 256],
448    pub flags: u32,
449    pub supported_dialects: u16,
450    pub preferred_dialect: u16,
451}
452
453#[repr(C)]
454pub struct ClapHostState {
455    pub mark_dirty: Option<unsafe extern "C" fn(*const ClapHost)>,
456}
457
458#[repr(C)]
459pub struct HostData {
460    pub host: *mut ClapHost,
461    pub plugin: *const ClapPlugin,
462}
463
464// ─── Plugin instance wrapper ───
465
466use libloading::Library;
467
468pub struct PluginInstance {
469    _library: Library,
470    entry: *const ClapPluginEntry,
471    plugin: *const ClapPlugin,
472    host: Box<ClapHost>,
473    param_count: u32,
474    gui: Option<*const ClapPluginGui>,
475    gui_created: bool,
476}
477
478unsafe impl Send for PluginInstance {}
479
480impl PluginInstance {
481    /// Load a CLAP plugin from `plugin_path` and instantiate the plugin with `plugin_id`.
482    /// If `plugin_id` is empty, uses the first plugin in the factory.
483    pub fn new(plugin_path: &str, plugin_id: &str) -> Result<Self, String> {
484        let path = Path::new(plugin_path);
485        if !path.exists() {
486            return Err(format!("plugin path does not exist: {plugin_path}"));
487        }
488
489        let library =
490            unsafe { Library::new(path) }.map_err(|e| format!("failed to load library: {e}"))?;
491
492        let entry: libloading::Symbol<*const ClapPluginEntry> = unsafe {
493            library
494                .get(b"clap_entry\0")
495                .map_err(|e| format!("clap_entry not found: {e}"))?
496        };
497
498        let entry = unsafe { &**entry };
499
500        if let Some(init) = entry.init {
501            let plugin_path_c = CString::new(plugin_path).map_err(|e| e.to_string())?;
502            if !unsafe { init(plugin_path_c.as_ptr()) } {
503                return Err("clap_entry.init() failed".to_string());
504            }
505        }
506
507        let factory = if let Some(get_factory) = entry.get_factory {
508            let factory_id = CString::new("clap.plugin-factory").unwrap();
509            let factory_ptr = unsafe { get_factory(factory_id.as_ptr()) };
510            if factory_ptr.is_null() {
511                return Err("clap.plugin-factory not found".to_string());
512            }
513            unsafe { &*(factory_ptr as *const ClapPluginFactory) }
514        } else {
515            return Err("clap_entry.get_factory is null".to_string());
516        };
517
518        let descriptor = if plugin_id.is_empty() {
519            let count = factory
520                .get_plugin_count
521                .map(|f| unsafe { f(factory) })
522                .unwrap_or(0);
523            if count == 0 {
524                return Err("plugin factory is empty".to_string());
525            }
526            factory
527                .get_plugin_descriptor
528                .and_then(|f| {
529                    let d = unsafe { f(factory, 0) };
530                    if d.is_null() { None } else { Some(d) }
531                })
532                .ok_or("get_plugin_descriptor returned null")?
533        } else {
534            let count = factory
535                .get_plugin_count
536                .map(|f| unsafe { f(factory) })
537                .unwrap_or(0);
538            let mut found = None;
539            for i in 0..count {
540                if let Some(desc) = factory
541                    .get_plugin_descriptor
542                    .map(|f| unsafe { f(factory, i) })
543                {
544                    if desc.is_null() {
545                        continue;
546                    }
547                    let id = unsafe { CStr::from_ptr((*desc).id) };
548                    if id.to_bytes() == plugin_id.as_bytes() {
549                        found = Some(desc);
550                        break;
551                    }
552                }
553            }
554            found.ok_or(format!("plugin id '{}' not found", plugin_id))?
555        };
556
557        let actual_id = unsafe { CStr::from_ptr((*descriptor).id) }
558            .to_str()
559            .map_err(|e| e.to_string())?;
560        let plugin_id_c = CString::new(actual_id).map_err(|e| e.to_string())?;
561
562        let mut host = Box::new(ClapHost {
563            clap_version: CLAP_VERSION,
564            host_data: ptr::null_mut(),
565            name: c"maolan-plugin-host".as_ptr(),
566            vendor: c"Maolan".as_ptr(),
567            url: c"https://maolan.github.io".as_ptr(),
568            version: c"0.1.0".as_ptr(),
569            get_extension: Some(host_get_extension),
570            request_restart: Some(host_request_restart),
571            request_process: Some(host_request_process),
572            request_callback: Some(host_request_callback),
573        });
574
575        let plugin = factory.create_plugin.ok_or("create_plugin is null")?;
576        let plugin = unsafe { plugin(factory, &*host, plugin_id_c.as_ptr()) };
577        if plugin.is_null() {
578            if let Some(deinit) = entry.deinit {
579                unsafe { deinit() };
580            }
581            return Err("create_plugin returned null".to_string());
582        }
583
584        // Set host_data BEFORE calling plugin.init() so callbacks work during init.
585        let host_data = Box::into_raw(Box::new(HostData {
586            host: &mut *host,
587            plugin,
588        }));
589        host.host_data = host_data.cast::<c_void>();
590
591        let init = unsafe { (*plugin).init }.ok_or("plugin.init is null")?;
592        if !unsafe { init(plugin) } {
593            unsafe {
594                if let Some(destroy) = (*plugin).destroy {
595                    destroy(plugin);
596                }
597            }
598            if let Some(deinit) = entry.deinit {
599                unsafe { deinit() };
600            }
601            unsafe {
602                let _ = Box::from_raw(host_data);
603            }
604            return Err("plugin.init() returned false".to_string());
605        }
606
607        let param_count = unsafe {
608            let params_ext = (*plugin)
609                .get_extension
610                .map(|f| f(plugin, CLAP_EXT_PARAMS.as_ptr()));
611            if let Some(ext) = params_ext {
612                if !ext.is_null() {
613                    let params = &*(ext as *const ClapPluginParams);
614                    params.count.map(|f| f(plugin)).unwrap_or(0)
615                } else {
616                    0
617                }
618            } else {
619                0
620            }
621        };
622
623        Ok(Self {
624            _library: library,
625            entry: entry as *const ClapPluginEntry,
626            plugin,
627            host,
628            param_count,
629            gui: None,
630            gui_created: false,
631        })
632    }
633
634    pub fn name(&self) -> String {
635        unsafe {
636            if let Some(desc) = (*self.plugin).desc.as_ref() {
637                CStr::from_ptr(desc.name).to_string_lossy().into_owned()
638            } else {
639                "unknown".to_string()
640            }
641        }
642    }
643
644    pub fn plugin_ptr(&self) -> *const ClapPlugin {
645        self.plugin
646    }
647
648    pub fn activate(
649        &self,
650        sample_rate: f64,
651        min_frames: u32,
652        max_frames: u32,
653    ) -> Result<(), String> {
654        let activate = unsafe { (*self.plugin).activate }.ok_or("activate is null")?;
655        if unsafe { activate(self.plugin, sample_rate, min_frames, max_frames) } {
656            Ok(())
657        } else {
658            Err("plugin.activate() returned false".to_string())
659        }
660    }
661
662    pub fn deactivate(&self) {
663        if let Some(deactivate) = unsafe { (*self.plugin).deactivate } {
664            unsafe { deactivate(self.plugin) };
665        }
666    }
667
668    pub fn start_processing(&self) -> Result<(), String> {
669        let start = unsafe { (*self.plugin).start_processing }.ok_or("start_processing is null")?;
670        if unsafe { start(self.plugin) } {
671            Ok(())
672        } else {
673            Err("plugin.start_processing() returned false".to_string())
674        }
675    }
676
677    pub fn stop_processing(&self) {
678        if let Some(stop) = unsafe { (*self.plugin).stop_processing } {
679            unsafe { stop(self.plugin) };
680        }
681    }
682
683    pub fn reset(&self) {
684        if let Some(reset) = unsafe { (*self.plugin).reset } {
685            unsafe { reset(self.plugin) };
686        }
687    }
688
689    pub fn process(&self, process: &ClapProcess) -> Result<(), String> {
690        let process_fn = unsafe { (*self.plugin).process }.ok_or("process is null")?;
691        let status = unsafe { process_fn(self.plugin, process) };
692        // CLAP 1.1 process status values:
693        // 0 = CONTINUE, 1 = TAIL, 2 = SLEEP, 3 = CONTINUE_IF_NOT_QUIET, 4 = ERROR
694        if status == 4 {
695            Err("plugin.process() returned CLAP_PROCESS_ERROR".to_string())
696        } else {
697            Ok(())
698        }
699    }
700
701    pub fn param_count(&self) -> u32 {
702        self.param_count
703    }
704
705    pub fn gui_is_supported(&mut self) -> bool {
706        let ext = unsafe {
707            (*self.plugin)
708                .get_extension
709                .map(|f| f(self.plugin, CLAP_EXT_GUI.as_ptr()))
710        };
711        if let Some(ptr) = ext
712            && !ptr.is_null()
713        {
714            self.gui = Some(ptr as *const ClapPluginGui);
715            return true;
716        }
717        false
718    }
719
720    pub fn gui_create(&mut self, api: &str, is_floating: bool) -> Result<(), String> {
721        let gui = self.gui.ok_or("GUI extension not available")?;
722        let create = unsafe { (*gui).create }.ok_or("gui.create is null")?;
723        let api_c = CString::new(api).map_err(|e| e.to_string())?;
724        if unsafe { create(self.plugin, api_c.as_ptr(), is_floating) } {
725            self.gui_created = true;
726            Ok(())
727        } else {
728            Err("plugin.gui.create() returned false".to_string())
729        }
730    }
731
732    pub fn gui_set_parent(&self, window_id: u64) -> Result<(), String> {
733        let gui = self.gui.ok_or("GUI extension not available")?;
734        let set_parent = unsafe { (*gui).set_parent }.ok_or("gui.set_parent is null")?;
735        let api = c"x11".as_ptr();
736        let window = ClapWindow {
737            api,
738            clap_window__: ClapWindowUnion {
739                x11: window_id as c_ulong,
740            },
741        };
742        if unsafe { set_parent(self.plugin, &window) } {
743            Ok(())
744        } else {
745            Err("plugin.gui.set_parent() returned false".to_string())
746        }
747    }
748
749    pub fn gui_show(&self) -> Result<(), String> {
750        let gui = self.gui.ok_or("GUI extension not available")?;
751        let show = unsafe { (*gui).show }.ok_or("gui.show is null")?;
752        if unsafe { show(self.plugin) } {
753            Ok(())
754        } else {
755            Err("plugin.gui.show() returned false".to_string())
756        }
757    }
758
759    pub fn gui_hide(&self) -> Result<(), String> {
760        let gui = self.gui.ok_or("GUI extension not available")?;
761        let hide = unsafe { (*gui).hide }.ok_or("gui.hide is null")?;
762        if unsafe { hide(self.plugin) } {
763            Ok(())
764        } else {
765            Err("plugin.gui.hide() returned false".to_string())
766        }
767    }
768
769    pub fn gui_created(&self) -> bool {
770        self.gui_created
771    }
772
773    pub fn gui_destroy(&mut self) {
774        if self.gui_created {
775            if let Some(gui) = self.gui
776                && let Some(destroy) = unsafe { (*gui).destroy }
777            {
778                unsafe { destroy(self.plugin) };
779            }
780            self.gui_created = false;
781        }
782    }
783}
784
785impl Drop for PluginInstance {
786    fn drop(&mut self) {
787        self.gui_destroy();
788        if let Some(destroy) = unsafe { (*self.plugin).destroy } {
789            unsafe { destroy(self.plugin) };
790        }
791        // Free the HostData allocation.
792        if !self.host.host_data.is_null() {
793            unsafe {
794                let _ = Box::from_raw(self.host.host_data as *mut HostData);
795            }
796        }
797        // Balance the clap_entry.init() call.
798        if let Some(deinit) = unsafe { (*self.entry).deinit } {
799            unsafe { deinit() };
800        }
801    }
802}
803
804// ─── Host callbacks ───
805
806unsafe extern "C" fn host_get_extension(
807    _host: *const ClapHost,
808    id: *const c_char,
809) -> *const c_void {
810    let id = unsafe { CStr::from_ptr(id).to_bytes() };
811    match id {
812        b"clap.params" => &CLAP_HOST_PARAMS as *const _ as *const c_void,
813        b"clap.audio-ports" => &CLAP_HOST_AUDIO_PORTS as *const _ as *const c_void,
814        b"clap.latency" => &CLAP_HOST_LATENCY as *const _ as *const c_void,
815        b"clap.thread-pool" => &CLAP_HOST_THREAD_POOL as *const _ as *const c_void,
816        b"clap.host.gui" => &CLAP_HOST_GUI as *const _ as *const c_void,
817        b"clap.thread-check" => &CLAP_HOST_THREAD_CHECK as *const _ as *const c_void,
818        b"clap.log" => &CLAP_HOST_LOG as *const _ as *const c_void,
819        b"clap.timer-support" => &CLAP_HOST_TIMER_SUPPORT as *const _ as *const c_void,
820        b"clap.posix-fd-support" => &CLAP_HOST_POSIX_FD_SUPPORT as *const _ as *const c_void,
821        b"clap.state" => &CLAP_HOST_STATE as *const _ as *const c_void,
822        _ => ptr::null(),
823    }
824}
825
826unsafe extern "C" fn host_request_restart(_host: *const ClapHost) {}
827unsafe extern "C" fn host_request_process(_host: *const ClapHost) {}
828unsafe extern "C" fn host_request_callback(_host: *const ClapHost) {}
829
830static CLAP_HOST_PARAMS: ClapHostParams = ClapHostParams {
831    resize: Some(host_params_resize),
832    clear: Some(host_params_clear),
833    request_flush: Some(host_params_request_flush),
834};
835
836static CLAP_HOST_AUDIO_PORTS: ClapHostAudioPorts = ClapHostAudioPorts {
837    is_rescan_flag_supported: Some(host_audio_ports_is_rescan_flag_supported),
838    rescan: Some(host_audio_ports_rescan),
839};
840
841static CLAP_HOST_LATENCY: ClapHostLatency = ClapHostLatency {
842    changed: Some(host_latency_changed),
843};
844
845static CLAP_HOST_THREAD_POOL: ClapHostThreadPool = ClapHostThreadPool {
846    request_exec: Some(host_thread_pool_request_exec),
847};
848
849static CLAP_HOST_GUI: ClapHostGui = ClapHostGui {
850    resize_hints_changed: Some(host_gui_resize_hints_changed),
851    request_resize: Some(host_gui_request_resize),
852    request_show: Some(host_gui_request_show),
853    request_hide: Some(host_gui_request_hide),
854    closed: Some(host_gui_closed),
855};
856
857static CLAP_HOST_THREAD_CHECK: ClapHostThreadCheck = ClapHostThreadCheck {
858    is_main_thread: Some(host_thread_check_is_main_thread),
859    is_audio_thread: Some(host_thread_check_is_audio_thread),
860};
861
862static CLAP_HOST_LOG: ClapHostLog = ClapHostLog {
863    log: Some(host_log_log),
864};
865
866static CLAP_HOST_TIMER_SUPPORT: ClapHostTimerSupport = ClapHostTimerSupport {
867    register_timer: Some(host_timer_support_register_timer),
868    unregister_timer: Some(host_timer_support_unregister_timer),
869};
870
871static CLAP_HOST_POSIX_FD_SUPPORT: ClapHostPosixFdSupport = ClapHostPosixFdSupport {
872    register_fd: Some(host_posix_fd_support_register_fd),
873    modify_fd: Some(host_posix_fd_support_modify_fd),
874    unregister_fd: Some(host_posix_fd_support_unregister_fd),
875};
876
877static CLAP_HOST_STATE: ClapHostState = ClapHostState {
878    mark_dirty: Some(host_state_mark_dirty),
879};
880
881unsafe extern "C" fn host_params_resize(_host: *const ClapHost, _capacity: u32) -> bool {
882    true
883}
884unsafe extern "C" fn host_params_clear(_host: *const ClapHost, _begin: u32, _end: u32) {}
885unsafe extern "C" fn host_params_request_flush(_host: *const ClapHost) {
886    crate::host::request_params_flush();
887}
888unsafe extern "C" fn host_audio_ports_is_rescan_flag_supported(
889    _host: *const ClapHost,
890    _flag: u32,
891) -> bool {
892    false
893}
894unsafe extern "C" fn host_audio_ports_rescan(_host: *const ClapHost, _flag: u32) {
895    crate::host::request_audio_ports_rescan();
896}
897unsafe extern "C" fn host_latency_changed(_host: *const ClapHost) {}
898unsafe extern "C" fn host_thread_pool_request_exec(host: *const ClapHost, num_tasks: u32) -> bool {
899    if host.is_null() {
900        return false;
901    }
902    let host_data = unsafe { &*((*host).host_data as *const HostData) };
903    let plugin = host_data.plugin;
904    if plugin.is_null() {
905        return false;
906    }
907    // Query the plugin's thread-pool extension.
908    let ext = unsafe {
909        (*plugin)
910            .get_extension
911            .map(|f| f(plugin, CLAP_EXT_THREAD_POOL.as_ptr()))
912    };
913    let ext = match ext {
914        Some(p) if !p.is_null() => p,
915        _ => return false,
916    };
917    let tp = unsafe { &*(ext as *const ClapPluginThreadPool) };
918    let Some(exec) = tp.exec else {
919        return false;
920    };
921    // Execute tasks sequentially on the calling thread (audio thread).
922    for task_index in 0..num_tasks {
923        unsafe { exec(plugin, task_index) };
924    }
925    true
926}
927
928unsafe extern "C" fn host_gui_resize_hints_changed(_host: *const ClapHost) {}
929unsafe extern "C" fn host_gui_request_resize(
930    _host: *const ClapHost,
931    _width: u32,
932    _height: u32,
933) -> bool {
934    false
935}
936unsafe extern "C" fn host_gui_request_show(_host: *const ClapHost) -> bool {
937    false
938}
939unsafe extern "C" fn host_gui_request_hide(_host: *const ClapHost) -> bool {
940    false
941}
942unsafe extern "C" fn host_gui_closed(_host: *const ClapHost, _was_destroyed: bool) {}
943
944unsafe extern "C" fn host_state_mark_dirty(_host: *const ClapHost) {
945    tracing::info!("Plugin called clap_host_state.mark_dirty()");
946}
947
948unsafe extern "C" fn host_thread_check_is_main_thread(_host: *const ClapHost) -> bool {
949    current_thread_type() == ThreadType::MainThread
950}
951
952unsafe extern "C" fn host_thread_check_is_audio_thread(_host: *const ClapHost) -> bool {
953    matches!(
954        current_thread_type(),
955        ThreadType::AudioThread | ThreadType::AudioThreadPool
956    )
957}
958
959unsafe extern "C" fn host_log_log(_host: *const ClapHost, severity: u32, msg: *const c_char) {
960    if msg.is_null() {
961        return;
962    }
963    let msg = unsafe { CStr::from_ptr(msg) }.to_string_lossy();
964    match severity {
965        0 => tracing::debug!(target: "clap_plugin", "{msg}"),
966        1 => tracing::info!(target: "clap_plugin", "{msg}"),
967        2 => tracing::warn!(target: "clap_plugin", "{msg}"),
968        3..=5 => tracing::error!(target: "clap_plugin", "{msg}"),
969        _ => tracing::info!(target: "clap_plugin", "{msg}"),
970    }
971}
972
973unsafe extern "C" fn host_timer_support_register_timer(
974    _host: *const ClapHost,
975    period_ms: u32,
976    timer_id: *mut u32,
977) -> bool {
978    if timer_id.is_null() {
979        return false;
980    }
981    let id = next_timer_id();
982    unsafe { *timer_id = id };
983    let mut timers = host_timers().lock().unwrap();
984    timers.push(HostTimer {
985        id,
986        period_ms,
987        deadline: Instant::now() + Duration::from_millis(period_ms as u64),
988    });
989    true
990}
991
992unsafe extern "C" fn host_timer_support_unregister_timer(
993    _host: *const ClapHost,
994    timer_id: u32,
995) -> bool {
996    let mut timers = host_timers().lock().unwrap();
997    if let Some(pos) = timers.iter().position(|t| t.id == timer_id) {
998        timers.swap_remove(pos);
999        true
1000    } else {
1001        false
1002    }
1003}
1004
1005unsafe extern "C" fn host_posix_fd_support_register_fd(
1006    _host: *const ClapHost,
1007    fd: i32,
1008    flags: u32,
1009) -> bool {
1010    let mut fds = host_fds().lock().unwrap();
1011    if fds.iter().any(|f| f.fd == fd) {
1012        return false;
1013    }
1014    fds.push(HostFd { fd, flags });
1015    true
1016}
1017
1018unsafe extern "C" fn host_posix_fd_support_modify_fd(
1019    _host: *const ClapHost,
1020    fd: i32,
1021    flags: u32,
1022) -> bool {
1023    let mut fds = host_fds().lock().unwrap();
1024    if let Some(f) = fds.iter_mut().find(|f| f.fd == fd) {
1025        f.flags = flags;
1026        true
1027    } else {
1028        false
1029    }
1030}
1031
1032unsafe extern "C" fn host_posix_fd_support_unregister_fd(_host: *const ClapHost, fd: i32) -> bool {
1033    let mut fds = host_fds().lock().unwrap();
1034    if let Some(pos) = fds.iter().position(|f| f.fd == fd) {
1035        fds.swap_remove(pos);
1036        true
1037    } else {
1038        false
1039    }
1040}
1041
1042// ─── Empty event lists for hosts that don't send/receive events ───
1043
1044unsafe extern "C" fn empty_input_events_size(_: *const ClapInputEvents) -> u32 {
1045    0
1046}
1047
1048unsafe extern "C" fn empty_input_events_get(
1049    _: *const ClapInputEvents,
1050    _index: u32,
1051) -> *const ClapEventHeader {
1052    ptr::null()
1053}
1054
1055unsafe extern "C" fn empty_output_events_try_push(
1056    _: *const ClapOutputEvents,
1057    _: *const ClapEventHeader,
1058) -> bool {
1059    false
1060}
1061
1062pub fn empty_input_events() -> ClapInputEvents {
1063    ClapInputEvents {
1064        ctx: ptr::null(),
1065        size: Some(empty_input_events_size),
1066        get: Some(empty_input_events_get),
1067    }
1068}
1069
1070pub fn empty_output_events() -> ClapOutputEvents {
1071    ClapOutputEvents {
1072        ctx: ptr::null_mut(),
1073        try_push: Some(empty_output_events_try_push),
1074    }
1075}
1076
1077// ─── Generic event buffer (DAW → plugin) ───
1078
1079/// Stores heterogeneous CLAP events in a flat byte-per-event list.
1080pub struct EventBuffer {
1081    events: Vec<Vec<u8>>,
1082}
1083
1084impl Default for EventBuffer {
1085    fn default() -> Self {
1086        Self::new()
1087    }
1088}
1089
1090impl EventBuffer {
1091    pub fn new() -> Self {
1092        Self { events: Vec::new() }
1093    }
1094
1095    pub fn push_param_value(&mut self, param_id: u32, value: f64, sample_offset: u32) {
1096        let ev = ClapEventParamValue {
1097            header: ClapEventHeader {
1098                size: std::mem::size_of::<ClapEventParamValue>() as u32,
1099                time: sample_offset,
1100                space_id: CLAP_CORE_EVENT_SPACE_ID,
1101                type_: CLAP_EVENT_PARAM_VALUE,
1102                flags: 0,
1103            },
1104            param_id,
1105            cookie: ptr::null_mut(),
1106            note_id: -1,
1107            port_index: -1,
1108            channel: -1,
1109            key: -1,
1110            value,
1111        };
1112        self.events.push(unsafe {
1113            std::slice::from_raw_parts(
1114                &ev as *const _ as *const u8,
1115                std::mem::size_of::<ClapEventParamValue>(),
1116            )
1117            .to_vec()
1118        });
1119    }
1120
1121    pub fn push_param_mod(&mut self, param_id: u32, amount: f64, sample_offset: u32) {
1122        let ev = ClapEventParamMod {
1123            header: ClapEventHeader {
1124                size: std::mem::size_of::<ClapEventParamMod>() as u32,
1125                time: sample_offset,
1126                space_id: CLAP_CORE_EVENT_SPACE_ID,
1127                type_: CLAP_EVENT_PARAM_MOD,
1128                flags: 0,
1129            },
1130            param_id,
1131            cookie: ptr::null_mut(),
1132            note_id: -1,
1133            port_index: -1,
1134            channel: -1,
1135            key: -1,
1136            amount,
1137        };
1138        self.events.push(unsafe {
1139            std::slice::from_raw_parts(
1140                &ev as *const _ as *const u8,
1141                std::mem::size_of::<ClapEventParamMod>(),
1142            )
1143            .to_vec()
1144        });
1145    }
1146
1147    pub fn push_param_gesture_begin(&mut self, param_id: u32, sample_offset: u32) {
1148        let ev = ClapEventParamGesture {
1149            header: ClapEventHeader {
1150                size: std::mem::size_of::<ClapEventParamGesture>() as u32,
1151                time: sample_offset,
1152                space_id: CLAP_CORE_EVENT_SPACE_ID,
1153                type_: CLAP_EVENT_PARAM_GESTURE_BEGIN,
1154                flags: 0,
1155            },
1156            param_id,
1157        };
1158        self.events.push(unsafe {
1159            std::slice::from_raw_parts(
1160                &ev as *const _ as *const u8,
1161                std::mem::size_of::<ClapEventParamGesture>(),
1162            )
1163            .to_vec()
1164        });
1165    }
1166
1167    pub fn push_param_gesture_end(&mut self, param_id: u32, sample_offset: u32) {
1168        let ev = ClapEventParamGesture {
1169            header: ClapEventHeader {
1170                size: std::mem::size_of::<ClapEventParamGesture>() as u32,
1171                time: sample_offset,
1172                space_id: CLAP_CORE_EVENT_SPACE_ID,
1173                type_: CLAP_EVENT_PARAM_GESTURE_END,
1174                flags: 0,
1175            },
1176            param_id,
1177        };
1178        self.events.push(unsafe {
1179            std::slice::from_raw_parts(
1180                &ev as *const _ as *const u8,
1181                std::mem::size_of::<ClapEventParamGesture>(),
1182            )
1183            .to_vec()
1184        });
1185    }
1186
1187    pub fn push_note_on(
1188        &mut self,
1189        note_id: i32,
1190        port_index: i16,
1191        channel: i16,
1192        key: i16,
1193        velocity: f64,
1194        sample_offset: u32,
1195    ) {
1196        let ev = ClapEventNote {
1197            header: ClapEventHeader {
1198                size: std::mem::size_of::<ClapEventNote>() as u32,
1199                time: sample_offset,
1200                space_id: CLAP_CORE_EVENT_SPACE_ID,
1201                type_: CLAP_EVENT_NOTE_ON,
1202                flags: 0,
1203            },
1204            note_id,
1205            port_index,
1206            channel,
1207            key,
1208            velocity,
1209        };
1210        self.events.push(unsafe {
1211            std::slice::from_raw_parts(
1212                &ev as *const _ as *const u8,
1213                std::mem::size_of::<ClapEventNote>(),
1214            )
1215            .to_vec()
1216        });
1217    }
1218
1219    pub fn push_note_off(
1220        &mut self,
1221        note_id: i32,
1222        port_index: i16,
1223        channel: i16,
1224        key: i16,
1225        velocity: f64,
1226        sample_offset: u32,
1227    ) {
1228        let ev = ClapEventNote {
1229            header: ClapEventHeader {
1230                size: std::mem::size_of::<ClapEventNote>() as u32,
1231                time: sample_offset,
1232                space_id: CLAP_CORE_EVENT_SPACE_ID,
1233                type_: CLAP_EVENT_NOTE_OFF,
1234                flags: 0,
1235            },
1236            note_id,
1237            port_index,
1238            channel,
1239            key,
1240            velocity,
1241        };
1242        self.events.push(unsafe {
1243            std::slice::from_raw_parts(
1244                &ev as *const _ as *const u8,
1245                std::mem::size_of::<ClapEventNote>(),
1246            )
1247            .to_vec()
1248        });
1249    }
1250
1251    pub fn push_midi(&mut self, data: [u8; 3], port_index: u16, sample_offset: u32) {
1252        let ev = ClapEventMidi {
1253            header: ClapEventHeader {
1254                size: std::mem::size_of::<ClapEventMidi>() as u32,
1255                time: sample_offset,
1256                space_id: CLAP_CORE_EVENT_SPACE_ID,
1257                type_: CLAP_EVENT_MIDI,
1258                flags: 0,
1259            },
1260            port_index,
1261            data,
1262        };
1263        self.events.push(unsafe {
1264            std::slice::from_raw_parts(
1265                &ev as *const _ as *const u8,
1266                std::mem::size_of::<ClapEventMidi>(),
1267            )
1268            .to_vec()
1269        });
1270    }
1271
1272    pub fn len(&self) -> usize {
1273        self.events.len()
1274    }
1275
1276    pub fn is_empty(&self) -> bool {
1277        self.events.is_empty()
1278    }
1279
1280    pub fn as_input_events(&self) -> ClapInputEvents {
1281        ClapInputEvents {
1282            ctx: self as *const _ as *const c_void,
1283            size: Some(event_buffer_size),
1284            get: Some(event_buffer_get),
1285        }
1286    }
1287}
1288
1289unsafe extern "C" fn event_buffer_size(events: *const ClapInputEvents) -> u32 {
1290    let buf = unsafe { &*((*events).ctx as *const EventBuffer) };
1291    buf.events.len() as u32
1292}
1293
1294unsafe extern "C" fn event_buffer_get(
1295    events: *const ClapInputEvents,
1296    index: u32,
1297) -> *const ClapEventHeader {
1298    let buf = unsafe { &*((*events).ctx as *const EventBuffer) };
1299    buf.events
1300        .get(index as usize)
1301        .map(|bytes| bytes.as_ptr() as *const ClapEventHeader)
1302        .unwrap_or(ptr::null())
1303}
1304
1305// ─── Event capture (plugin → DAW echo) ───
1306
1307pub struct EventCapture {
1308    events: Vec<Vec<u8>>,
1309}
1310
1311impl Default for EventCapture {
1312    fn default() -> Self {
1313        Self::new()
1314    }
1315}
1316
1317impl EventCapture {
1318    pub fn new() -> Self {
1319        Self { events: Vec::new() }
1320    }
1321
1322    pub fn drain(&mut self) -> Vec<Vec<u8>> {
1323        std::mem::take(&mut self.events)
1324    }
1325
1326    pub fn as_output_events(&self) -> ClapOutputEvents {
1327        ClapOutputEvents {
1328            ctx: self as *const _ as *mut c_void,
1329            try_push: Some(event_capture_try_push),
1330        }
1331    }
1332}
1333
1334unsafe extern "C" fn event_capture_try_push(
1335    events: *const ClapOutputEvents,
1336    header: *const ClapEventHeader,
1337) -> bool {
1338    if header.is_null() {
1339        return false;
1340    }
1341    let header = unsafe { &*header };
1342    let size = header.size as usize;
1343    if size == 0 || size > 128 {
1344        return false;
1345    }
1346    let capture = unsafe { &mut *((*events).ctx as *mut EventCapture) };
1347    let bytes =
1348        unsafe { std::slice::from_raw_parts(header as *const _ as *const u8, size).to_vec() };
1349    capture.events.push(bytes);
1350    true
1351}