1#![cfg(feature = "egui-widgets")]
12
13use crate::error::{Error, Result};
14use crate::plugin::Plugin;
15use raw_window_handle::RawWindowHandle;
16use std::sync::{Arc, Mutex};
17
18#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct EditorRect {
21 pub x: f32,
23 pub y: f32,
25 pub width: f32,
27 pub height: f32,
29}
30
31pub struct EmbeddedEditor {
34 plugin: Arc<Mutex<Plugin>>,
35 #[cfg(target_os = "macos")]
36 inner: macos::MacEmbed,
37 #[cfg(target_os = "windows")]
38 inner: windows::WinEmbed,
39 #[cfg(target_os = "linux")]
40 inner: linux::LinuxEmbed,
41}
42
43impl EmbeddedEditor {
44 pub fn embed(
49 plugin: Arc<Mutex<Plugin>>,
50 parent: RawWindowHandle,
51 rect: EditorRect,
52 ) -> Result<Self> {
53 #[cfg(target_os = "macos")]
54 {
55 let inner = macos::MacEmbed::new(&plugin, parent, rect)?;
56 Ok(Self { plugin, inner })
57 }
58 #[cfg(target_os = "windows")]
59 {
60 let inner = windows::WinEmbed::new(&plugin, parent, rect)?;
61 Ok(Self { plugin, inner })
62 }
63 #[cfg(target_os = "linux")]
64 {
65 let inner = linux::LinuxEmbed::new(&plugin, parent, rect)?;
66 Ok(Self { plugin, inner })
67 }
68 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
69 {
70 let _ = (&plugin, parent, rect);
71 Err(Error::Other(
72 "editor embedding is not implemented on this platform".to_string(),
73 ))
74 }
75 }
76
77 pub fn set_rect(&self, rect: EditorRect) {
80 #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
81 self.inner.set_rect(rect);
82 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
83 let _ = rect;
84 }
85
86 pub fn close(self) {}
88}
89
90impl Drop for EmbeddedEditor {
91 fn drop(&mut self) {
92 if let Ok(mut p) = self.plugin.lock() {
94 let _ = p.close_editor();
95 }
96 }
97}
98
99#[cfg(target_os = "macos")]
100mod macos {
101 use super::*;
102 use objc2::{rc::Retained, MainThreadMarker, MainThreadOnly};
103 use objc2_app_kit::NSView;
104 use objc2_foundation::{NSPoint, NSRect, NSSize};
105
106 pub struct MacEmbed {
107 parent: Retained<NSView>,
108 child: Retained<NSView>,
109 }
110
111 impl MacEmbed {
112 pub fn new(
113 plugin: &Arc<Mutex<Plugin>>,
114 parent: RawWindowHandle,
115 rect: EditorRect,
116 ) -> Result<Self> {
117 let mtm = MainThreadMarker::new().ok_or_else(|| {
118 Error::Other("editor embedding must run on the main thread".to_string())
119 })?;
120 let RawWindowHandle::AppKit(h) = parent else {
121 return Err(Error::Other(
122 "expected an AppKit window handle for the parent".to_string(),
123 ));
124 };
125 let parent: Retained<NSView> =
127 unsafe { Retained::retain(h.ns_view.as_ptr() as *mut NSView) }
128 .ok_or_else(|| Error::Other("null parent NSView".to_string()))?;
129
130 let frame = NSRect::new(
132 NSPoint::new(rect.x as f64, 0.0),
133 NSSize::new(rect.width as f64, rect.height as f64),
134 );
135 let child = NSView::initWithFrame(NSView::alloc(mtm), frame);
136 parent.addSubview(&child);
137
138 let handle = crate::plugin::WindowHandle::from_nsview(
139 Retained::as_ptr(&child) as *mut std::ffi::c_void
140 );
141 plugin
142 .lock()
143 .map_err(|_| Error::Other("plugin lock poisoned".to_string()))?
144 .open_editor(handle)?;
145
146 let embed = Self { parent, child };
147 embed.set_rect(rect);
148 Ok(embed)
149 }
150
151 pub fn set_rect(&self, rect: EditorRect) {
152 let flipped = self.parent.isFlipped();
156 let parent_height = self.parent.bounds().size.height;
157 let y = if flipped {
158 rect.y as f64
159 } else {
160 parent_height - (rect.y + rect.height) as f64
161 };
162 let frame = NSRect::new(
163 NSPoint::new(rect.x as f64, y),
164 NSSize::new(rect.width as f64, rect.height as f64),
165 );
166 self.child.setFrame(frame);
167 }
168 }
169
170 impl Drop for MacEmbed {
171 fn drop(&mut self) {
172 self.child.removeFromSuperview();
173 }
174 }
175}
176
177#[cfg(target_os = "windows")]
178mod windows {
179 use super::*;
180 use winapi::shared::windef::HWND;
181 use winapi::um::libloaderapi::GetModuleHandleW;
182 use winapi::um::winuser::{
183 CreateWindowExW, DefWindowProcW, DestroyWindow, RegisterClassExW, SetWindowPos, ShowWindow,
184 CS_HREDRAW, CS_VREDRAW, SWP_NOZORDER, SW_SHOW, WNDCLASSEXW, WS_CHILD, WS_VISIBLE,
185 };
186
187 pub struct WinEmbed {
189 child: HWND,
190 }
191
192 impl WinEmbed {
193 pub fn new(
194 plugin: &Arc<Mutex<Plugin>>,
195 parent: RawWindowHandle,
196 rect: EditorRect,
197 ) -> Result<Self> {
198 let RawWindowHandle::Win32(h) = parent else {
199 return Err(Error::Other(
200 "expected a Win32 window handle for the parent".to_string(),
201 ));
202 };
203 unsafe {
204 let parent_hwnd = h.hwnd.get() as HWND;
205 let hinstance = GetModuleHandleW(std::ptr::null());
206
207 let class_name: Vec<u16> = "VST3EmbeddedEditor\0".encode_utf16().collect();
209 let mut wc: WNDCLASSEXW = std::mem::zeroed();
210 wc.cbSize = std::mem::size_of::<WNDCLASSEXW>() as u32;
211 wc.style = CS_HREDRAW | CS_VREDRAW;
212 wc.lpfnWndProc = Some(DefWindowProcW);
213 wc.hInstance = hinstance;
214 wc.lpszClassName = class_name.as_ptr();
215 RegisterClassExW(&wc);
216
217 let child = CreateWindowExW(
218 0,
219 class_name.as_ptr(),
220 std::ptr::null(),
221 WS_CHILD | WS_VISIBLE,
222 rect.x as i32,
223 rect.y as i32,
224 rect.width as i32,
225 rect.height as i32,
226 parent_hwnd,
227 std::ptr::null_mut(),
228 hinstance,
229 std::ptr::null_mut(),
230 );
231 if child.is_null() {
232 return Err(Error::Other("Failed to create child window".to_string()));
233 }
234
235 let handle = crate::plugin::WindowHandle::from_hwnd(child as *mut std::ffi::c_void);
236 if let Err(e) = plugin
237 .lock()
238 .map_err(|_| Error::Other("plugin lock poisoned".to_string()))?
239 .open_editor(handle)
240 {
241 DestroyWindow(child);
242 return Err(e);
243 }
244 ShowWindow(child, SW_SHOW);
245 Ok(Self { child })
246 }
247 }
248
249 pub fn set_rect(&self, rect: EditorRect) {
250 unsafe {
251 SetWindowPos(
252 self.child,
253 std::ptr::null_mut(),
254 rect.x as i32,
255 rect.y as i32,
256 rect.width as i32,
257 rect.height as i32,
258 SWP_NOZORDER,
259 );
260 }
261 }
262 }
263
264 impl Drop for WinEmbed {
265 fn drop(&mut self) {
266 unsafe {
267 DestroyWindow(self.child);
268 }
269 }
270 }
271}
272
273#[cfg(target_os = "linux")]
274mod linux {
275 use super::*;
276 use xcb::{x, Xid, XidNew};
277
278 pub struct LinuxEmbed {
280 connection: xcb::Connection,
281 child: x::Window,
282 }
283
284 impl LinuxEmbed {
285 pub fn new(
286 plugin: &Arc<Mutex<Plugin>>,
287 parent: RawWindowHandle,
288 rect: EditorRect,
289 ) -> Result<Self> {
290 let parent_id: u32 = match parent {
291 RawWindowHandle::Xcb(h) => h.window.get(),
292 RawWindowHandle::Xlib(h) => h.window as u32,
293 _ => {
294 return Err(Error::Other(
295 "expected an X11 (Xcb/Xlib) window handle for the parent".to_string(),
296 ))
297 }
298 };
299
300 let (connection, screen_number) = xcb::Connection::connect(None)
301 .map_err(|e| Error::Other(format!("Failed to connect to X server: {e}")))?;
302 let visual = {
303 let setup = connection.get_setup();
304 let screen = setup
305 .roots()
306 .nth(screen_number as usize)
307 .ok_or_else(|| Error::Other("No X11 screen found".to_string()))?;
308 screen.root_visual()
309 };
310 let parent_win: x::Window = x::Window::new(parent_id);
312 let child = connection.generate_id();
313
314 connection
315 .send_and_check_request(&x::CreateWindow {
316 depth: x::COPY_FROM_PARENT as u8,
317 wid: child,
318 parent: parent_win,
319 x: rect.x as i16,
320 y: rect.y as i16,
321 width: (rect.width as u16).max(1),
322 height: (rect.height as u16).max(1),
323 border_width: 0,
324 class: x::WindowClass::InputOutput,
325 visual,
326 value_list: &[x::Cw::EventMask(x::EventMask::EXPOSURE)],
327 })
328 .map_err(|e| Error::Other(format!("Failed to create X11 child window: {e}")))?;
329 connection.send_request(&x::MapWindow { window: child });
330 let _ = connection.flush();
331
332 let handle = crate::plugin::WindowHandle::from_x11(child.resource_id());
333 if let Err(e) = plugin
334 .lock()
335 .map_err(|_| Error::Other("plugin lock poisoned".to_string()))?
336 .open_editor(handle)
337 {
338 connection.send_request(&x::DestroyWindow { window: child });
339 let _ = connection.flush();
340 return Err(e);
341 }
342
343 Ok(Self { connection, child })
344 }
345
346 pub fn set_rect(&self, rect: EditorRect) {
347 self.connection.send_request(&x::ConfigureWindow {
348 window: self.child,
349 value_list: &[
350 x::ConfigWindow::X(rect.x as i32),
351 x::ConfigWindow::Y(rect.y as i32),
352 x::ConfigWindow::Width((rect.width as u32).max(1)),
353 x::ConfigWindow::Height((rect.height as u32).max(1)),
354 ],
355 });
356 let _ = self.connection.flush();
357 }
358 }
359
360 impl Drop for LinuxEmbed {
361 fn drop(&mut self) {
362 self.connection
363 .send_request(&x::DestroyWindow { window: self.child });
364 let _ = self.connection.flush();
365 }
366 }
367}