From 4224f6c7f5925e73504d9d95278e159920269678 Mon Sep 17 00:00:00 2001 From: glowcoil Date: Sat, 14 Feb 2026 13:52:58 -0600 Subject: [PATCH 01/16] update gain example to latest portlight version --- examples/gain/Cargo.toml | 2 +- examples/gain/src/lib.rs | 218 ++++++++++++++++++++------------------- 2 files changed, 113 insertions(+), 107 deletions(-) diff --git a/examples/gain/Cargo.toml b/examples/gain/Cargo.toml index 73ddf19a..ee9bd5a3 100644 --- a/examples/gain/Cargo.toml +++ b/examples/gain/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] coupler = { workspace = true, features = ["derive"] } -portlight = { git = "https://github.com/coupler-rs/portlight", rev = "fff171e9f3f70a9604102e1e1877ad67d0c2badd" } +portlight = { git = "https://github.com/coupler-rs/portlight", rev = "e517f1e7e1700b70a74523cf905d5c80f81b4e81" } flicker = { git = "https://github.com/coupler-rs/flicker", rev = "80aca05cb6c7406f8c2a4ba66d85849dc344afaa" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/examples/gain/src/lib.rs b/examples/gain/src/lib.rs index f5b2b83b..c338c1b9 100644 --- a/examples/gain/src/lib.rs +++ b/examples/gain/src/lib.rs @@ -1,7 +1,5 @@ -use std::cell::RefCell; use std::fmt::{self, Formatter}; use std::io::{self, Read, Write}; -use std::rc::Rc; use serde::{Deserialize, Serialize}; @@ -14,8 +12,8 @@ use coupler::{buffers::*, bus::*, engine::*, events::*, host::*, params::*, plug use flicker::Renderer; use portlight::{ - App, AppMode, AppOptions, Bitmap, Cursor, MouseButton, Point, RawWindow, Response, Window, - WindowContext, WindowOptions, + Bitmap, Context, Cursor, EventLoop, EventLoopMode, EventLoopOptions, Key, MouseButton, Point, + RawWindow, Response, Task, TaskHandle, Window, WindowEvent, WindowOptions, }; #[derive(Params, Serialize, Deserialize, Clone)] @@ -172,7 +170,8 @@ struct Gesture { struct ViewState { host: ViewHost, - params: Rc>, + params: GainParams, + window: Option, renderer: Renderer, framebuffer: Vec, mouse_pos: Point, @@ -180,10 +179,11 @@ struct ViewState { } impl ViewState { - fn new(host: ViewHost, params: Rc>) -> ViewState { + fn new(host: ViewHost, params: &GainParams) -> ViewState { ViewState { host, - params, + params: params.clone(), + window: None, renderer: Renderer::new(), framebuffer: Vec::new(), mouse_pos: Point { x: -1.0, y: -1.0 }, @@ -199,92 +199,96 @@ impl ViewState { window.set_cursor(Cursor::Arrow); } } +} - fn handle_event(&mut self, cx: &WindowContext, event: portlight::Event) -> Response { +impl Task for ViewState { + fn event(&mut self, _cx: &Context, _key: Key, event: portlight::Event) -> Response { use flicker::{Affine, Color, Path, Point}; use portlight::Event; - match event { - Event::Frame => { - let scale = cx.window().scale(); - let size = cx.window().size(); - let width = (size.width * scale) as usize; - let height = (size.height * scale) as usize; - self.framebuffer.resize(width * height, 0xFF000000); - - let mut target = self.renderer.attach(&mut self.framebuffer, width, height); - - target.clear(Color::rgba(21, 26, 31, 255)); - - let transform = Affine::scale(scale as f32); - - let value = self.params.borrow().gain; - - let center = Point::new(128.0, 128.0); - let radius = 32.0; - let angle1 = 0.75 * std::f32::consts::PI; - let angle2 = angle1 + value * 1.5 * std::f32::consts::PI; - let mut path = Path::new(); - path.move_to(center + radius * Point::new(angle1.cos(), angle1.sin())); - path.arc(radius, angle1, angle2); - path.line_to(center + (radius - 4.0) * Point::new(angle2.cos(), angle2.sin())); - path.arc(radius - 4.0, angle2, angle1); - path.close(); - target.fill_path(&path, transform, Color::rgba(240, 240, 245, 255)); - - let center = Point::new(128.0, 128.0); - let radius = 32.0; - let angle = 0.75 * std::f32::consts::PI; - let span = 1.5 * std::f32::consts::PI; - let mut path = Path::new(); - path.move_to(center + radius * Point::new(angle.cos(), angle.sin())); - path.arc(radius, angle, angle + span); - path.line_to(center + (radius - 4.0) * Point::new(-angle.cos(), angle.sin())); - path.arc(radius - 4.0, angle + span, angle); - path.close(); - target.stroke_path(&path, 1.0, transform, Color::rgba(240, 240, 245, 255)); - - cx.window().present(Bitmap::new(&self.framebuffer, width, height)); - } - Event::MouseMove(pos) => { - self.mouse_pos = pos; - if let Some(gesture) = &self.gesture { - let delta = -0.005 * (pos.y - gesture.start_mouse_pos.y) as f32; - let new_value = (gesture.start_value + delta).clamp(0.0, 1.0); - self.host.set_param(0, new_value as f64); - self.params.borrow_mut().gain = new_value; - } else { - self.update_cursor(cx.window()); + if let (Some(window), Event::Window(event)) = (&self.window, event) { + match event { + WindowEvent::Frame => { + let scale = window.scale(); + let size = window.size(); + let width = (size.width * scale) as usize; + let height = (size.height * scale) as usize; + self.framebuffer.resize(width * height, 0xFF000000); + + let mut target = self.renderer.attach(&mut self.framebuffer, width, height); + + target.clear(Color::rgba(21, 26, 31, 255)); + + let transform = Affine::scale(scale as f32); + + let value = self.params.gain; + + let center = Point::new(128.0, 128.0); + let radius = 32.0; + let angle1 = 0.75 * std::f32::consts::PI; + let angle2 = angle1 + value * 1.5 * std::f32::consts::PI; + let mut path = Path::new(); + path.move_to(center + radius * Point::new(angle1.cos(), angle1.sin())); + path.arc(radius, angle1, angle2); + path.line_to(center + (radius - 4.0) * Point::new(angle2.cos(), angle2.sin())); + path.arc(radius - 4.0, angle2, angle1); + path.close(); + target.fill_path(&path, transform, Color::rgba(240, 240, 245, 255)); + + let center = Point::new(128.0, 128.0); + let radius = 32.0; + let angle = 0.75 * std::f32::consts::PI; + let span = 1.5 * std::f32::consts::PI; + let mut path = Path::new(); + path.move_to(center + radius * Point::new(angle.cos(), angle.sin())); + path.arc(radius, angle, angle + span); + path.line_to(center + (radius - 4.0) * Point::new(-angle.cos(), angle.sin())); + path.arc(radius - 4.0, angle + span, angle); + path.close(); + target.stroke_path(&path, 1.0, transform, Color::rgba(240, 240, 245, 255)); + + window.present(Bitmap::new(&self.framebuffer, width, height)); } - } - Event::MouseDown(button) => { - if button == MouseButton::Left { - let pos = self.mouse_pos; - if pos.x >= 96.0 && pos.x < 160.0 && pos.y >= 96.0 && pos.y < 160.0 { - cx.window().set_cursor(Cursor::SizeNs); - self.host.begin_gesture(0); - let value = self.params.borrow().gain; - self.host.set_param(0, value as f64); - self.params.borrow_mut().gain = value; - self.gesture = Some(Gesture { - start_mouse_pos: pos, - start_value: value, - }); - return Response::Capture; + WindowEvent::MouseMove(pos) => { + self.mouse_pos = pos; + if let Some(gesture) = &self.gesture { + let delta = -0.005 * (pos.y - gesture.start_mouse_pos.y) as f32; + let new_value = (gesture.start_value + delta).clamp(0.0, 1.0); + self.host.set_param(0, new_value as f64); + self.params.gain = new_value; + } else { + self.update_cursor(window); } } - } - Event::MouseUp(button) => { - if button == MouseButton::Left { - if self.gesture.is_some() { - self.host.end_gesture(0); - self.gesture = None; - self.update_cursor(cx.window()); - return Response::Capture; + WindowEvent::MouseDown(button) => { + if button == MouseButton::Left { + let pos = self.mouse_pos; + if pos.x >= 96.0 && pos.x < 160.0 && pos.y >= 96.0 && pos.y < 160.0 { + window.set_cursor(Cursor::SizeNs); + self.host.begin_gesture(0); + let value = self.params.gain; + self.host.set_param(0, value as f64); + self.params.gain = value; + self.gesture = Some(Gesture { + start_mouse_pos: pos, + start_value: value, + }); + return Response::Capture; + } } } + WindowEvent::MouseUp(button) => { + if button == MouseButton::Left { + if self.gesture.is_some() { + self.host.end_gesture(0); + self.gesture = None; + self.update_cursor(window); + return Response::Capture; + } + } + } + _ => {} } - _ => {} } Response::Ignore @@ -293,9 +297,8 @@ impl ViewState { pub struct GainView { #[allow(unused)] - app: App, - window: Window, - params: Rc>, + event_loop: EventLoop, + task: TaskHandle, } impl GainView { @@ -304,35 +307,36 @@ impl GainView { parent: &ParentWindow, params: &GainParams, ) -> portlight::Result { - let app = AppOptions::new().mode(AppMode::Guest).build()?; + let event_loop = EventLoopOptions::new().mode(EventLoopMode::Guest).build()?; + + let task = event_loop.spawn(ViewState::new(host, params)); + + task.with(|state, cx| { + let mut options = WindowOptions::new(); + options.size(portlight::Size::new(256.0, 256.0)); - let mut options = WindowOptions::new(); - options.size(portlight::Size::new(256.0, 256.0)); + let raw_parent = match parent.as_raw() { + RawParent::Win32(window) => RawWindow::Win32(window), + RawParent::Cocoa(view) => RawWindow::AppKit(view), + RawParent::X11(window) => RawWindow::X11(window), + }; + unsafe { options.raw_parent(raw_parent) }; - let raw_parent = match parent.as_raw() { - RawParent::Win32(window) => RawWindow::Win32(window), - RawParent::Cocoa(view) => RawWindow::Cocoa(view), - RawParent::X11(window) => RawWindow::X11(window), - }; - unsafe { options.raw_parent(raw_parent) }; + let window = options.open(cx, Key(0))?; + window.show(); - let params = Rc::new(RefCell::new(params.clone())); - let mut state = ViewState::new(host, Rc::clone(¶ms)); - let window = options.open(app.handle(), move |cx, event| state.handle_event(cx, event))?; + state.window = Some(window); - window.show(); + portlight::Result::Ok(()) + })?; - Ok(GainView { - app, - window, - params, - }) + Ok(GainView { event_loop, task }) } } impl View for GainView { fn size(&self) -> Size { - let size = self.window.size(); + let size = self.task.with(|state, _| state.window.as_ref().unwrap().size()); Size { width: size.width, @@ -341,6 +345,8 @@ impl View for GainView { } fn param_changed(&mut self, id: ParamId, value: ParamValue) { - self.params.borrow_mut().set_param(id, value); + self.task.with(|state, _| { + state.params.set_param(id, value); + }); } } From c9e1272a113d1b17c3efb7de76477a1814d05994 Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Sun, 15 Feb 2026 21:29:51 -0800 Subject: [PATCH 02/16] Add fd callback registration for vst3 for linux --- examples/gain/src/lib.rs | 12 +++++- src/format/vst3/view.rs | 91 +++++++++++++++++++++++++++++++++++++++- src/view.rs | 11 +++++ 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/examples/gain/src/lib.rs b/examples/gain/src/lib.rs index c338c1b9..8772d9d0 100644 --- a/examples/gain/src/lib.rs +++ b/examples/gain/src/lib.rs @@ -1,6 +1,7 @@ use std::fmt::{self, Formatter}; use std::io::{self, Read, Write}; - +use std::os::fd::AsRawFd; +use std::os::raw::c_int; use serde::{Deserialize, Serialize}; use coupler::format::clap::*; @@ -349,4 +350,13 @@ impl View for GainView { state.params.set_param(id, value); }); } + + fn file_descriptor(&self) -> Option { + Some(self.event_loop.as_raw_fd()) + } + + fn poll(&mut self) { + // todo: is unwrapping really the right choice? + self.event_loop.poll().unwrap(); + } } diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index b4da8f09..a8e49589 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -3,7 +3,7 @@ use std::ffi::{c_void, CStr}; use std::sync::Arc; use vst3::Steinberg::Vst::{IComponentHandler, IComponentHandlerTrait}; -use vst3::{Class, ComPtr, Steinberg::*}; +use vst3::{Class, ComPtr, ComRef, ComWrapper, Steinberg::*}; use super::component::MainThreadState; use crate::params::{ParamId, ParamValue}; @@ -12,12 +12,15 @@ use crate::view::{ParentWindow, RawParent, View, ViewHost, ViewHostInner}; pub struct Vst3ViewHost { pub handler: RefCell>>, + // todo: not sure on best place, but this seems to match closest to the old code + pub plug_frame: RefCell>>, } impl Vst3ViewHost { pub fn new() -> Vst3ViewHost { Vst3ViewHost { handler: RefCell::new(None), + plug_frame: RefCell::new(None), } } } @@ -53,12 +56,65 @@ impl ViewHostInner for Vst3ViewHost { pub struct PlugView { main_thread_state: Arc>>, + // TODO: not sure this should go here, or nested somewhere + // under plugin state. + // In old code, it lived right under view (essentially same as PlugView) + // so this is kinda the same. + // Also - do we really want to be bringing back EventHandler? I'm guessing + // it was removed for a reason. + #[cfg(target_os = "linux")] + handler: ComWrapper>, +} + +// todo: not sure where to organize this +#[cfg(target_os = "linux")] +mod linux { + use super::*; + use vst3::Steinberg::Linux::*; + + pub(super) struct EventHandler { + // TODO: old code had this as JUST an Arc, + // but in order to be able to call poll via this state, we + // need it to be an unsafecell. Is there a better way? + // I tend to think this is safe because, if it's all getting called + // from the "main thread", it shouldn't be getting concurrently accessed, + // but I don't trust that logic... + state: Arc>>, + } + + impl EventHandler

{ + pub fn new(state: &Arc>>,) -> EventHandler

{ + EventHandler { + state: state.clone(), + } + } + } + + impl Class for EventHandler

{ + type Interfaces = (IEventHandler, ITimerHandler); + } + + impl IEventHandlerTrait for EventHandler

{ + unsafe fn onFDIsSet(&self, _fd: FileDescriptor) { + // todo: VERY NOT SURE if this is actually safe + let state = unsafe { &mut *self.state.get() }; + state.view.as_mut().unwrap().poll(); + } + } + + impl ITimerHandlerTrait for EventHandler

{ + unsafe fn onTimer(&self) { + let state = unsafe { &mut *self.state.get() }; + state.view.as_mut().unwrap().poll(); + } + } } impl PlugView

{ pub fn new(main_thread_state: &Arc>>) -> PlugView

{ PlugView { main_thread_state: main_thread_state.clone(), + handler: ComWrapper::new(linux::EventHandler::new(main_thread_state)), } } } @@ -108,6 +164,28 @@ impl IPlugViewTrait for PlugView

{ let view = main_thread_state.plugin.view(host, &parent); main_thread_state.view = Some(view); + #[cfg(target_os = "linux")] + { + use vst3::Steinberg::Linux::*; + + let Some(frame) = main_thread_state.view_host.plug_frame.borrow().clone() else { + return kNotInitialized; + }; + + if let Some(run_loop) = frame.cast::() { + let timer_handler = self.handler.as_com_ref::().unwrap(); + run_loop.registerTimer(timer_handler.as_ptr(), 16); + + if let Some(fd) = (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor() { + let event_handler = self.handler.as_com_ref::().unwrap(); + run_loop.registerEventHandler(event_handler.as_ptr(), fd); + } + } + } + + // todo: old code had this - do we need it? + // editor_state.editor.replace(Some(editor)); + kResultOk } @@ -162,7 +240,16 @@ impl IPlugViewTrait for PlugView

{ } unsafe fn setFrame(&self, _frame: *mut IPlugFrame) -> tresult { - kNotImplemented + let main_thread_state = &mut *self.main_thread_state.get(); + + if let Some(frame) = ComRef::from_raw(_frame) { + main_thread_state + .view_host + .plug_frame + .replace(Some(frame.to_com_ptr())); + } + + kResultOk } unsafe fn canResize(&self) -> tresult { diff --git a/src/view.rs b/src/view.rs index d73cdbdb..d9240d8d 100644 --- a/src/view.rs +++ b/src/view.rs @@ -1,5 +1,6 @@ use std::ffi::{c_ulong, c_void}; use std::marker::PhantomData; +use std::os::raw::c_int; use std::rc::Rc; use crate::params::{ParamId, ParamValue}; @@ -67,6 +68,10 @@ pub struct Size { pub trait View: Sized + 'static { fn size(&self) -> Size; fn param_changed(&mut self, id: ParamId, value: ParamValue); + #[cfg(target_os = "linux")] + fn file_descriptor(&self) -> Option; + #[cfg(target_os = "linux")] + fn poll(&mut self); } pub struct NoView; @@ -80,4 +85,10 @@ impl View for NoView { } fn param_changed(&mut self, _id: ParamId, _value: ParamValue) {} + + fn file_descriptor(&self) -> Option { + None + } + + fn poll(&mut self) {} } From 49fa8369ef7f10afddec1001ef734498b95e6af0 Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Sun, 22 Feb 2026 21:05:33 -0800 Subject: [PATCH 03/16] figuring out next steps --- src/format/vst3/view.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index a8e49589..c8b0e09f 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -56,12 +56,7 @@ impl ViewHostInner for Vst3ViewHost { pub struct PlugView { main_thread_state: Arc>>, - // TODO: not sure this should go here, or nested somewhere - // under plugin state. - // In old code, it lived right under view (essentially same as PlugView) - // so this is kinda the same. - // Also - do we really want to be bringing back EventHandler? I'm guessing - // it was removed for a reason. + // TODO: consider having PlugView directly implement IEvent/ITimerHandler #[cfg(target_os = "linux")] handler: ComWrapper>, } @@ -83,7 +78,7 @@ mod linux { } impl EventHandler

{ - pub fn new(state: &Arc>>,) -> EventHandler

{ + pub fn new(state: &Arc>>) -> EventHandler

{ EventHandler { state: state.clone(), } @@ -176,7 +171,9 @@ impl IPlugViewTrait for PlugView

{ let timer_handler = self.handler.as_com_ref::().unwrap(); run_loop.registerTimer(timer_handler.as_ptr(), 16); - if let Some(fd) = (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor() { + if let Some(fd) = + (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor() + { let event_handler = self.handler.as_com_ref::().unwrap(); run_loop.registerEventHandler(event_handler.as_ptr(), fd); } @@ -243,10 +240,7 @@ impl IPlugViewTrait for PlugView

{ let main_thread_state = &mut *self.main_thread_state.get(); if let Some(frame) = ComRef::from_raw(_frame) { - main_thread_state - .view_host - .plug_frame - .replace(Some(frame.to_com_ptr())); + main_thread_state.view_host.plug_frame.replace(Some(frame.to_com_ptr())); } kResultOk From b23c46a7eb302e5e8eebd5fd1467949db54f0bbb Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Sun, 22 Feb 2026 21:49:04 -0800 Subject: [PATCH 04/16] figuring out next steps --- .idea/.gitignore | 10 ++++++++++ .idea/coupler.iml | 17 +++++++++++++++++ .idea/modules.xml | 8 ++++++++ .idea/vcs.xml | 6 ++++++ src/format/vst3/view.rs | 31 +++++++++++++++++++------------ 5 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/coupler.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..ab1f4164 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/coupler.iml b/.idea/coupler.iml new file mode 100644 index 00000000..51ed5db9 --- /dev/null +++ b/.idea/coupler.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..f2876134 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index c8b0e09f..8b245643 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -2,13 +2,13 @@ use std::cell::{RefCell, UnsafeCell}; use std::ffi::{c_void, CStr}; use std::sync::Arc; -use vst3::Steinberg::Vst::{IComponentHandler, IComponentHandlerTrait}; -use vst3::{Class, ComPtr, ComRef, ComWrapper, Steinberg::*}; - use super::component::MainThreadState; use crate::params::{ParamId, ParamValue}; use crate::plugin::Plugin; use crate::view::{ParentWindow, RawParent, View, ViewHost, ViewHostInner}; +use vst3::Steinberg::Linux::IEventHandlerTrait; +use vst3::Steinberg::Vst::{IComponentHandler, IComponentHandlerTrait}; +use vst3::{Class, ComPtr, ComRef, ComWrapper, Steinberg::*}; pub struct Vst3ViewHost { pub handler: RefCell>>, @@ -61,12 +61,25 @@ pub struct PlugView { handler: ComWrapper>, } +impl IEventHandlerTrait for PlugView

{ + unsafe fn onFDIsSet(&self, _fd: FileDescriptor) { + // todo: VERY NOT SURE if this is actually safe - + // guard at least against re-entrant calls with Cell? + let state = unsafe { &mut *self.main_thread_state.get() }; + state.view.as_mut().unwrap().poll(); + } +} + // todo: not sure where to organize this #[cfg(target_os = "linux")] mod linux { use super::*; use vst3::Steinberg::Linux::*; + impl Class for PlugView

{ + type Interfaces = (IEventHandler, ITimerHandler, IPlugView); + } + pub(super) struct EventHandler { // TODO: old code had this as JUST an Arc, // but in order to be able to call poll via this state, we @@ -114,10 +127,6 @@ impl PlugView

{ } } -impl Class for PlugView

{ - type Interfaces = (IPlugView,); -} - impl IPlugViewTrait for PlugView

{ unsafe fn isPlatformTypeSupported(&self, type_: FIDString) -> tresult { #[cfg(target_os = "windows")] @@ -174,15 +183,13 @@ impl IPlugViewTrait for PlugView

{ if let Some(fd) = (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor() { - let event_handler = self.handler.as_com_ref::().unwrap(); - run_loop.registerEventHandler(event_handler.as_ptr(), fd); + if let Some(event_handler) = self.as_com_ref::() { + run_loop.registerEventHandler(event_handler, fd); + } } } } - // todo: old code had this - do we need it? - // editor_state.editor.replace(Some(editor)); - kResultOk } From e590da4ba469f9af0d32e8d1988cd71483177a8e Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Mon, 23 Feb 2026 21:04:36 -0800 Subject: [PATCH 05/16] WIP: trying to have PlugView implement the event/timer handler --- src/format/vst3/view.rs | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index 8b245643..9d233325 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -6,7 +6,7 @@ use super::component::MainThreadState; use crate::params::{ParamId, ParamValue}; use crate::plugin::Plugin; use crate::view::{ParentWindow, RawParent, View, ViewHost, ViewHostInner}; -use vst3::Steinberg::Linux::IEventHandlerTrait; +use vst3::Steinberg::Linux::{FileDescriptor, IEventHandlerTrait}; use vst3::Steinberg::Vst::{IComponentHandler, IComponentHandlerTrait}; use vst3::{Class, ComPtr, ComRef, ComWrapper, Steinberg::*}; @@ -56,19 +56,9 @@ impl ViewHostInner for Vst3ViewHost { pub struct PlugView { main_thread_state: Arc>>, - // TODO: consider having PlugView directly implement IEvent/ITimerHandler - #[cfg(target_os = "linux")] - handler: ComWrapper>, } -impl IEventHandlerTrait for PlugView

{ - unsafe fn onFDIsSet(&self, _fd: FileDescriptor) { - // todo: VERY NOT SURE if this is actually safe - - // guard at least against re-entrant calls with Cell? - let state = unsafe { &mut *self.main_thread_state.get() }; - state.view.as_mut().unwrap().poll(); - } -} + // todo: not sure where to organize this #[cfg(target_os = "linux")] @@ -76,6 +66,22 @@ mod linux { use super::*; use vst3::Steinberg::Linux::*; + impl IEventHandlerTrait for PlugView

{ + unsafe fn onFDIsSet(&self, _fd: FileDescriptor) { + // todo: VERY NOT SURE if this is actually safe - + // guard at least against re-entrant calls with Cell? + let state = unsafe { &mut *self.main_thread_state.get() }; + state.view.as_mut().unwrap().poll(); + } + } + + impl ITimerHandlerTrait for PlugView

{ + unsafe fn onTimer(&self) { + let state = unsafe { &mut *self.main_thread_state.get() }; + state.view.as_mut().unwrap().poll(); + } + } + impl Class for PlugView

{ type Interfaces = (IEventHandler, ITimerHandler, IPlugView); } @@ -122,7 +128,6 @@ impl PlugView

{ pub fn new(main_thread_state: &Arc>>) -> PlugView

{ PlugView { main_thread_state: main_thread_state.clone(), - handler: ComWrapper::new(linux::EventHandler::new(main_thread_state)), } } } @@ -177,15 +182,13 @@ impl IPlugViewTrait for PlugView

{ }; if let Some(run_loop) = frame.cast::() { - let timer_handler = self.handler.as_com_ref::().unwrap(); - run_loop.registerTimer(timer_handler.as_ptr(), 16); + let stupid_view = ComWrapper::new(PlugView::new(&self.main_thread_state)); + run_loop.registerTimer(stupid_view.as_com_ref::().unwrap().as_ptr(), 16); if let Some(fd) = (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor() { - if let Some(event_handler) = self.as_com_ref::() { - run_loop.registerEventHandler(event_handler, fd); - } + run_loop.registerEventHandler(stupid_view.as_com_ref::().unwrap().as_ptr(), 16); } } } From 0fb07604d13ad6ee1fc8e2c584ad496169c6a6f7 Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Thu, 26 Feb 2026 21:33:27 -0800 Subject: [PATCH 06/16] Refactor to let PlugView itself implement the event / timer callbacks. --- src/format/vst3/component.rs | 6 +++- src/format/vst3/view.rs | 61 +++++++++++++++++++++++++++++------- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/format/vst3/component.rs b/src/format/vst3/component.rs index 139d7863..3d626039 100644 --- a/src/format/vst3/component.rs +++ b/src/format/vst3/component.rs @@ -716,7 +716,11 @@ impl IEditControllerTrait for Component

{ return ptr::null_mut(); } + // create the view but populate the self_ptr so the view is able to access itself let view = ComWrapper::new(PlugView::new(&self.main_thread_state)); - view.to_com_ptr::().unwrap().into_raw() + let com_ptr = view.to_com_ptr::().unwrap(); + let raw_ptr = com_ptr.as_ptr(); + view.set_self_ptr(raw_ptr); + com_ptr.into_raw() } } diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index 9d233325..99056612 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -1,4 +1,4 @@ -use std::cell::{RefCell, UnsafeCell}; +use std::cell::{Cell, RefCell, UnsafeCell}; use std::ffi::{c_void, CStr}; use std::sync::Arc; @@ -8,7 +8,7 @@ use crate::plugin::Plugin; use crate::view::{ParentWindow, RawParent, View, ViewHost, ViewHostInner}; use vst3::Steinberg::Linux::{FileDescriptor, IEventHandlerTrait}; use vst3::Steinberg::Vst::{IComponentHandler, IComponentHandlerTrait}; -use vst3::{Class, ComPtr, ComRef, ComWrapper, Steinberg::*}; +use vst3::{Class, Interface, ComPtr, ComRef, ComWrapper, Steinberg::*}; pub struct Vst3ViewHost { pub handler: RefCell>>, @@ -56,10 +56,14 @@ impl ViewHostInner for Vst3ViewHost { pub struct PlugView { main_thread_state: Arc>>, + /// Raw pointer allows accessing this PlugView as a COM object when needed. + /// We use this rather than a ComPtr to avoid a circular reference / memory leak. + /// It needs to be a cell so we can have interior mutability and actually populate this + /// field after construction. This will allow us to later turn the IPlugView into + /// IEventHandler / ITimerHandler. + self_ptr: Cell>, } - - // todo: not sure where to organize this #[cfg(target_os = "linux")] mod linux { @@ -128,8 +132,39 @@ impl PlugView

{ pub fn new(main_thread_state: &Arc>>) -> PlugView

{ PlugView { main_thread_state: main_thread_state.clone(), + self_ptr: Cell::new(None), } } + + // must be called after construction + pub fn set_self_ptr(&self, ptr: *mut IPlugView) { + self.self_ptr.set(Some(ptr)); + } +} + +// todo: where would we like to put this? +/// Safely transitions from one COM interface type to another (like IPlugView to ITimerHandler) +/// using queryInterface. +macro_rules! query_interface { + ($source_ptr:expr, $interface_type:ty) => {{ + let mut result_obj: *mut c_void = std::ptr::null_mut(); + let unknown_ptr = $source_ptr as *mut FUnknown; + let iid = <$interface_type>::IID; + + let result = unsafe { + ((*(*unknown_ptr).vtbl).queryInterface)( + unknown_ptr, + iid.as_ptr() as *const _, + &mut result_obj, + ) + }; + + if result == kResultOk && !result_obj.is_null() { + Some(result_obj as *mut $interface_type) + } else { + None + } + }}; } impl IPlugViewTrait for PlugView

{ @@ -182,13 +217,17 @@ impl IPlugViewTrait for PlugView

{ }; if let Some(run_loop) = frame.cast::() { - let stupid_view = ComWrapper::new(PlugView::new(&self.main_thread_state)); - run_loop.registerTimer(stupid_view.as_com_ref::().unwrap().as_ptr(), 16); - - if let Some(fd) = - (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor() - { - run_loop.registerEventHandler(stupid_view.as_com_ref::().unwrap().as_ptr(), 16); + if let Some(ptr) = self.self_ptr.get() { + if let Some(timer_handler_ptr) = query_interface!(ptr, ITimerHandler) { + run_loop.registerTimer(timer_handler_ptr, 16); + + if let Some(fd) = + (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor() { + if let Some(event_handler_ptr) = query_interface!(ptr, IEventHandler) { + run_loop.registerEventHandler(event_handler_ptr, 16); + } + } + } } } } From 42a79edab261133d8996366f6fa73510fcee5d1b Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Thu, 26 Feb 2026 21:36:07 -0800 Subject: [PATCH 07/16] Update todo for state --- src/format/vst3/view.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index 99056612..562ffded 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -91,12 +91,7 @@ mod linux { } pub(super) struct EventHandler { - // TODO: old code had this as JUST an Arc, - // but in order to be able to call poll via this state, we - // need it to be an unsafecell. Is there a better way? - // I tend to think this is safe because, if it's all getting called - // from the "main thread", it shouldn't be getting concurrently accessed, - // but I don't trust that logic... + // TODO: This should be refactored to use a RefCell state: Arc>>, } From 75c6b7981f11db9750ba1bc721c17d6f04380795 Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Thu, 26 Feb 2026 21:43:08 -0800 Subject: [PATCH 08/16] remove accidentally-added ide files --- .idea/.gitignore | 10 ---------- .idea/coupler.iml | 17 ----------------- .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ 4 files changed, 41 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/coupler.iml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index ab1f4164..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Ignored default folder with query files -/queries/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/coupler.iml b/.idea/coupler.iml deleted file mode 100644 index 51ed5db9..00000000 --- a/.idea/coupler.iml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index f2876134..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddf..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From d3c5bf68ebdb6f7037ce2555687ddb2113660f44 Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Thu, 26 Feb 2026 21:54:13 -0800 Subject: [PATCH 09/16] refactor PlugView new to perform the self_ptr assignment, but the plugin crashes the host on close --- examples/gain/src/lib.rs | 1 - src/format/vst3/component.rs | 7 ++----- src/format/vst3/view.rs | 18 +++++++----------- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/examples/gain/src/lib.rs b/examples/gain/src/lib.rs index 8772d9d0..a6b6f1f8 100644 --- a/examples/gain/src/lib.rs +++ b/examples/gain/src/lib.rs @@ -356,7 +356,6 @@ impl View for GainView { } fn poll(&mut self) { - // todo: is unwrapping really the right choice? self.event_loop.poll().unwrap(); } } diff --git a/src/format/vst3/component.rs b/src/format/vst3/component.rs index 3d626039..76113213 100644 --- a/src/format/vst3/component.rs +++ b/src/format/vst3/component.rs @@ -717,10 +717,7 @@ impl IEditControllerTrait for Component

{ } // create the view but populate the self_ptr so the view is able to access itself - let view = ComWrapper::new(PlugView::new(&self.main_thread_state)); - let com_ptr = view.to_com_ptr::().unwrap(); - let raw_ptr = com_ptr.as_ptr(); - view.set_self_ptr(raw_ptr); - com_ptr.into_raw() + let view = PlugView::new(&self.main_thread_state); + view.into_raw() } } diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index 562ffded..955640dc 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -12,7 +12,6 @@ use vst3::{Class, Interface, ComPtr, ComRef, ComWrapper, Steinberg::*}; pub struct Vst3ViewHost { pub handler: RefCell>>, - // todo: not sure on best place, but this seems to match closest to the old code pub plug_frame: RefCell>>, } @@ -64,7 +63,6 @@ pub struct PlugView { self_ptr: Cell>, } -// todo: not sure where to organize this #[cfg(target_os = "linux")] mod linux { use super::*; @@ -109,7 +107,6 @@ mod linux { impl IEventHandlerTrait for EventHandler

{ unsafe fn onFDIsSet(&self, _fd: FileDescriptor) { - // todo: VERY NOT SURE if this is actually safe let state = unsafe { &mut *self.state.get() }; state.view.as_mut().unwrap().poll(); } @@ -124,16 +121,15 @@ mod linux { } impl PlugView

{ - pub fn new(main_thread_state: &Arc>>) -> PlugView

{ - PlugView { + pub fn new(main_thread_state: &Arc>>) -> ComPtr { + let view = ComWrapper::new(PlugView { main_thread_state: main_thread_state.clone(), self_ptr: Cell::new(None), - } - } - - // must be called after construction - pub fn set_self_ptr(&self, ptr: *mut IPlugView) { - self.self_ptr.set(Some(ptr)); + }); + let com_ptr = view.to_com_ptr::().unwrap(); + let raw_ptr = com_ptr.as_ptr(); + view.self_ptr.set(Some(raw_ptr)); + com_ptr } } From b7761e7605cd58de1eb7a58cf41f89600bd2fa41 Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Thu, 26 Feb 2026 21:58:48 -0800 Subject: [PATCH 10/16] add proper unregistration of handlers on removed --- src/format/vst3/view.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index 955640dc..c314e266 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -229,6 +229,27 @@ impl IPlugViewTrait for PlugView

{ unsafe fn removed(&self) -> tresult { let main_thread_state = &mut *self.main_thread_state.get(); + #[cfg(target_os = "linux")] + { + use vst3::Steinberg::Linux::*; + + let Some(frame) = main_thread_state.view_host.plug_frame.borrow().clone() else { + return kNotInitialized; + }; + + if let Some(run_loop) = frame.cast::() { + if let Some(ptr) = self.self_ptr.get() { + if let Some(timer_handler_ptr) = query_interface!(ptr, ITimerHandler) { + run_loop.unregisterTimer(timer_handler_ptr); + + if let Some(event_handler_ptr) = query_interface!(ptr, IEventHandler) { + run_loop.unregisterEventHandler(event_handler_ptr); + } + } + } + } + } + main_thread_state.view = None; kResultOk From daa22cda5820454710b146c7d9cccb803327eab0 Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Thu, 26 Feb 2026 22:04:14 -0800 Subject: [PATCH 11/16] cleanup macro / todos --- src/format/vst3/view.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index c314e266..1d456cc7 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -70,8 +70,6 @@ mod linux { impl IEventHandlerTrait for PlugView

{ unsafe fn onFDIsSet(&self, _fd: FileDescriptor) { - // todo: VERY NOT SURE if this is actually safe - - // guard at least against re-entrant calls with Cell? let state = unsafe { &mut *self.main_thread_state.get() }; state.view.as_mut().unwrap().poll(); } @@ -133,7 +131,6 @@ impl PlugView

{ } } -// todo: where would we like to put this? /// Safely transitions from one COM interface type to another (like IPlugView to ITimerHandler) /// using queryInterface. macro_rules! query_interface { @@ -145,7 +142,7 @@ macro_rules! query_interface { let result = unsafe { ((*(*unknown_ptr).vtbl).queryInterface)( unknown_ptr, - iid.as_ptr() as *const _, + iid.as_ptr() as *const TUID, &mut result_obj, ) }; From 3cbdb917ef7feceffb01f258af9a52ec6745b402 Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Thu, 26 Feb 2026 22:05:24 -0800 Subject: [PATCH 12/16] remove incorrect comment --- src/format/vst3/component.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/format/vst3/component.rs b/src/format/vst3/component.rs index 76113213..953a8d68 100644 --- a/src/format/vst3/component.rs +++ b/src/format/vst3/component.rs @@ -716,7 +716,6 @@ impl IEditControllerTrait for Component

{ return ptr::null_mut(); } - // create the view but populate the self_ptr so the view is able to access itself let view = PlugView::new(&self.main_thread_state); view.into_raw() } From 7ea1c42d739a1b2718f14d317ed99a650d5e3098 Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Thu, 26 Feb 2026 22:07:04 -0800 Subject: [PATCH 13/16] remove unused eventhandler --- src/format/vst3/view.rs | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index 1d456cc7..3167a13d 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -85,37 +85,6 @@ mod linux { impl Class for PlugView

{ type Interfaces = (IEventHandler, ITimerHandler, IPlugView); } - - pub(super) struct EventHandler { - // TODO: This should be refactored to use a RefCell - state: Arc>>, - } - - impl EventHandler

{ - pub fn new(state: &Arc>>) -> EventHandler

{ - EventHandler { - state: state.clone(), - } - } - } - - impl Class for EventHandler

{ - type Interfaces = (IEventHandler, ITimerHandler); - } - - impl IEventHandlerTrait for EventHandler

{ - unsafe fn onFDIsSet(&self, _fd: FileDescriptor) { - let state = unsafe { &mut *self.state.get() }; - state.view.as_mut().unwrap().poll(); - } - } - - impl ITimerHandlerTrait for EventHandler

{ - unsafe fn onTimer(&self) { - let state = unsafe { &mut *self.state.get() }; - state.view.as_mut().unwrap().poll(); - } - } } impl PlugView

{ From 8385848872de34f71d4fddc91cbbec1804e90910 Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Sat, 28 Feb 2026 18:44:57 -0800 Subject: [PATCH 14/16] use proper com crate mechanism for casting handler interfaces instead of manually calling queryInterface --- src/format/vst3/view.rs | 57 +++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index 3167a13d..6fb04fe1 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -100,30 +100,6 @@ impl PlugView

{ } } -/// Safely transitions from one COM interface type to another (like IPlugView to ITimerHandler) -/// using queryInterface. -macro_rules! query_interface { - ($source_ptr:expr, $interface_type:ty) => {{ - let mut result_obj: *mut c_void = std::ptr::null_mut(); - let unknown_ptr = $source_ptr as *mut FUnknown; - let iid = <$interface_type>::IID; - - let result = unsafe { - ((*(*unknown_ptr).vtbl).queryInterface)( - unknown_ptr, - iid.as_ptr() as *const TUID, - &mut result_obj, - ) - }; - - if result == kResultOk && !result_obj.is_null() { - Some(result_obj as *mut $interface_type) - } else { - None - } - }}; -} - impl IPlugViewTrait for PlugView

{ unsafe fn isPlatformTypeSupported(&self, type_: FIDString) -> tresult { #[cfg(target_os = "windows")] @@ -175,16 +151,20 @@ impl IPlugViewTrait for PlugView

{ if let Some(run_loop) = frame.cast::() { if let Some(ptr) = self.self_ptr.get() { - if let Some(timer_handler_ptr) = query_interface!(ptr, ITimerHandler) { - run_loop.registerTimer(timer_handler_ptr, 16); - - if let Some(fd) = - (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor() { - if let Some(event_handler_ptr) = query_interface!(ptr, IEventHandler) { - run_loop.registerEventHandler(event_handler_ptr, 16); + if let Some(com_ref) = unsafe {ComRef::from_raw(ptr)} { + if let Some(timer_handler_ptr) = com_ref.cast::() { + unsafe { + run_loop.registerTimer(timer_handler_ptr.as_ptr(), 16); + } + if let Some(fd) = + (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor() { + if let Some(event_handler_ptr) = com_ref.cast::() { + run_loop.registerEventHandler(event_handler_ptr.as_ptr(), 16); + } } } } + } } } @@ -205,11 +185,16 @@ impl IPlugViewTrait for PlugView

{ if let Some(run_loop) = frame.cast::() { if let Some(ptr) = self.self_ptr.get() { - if let Some(timer_handler_ptr) = query_interface!(ptr, ITimerHandler) { - run_loop.unregisterTimer(timer_handler_ptr); - - if let Some(event_handler_ptr) = query_interface!(ptr, IEventHandler) { - run_loop.unregisterEventHandler(event_handler_ptr); + if let Some(com_ref) = unsafe {ComRef::from_raw(ptr)} { + if let Some(timer_handler_ptr) = com_ref.cast::() { + unsafe { + run_loop.unregisterTimer(timer_handler_ptr.as_ptr()); + } + } + if let Some(event_handler_ptr) = com_ref.cast::() { + unsafe { + run_loop.unregisterEventHandler(event_handler_ptr.as_ptr()); + } } } } From 66a0a6ae84b0f7d8a6cfff5c1d0584ef41b5099c Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Sat, 28 Feb 2026 18:54:17 -0800 Subject: [PATCH 15/16] properly register / unregister fd handler --- src/format/vst3/view.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index 6fb04fe1..c2bd0d62 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -156,10 +156,11 @@ impl IPlugViewTrait for PlugView

{ unsafe { run_loop.registerTimer(timer_handler_ptr.as_ptr(), 16); } + // todo: fd is unused - rethink this check if let Some(fd) = (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor() { if let Some(event_handler_ptr) = com_ref.cast::() { - run_loop.registerEventHandler(event_handler_ptr.as_ptr(), 16); + run_loop.registerEventHandler(event_handler_ptr.as_ptr(), fd); } } } @@ -191,9 +192,14 @@ impl IPlugViewTrait for PlugView

{ run_loop.unregisterTimer(timer_handler_ptr.as_ptr()); } } - if let Some(event_handler_ptr) = com_ref.cast::() { - unsafe { - run_loop.unregisterEventHandler(event_handler_ptr.as_ptr()); + // confirm an event handler actually was registered + // before trying to unregister (we only register if we can get a + // file descriptor) + if (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor().is_some() { + if let Some(event_handler_ptr) = com_ref.cast::() { + unsafe { + run_loop.unregisterEventHandler(event_handler_ptr.as_ptr()); + } } } } From e4346f7f69b48c17897db2fc6d03cd5b55edf50d Mon Sep 17 00:00:00 2001 From: Kyle Hipke Date: Sun, 1 Mar 2026 18:15:08 -0800 Subject: [PATCH 16/16] remove accidental leftover todo --- src/format/vst3/view.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/format/vst3/view.rs b/src/format/vst3/view.rs index c2bd0d62..70c77c3c 100644 --- a/src/format/vst3/view.rs +++ b/src/format/vst3/view.rs @@ -156,7 +156,6 @@ impl IPlugViewTrait for PlugView

{ unsafe { run_loop.registerTimer(timer_handler_ptr.as_ptr(), 16); } - // todo: fd is unused - rethink this check if let Some(fd) = (*self.main_thread_state.get()).view.as_ref().unwrap().file_descriptor() { if let Some(event_handler_ptr) = com_ref.cast::() {