From da86d1c6b189218f7e3075829ae4c46d3662bf1b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 22 Apr 2022 19:22:23 +0200 Subject: [PATCH] Open a basic baseview window for standalone target --- Cargo.lock | 1 + Cargo.toml | 10 +++- src/plugin.rs | 2 + src/wrapper/standalone.rs | 3 ++ src/wrapper/standalone/context.rs | 46 +++++++++++++++- src/wrapper/standalone/wrapper.rs | 89 ++++++++++++++++++++++++++++--- 6 files changed, 141 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4712cfb0..7f3ad236 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2008,6 +2008,7 @@ dependencies = [ "assert_no_alloc", "atomic_float", "atomic_refcell", + "baseview 0.1.0 (git+https://github.com/robbert-vdh/baseview.git?branch=feature/resize)", "bitflags", "cfg-if 1.0.0", "clap-sys", diff --git a/Cargo.toml b/Cargo.toml index 7880c032..c26a3a56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ assert_process_allocs = ["dep:assert_no_alloc"] # Enables an export target for standalone binaries through the # `nih_export_standalone()` function. Disabled by default, as this requires # building additional dependencies for audio and MIDI handling. -standalone = [] +standalone = ["dep:baseview"] # Enables the `nih_export_vst3!()` macro. Enabled by default. This feature # exists mostly for GPL-compliance reasons, since even if you don't use the VST3 # wrapper you might otherwise still include a couple (unused) symbols from the @@ -75,11 +75,17 @@ raw-window-handle = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" simplelog = "0.12" -vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/note-off-event", optional = true } widestring = "1.0.0-beta.1" +# Used for the `assert_process_allocs` feature assert_no_alloc = { version = "1.1", optional = true } +# Used for the `standalone` feature +baseview = { git = "https://github.com/robbert-vdh/baseview.git", branch = "feature/resize", optional = true } + +# Used for the `vst3` feature +vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/note-off-event", optional = true } + [target.'cfg(all(target_family = "unix", not(target_os = "macos")))'.dependencies] libc = "0.2.124" diff --git a/src/plugin.rs b/src/plugin.rs index 2a0a8e20..34c2f72a 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -226,6 +226,8 @@ pub trait Editor: Send + Sync { // otherwise be basically impossible to have this still be GUI-framework agnostic. Any // callback that deos involve actual GUI operations will still be spooled to the IRunLoop // instance. + // TODO: This function should return an `Option` instead. Right now window opening failures are + // always fatal. This would need to be fixed in basevie first. fn spawn( &self, parent: ParentWindowHandle, diff --git a/src/wrapper/standalone.rs b/src/wrapper/standalone.rs index 2593afe7..5e266205 100644 --- a/src/wrapper/standalone.rs +++ b/src/wrapper/standalone.rs @@ -65,6 +65,9 @@ pub fn nih_export_standalone_with_args { + pub(super) wrapper: Arc>, +} + /// A [`ProcessContext`] implementation for the standalone wrapper. This is a separate object so it /// can hold on to lock guards for event queues. Otherwise reading these events would require /// constant unnecessary atomic operations to lock the uncontested RwLocks. @@ -14,6 +24,40 @@ pub(crate) struct WrapperProcessContext<'a, P: Plugin> { pub(super) transport: Transport, } +impl GuiContext for WrapperGuiContext

{ + fn plugin_api(&self) -> PluginApi { + PluginApi::Standalone + } + + fn request_resize(&self) -> bool { + nih_debug_assert_failure!("TODO: WrapperGuiContext::request_resize()"); + + true + } + + // All of these functions are supposed to be called from the main thread, so we'll put some + // trust in the caller and assume that this is indeed the case + unsafe fn raw_begin_set_parameter(&self, param: ParamPtr) { + nih_debug_assert_failure!("TODO: WrapperGuiContext::raw_begin_set_parameter()"); + } + + unsafe fn raw_set_parameter_normalized(&self, param: ParamPtr, normalized: f32) { + nih_debug_assert_failure!("TODO: WrapperGuiContext::raw_set_parameter_normalized()"); + } + + unsafe fn raw_end_set_parameter(&self, param: ParamPtr) { + nih_debug_assert_failure!("TODO: WrapperGuiContext::raw_end_set_parameter()"); + } + + fn get_state(&self) -> crate::wrapper::state::PluginState { + todo!("WrapperGuiContext::get_state()"); + } + + fn set_state(&self, state: crate::wrapper::state::PluginState) { + nih_debug_assert_failure!("TODO: WrapperGuiContext::set_state()"); + } +} + impl ProcessContext for WrapperProcessContext<'_, P> { fn plugin_api(&self) -> PluginApi { PluginApi::Standalone diff --git a/src/wrapper/standalone/wrapper.rs b/src/wrapper/standalone/wrapper.rs index 103e654a..9d7a6a13 100644 --- a/src/wrapper/standalone/wrapper.rs +++ b/src/wrapper/standalone/wrapper.rs @@ -1,9 +1,12 @@ +use baseview::{EventStatus, Window, WindowHandler, WindowOpenOptions}; use parking_lot::RwLock; +use raw_window_handle::HasRawWindowHandle; +use std::any::Any; use std::sync::Arc; -use super::context::WrapperProcessContext; +use super::context::{WrapperGuiContext, WrapperProcessContext}; use crate::context::Transport; -use crate::plugin::{BufferConfig, BusConfig, Editor, Plugin}; +use crate::plugin::{BufferConfig, BusConfig, Editor, ParentWindowHandle, Plugin}; /// Configuration for a standalone plugin that would normally be provided by the DAW. #[derive(Debug, Clone)] @@ -17,6 +20,12 @@ pub struct WrapperConfig { /// The audio backend's period size. pub period_size: u32, + /// The editor's DPI scaling factor. Currently baseview has no way to report this to us, so + /// we'll expose it as a command line option instead. + /// + /// This option is ignored on macOS. + pub dpi_scale: f32, + /// The current tempo. pub tempo: f32, /// The time signature's numerator. @@ -31,7 +40,7 @@ pub struct Wrapper { /// The plugin's editor, if it has one. This object does not do anything on its own, but we need /// to instantiate this in advance so we don't need to lock the entire [`Plugin`] object when /// creating an editor. - editor: Option>, + editor: Option>, config: WrapperConfig, @@ -49,12 +58,28 @@ pub enum WrapperError { InitializationFailed, } +/// We need a window handler for baseview, but since we only create a window to report the plugin in +/// this won't do much. +struct WrapperWindowHandler { + /// The editor handle for the plugin's open editor. The editor should clean itself up when it + /// gets dropped. + _editor_handle: Box, +} + +impl WindowHandler for WrapperWindowHandler { + fn on_frame(&mut self, _window: &mut Window) {} + + fn on_event(&mut self, _window: &mut Window, _event: baseview::Event) -> EventStatus { + EventStatus::Ignored + } +} + impl Wrapper

{ /// Instantiate a new instance of the standalone wrapper. Returns an error if the plugin does /// not accept the IO configuration from the wrapper config. pub fn new(config: WrapperConfig) -> Result, WrapperError> { let plugin = P::default(); - let editor = plugin.editor(); + let editor = plugin.editor().map(Arc::from); let wrapper = Arc::new(Wrapper { plugin: RwLock::new(plugin), @@ -97,13 +122,63 @@ impl Wrapper

{ /// Will return an error if the plugin threw an error during audio processing or if the editor /// could not be opened. pub fn run(self: Arc) -> Result<(), WrapperError> { - // TODO: Open the editor and block until it is closed - // TODO: Do IO things - // TODO: Block until SIGINT is received if the plugin does not have an editor + // TODO: Do IO things, kinda important + + match self.editor.clone() { + Some(editor) => { + let context = self.clone().make_gui_context(); + let (width, height) = editor.size(); + + // DPI scaling should not be used on macOS since the OS handles it there + #[cfg(target_os = "macos")] + let scaling_policy = baseview::WindowScalePolicy::SystemScaleFactor; + #[cfg(not(target_os = "macos"))] + let scaling_policy = { + editor.set_scale_factor(self.config.dpi_scale); + baseview::WindowScalePolicy::ScaleFactor(self.config.dpi_scale as f64) + }; + + Window::open_blocking( + WindowOpenOptions { + title: String::from(P::NAME), + size: baseview::Size { + width: width as f64, + height: height as f64, + }, + scale: scaling_policy, + gl_config: None, + }, + move |window| { + // TODO: This spawn function should be able to fail and return an error, but + // baseview does not support this yet. Once this is added, we should + // immediately close the parent window when this happens so the loop + // can exit. + let editor_handle = editor.spawn( + ParentWindowHandle { + handle: window.raw_window_handle(), + }, + context, + ); + + WrapperWindowHandler { + _editor_handle: editor_handle, + } + }, + ) + } + None => { + // TODO: Block until SIGINT is received if the plugin does not have an editor + todo!("Support standalone plugins without editors"); + } + } Ok(()) } + fn make_gui_context(self: Arc) -> Arc> { + Arc::new(WrapperGuiContext { wrapper: self }) + } + fn make_process_context(&self, transport: Transport) -> WrapperProcessContext<'_, P> { WrapperProcessContext { wrapper: self,