#![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};
#[cfg(target_os = "macos")]
use truce_core::editor::fit_size;
use truce_core::editor::{
ClosureBridge, Editor, PluginContext, RawWindowHandle, SendPtr, fit_logical_size,
};
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;
#[cfg(all(unix, not(target_os = "macos")))]
use x11_dl::xlib;
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>>,
#[cfg(target_os = "macos")]
fit_observer_children: Vec<usize>,
_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")]
let fit_observer_children = anchor_child_for_resize(parent_ptr, editor.as_ref());
#[cfg(target_os = "macos")]
install_child_cursor_update(parent_ptr);
#[cfg(target_os = "windows")]
fit_win32_parent_to_child(parent_ptr);
#[cfg(all(unix, not(target_os = "macos")))]
prepare_x11_parent_window(parent_ptr, pref_w, pref_h, !editor.can_resize());
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()),
#[cfg(target_os = "macos")]
fit_observer_children,
_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) {
#[cfg(target_os = "macos")]
remove_fit_observers(&ui.fit_observer_children);
if 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) = fit_logical_size(req_w, req_h, editor.as_ref());
editor.set_size(cw, ch);
}
0
}
#[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(all(unix, not(target_os = "macos")))]
fn prepare_x11_parent_window(parent: *mut c_void, w: u32, h: u32, fit: bool) {
if parent.is_null() {
return;
}
let Ok(lib) = xlib::Xlib::open() else {
return;
};
let parent_id = parent as usize as xlib::Window;
unsafe {
let display = (lib.XOpenDisplay)(std::ptr::null());
if display.is_null() {
return;
}
(lib.XSetWindowBackground)(display, parent_id, 0xFF00_0000);
(lib.XClearWindow)(display, parent_id);
if fit && w > 0 && h > 0 {
let child = x11_first_child(&lib, display, parent_id);
(lib.XResizeWindow)(display, parent_id, w, h);
if let Some(c) = child {
(lib.XResizeWindow)(display, c, w, h);
}
}
(lib.XFlush)(display);
(lib.XCloseDisplay)(display);
}
}
#[cfg(all(unix, not(target_os = "macos")))]
unsafe fn x11_first_child(
lib: &xlib::Xlib,
display: *mut xlib::Display,
parent: xlib::Window,
) -> Option<xlib::Window> {
let mut root: xlib::Window = 0;
let mut parent_ret: xlib::Window = 0;
let mut children: *mut xlib::Window = std::ptr::null_mut();
let mut n: std::os::raw::c_uint = 0;
unsafe {
if (lib.XQueryTree)(
display,
parent,
&raw mut root,
&raw mut parent_ret,
&raw mut children,
&raw mut n,
) == 0
{
return None;
}
let first = if n > 0 && !children.is_null() {
Some(*children)
} else {
None
};
if !children.is_null() {
(lib.XFree)(children.cast());
}
first
}
}
#[cfg(target_os = "macos")]
#[repr(C)]
#[derive(Clone, Copy)]
struct NSPoint {
x: f64,
y: f64,
}
#[cfg(target_os = "macos")]
#[repr(C)]
#[derive(Clone, Copy)]
struct NSSize {
width: f64,
height: f64,
}
#[cfg(target_os = "macos")]
#[repr(C)]
#[derive(Clone, Copy)]
struct NSRect {
origin: NSPoint,
size: NSSize,
}
#[cfg(target_os = "macos")]
type FitConstraints = ((u32, u32), (u32, u32), Option<(u32, u32)>);
#[cfg(target_os = "macos")]
fn fit_constraints() -> &'static std::sync::Mutex<std::collections::HashMap<usize, FitConstraints>>
{
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
static MAP: OnceLock<Mutex<HashMap<usize, FitConstraints>>> = OnceLock::new();
MAP.get_or_init(|| Mutex::new(HashMap::new()))
}
#[cfg(target_os = "macos")]
unsafe fn anchor_child_for_resize(parent: *mut c_void, editor: &dyn Editor) -> Vec<usize> {
use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl};
const NSVIEW_NOT_SIZABLE: u64 = 0;
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;
let mut observed = Vec::new();
if parent.is_null() {
return observed;
}
let resizable = editor.can_resize();
let needs_fit = resizable
&& (editor.aspect_ratio().is_some()
|| editor.min_size() != (1, 1)
|| editor.max_size() != (u32::MAX, u32::MAX));
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 observed;
}
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 needs_fit {
unsafe { install_fit_observer(parent_obj, child, editor) };
let _: () = msg_send![child, setAutoresizingMask: NSVIEW_NOT_SIZABLE];
unsafe { fit_view_into_superview(child) };
observed.push(child as usize);
} else 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];
}
}
observed
}
#[cfg(target_os = "macos")]
unsafe fn install_fit_observer(
parent: *mut objc::runtime::Object,
child: *mut objc::runtime::Object,
editor: &dyn Editor,
) {
use objc::runtime::{Class, Object, Sel, class_addMethod};
use objc::{class, msg_send, sel, sel_impl};
extern "C" fn frame_changed(this: &Object, _sel: Sel, _note: *mut Object) {
unsafe { fit_view_into_superview(core::ptr::from_ref(this).cast_mut()) };
}
type ImpFn = unsafe extern "C" fn();
fit_constraints()
.lock()
.expect("fit constraints poisoned")
.insert(
child as usize,
(editor.min_size(), editor.max_size(), editor.aspect_ratio()),
);
let class_ptr: *mut Class = msg_send![child, class];
let type_encoding = c"v@:@".as_ptr();
let imp: ImpFn = unsafe {
core::mem::transmute::<extern "C" fn(&Object, Sel, *mut Object), ImpFn>(frame_changed)
};
unsafe { class_addMethod(class_ptr, sel!(frameChanged:), imp, type_encoding) };
let _: () = msg_send![parent, setPostsFrameChangedNotifications: true];
let center: *mut Object = msg_send![class!(NSNotificationCenter), defaultCenter];
let name = unsafe { ns_string("NSViewFrameDidChangeNotification") };
let _: () = msg_send![center, addObserver: child selector: sel!(frameChanged:) name: name object: parent];
}
#[cfg(target_os = "macos")]
unsafe fn fit_view_into_superview(child: *mut objc::runtime::Object) {
use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl};
let Some((min, max, aspect)) = fit_constraints()
.lock()
.expect("fit constraints poisoned")
.get(&(child as usize))
.copied()
else {
return;
};
let superview: *mut Object = msg_send![child, superview];
if superview.is_null() {
return;
}
let sframe: NSRect = msg_send![superview, frame];
let (fw, fh) = fit_size(
pt_to_u32(sframe.size.width),
pt_to_u32(sframe.size.height),
min,
max,
aspect,
);
let new_frame = NSRect {
origin: NSPoint {
x: 0.0,
y: sframe.size.height - f64::from(fh),
},
size: NSSize {
width: f64::from(fw),
height: f64::from(fh),
},
};
let _: () = msg_send![child, setFrame: new_frame];
}
#[cfg(target_os = "macos")]
unsafe fn remove_fit_observers(children: &[usize]) {
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
if children.is_empty() {
return;
}
let center: *mut Object = msg_send![class!(NSNotificationCenter), defaultCenter];
let mut map = fit_constraints().lock().expect("fit constraints poisoned");
for &child in children {
let obj = child as *mut Object;
let _: () = msg_send![center, removeObserver: obj];
map.remove(&child);
}
}
#[cfg(target_os = "macos")]
unsafe fn ns_string(s: &str) -> *mut objc::runtime::Object {
use objc::{class, msg_send, sel, sel_impl};
let c = CString::new(s).expect("notification name has no interior nul");
msg_send![class!(NSString), stringWithUTF8String: c.as_ptr()]
}
#[cfg(target_os = "macos")]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn pt_to_u32(v: f64) -> u32 {
if !v.is_finite() || v <= 0.0 {
0
} else if v >= f64::from(u32::MAX) {
u32::MAX
} else {
v.round() as u32
}
}
#[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) }
}