2022-03-05 23:37:35 +11:00
|
|
|
use atomic_float::AtomicF32;
|
2022-02-07 04:46:16 +11:00
|
|
|
use parking_lot::RwLock;
|
|
|
|
use raw_window_handle::RawWindowHandle;
|
|
|
|
use std::any::Any;
|
|
|
|
use std::ffi::{c_void, CStr};
|
|
|
|
use std::mem;
|
2022-03-05 23:37:35 +11:00
|
|
|
use std::sync::atomic::Ordering;
|
2022-02-07 04:46:16 +11:00
|
|
|
use std::sync::Arc;
|
|
|
|
use vst3_sys::base::{kInvalidArgument, kResultFalse, kResultOk, tresult, TBool};
|
2022-03-28 04:23:34 +11:00
|
|
|
use vst3_sys::gui::{IPlugFrame, IPlugView, IPlugViewContentScaleSupport, ViewRect};
|
2022-04-21 01:18:41 +10:00
|
|
|
use vst3_sys::utils::SharedVstPtr;
|
2022-02-07 04:46:16 +11:00
|
|
|
use vst3_sys::VST3;
|
|
|
|
|
|
|
|
use super::inner::WrapperInner;
|
2022-02-17 06:43:53 +11:00
|
|
|
use super::util::{ObjectPtr, VstPtr};
|
2022-03-04 04:29:37 +11:00
|
|
|
use crate::plugin::{Editor, ParentWindowHandle, Vst3Plugin};
|
2022-02-07 04:46:16 +11:00
|
|
|
|
|
|
|
// Alias needed for the VST3 attribute macro
|
|
|
|
use vst3_sys as vst3_com;
|
|
|
|
|
2022-04-21 00:53:06 +10:00
|
|
|
// Thanks for putting this behind a platform-specific ifdef...
|
|
|
|
// NOTE: This should also be used on the BSDs, but vst3-sys exposes these interfaces only for Linux
|
|
|
|
#[cfg(all(target_os = "linux"))]
|
2022-04-21 01:18:41 +10:00
|
|
|
use vst3_sys::gui::linux::{FileDescriptor, IEventHandler, IRunLoop};
|
2022-04-21 00:53:06 +10:00
|
|
|
|
2022-02-07 04:46:16 +11:00
|
|
|
// Window handle type constants missing from vst3-sys
|
|
|
|
#[allow(unused)]
|
|
|
|
const VST3_PLATFORM_HWND: &str = "HWND";
|
|
|
|
#[allow(unused)]
|
|
|
|
const VST3_PLATFORM_HIVIEW: &str = "HIView";
|
|
|
|
#[allow(unused)]
|
|
|
|
const VST3_PLATFORM_NSVIEW: &str = "NSView";
|
|
|
|
#[allow(unused)]
|
|
|
|
const VST3_PLATFORM_UIVIEW: &str = "UIView";
|
|
|
|
#[allow(unused)]
|
|
|
|
const VST3_PLATFORM_X11_WINDOW: &str = "X11EmbedWindowID";
|
|
|
|
|
2022-03-04 09:05:01 +11:00
|
|
|
/// The plugin's [`IPlugView`] instance created in [`IEditController::create_view()`] if `P` has an
|
2022-02-07 04:46:16 +11:00
|
|
|
/// editor. This is managed separately so the lifetime bounds match up.
|
2022-03-05 23:37:35 +11:00
|
|
|
#[VST3(implements(IPlugView, IPlugViewContentScaleSupport))]
|
2022-03-01 05:21:14 +11:00
|
|
|
pub(crate) struct WrapperView<P: Vst3Plugin> {
|
2022-02-07 04:46:16 +11:00
|
|
|
inner: Arc<WrapperInner<P>>,
|
|
|
|
editor: Arc<dyn Editor>,
|
|
|
|
editor_handle: RwLock<Option<Box<dyn Any>>>,
|
2022-02-17 06:41:00 +11:00
|
|
|
|
2022-03-05 23:37:35 +11:00
|
|
|
/// The `IPlugFrame` instance passed by the host during [IPlugView::set_frame()].
|
2022-04-21 00:53:06 +10:00
|
|
|
plug_frame: RwLock<Option<VstPtr<dyn IPlugFrame>>>,
|
2022-04-21 01:18:41 +10:00
|
|
|
/// Allows handling events events on the host's GUI thread when using Linux. Needed because
|
|
|
|
/// otherwise REAPER doesn't like us very much. The event handler could be implemented directly
|
|
|
|
/// on this object but vst3-sys does not let us conditionally implement interfaces.
|
2022-04-21 00:53:06 +10:00
|
|
|
#[cfg(all(target_os = "linux"))]
|
2022-04-21 01:18:41 +10:00
|
|
|
run_loop_event_handler: RwLock<Option<Box<RunLoopEventHandler>>>,
|
2022-04-21 00:53:06 +10:00
|
|
|
|
2022-03-05 23:37:35 +11:00
|
|
|
/// The DPI scaling factor as passed to the [IPlugViewContentScaleSupport::set_scale_factor()]
|
|
|
|
/// function. Defaults to 1.0, and will be kept there on macOS. When reporting and handling size
|
|
|
|
/// the sizes communicated to and from the DAW should be scaled by this factor since NIH-plug's
|
|
|
|
/// APIs only deal in logical pixels.
|
|
|
|
scaling_factor: AtomicF32,
|
2022-02-07 04:46:16 +11:00
|
|
|
}
|
|
|
|
|
2022-04-21 01:18:41 +10:00
|
|
|
// This doesn't need to be a separate struct, but vst3-sys does not let us implement interfaces
|
|
|
|
// conditionally and the interface is only exposed when compiling on Linux
|
|
|
|
#[cfg(all(target_os = "linux"))]
|
|
|
|
#[VST3(implements(IEventHandler))]
|
|
|
|
struct RunLoopEventHandler {
|
|
|
|
run_loop: VstPtr<dyn IRunLoop>,
|
|
|
|
}
|
|
|
|
|
2022-03-01 05:21:14 +11:00
|
|
|
impl<P: Vst3Plugin> WrapperView<P> {
|
2022-02-07 04:46:16 +11:00
|
|
|
pub fn new(inner: Arc<WrapperInner<P>>, editor: Arc<dyn Editor>) -> Box<Self> {
|
2022-03-05 23:37:35 +11:00
|
|
|
Self::allocate(
|
|
|
|
inner,
|
|
|
|
editor,
|
|
|
|
RwLock::new(None),
|
|
|
|
RwLock::new(None),
|
2022-04-21 00:53:06 +10:00
|
|
|
#[cfg(all(target_os = "linux"))]
|
|
|
|
RwLock::new(None),
|
2022-03-05 23:37:35 +11:00
|
|
|
AtomicF32::new(1.0),
|
|
|
|
)
|
2022-02-07 04:46:16 +11:00
|
|
|
}
|
2022-03-28 04:23:34 +11:00
|
|
|
|
|
|
|
/// Ask the host to resize the view to the size specified by [Editor::size()]. Will return false
|
|
|
|
/// if the host doesn't like you.
|
|
|
|
pub fn request_resize(&self) -> bool {
|
|
|
|
// Don't do anything if the editor is not open, because that would be strange
|
|
|
|
if self
|
|
|
|
.editor_handle
|
|
|
|
.try_read()
|
|
|
|
.map(|e| e.is_none())
|
|
|
|
.unwrap_or(true)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
match &*self.plug_frame.read() {
|
|
|
|
Some(plug_frame) => {
|
|
|
|
let (unscaled_width, unscaled_height) = self.editor.size();
|
|
|
|
let scaling_factor = self.scaling_factor.load(Ordering::Relaxed);
|
|
|
|
let mut size = ViewRect {
|
|
|
|
right: (unscaled_width as f32 * scaling_factor).round() as i32,
|
|
|
|
bottom: (unscaled_height as f32 * scaling_factor).round() as i32,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
// The argument types are a bit wonky here because you can't construct a
|
|
|
|
// `SharedVstPtr`. This _should_ work however.
|
|
|
|
// FIXME: Run this in the `IRonLoop` on Linux. Otherwise REAPER will be very cross
|
|
|
|
// with us.
|
|
|
|
let plug_view: SharedVstPtr<dyn IPlugView> = unsafe { mem::transmute(self) };
|
|
|
|
let result = unsafe { plug_frame.resize_view(plug_view, &mut size) };
|
|
|
|
|
|
|
|
result == kResultOk
|
|
|
|
}
|
|
|
|
None => false,
|
|
|
|
}
|
|
|
|
}
|
2022-02-07 04:46:16 +11:00
|
|
|
}
|
|
|
|
|
2022-03-01 05:21:14 +11:00
|
|
|
impl<P: Vst3Plugin> IPlugView for WrapperView<P> {
|
2022-02-07 04:46:16 +11:00
|
|
|
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
|
|
|
|
unsafe fn is_platform_type_supported(&self, type_: vst3_sys::base::FIDString) -> tresult {
|
|
|
|
let type_ = CStr::from_ptr(type_);
|
|
|
|
match type_.to_str() {
|
|
|
|
Ok(type_) if type_ == VST3_PLATFORM_X11_WINDOW => kResultOk,
|
|
|
|
_ => {
|
|
|
|
nih_debug_assert_failure!("Invalid window handle type: {:?}", type_);
|
|
|
|
kResultFalse
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(all(target_os = "macos"))]
|
|
|
|
unsafe fn is_platform_type_supported(&self, type_: vst3_sys::base::FIDString) -> tresult {
|
|
|
|
let type_ = CStr::from_ptr(type_);
|
|
|
|
match type_.to_str() {
|
|
|
|
Ok(type_) if type_ == VST3_PLATFORM_NSVIEW => kResultOk,
|
|
|
|
_ => {
|
|
|
|
nih_debug_assert_failure!("Invalid window handle type: {:?}", type_);
|
|
|
|
kResultFalse
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(all(target_os = "windows"))]
|
|
|
|
unsafe fn is_platform_type_supported(&self, type_: vst3_sys::base::FIDString) -> tresult {
|
|
|
|
let type_ = CStr::from_ptr(type_);
|
|
|
|
match type_.to_str() {
|
|
|
|
Ok(type_) if type_ == VST3_PLATFORM_HWND => kResultOk,
|
|
|
|
_ => {
|
|
|
|
nih_debug_assert_failure!("Invalid window handle type: {:?}", type_);
|
|
|
|
kResultFalse
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn attached(&self, parent: *mut c_void, type_: vst3_sys::base::FIDString) -> tresult {
|
|
|
|
let mut editor_handle = self.editor_handle.write();
|
|
|
|
if editor_handle.is_none() {
|
|
|
|
let type_ = CStr::from_ptr(type_);
|
|
|
|
let handle = match type_.to_str() {
|
|
|
|
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
|
|
|
|
Ok(type_) if type_ == VST3_PLATFORM_X11_WINDOW => {
|
2022-02-08 07:58:59 +11:00
|
|
|
let mut handle = raw_window_handle::XcbHandle::empty();
|
2022-02-07 04:46:16 +11:00
|
|
|
handle.window = parent as usize as u32;
|
|
|
|
RawWindowHandle::Xcb(handle)
|
|
|
|
}
|
|
|
|
#[cfg(all(target_os = "macos"))]
|
|
|
|
Ok(type_) if type_ == VST3_PLATFORM_NSVIEW => {
|
2022-02-08 07:58:59 +11:00
|
|
|
let mut handle = raw_window_handle::AppKitHandle::empty();
|
2022-02-07 04:46:16 +11:00
|
|
|
handle.ns_view = parent;
|
2022-02-08 07:58:59 +11:00
|
|
|
RawWindowHandle::AppKit(handle)
|
2022-02-07 04:46:16 +11:00
|
|
|
}
|
|
|
|
#[cfg(all(target_os = "windows"))]
|
|
|
|
Ok(type_) if type_ == VST3_PLATFORM_HWND => {
|
2022-02-08 07:58:59 +11:00
|
|
|
let mut handle = raw_window_handle::Win32Handle::empty();
|
2022-02-07 04:46:16 +11:00
|
|
|
handle.hwnd = parent;
|
2022-02-08 07:58:59 +11:00
|
|
|
RawWindowHandle::Win32(handle)
|
2022-02-07 04:46:16 +11:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
nih_debug_assert_failure!("Unknown window handle type: {:?}", type_);
|
|
|
|
return kInvalidArgument;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-03-04 02:33:41 +11:00
|
|
|
*editor_handle = Some(self.editor.spawn(
|
|
|
|
ParentWindowHandle { handle },
|
|
|
|
self.inner.clone().make_gui_context(),
|
|
|
|
));
|
2022-02-17 06:43:53 +11:00
|
|
|
*self.inner.plug_view.write() = Some(ObjectPtr::from(self));
|
|
|
|
|
2022-02-07 04:46:16 +11:00
|
|
|
kResultOk
|
|
|
|
} else {
|
2022-03-04 04:29:37 +11:00
|
|
|
nih_debug_assert_failure!(
|
|
|
|
"Host tried to attach editor while the editor is already attached"
|
|
|
|
);
|
|
|
|
|
2022-02-07 04:46:16 +11:00
|
|
|
kResultFalse
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn removed(&self) -> tresult {
|
|
|
|
let mut editor_handle = self.editor_handle.write();
|
|
|
|
if editor_handle.is_some() {
|
2022-02-17 06:43:53 +11:00
|
|
|
*self.inner.plug_view.write() = None;
|
2022-02-07 04:46:16 +11:00
|
|
|
*editor_handle = None;
|
2022-02-17 06:43:53 +11:00
|
|
|
|
2022-02-07 04:46:16 +11:00
|
|
|
kResultOk
|
|
|
|
} else {
|
2022-03-04 04:29:37 +11:00
|
|
|
nih_debug_assert_failure!("Host tried to remove the editor without an active editor");
|
|
|
|
|
2022-02-07 04:46:16 +11:00
|
|
|
kResultFalse
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn on_wheel(&self, _distance: f32) -> tresult {
|
|
|
|
// We'll let the plugin use the OS' input mechamisms because not all DAWs (or very few
|
|
|
|
// actually) implement these functions
|
|
|
|
kResultOk
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn on_key_down(
|
|
|
|
&self,
|
|
|
|
_key: vst3_sys::base::char16,
|
|
|
|
_key_code: i16,
|
|
|
|
_modifiers: i16,
|
|
|
|
) -> tresult {
|
|
|
|
kResultOk
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn on_key_up(
|
|
|
|
&self,
|
|
|
|
_key: vst3_sys::base::char16,
|
|
|
|
_key_code: i16,
|
|
|
|
_modifiers: i16,
|
|
|
|
) -> tresult {
|
|
|
|
kResultOk
|
|
|
|
}
|
|
|
|
|
2022-03-28 04:23:34 +11:00
|
|
|
unsafe fn get_size(&self, size: *mut ViewRect) -> tresult {
|
2022-02-07 04:46:16 +11:00
|
|
|
check_null_ptr!(size);
|
|
|
|
|
|
|
|
*size = mem::zeroed();
|
|
|
|
|
2022-03-28 04:23:34 +11:00
|
|
|
// TODO: This is technically incorrect during resizing, this should still report the old
|
|
|
|
// size until `.on_size()` has been called. We should probably only bother fixing this
|
|
|
|
// if it turns out to be an issue.
|
2022-03-05 23:37:35 +11:00
|
|
|
let (unscaled_width, unscaled_height) = self.editor.size();
|
|
|
|
let scaling_factor = self.scaling_factor.load(Ordering::Relaxed);
|
2022-02-07 04:46:16 +11:00
|
|
|
let size = &mut *size;
|
|
|
|
size.left = 0;
|
2022-03-05 23:37:35 +11:00
|
|
|
size.right = (unscaled_width as f32 * scaling_factor).round() as i32;
|
2022-02-07 04:46:16 +11:00
|
|
|
size.top = 0;
|
2022-03-05 23:37:35 +11:00
|
|
|
size.bottom = (unscaled_height as f32 * scaling_factor).round() as i32;
|
2022-02-07 04:46:16 +11:00
|
|
|
|
|
|
|
kResultOk
|
|
|
|
}
|
|
|
|
|
2022-03-28 04:23:34 +11:00
|
|
|
unsafe fn on_size(&self, new_size: *mut ViewRect) -> tresult {
|
2022-03-05 23:37:35 +11:00
|
|
|
check_null_ptr!(new_size);
|
|
|
|
|
2022-03-28 04:23:34 +11:00
|
|
|
// TODO: Implement Host->Plugin resizing
|
2022-03-05 23:37:35 +11:00
|
|
|
let (unscaled_width, unscaled_height) = self.editor.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 = (*new_size).right - (*new_size).left;
|
|
|
|
let height = (*new_size).bottom - (*new_size).top;
|
|
|
|
if width == editor_width && height == editor_height {
|
|
|
|
kResultOk
|
|
|
|
} else {
|
|
|
|
kResultFalse
|
|
|
|
}
|
2022-02-07 04:46:16 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn on_focus(&self, _state: TBool) -> tresult {
|
|
|
|
kResultOk
|
|
|
|
}
|
|
|
|
|
2022-02-17 06:41:00 +11:00
|
|
|
unsafe fn set_frame(&self, frame: *mut c_void) -> tresult {
|
|
|
|
// The correct argument type is missing from the bindings
|
|
|
|
let frame: SharedVstPtr<dyn IPlugFrame> = mem::transmute(frame);
|
|
|
|
match frame.upgrade() {
|
2022-04-21 00:53:06 +10:00
|
|
|
Some(frame) => {
|
|
|
|
// On Linux the host will expose another interface that lets us run code on the
|
|
|
|
// host's GUI thread. REAPER will segfault when we don't do this for resizes.
|
|
|
|
#[cfg(all(target_os = "linux"))]
|
|
|
|
{
|
2022-04-21 01:18:41 +10:00
|
|
|
*self.run_loop_event_handler.write() = frame
|
|
|
|
.cast()
|
|
|
|
.map(|run_loop| RunLoopEventHandler::allocate(VstPtr::from(run_loop)));
|
2022-04-21 00:53:06 +10:00
|
|
|
}
|
|
|
|
*self.plug_frame.write() = Some(VstPtr::from(frame));
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
#[cfg(all(target_os = "linux"))]
|
|
|
|
{
|
2022-04-21 01:18:41 +10:00
|
|
|
*self.run_loop_event_handler.write() = None;
|
2022-04-21 00:53:06 +10:00
|
|
|
}
|
|
|
|
*self.plug_frame.write() = None;
|
|
|
|
}
|
2022-02-17 06:41:00 +11:00
|
|
|
}
|
|
|
|
|
2022-02-07 04:46:16 +11:00
|
|
|
kResultOk
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn can_resize(&self) -> tresult {
|
2022-03-28 04:23:34 +11:00
|
|
|
// TODO: Implement Host->Plugin resizing
|
2022-02-07 04:46:16 +11:00
|
|
|
kResultFalse
|
|
|
|
}
|
|
|
|
|
2022-03-28 04:23:34 +11:00
|
|
|
unsafe fn check_size_constraint(&self, rect: *mut ViewRect) -> tresult {
|
2022-02-07 04:46:16 +11:00
|
|
|
check_null_ptr!(rect);
|
|
|
|
|
2022-03-28 04:23:34 +11:00
|
|
|
// TODO: Implement Host->Plugin resizing
|
2022-02-07 04:46:16 +11:00
|
|
|
if (*rect).right - (*rect).left > 0 && (*rect).bottom - (*rect).top > 0 {
|
|
|
|
kResultOk
|
|
|
|
} else {
|
|
|
|
kResultFalse
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-05 23:37:35 +11:00
|
|
|
|
|
|
|
impl<P: Vst3Plugin> IPlugViewContentScaleSupport for WrapperView<P> {
|
|
|
|
unsafe fn set_scale_factor(&self, factor: f32) -> tresult {
|
|
|
|
// On macOS scaling is done by the OS, and all window sizes are in logical pixels
|
|
|
|
if cfg!(target_os = "macos") {
|
|
|
|
nih_debug_assert_failure!("Ignoring host request to set explicit DPI scaling factor");
|
|
|
|
return kResultFalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.editor.set_scale_factor(factor) {
|
|
|
|
self.scaling_factor.store(factor, Ordering::Relaxed);
|
|
|
|
kResultOk
|
|
|
|
} else {
|
|
|
|
kResultFalse
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-04-21 01:18:41 +10:00
|
|
|
|
|
|
|
#[cfg(all(target_os = "linux"))]
|
|
|
|
impl IEventHandler for RunLoopEventHandler {
|
|
|
|
unsafe fn on_fd_is_set(&self, fd: FileDescriptor) {
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
}
|