use crossbeam::atomic::AtomicCell;
use fragile::Fragile;
use nice_plug_core::editor::HostMainThreadCaller;
use nice_plug_core::editor::dpi::{PhysicalSize, Size};
use nice_plug_core::editor::{
Editor, EditorHandle, HostCallbacks, HostMethods, ParentWindowHandle,
};
use parking_lot::{Mutex, RwLock};
use std::error::Error;
use std::ffi::{CStr, c_ulong, c_void};
use std::num::NonZeroIsize;
use std::ptr::NonNull;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Weak};
use vst3::Steinberg::{
FIDString, TBool, char16, int16, kInvalidArgument, kNotImplemented, kPlatformTypeHWND,
kPlatformTypeNSView, kPlatformTypeX11EmbedWindowID, kResultFalse, kResultOk, tresult,
};
use vst3::Steinberg::{
IPlugFrame, 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: Weak<WrapperInner<P>>,
editor: Weak<Mutex<P::Editor>>,
plug_frame: RwLock<Option<ComPtr<IPlugFrame>>>,
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
run_loop_event_handler: RwLock<Option<ComWrapper<RunLoopEventHandler<P>>>>,
fallback_scale_factor: AtomicCell<Option<f32>>,
}
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: Weak<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: Weak<WrapperInner<P>>, editor: Weak<Mutex<P::Editor>>) -> Self {
Self {
inner,
editor,
plug_frame: RwLock::new(None),
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
run_loop_event_handler: RwLock::new(None),
fallback_scale_factor: AtomicCell::new(None),
}
}
#[must_use]
pub unsafe fn request_resize(
this: &ComWrapper<Self>,
new_size: Size,
scale_factor: f64,
) -> bool {
use vst3::Steinberg::IPlugFrameTrait;
let Some(inner) = Weak::upgrade(&(**this).inner) else {
return false;
};
if !inner.is_editor_open.load(Ordering::SeqCst) {
return false;
}
match &*this.plug_frame.read() {
Some(plug_frame) => {
let physical_size: PhysicalSize<u32> = new_size.to_physical(scale_factor);
let mut size = ViewRect {
left: 0,
top: 0,
right: physical_size.width as i32,
bottom: physical_size.height 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 to resize the window to {:?}, we currently don't handle \
this for VST3 plugins",
new_size
);
}
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 let Some(inner) = self.inner.upgrade()
&& let Some(editor_window) = inner.editor_window.borrow().as_ref()
{
if editor_window
.get()
.handle
.on_virtual_key_from_host(key_code, is_down, modifiers)
{
kResultOk
} else {
kResultFalse
}
} else {
kResultFalse
}
}
}
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
impl<P: Vst3Plugin> RunLoopEventHandler<P> {
pub fn new(inner: Weak<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 {
check_null_ptr!(parent);
let Some(inner) = self.inner.upgrade() else {
return kResultFalse;
};
let mut window = inner.editor_window.borrow_mut();
if window.is_none() {
let parent_handle = if unsafe { fid_matches(type_, kPlatformTypeX11EmbedWindowID) } {
#[allow(clippy::unnecessary_cast)]
let w = parent as usize as c_ulong;
ParentWindowHandle::XlibWindow(w)
} else if unsafe { fid_matches(type_, kPlatformTypeNSView) } {
let w = NonNull::new(parent).unwrap();
ParentWindowHandle::AppKitNsView(w)
} else if unsafe { fid_matches(type_, kPlatformTypeHWND) } {
let w = NonZeroIsize::new(parent as isize).unwrap();
ParentWindowHandle::Win32Hwnd(w)
} else {
crate::nice_debug_assert_failure!("Unknown window handle type: {:?}", unsafe {
CStr::from_ptr(type_)
});
return kInvalidArgument;
};
#[derive(Debug, Clone, Copy, thiserror::Error)]
enum ResizeError {
#[error("Failed to post window resize task")]
FailedToPostTask,
#[error("Attempted to close window when plugin was closed")]
PluginClosed,
}
struct Vst3HostCallbacks<P: Vst3Plugin> {
inner: Weak<WrapperInner<P>>,
}
impl<P: Vst3Plugin> HostCallbacks for Vst3HostCallbacks<P> {
fn request_resize(
&mut self,
new_size: Size,
scale_factor: f64,
) -> Result<(), Box<dyn Error>> {
if let Some(inner) = self.inner.upgrade() {
let view = inner.plug_view.read().clone().unwrap();
if let Err(task) = view.do_maybe_in_run_loop(Task::RequestResize {
size: new_size,
scale_factor,
}) {
if !inner.schedule_gui(task) {
return Err(ResizeError::FailedToPostTask.into());
}
}
Ok(())
} else {
Err(ResizeError::PluginClosed.into())
}
}
fn destroyed(&mut self) {
}
}
let callbacks: Box<dyn HostCallbacks> = Box::new(Vst3HostCallbacks {
inner: Arc::downgrade(&inner),
});
struct Vst3HostMainThreadCaller<P: Vst3Plugin> {
inner: Weak<WrapperInner<P>>,
}
impl<P: Vst3Plugin> HostMainThreadCaller for Vst3HostMainThreadCaller<P> {
fn call_main_thread(&mut self) {
if let Some(inner) = self.inner.upgrade() {
let view = inner.plug_view.read().clone().unwrap();
if let Err(task) = view.do_maybe_in_run_loop(Task::CallMainThread) {
if !inner.schedule_gui(task) {
crate::nice_error!("Failed to post Task::CallMainThread");
}
}
}
}
}
let main_thread_caller: Box<dyn HostMainThreadCaller> =
Box::new(Vst3HostMainThreadCaller {
inner: Arc::downgrade(&inner),
});
let fallback_scale_factor = self.fallback_scale_factor.load().map(|s| s as f64);
match self.editor.upgrade().unwrap().lock().spawn(
Some(parent_handle),
false,
fallback_scale_factor,
inner.clone().make_gui_context(),
Some(HostMethods {
callbacks,
main_thread_caller,
}),
) {
Ok(editor_window) => match editor_window.handle.show(&editor_window.window) {
Ok(()) => {
*window = Some(Fragile::new(editor_window));
inner.is_editor_open.store(true, Ordering::SeqCst);
kResultOk
}
Err(e) => {
crate::nice_error!("Failed to show editor: {}", e);
kResultFalse
}
},
Err(e) => {
crate::nice_error!("Failed to create editor: {}", e);
kResultFalse
}
}
} else {
crate::nice_debug_assert_failure!(
"Host tried to attach editor while the editor is already attached"
);
kResultFalse
}
}
unsafe fn removed(&self) -> tresult {
let Some(inner) = self.inner.upgrade() else {
return kResultFalse;
};
let mut editor_handle = inner.editor_window.borrow_mut();
if editor_handle.is_some() {
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);
let Some(editor) = self.editor.upgrade() else {
return kResultFalse;
};
let size = unsafe { &mut *size };
let editor_size: PhysicalSize<i32> = editor.lock().size().cast();
size.left = 0;
size.right = editor_size.width;
size.top = 0;
size.bottom = editor_size.height;
kResultOk
}
unsafe fn onSize(&self, new_size: *mut ViewRect) -> tresult {
check_null_ptr!(new_size);
let Some(inner) = self.inner.upgrade() else {
return kResultFalse;
};
let phys_width = unsafe { (*new_size).right - (*new_size).left };
let phys_height = unsafe { (*new_size).bottom - (*new_size).top };
if phys_width <= 0 || phys_height <= 0 {
return kResultFalse;
}
let size = PhysicalSize {
width: phys_width as u32,
height: phys_height as u32,
};
if let Some(editor_window) = inner.editor_window.borrow().as_ref() {
let editor_window = editor_window.get();
if let Err(e) = editor_window.handle.set_size(size, &editor_window.window) {
crate::nice_error!("Failed to resize window to {:?}: {}", size, e);
kResultFalse
} else {
kResultOk
}
} else {
kResultFalse
}
}
unsafe fn onFocus(&self, _state: TBool) -> tresult {
kNotImplemented
}
unsafe fn setFrame(&self, frame: *mut IPlugFrame) -> tresult {
let Some(inner) = self.inner.upgrade() else {
return kResultFalse;
};
#[cfg(not(all(target_family = "unix", not(target_os = "macos"))))]
let _ = inner;
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(Arc::downgrade(&inner), 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 {
let Some(editor) = self.editor.upgrade() else {
return kResultFalse;
};
if editor.lock().resize_hint().can_resize {
kResultOk
} else {
kResultFalse
}
}
unsafe fn checkSizeConstraint(&self, rect: *mut ViewRect) -> tresult {
check_null_ptr!(rect);
let size = unsafe {
PhysicalSize::new((*rect).right - (*rect).left, (*rect).bottom - (*rect).top)
};
if size.width <= 0 || size.height <= 0 {
return kResultFalse;
}
let Some(inner) = self.inner.upgrade() else {
return kResultFalse;
};
let size: PhysicalSize<u32> = size.cast();
if let Some(editor_window) = inner.editor_window.borrow().as_ref() {
let editor_window = editor_window.get();
if let Some(adjusted_size) = editor_window
.handle
.adjust_size(size, &editor_window.window)
{
if adjusted_size == size {
kResultOk
} else {
unsafe {
(*rect).right = (*rect).left + adjusted_size.width as i32;
(*rect).bottom = (*rect).top + adjusted_size.height as i32;
}
kResultOk
}
} else {
kResultFalse
}
} else {
kResultFalse
}
}
}
impl<P: Vst3Plugin> IPlugViewContentScaleSupportTrait for WrapperView<P> {
unsafe fn setContentScaleFactor(&self, scale_factor: ScaleFactor) -> tresult {
let Some(inner) = self.inner.upgrade() else {
return kResultFalse;
};
if !scale_factor.is_finite() || !scale_factor.is_sign_positive() {
return kResultFalse;
}
if let Some(editor_window) = inner.editor_window.borrow().as_ref() {
let editor_window = editor_window.get();
if let Err(e) = editor_window
.handle
.set_fallback_scale_factor(scale_factor as f64, &editor_window.window)
{
crate::nice_error!("Failed to set suggested scale factor: {}", e);
kResultFalse
} else {
self.fallback_scale_factor.store(Some(scale_factor));
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() {
if let Some(inner) = self.inner.upgrade() {
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() {
if let Some(inner) = self.inner.upgrade() {
posting_failed |= !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());
}
}
}