#![allow(clippy::cast_ptr_alignment)]
use std::ffi::{CStr, CString, c_char, c_void};
use std::marker::PhantomData;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use truce_core::Float;
use truce_core::TransportSlot;
use truce_core::cast::{len_u32, size_of_u32};
use truce_core::editor::{ClosureBridge, Editor, PluginContext, RawWindowHandle, SendPtr};
use truce_core::events::TransportInfo;
use truce_core::export::PluginExport;
use truce_params::Params;
use crate::atom::AtomSequenceReader;
use crate::types::LV2Feature;
use crate::urid::UridMap;
pub type Lv2UiHandle = *mut c_void;
pub type Lv2UiController = *mut c_void;
pub type Lv2UiWriteFn = unsafe extern "C" fn(
controller: Lv2UiController,
port_index: u32,
buffer_size: u32,
port_protocol: u32,
buffer: *const c_void,
);
pub type Lv2UiInstantiateFn = unsafe extern "C" fn(
descriptor: *const Lv2UiDescriptor,
plugin_uri: *const c_char,
bundle_path: *const c_char,
write_function: Lv2UiWriteFn,
controller: Lv2UiController,
widget: *mut *mut c_void,
features: *const *const LV2Feature,
) -> Lv2UiHandle;
pub type Lv2UiCleanupFn = unsafe extern "C" fn(handle: Lv2UiHandle);
pub type Lv2UiPortEventFn = unsafe extern "C" fn(
handle: Lv2UiHandle,
port_index: u32,
buffer_size: u32,
format: u32,
buffer: *const c_void,
);
pub type Lv2UiExtensionDataFn = unsafe extern "C" fn(uri: *const c_char) -> *const c_void;
#[repr(C)]
pub struct Lv2UiDescriptor {
pub uri: *const c_char,
pub instantiate: Lv2UiInstantiateFn,
pub cleanup: Lv2UiCleanupFn,
pub port_event: Lv2UiPortEventFn,
pub extension_data: Lv2UiExtensionDataFn,
}
unsafe impl Send for Lv2UiDescriptor {}
unsafe impl Sync for Lv2UiDescriptor {}
pub const LV2_UI__PARENT: &str = "http://lv2plug.in/ns/extensions/ui#parent";
pub const LV2_UI__RESIZE: &str = "http://lv2plug.in/ns/extensions/ui#resize";
pub const LV2_UI__TOUCH: &str = "http://lv2plug.in/ns/extensions/ui#touch";
#[repr(C)]
pub struct Lv2UiResize {
pub handle: *mut c_void,
pub ui_resize:
Option<unsafe extern "C" fn(handle: *mut c_void, width: i32, height: i32) -> i32>,
}
#[repr(C)]
pub struct Lv2UiTouch {
pub handle: *mut c_void,
pub touch: Option<unsafe extern "C" fn(handle: *mut c_void, port_index: u32, grabbed: bool)>,
}
pub struct Lv2UiInstance<P: PluginExport> {
_plugin: Box<P>,
params: Arc<P::Params>,
param_slots: Vec<ParamSlot>,
meter_slots: Arc<Vec<MeterSlot>>,
editor: Option<Box<dyn Editor>>,
opened: AtomicBool,
urid_map: UridMap,
notify_port_index: u32,
atom_event_transfer_urid: crate::urid::Urid,
transport_slot: Arc<TransportSlot>,
notify_scratch: core::cell::RefCell<Vec<u8>>,
_phantom: PhantomData<P>,
}
struct ParamSlot {
id: u32,
port_index: u32,
range: truce_params::ParamRange,
}
struct MeterSlot {
id: u32,
port_index: u32,
value: AtomicU32,
}
pub unsafe fn instantiate_ui<P: PluginExport>(
_descriptor: *const Lv2UiDescriptor,
_plugin_uri: *const c_char,
_bundle_path: *const c_char,
write_function: Lv2UiWriteFn,
controller: Lv2UiController,
widget: *mut *mut c_void,
features: *const *const LV2Feature,
) -> Lv2UiHandle {
unsafe {
let parsed = parse_features(features);
let Some(parent_ptr) = parsed.parent else {
return std::ptr::null_mut();
};
let mut plugin = Box::new(P::create());
let params_arc = plugin.params_arc();
let param_infos = plugin.params().param_infos();
let layout = crate::derive_port_layout::<P>(&plugin);
let control_start = layout.control_start();
let param_slots: Vec<ParamSlot> = param_infos
.iter()
.enumerate()
.map(|(i, pi)| ParamSlot {
id: pi.id,
port_index: control_start + len_u32(i),
range: pi.range,
})
.collect();
let meter_ids = plugin.params().meter_ids();
let meter_start = layout.meter_start();
let meter_slots: Arc<Vec<MeterSlot>> = Arc::new(
meter_ids
.iter()
.enumerate()
.map(|(i, &id)| MeterSlot {
id,
port_index: meter_start + len_u32(i),
value: AtomicU32::new(0),
})
.collect(),
);
let Some(mut editor) = plugin.editor() else {
return std::ptr::null_mut();
};
let urid_map = UridMap::from_host(parsed.urid_map_handle, parsed.urid_map_fn);
let atom_event_transfer_urid =
urid_map.intern("http://lv2plug.in/ns/ext/atom#eventTransfer");
let transport_slot = TransportSlot::new();
let ctx = build_editor_context::<P>(
params_arc.clone(),
¶m_slots,
meter_slots.clone(),
write_function,
controller,
transport_slot.clone(),
parsed.touch,
parsed.resize,
);
let (pref_w, pref_h) = editor.size();
#[cfg(target_os = "macos")]
let handle = RawWindowHandle::AppKit(parent_ptr);
#[cfg(target_os = "windows")]
let handle = RawWindowHandle::Win32(parent_ptr);
#[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
let handle = RawWindowHandle::X11(parent_ptr as u64);
editor.open(handle, ctx);
if let Some(resize) = parsed.resize
&& let Some(func) = resize.ui_resize
{
#[allow(clippy::cast_possible_wrap)]
let (w, h) = (pref_w as i32, pref_h as i32);
func(resize.handle, w, h);
}
#[cfg(target_os = "macos")]
resize_ns_view(parent_ptr, pref_w, pref_h);
#[cfg(target_os = "macos")]
anchor_child_for_resize(parent_ptr, editor.can_resize());
#[cfg(target_os = "macos")]
install_child_cursor_update(parent_ptr);
#[cfg(target_os = "windows")]
fit_win32_parent_to_child(parent_ptr);
if !widget.is_null() {
*widget = parent_ptr;
}
let ui = Box::new(Lv2UiInstance::<P> {
_plugin: plugin,
params: params_arc,
param_slots,
meter_slots,
editor: Some(editor),
opened: AtomicBool::new(true),
urid_map,
notify_port_index: layout.notify_out_port(),
atom_event_transfer_urid,
transport_slot,
notify_scratch: core::cell::RefCell::new(Vec::new()),
_phantom: PhantomData,
});
Box::into_raw(ui) as Lv2UiHandle
}
}
pub unsafe fn cleanup_ui<P: PluginExport>(handle: Lv2UiHandle) {
unsafe {
if handle.is_null() {
return;
}
let mut ui = Box::from_raw(handle.cast::<Lv2UiInstance<P>>());
if ui.opened.swap(false, Ordering::AcqRel)
&& let Some(mut ed) = ui.editor.take()
{
ed.close();
}
drop(ui);
}
}
pub unsafe fn port_event<P: PluginExport>(
handle: Lv2UiHandle,
port_index: u32,
buffer_size: u32,
format: u32,
buffer: *const c_void,
) {
unsafe {
if handle.is_null() || buffer.is_null() {
return;
}
let ui = &*(handle as *const Lv2UiInstance<P>);
if format == 0 {
if buffer_size < size_of_u32::<f32>() {
return;
}
let value = *buffer.cast::<f32>();
if !value.is_finite() {
return;
}
if let Some(slot) = ui.param_slots.iter().find(|s| s.port_index == port_index) {
ui.params.set_plain(slot.id, f64::from(value));
return;
}
if let Some(meter) = ui.meter_slots.iter().find(|m| m.port_index == port_index) {
meter.value.store(value.to_bits(), Ordering::Relaxed);
}
return;
}
if port_index == ui.notify_port_index
&& ui.atom_event_transfer_urid != 0
&& format == ui.atom_event_transfer_urid
{
decode_notify_atom::<P>(ui, buffer, buffer_size);
}
}
}
unsafe fn decode_notify_atom<P: PluginExport>(
ui: &Lv2UiInstance<P>,
buffer: *const c_void,
buffer_size: u32,
) {
unsafe {
use crate::atom::{Atom, AtomSequence, AtomSequenceBody};
#[repr(C)]
struct OneEvent {
seq_header: AtomSequence,
event_time: i64,
event_body: Atom,
}
let header_size = core::mem::size_of::<Atom>();
if (buffer_size as usize) < header_size {
return;
}
let atom_hdr = *buffer.cast::<Atom>();
if atom_hdr.type_ != ui.urid_map.atom_object && atom_hdr.type_ != ui.urid_map.atom_blank {
return;
}
let body_ptr = buffer.cast::<u8>().add(header_size);
let body_size = atom_hdr.size as usize;
if header_size + body_size > buffer_size as usize {
return;
}
let needed = core::mem::size_of::<OneEvent>() + body_size + 8;
let Ok(mut scratch) = ui.notify_scratch.try_borrow_mut() else {
return;
};
if scratch.len() < needed {
scratch.resize(needed, 0);
}
let one = scratch.as_mut_ptr().cast::<OneEvent>();
(*one).seq_header.atom.type_ = ui.urid_map.atom_sequence;
(*one).seq_header.atom.size = len_u32(
core::mem::size_of::<AtomSequenceBody>()
+ core::mem::size_of::<i64>()
+ core::mem::size_of::<Atom>()
+ body_size,
);
(*one).seq_header.body.unit = 0;
(*one).seq_header.body.pad = 0;
(*one).event_time = 0;
(*one).event_body = atom_hdr;
let ev_body_dest = scratch.as_mut_ptr().add(core::mem::size_of::<OneEvent>());
core::ptr::copy_nonoverlapping(body_ptr, ev_body_dest, body_size);
let mut info = TransportInfo::default();
let reader = AtomSequenceReader::new(scratch.as_ptr().cast::<AtomSequence>(), &ui.urid_map);
if reader.apply_time_position(&mut info) {
ui.transport_slot.write(&info);
}
}
}
pub unsafe fn ui_extension_data<P: PluginExport>(uri: *const c_char) -> *const c_void {
if uri.is_null() {
return std::ptr::null();
}
let cstr = unsafe { CStr::from_ptr(uri) };
if cstr.to_bytes() == LV2_UI__RESIZE.as_bytes() {
return ui_resize_iface::<P>();
}
std::ptr::null()
}
fn ui_resize_iface<P: PluginExport>() -> *const c_void {
use std::any::TypeId;
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
static CACHE: OnceLock<Mutex<HashMap<TypeId, usize>>> = OnceLock::new();
let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
let mut map = cache.lock().expect("ui_resize cache poisoned");
let entry = map.entry(TypeId::of::<P>()).or_insert_with(|| {
let iface = Box::leak(Box::new(Lv2UiResize {
handle: std::ptr::null_mut(),
ui_resize: Some(ui_resize_dispatch::<P>),
}));
std::ptr::from_ref(iface) as usize
});
*entry as *const c_void
}
unsafe extern "C" fn ui_resize_dispatch<P: PluginExport>(
handle: *mut c_void,
width: i32,
height: i32,
) -> i32 {
if handle.is_null() || width <= 0 || height <= 0 {
return 1;
}
unsafe {
let inst = &mut *handle.cast::<Lv2UiInstance<P>>();
let Some(editor) = inst.editor.as_mut() else {
return 1;
};
#[allow(clippy::cast_sign_loss)]
let (req_w, req_h) = (width as u32, height as u32);
let (cw, ch) = clamp_logical_to_editor(req_w, req_h, editor.as_ref());
editor.set_size(cw, ch);
}
0
}
fn clamp_logical_to_editor(w: u32, h: u32, editor: &dyn Editor) -> (u32, u32) {
let (min_w, min_h) = editor.min_size();
let (max_w, max_h) = editor.max_size();
let mut w = w.clamp(min_w.max(1), max_w);
let mut h = h.clamp(min_h.max(1), max_h);
if let Some((num, denom)) = editor.aspect_ratio()
&& num > 0
&& denom > 0
{
let num64 = u64::from(num);
let denom64 = u64::from(denom);
let h_implied = (u64::from(w) * denom64 / num64).clamp(1, u64::from(u32::MAX));
#[allow(clippy::cast_possible_truncation)]
let h_implied_u32 = h_implied as u32;
if h_implied_u32 >= min_h.max(1) && h_implied_u32 <= max_h {
h = h_implied_u32;
} else {
let w_implied = (u64::from(h) * num64 / denom64).clamp(1, u64::from(u32::MAX));
#[allow(clippy::cast_possible_truncation)]
let w_implied_u32 = w_implied as u32;
w = w_implied_u32.clamp(min_w.max(1), max_w);
}
}
(w, h)
}
#[repr(C)]
struct UridMapFeature {
handle: *mut c_void,
map: Option<unsafe extern "C" fn(*mut c_void, *const c_char) -> crate::urid::Urid>,
}
struct ParsedFeatures {
parent: Option<*mut c_void>,
resize: Option<&'static Lv2UiResize>,
touch: Option<&'static Lv2UiTouch>,
urid_map_handle: *mut c_void,
urid_map_fn: Option<unsafe extern "C" fn(*mut c_void, *const c_char) -> crate::urid::Urid>,
}
unsafe fn parse_features(features: *const *const LV2Feature) -> ParsedFeatures {
let mut out = ParsedFeatures {
parent: None,
resize: None,
touch: None,
urid_map_handle: std::ptr::null_mut(),
urid_map_fn: None,
};
unsafe {
if features.is_null() {
return out;
}
let parent_uri = CString::new(LV2_UI__PARENT).unwrap();
let resize_uri = CString::new(LV2_UI__RESIZE).unwrap();
let touch_uri = CString::new(LV2_UI__TOUCH).unwrap();
let map_uri = CString::new(crate::types::LV2_URID__MAP).unwrap();
let mut i = 0usize;
loop {
let feat_ptr = *features.add(i);
if feat_ptr.is_null() {
break;
}
let feat = &*feat_ptr;
if !feat.uri.is_null() {
let feat_uri = CStr::from_ptr(feat.uri);
if out.parent.is_none() && feat_uri == parent_uri.as_c_str() {
out.parent = Some(feat.data);
} else if out.resize.is_none()
&& feat_uri == resize_uri.as_c_str()
&& !feat.data.is_null()
{
out.resize = Some(&*(feat.data as *const Lv2UiResize));
} else if out.touch.is_none()
&& feat_uri == touch_uri.as_c_str()
&& !feat.data.is_null()
{
out.touch = Some(&*(feat.data as *const Lv2UiTouch));
} else if out.urid_map_fn.is_none() && feat_uri == map_uri.as_c_str() {
let map_feat = feat.data as *const UridMapFeature;
if !map_feat.is_null() {
out.urid_map_handle = (*map_feat).handle;
out.urid_map_fn = (*map_feat).map;
}
}
}
i += 1;
}
}
out
}
#[cfg(target_os = "windows")]
#[allow(clippy::items_after_statements)]
unsafe fn fit_win32_parent_to_child(parent: *mut c_void) {
unsafe {
if parent.is_null() {
return;
}
const SWP_NOMOVE: u32 = 0x0002;
const SWP_NOZORDER: u32 = 0x0004;
const SWP_NOACTIVATE: u32 = 0x0010;
const GW_CHILD: u32 = 5;
#[allow(clippy::upper_case_acronyms)]
#[repr(C)]
struct RECT {
left: i32,
top: i32,
right: i32,
bottom: i32,
}
unsafe extern "system" {
fn SetWindowPos(
hwnd: *mut c_void,
hwnd_insert_after: *mut c_void,
x: i32,
y: i32,
cx: i32,
cy: i32,
flags: u32,
) -> i32;
fn GetWindow(hwnd: *mut c_void, cmd: u32) -> *mut c_void;
fn GetClientRect(hwnd: *mut c_void, rect: *mut RECT) -> i32;
}
let child = GetWindow(parent, GW_CHILD);
if child.is_null() {
return;
}
let mut rect = RECT {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
if GetClientRect(child, &raw mut rect) == 0 {
return;
}
let w = rect.right - rect.left;
let h = rect.bottom - rect.top;
if w <= 0 || h <= 0 {
return;
}
let _ = SetWindowPos(
parent,
std::ptr::null_mut(),
0,
0,
w,
h,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE,
);
}
}
#[cfg(target_os = "macos")]
unsafe fn anchor_child_for_resize(parent: *mut c_void, resizable: bool) {
use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl};
#[repr(C)]
#[derive(Clone, Copy)]
struct NSPoint {
x: f64,
y: f64,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct NSSize {
width: f64,
height: f64,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct NSRect {
origin: NSPoint,
size: NSSize,
}
const NSVIEW_WIDTH_SIZABLE: u64 = 2;
const NSVIEW_MAX_X_MARGIN: u64 = 4;
const NSVIEW_MIN_Y_MARGIN: u64 = 8;
const NSVIEW_HEIGHT_SIZABLE: u64 = 16;
if parent.is_null() {
return;
}
let parent_obj = parent.cast::<Object>();
let parent_frame: NSRect = msg_send![parent_obj, frame];
let subviews: *mut Object = msg_send![parent_obj, subviews];
if subviews.is_null() {
return;
}
let count: usize = msg_send![subviews, count];
for i in 0..count {
let child: *mut Object = msg_send![subviews, objectAtIndex: i];
if child.is_null() {
continue;
}
if resizable {
let new_frame = NSRect {
origin: NSPoint { x: 0.0, y: 0.0 },
size: parent_frame.size,
};
let _: () = msg_send![child, setFrame: new_frame];
let _: () =
msg_send![child, setAutoresizingMask: NSVIEW_WIDTH_SIZABLE | NSVIEW_HEIGHT_SIZABLE];
} else {
let child_frame: NSRect = msg_send![child, frame];
let new_origin = NSPoint {
x: child_frame.origin.x,
y: parent_frame.size.height - child_frame.size.height,
};
let _: () = msg_send![child, setFrameOrigin: new_origin];
let _: () =
msg_send![child, setAutoresizingMask: NSVIEW_MIN_Y_MARGIN | NSVIEW_MAX_X_MARGIN];
}
}
}
#[cfg(target_os = "macos")]
unsafe fn resize_ns_view(view: *mut c_void, width: u32, height: u32) {
use objc::{class, msg_send, sel, sel_impl};
#[repr(C)]
struct NSSize {
width: f64,
height: f64,
}
if view.is_null() {
return;
}
let size = NSSize {
width: f64::from(width),
height: f64::from(height),
};
let _: () = msg_send![view.cast::<objc::runtime::Object>(), setFrameSize: size];
let _: () = msg_send![
view.cast::<objc::runtime::Object>(),
invalidateIntrinsicContentSize
];
let _ = class!(NSView); }
#[cfg(target_os = "macos")]
unsafe fn install_child_cursor_update(parent: *mut c_void) {
use objc::runtime::{Class, Object, Sel, class_addMethod};
use objc::{class, msg_send, sel, sel_impl};
extern "C" fn cursor_update(_this: &Object, _sel: Sel, _event: *mut Object) {
unsafe {
let cursor: *mut Object = msg_send![class!(NSCursor), arrowCursor];
let _: () = msg_send![cursor, set];
}
}
type ImpFn = unsafe extern "C" fn();
if parent.is_null() {
return;
}
let subviews: *mut Object = msg_send![parent.cast::<Object>(), subviews];
if subviews.is_null() {
return;
}
let count: usize = msg_send![subviews, count];
for i in 0..count {
let child: *mut Object = msg_send![subviews, objectAtIndex: i];
if child.is_null() {
continue;
}
let class_ptr: *mut Class = msg_send![child, class];
let selector = sel!(cursorUpdate:);
let type_encoding = c"v@:@".as_ptr();
let imp: ImpFn = unsafe {
core::mem::transmute::<extern "C" fn(&Object, Sel, *mut Object), ImpFn>(cursor_update)
};
unsafe { class_addMethod(class_ptr, selector, imp, type_encoding) };
}
}
#[allow(clippy::needless_pass_by_value)]
#[allow(clippy::too_many_arguments)]
fn build_editor_context<P: PluginExport>(
params: Arc<P::Params>,
slots: &[ParamSlot],
meter_slots: Arc<Vec<MeterSlot>>,
write_function: Lv2UiWriteFn,
controller: Lv2UiController,
transport_slot: Arc<TransportSlot>,
touch: Option<&'static Lv2UiTouch>,
resize: Option<&'static Lv2UiResize>,
) -> PluginContext {
let slots_for_set: Vec<(u32, u32, truce_params::ParamRange)> = slots
.iter()
.map(|s| (s.id, s.port_index, s.range))
.collect();
let slots_for_begin: Vec<(u32, u32)> = slots.iter().map(|s| (s.id, s.port_index)).collect();
let slots_for_end = slots_for_begin.clone();
let controller_ptr: SendPtr<core::ffi::c_void> =
unsafe { SendPtr::new(controller.cast_const()) };
let params_get = params.clone();
let params_get_plain = params.clone();
let params_format = params.clone();
let params_set = params.clone();
let params_for_ctx = params.clone();
let meter_slots_for_get = meter_slots.clone();
let write_set = write_function;
let touch_fn = touch.and_then(|t| t.touch);
let touch_handle = touch.map_or(0, |t| t.handle as usize);
let host_resize_fn = resize.and_then(|r| r.ui_resize);
let host_resize_handle = resize.map_or(0, |r| r.handle as usize);
PluginContext::from_closures(
ClosureBridge {
begin_edit: Box::new(move |id: u32| {
let Some(func) = touch_fn else { return };
let Some((_, port_index)) = slots_for_begin.iter().find(|(pid, _)| *pid == id)
else {
return;
};
unsafe { func(touch_handle as *mut c_void, *port_index, true) };
}),
end_edit: Box::new(move |id: u32| {
let Some(func) = touch_fn else { return };
let Some((_, port_index)) = slots_for_end.iter().find(|(pid, _)| *pid == id) else {
return;
};
unsafe { func(touch_handle as *mut c_void, *port_index, false) };
}),
request_resize: Box::new(move |w: u32, h: u32| {
let Some(func) = host_resize_fn else {
return false;
};
#[allow(clippy::cast_possible_wrap)]
let (iw, ih) = (w as i32, h as i32);
unsafe { func(host_resize_handle as *mut c_void, iw, ih) == 0 }
}),
set_param: Box::new(move |id: u32, normalized: f64| {
let Some((_, port_index, range)) =
slots_for_set.iter().find(|(pid, _, _)| *pid == id)
else {
return;
};
let plain = f32::from_f64(range.denormalize(normalized));
params_set.set_normalized(id, normalized);
unsafe {
let value = plain;
write_set(
controller_ptr.as_ptr().cast_mut(),
*port_index,
size_of_u32::<f32>(),
0, (&raw const value).cast::<c_void>(),
);
}
}),
get_param: Box::new(move |id: u32| params_get.get_normalized(id).unwrap_or(0.0)),
get_param_plain: Box::new(move |id: u32| params_get_plain.get_plain(id).unwrap_or(0.0)),
format_param: Box::new(move |id: u32| {
let v = params_format.get_plain(id).unwrap_or(0.0);
params_format.format_value(id, v).unwrap_or_default()
}),
get_meter: Box::new(move |id: u32| {
meter_slots_for_get
.iter()
.find(|m| m.id == id)
.map_or(0.0, |m| f32::from_bits(m.value.load(Ordering::Relaxed)))
}),
get_state: Box::new(Vec::new),
set_state: Box::new(|_bytes: Vec<u8>| {}),
transport: Box::new(move || transport_slot.read()),
},
params_for_ctx,
)
}
#[must_use]
pub fn ui_descriptor<P: PluginExport>(uri: &'static CStr) -> Lv2UiDescriptor {
Lv2UiDescriptor {
uri: uri.as_ptr(),
instantiate: instantiate_ui_tramp::<P>,
cleanup: cleanup_ui_tramp::<P>,
port_event: port_event_tramp::<P>,
extension_data: ui_extension_data_tramp::<P>,
}
}
unsafe extern "C" fn instantiate_ui_tramp<P: PluginExport>(
descriptor: *const Lv2UiDescriptor,
plugin_uri: *const c_char,
bundle_path: *const c_char,
write_function: Lv2UiWriteFn,
controller: Lv2UiController,
widget: *mut *mut c_void,
features: *const *const LV2Feature,
) -> Lv2UiHandle {
unsafe {
instantiate_ui::<P>(
descriptor,
plugin_uri,
bundle_path,
write_function,
controller,
widget,
features,
)
}
}
unsafe extern "C" fn cleanup_ui_tramp<P: PluginExport>(handle: Lv2UiHandle) {
unsafe {
cleanup_ui::<P>(handle);
}
}
unsafe extern "C" fn port_event_tramp<P: PluginExport>(
handle: Lv2UiHandle,
port_index: u32,
buffer_size: u32,
format: u32,
buffer: *const c_void,
) {
unsafe {
port_event::<P>(handle, port_index, buffer_size, format, buffer);
}
}
unsafe extern "C" fn ui_extension_data_tramp<P: PluginExport>(uri: *const c_char) -> *const c_void {
unsafe { ui_extension_data::<P>(uri) }
}