use atomic_float::AtomicF32;
use nice_plug_core::editor::{Editor, ParentWindowHandle};
use parking_lot::{Mutex, RwLock};
use std::any::Any;
use std::ffi::{CStr, c_void};
use std::mem;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use vst3::Steinberg::{
FIDString, TBool, char16, int16, kInvalidArgument, kNotImplemented, kPlatformTypeHWND,
kPlatformTypeNSView, kPlatformTypeX11EmbedWindowID, kResultFalse, kResultOk, tresult,
};
use vst3::Steinberg::{
IPlugFrame, IPlugFrameTrait, IPlugView, IPlugViewContentScaleSupport,
IPlugViewContentScaleSupport_::ScaleFactor, IPlugViewContentScaleSupportTrait, IPlugViewTrait,
ViewRect,
};
use vst3::{Class, ComPtr, ComRef, ComWrapper};
use super::inner::{Task, WrapperInner};
use crate::editor::{Modifiers, VirtualKeyCode};
use crate::wrapper::vst3::{Vst3Plugin, util::fid_matches};
const VKEY_FIRST_CODE: i16 = 1;
const VKEY_LAST_CODE: i16 = 77;
fn vst3_virtual_key_code(raw: i16) -> Option<VirtualKeyCode> {
Some(match raw {
1 => VirtualKeyCode::Backspace,
2 => VirtualKeyCode::Tab,
3 => VirtualKeyCode::Clear,
4 => VirtualKeyCode::Return,
5 => VirtualKeyCode::Pause,
6 => VirtualKeyCode::Escape,
7 => VirtualKeyCode::Space,
8 => VirtualKeyCode::Next,
9 => VirtualKeyCode::End,
10 => VirtualKeyCode::Home,
11 => VirtualKeyCode::ArrowLeft,
12 => VirtualKeyCode::ArrowUp,
13 => VirtualKeyCode::ArrowRight,
14 => VirtualKeyCode::ArrowDown,
15 => VirtualKeyCode::PageUp,
16 => VirtualKeyCode::PageDown,
17 => VirtualKeyCode::Select,
18 => VirtualKeyCode::Print,
19 => VirtualKeyCode::NumpadEnter,
20 => VirtualKeyCode::Snapshot,
21 => VirtualKeyCode::Insert,
22 => VirtualKeyCode::Delete,
23 => VirtualKeyCode::Help,
24 => VirtualKeyCode::Numpad0,
25 => VirtualKeyCode::Numpad1,
26 => VirtualKeyCode::Numpad2,
27 => VirtualKeyCode::Numpad3,
28 => VirtualKeyCode::Numpad4,
29 => VirtualKeyCode::Numpad5,
30 => VirtualKeyCode::Numpad6,
31 => VirtualKeyCode::Numpad7,
32 => VirtualKeyCode::Numpad8,
33 => VirtualKeyCode::Numpad9,
34 => VirtualKeyCode::NumpadMultiply,
35 => VirtualKeyCode::NumpadAdd,
36 => VirtualKeyCode::NumpadSeparator,
37 => VirtualKeyCode::NumpadSubtract,
38 => VirtualKeyCode::NumpadDecimal,
39 => VirtualKeyCode::NumpadDivide,
40 => VirtualKeyCode::F1,
41 => VirtualKeyCode::F2,
42 => VirtualKeyCode::F3,
43 => VirtualKeyCode::F4,
44 => VirtualKeyCode::F5,
45 => VirtualKeyCode::F6,
46 => VirtualKeyCode::F7,
47 => VirtualKeyCode::F8,
48 => VirtualKeyCode::F9,
49 => VirtualKeyCode::F10,
50 => VirtualKeyCode::F11,
51 => VirtualKeyCode::F12,
52 => VirtualKeyCode::NumLock,
53 => VirtualKeyCode::ScrollLock,
54 => VirtualKeyCode::Shift,
55 => VirtualKeyCode::Control,
56 => VirtualKeyCode::Alt,
57 => VirtualKeyCode::Equals,
58 => VirtualKeyCode::ContextMenu,
59 => VirtualKeyCode::MediaPlay,
60 => VirtualKeyCode::MediaStop,
61 => VirtualKeyCode::MediaPrevTrack,
62 => VirtualKeyCode::MediaNextTrack,
63 => VirtualKeyCode::VolumeUp,
64 => VirtualKeyCode::VolumeDown,
65 => VirtualKeyCode::F13,
66 => VirtualKeyCode::F14,
67 => VirtualKeyCode::F15,
68 => VirtualKeyCode::F16,
69 => VirtualKeyCode::F17,
70 => VirtualKeyCode::F18,
71 => VirtualKeyCode::F19,
72 => VirtualKeyCode::F20,
73 => VirtualKeyCode::F21,
74 => VirtualKeyCode::F22,
75 => VirtualKeyCode::F23,
76 => VirtualKeyCode::F24,
77 => VirtualKeyCode::Super,
_ => return None,
})
}
fn vst3_modifiers(raw: i16) -> Modifiers {
Modifiers::from_bits_truncate(((raw as u32) & 0x0F) as u8)
}
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
use {
crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY},
crossbeam::queue::ArrayQueue,
libc,
std::cell::Cell,
vst3::Steinberg::Linux::{
FileDescriptor, IEventHandler, IEventHandlerTrait, IRunLoop, IRunLoopTrait,
},
};
pub(crate) struct WrapperView<P: Vst3Plugin> {
inner: Arc<WrapperInner<P>>,
editor: Arc<Mutex<Box<dyn Editor>>>,
editor_handle: Mutex<Option<Box<dyn Any + Send>>>,
plug_frame: RwLock<Option<ComPtr<IPlugFrame>>>,
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
run_loop_event_handler: RwLock<Option<ComWrapper<RunLoopEventHandler<P>>>>,
scaling_factor: AtomicF32,
}
impl<P: Vst3Plugin> Class for WrapperView<P> {
type Interfaces = (IPlugView, IPlugViewContentScaleSupport);
}
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
struct RunLoopEventHandler<P: Vst3Plugin> {
inner: Arc<WrapperInner<P>>,
run_loop: ComPtr<IRunLoop>,
socket_read_fd: i32,
socket_write_fd: i32,
tasks: ArrayQueue<Task<P>>,
event_handler_ptr: EventHandlerSelfRefPtr,
}
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
struct EventHandlerSelfRefPtr(Cell<*mut IEventHandler>);
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
unsafe impl Send for EventHandlerSelfRefPtr {}
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
unsafe impl Sync for EventHandlerSelfRefPtr {}
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
impl<P: Vst3Plugin> Class for RunLoopEventHandler<P> {
type Interfaces = (IEventHandler,);
}
impl<P: Vst3Plugin> WrapperView<P> {
pub fn new(inner: Arc<WrapperInner<P>>, editor: Arc<Mutex<Box<dyn Editor>>>) -> Self {
Self {
inner,
editor,
editor_handle: Mutex::new(None),
plug_frame: RwLock::new(None),
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
run_loop_event_handler: RwLock::new(None),
scaling_factor: AtomicF32::new(1.0),
}
}
#[must_use]
pub unsafe fn request_resize(this: &ComWrapper<Self>) -> bool {
if !this.inner.is_editor_open.load(Ordering::SeqCst) {
return false;
}
match &*this.plug_frame.read() {
Some(plug_frame) => {
let (unscaled_width, unscaled_height) = this.editor.lock().size();
let scaling_factor = this.scaling_factor.load(Ordering::Relaxed);
let mut size = ViewRect {
left: 0,
top: 0,
right: (unscaled_width as f32 * scaling_factor).round() as i32,
bottom: (unscaled_height as f32 * scaling_factor).round() as i32,
};
let plug_view = this.as_com_ref::<IPlugView>().unwrap();
let result = unsafe { plug_frame.resizeView(plug_view.as_ptr(), &mut size) };
#[cfg(debug_assertions)]
if result != kResultOk {
crate::nice_warn!(
"The host denied the resize, we currently don't handle this for VST3 \
plugins"
);
}
result == kResultOk
}
None => false,
}
}
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
pub fn do_maybe_in_run_loop(&self, task: Task<P>) -> Result<(), Task<P>> {
match &*self.run_loop_event_handler.read() {
Some(run_loop) => run_loop.post_task(task),
None => Err(task),
}
}
#[cfg(not(target_os = "linux"))]
pub fn do_maybe_in_run_loop(&self, task: Task<P>) -> Result<(), Task<P>> {
Err(task)
}
fn dispatch_virtual_key(&self, key_code: i16, is_down: bool, modifiers: i16) -> tresult {
if !(VKEY_FIRST_CODE..=VKEY_LAST_CODE).contains(&key_code) {
return kResultFalse;
}
let Some(key_code) = vst3_virtual_key_code(key_code) else {
return kResultFalse;
};
let modifiers = vst3_modifiers(modifiers);
if self
.editor
.lock()
.on_virtual_key_from_host(key_code, is_down, modifiers)
{
kResultOk
} else {
kResultFalse
}
}
}
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
impl<P: Vst3Plugin> RunLoopEventHandler<P> {
pub fn new(inner: Arc<WrapperInner<P>>, run_loop: ComPtr<IRunLoop>) -> ComWrapper<Self> {
let mut sockets = [0i32; 2];
assert_eq!(
unsafe {
libc::socketpair(
libc::AF_UNIX,
libc::SOCK_STREAM | libc::SOCK_CLOEXEC | libc::SOCK_NONBLOCK,
0,
sockets.as_mut_ptr(),
)
},
0
);
let [socket_read_fd, socket_write_fd] = sockets;
let handler = ComWrapper::new(RunLoopEventHandler {
inner,
run_loop,
socket_read_fd,
socket_write_fd,
tasks: ArrayQueue::new(TASK_QUEUE_CAPACITY),
event_handler_ptr: EventHandlerSelfRefPtr(Cell::new(std::ptr::null_mut())),
});
let event_handler_ptr = handler.to_com_ptr::<IEventHandler>().unwrap().into_raw();
handler.event_handler_ptr.0.set(event_handler_ptr);
assert_eq!(
unsafe {
handler
.run_loop
.registerEventHandler(event_handler_ptr, handler.socket_read_fd)
},
kResultOk
);
handler
}
pub fn post_task(&self, task: Task<P>) -> Result<(), Task<P>> {
self.tasks.push(task)?;
let notify_value = 1i8;
const NOTIFY_VALUE_SIZE: usize = std::mem::size_of::<i8>();
assert_eq!(
unsafe {
libc::write(
self.socket_write_fd,
¬ify_value as *const _ as *const c_void,
NOTIFY_VALUE_SIZE,
)
},
NOTIFY_VALUE_SIZE as isize
);
Ok(())
}
}
impl<P: Vst3Plugin> IPlugViewTrait for WrapperView<P> {
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
unsafe fn isPlatformTypeSupported(&self, type_: FIDString) -> tresult {
if unsafe { fid_matches(type_, kPlatformTypeX11EmbedWindowID) } {
kResultOk
} else {
crate::nice_debug_assert_failure!("Invalid window handle type: {:?}", unsafe {
CStr::from_ptr(type_)
});
kResultFalse
}
}
#[cfg(target_os = "macos")]
unsafe fn isPlatformTypeSupported(&self, type_: FIDString) -> tresult {
if unsafe { fid_matches(type_, kPlatformTypeNSView) } {
kResultOk
} else {
crate::nice_debug_assert_failure!("Invalid window handle type: {:?}", unsafe {
CStr::from_ptr(type_)
});
kResultFalse
}
}
#[cfg(target_os = "windows")]
unsafe fn isPlatformTypeSupported(&self, type_: FIDString) -> tresult {
if unsafe { fid_matches(type_, kPlatformTypeHWND) } {
kResultOk
} else {
crate::nice_debug_assert_failure!("Invalid window handle type: {:?}", unsafe {
CStr::from_ptr(type_)
});
kResultFalse
}
}
unsafe fn attached(&self, parent: *mut c_void, type_: FIDString) -> tresult {
let mut editor_handle = self.editor_handle.lock();
if editor_handle.is_none() {
let parent_handle = if unsafe { fid_matches(type_, kPlatformTypeX11EmbedWindowID) } {
ParentWindowHandle::X11Window(parent as usize as u32)
} else if unsafe { fid_matches(type_, kPlatformTypeNSView) } {
ParentWindowHandle::AppKitNsView(parent)
} else if unsafe { fid_matches(type_, kPlatformTypeHWND) } {
ParentWindowHandle::Win32Hwnd(parent)
} else {
crate::nice_debug_assert_failure!("Unknown window handle type: {:?}", unsafe {
CStr::from_ptr(type_)
});
return kInvalidArgument;
};
*editor_handle = Some(
self.editor
.lock()
.spawn(parent_handle, self.inner.clone().make_gui_context()),
);
self.inner.is_editor_open.store(true, Ordering::SeqCst);
kResultOk
} else {
crate::nice_debug_assert_failure!(
"Host tried to attach editor while the editor is already attached"
);
kResultFalse
}
}
unsafe fn removed(&self) -> tresult {
let mut editor_handle = self.editor_handle.lock();
if editor_handle.is_some() {
self.inner.is_editor_open.store(false, Ordering::SeqCst);
*editor_handle = None;
kResultOk
} else {
crate::nice_debug_assert_failure!(
"Host tried to remove the editor without an active editor"
);
kResultFalse
}
}
unsafe fn onWheel(&self, _distance: f32) -> tresult {
kNotImplemented
}
unsafe fn onKeyDown(&self, _key: char16, key_code: int16, modifiers: int16) -> tresult {
self.dispatch_virtual_key(key_code, true, modifiers)
}
unsafe fn onKeyUp(&self, _key: char16, key_code: int16, modifiers: int16) -> tresult {
self.dispatch_virtual_key(key_code, false, modifiers)
}
unsafe fn getSize(&self, size: *mut ViewRect) -> tresult {
check_null_ptr!(size);
unsafe { *size = mem::zeroed() };
let (unscaled_width, unscaled_height) = self.editor.lock().size();
let scaling_factor = self.scaling_factor.load(Ordering::Relaxed);
let size = unsafe { &mut *size };
size.left = 0;
size.right = (unscaled_width as f32 * scaling_factor).round() as i32;
size.top = 0;
size.bottom = (unscaled_height as f32 * scaling_factor).round() as i32;
kResultOk
}
unsafe fn onSize(&self, new_size: *mut ViewRect) -> tresult {
check_null_ptr!(new_size);
let (unscaled_width, unscaled_height) = self.editor.lock().size();
let scaling_factor = self.scaling_factor.load(Ordering::Relaxed);
let (editor_width, editor_height) = (
(unscaled_width as f32 * scaling_factor).round() as i32,
(unscaled_height as f32 * scaling_factor).round() as i32,
);
let width = unsafe { (*new_size).right - (*new_size).left };
let height = unsafe { (*new_size).bottom - (*new_size).top };
if width == editor_width && height == editor_height {
kResultOk
} else {
kResultFalse
}
}
unsafe fn onFocus(&self, _state: TBool) -> tresult {
kNotImplemented
}
unsafe fn setFrame(&self, frame: *mut IPlugFrame) -> tresult {
match unsafe { ComRef::from_raw(frame) } {
Some(frame) => {
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
{
*self.run_loop_event_handler.write() = frame
.cast()
.map(|run_loop| RunLoopEventHandler::new(self.inner.clone(), run_loop));
}
*self.plug_frame.write() = Some(frame.to_com_ptr());
}
None => {
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
{
*self.run_loop_event_handler.write() = None;
}
*self.plug_frame.write() = None;
}
}
kResultOk
}
unsafe fn canResize(&self) -> tresult {
kResultFalse
}
unsafe fn checkSizeConstraint(&self, rect: *mut ViewRect) -> tresult {
check_null_ptr!(rect);
unsafe {
if (*rect).right - (*rect).left > 0 && (*rect).bottom - (*rect).top > 0 {
kResultOk
} else {
kResultFalse
}
}
}
}
impl<P: Vst3Plugin> IPlugViewContentScaleSupportTrait for WrapperView<P> {
unsafe fn setContentScaleFactor(&self, factor: ScaleFactor) -> tresult {
if cfg!(target_os = "macos") {
crate::nice_debug_assert_failure!(
"Ignoring host request to set explicit DPI scaling factor"
);
return kResultFalse;
}
if self.editor.lock().set_scale_factor(factor) {
self.scaling_factor.store(factor, Ordering::Relaxed);
kResultOk
} else {
kResultFalse
}
}
}
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
impl<P: Vst3Plugin> IEventHandlerTrait for RunLoopEventHandler<P> {
unsafe fn onFDIsSet(&self, _fd: FileDescriptor) {
let mut notify_value = [0; 32];
loop {
let read_result = unsafe {
libc::read(
self.socket_read_fd,
&mut notify_value as *mut _ as *mut c_void,
std::mem::size_of_val(¬ify_value),
)
};
if read_result <= 0 {
break;
}
}
while let Some(task) = self.tasks.pop() {
self.inner.execute(task, true);
}
}
}
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
impl<P: Vst3Plugin> Drop for RunLoopEventHandler<P> {
fn drop(&mut self) {
let mut posting_failed = false;
while let Some(task) = self.tasks.pop() {
posting_failed |= !self
.inner
.event_loop
.borrow()
.as_ref()
.unwrap()
.schedule_gui(task);
}
if posting_failed {
crate::nice_debug_assert_failure!(
"Outstanding tasks have been dropped when closing the editor as the task queue \
was full"
);
}
unsafe {
libc::close(self.socket_read_fd);
libc::close(self.socket_write_fd);
}
unsafe {
self.run_loop
.unregisterEventHandler(self.event_handler_ptr.0.get());
}
}
}