#![cfg(feature = "egui-widgets")]
use crate::error::{Error, Result};
use crate::plugin::Plugin;
use raw_window_handle::RawWindowHandle;
use std::sync::{Arc, Mutex};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct EditorRect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
pub struct EmbeddedEditor {
plugin: Arc<Mutex<Plugin>>,
#[cfg(target_os = "macos")]
inner: macos::MacEmbed,
#[cfg(target_os = "windows")]
inner: windows::WinEmbed,
#[cfg(target_os = "linux")]
inner: linux::LinuxEmbed,
}
impl EmbeddedEditor {
pub fn embed(
plugin: Arc<Mutex<Plugin>>,
parent: RawWindowHandle,
rect: EditorRect,
) -> Result<Self> {
#[cfg(target_os = "macos")]
{
let inner = macos::MacEmbed::new(&plugin, parent, rect)?;
Ok(Self { plugin, inner })
}
#[cfg(target_os = "windows")]
{
let inner = windows::WinEmbed::new(&plugin, parent, rect)?;
Ok(Self { plugin, inner })
}
#[cfg(target_os = "linux")]
{
let inner = linux::LinuxEmbed::new(&plugin, parent, rect)?;
Ok(Self { plugin, inner })
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
let _ = (&plugin, parent, rect);
Err(Error::Other(
"editor embedding is not implemented on this platform".to_string(),
))
}
}
pub fn set_rect(&self, rect: EditorRect) {
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
self.inner.set_rect(rect);
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
let _ = rect;
}
pub fn close(self) {}
}
impl Drop for EmbeddedEditor {
fn drop(&mut self) {
if let Ok(mut p) = self.plugin.lock() {
let _ = p.close_editor();
}
}
}
#[cfg(target_os = "macos")]
mod macos {
use super::*;
use objc2::{rc::Retained, MainThreadMarker, MainThreadOnly};
use objc2_app_kit::NSView;
use objc2_foundation::{NSPoint, NSRect, NSSize};
pub struct MacEmbed {
parent: Retained<NSView>,
child: Retained<NSView>,
}
impl MacEmbed {
pub fn new(
plugin: &Arc<Mutex<Plugin>>,
parent: RawWindowHandle,
rect: EditorRect,
) -> Result<Self> {
let mtm = MainThreadMarker::new().ok_or_else(|| {
Error::Other("editor embedding must run on the main thread".to_string())
})?;
let RawWindowHandle::AppKit(h) = parent else {
return Err(Error::Other(
"expected an AppKit window handle for the parent".to_string(),
));
};
let parent: Retained<NSView> =
unsafe { Retained::retain(h.ns_view.as_ptr() as *mut NSView) }
.ok_or_else(|| Error::Other("null parent NSView".to_string()))?;
let frame = NSRect::new(
NSPoint::new(rect.x as f64, 0.0),
NSSize::new(rect.width as f64, rect.height as f64),
);
let child = NSView::initWithFrame(NSView::alloc(mtm), frame);
parent.addSubview(&child);
let handle = crate::plugin::WindowHandle::from_nsview(
Retained::as_ptr(&child) as *mut std::ffi::c_void
);
plugin
.lock()
.map_err(|_| Error::Other("plugin lock poisoned".to_string()))?
.open_editor(handle)?;
let embed = Self { parent, child };
embed.set_rect(rect);
Ok(embed)
}
pub fn set_rect(&self, rect: EditorRect) {
let flipped = self.parent.isFlipped();
let parent_height = self.parent.bounds().size.height;
let y = if flipped {
rect.y as f64
} else {
parent_height - (rect.y + rect.height) as f64
};
let frame = NSRect::new(
NSPoint::new(rect.x as f64, y),
NSSize::new(rect.width as f64, rect.height as f64),
);
self.child.setFrame(frame);
}
}
impl Drop for MacEmbed {
fn drop(&mut self) {
self.child.removeFromSuperview();
}
}
}
#[cfg(target_os = "windows")]
mod windows {
use super::*;
use winapi::shared::windef::HWND;
use winapi::um::libloaderapi::GetModuleHandleW;
use winapi::um::winuser::{
CreateWindowExW, DefWindowProcW, DestroyWindow, RegisterClassExW, SetWindowPos, ShowWindow,
CS_HREDRAW, CS_VREDRAW, SWP_NOZORDER, SW_SHOW, WNDCLASSEXW, WS_CHILD, WS_VISIBLE,
};
pub struct WinEmbed {
child: HWND,
}
impl WinEmbed {
pub fn new(
plugin: &Arc<Mutex<Plugin>>,
parent: RawWindowHandle,
rect: EditorRect,
) -> Result<Self> {
let RawWindowHandle::Win32(h) = parent else {
return Err(Error::Other(
"expected a Win32 window handle for the parent".to_string(),
));
};
unsafe {
let parent_hwnd = h.hwnd.get() as HWND;
let hinstance = GetModuleHandleW(std::ptr::null());
let class_name: Vec<u16> = "VST3EmbeddedEditor\0".encode_utf16().collect();
let mut wc: WNDCLASSEXW = std::mem::zeroed();
wc.cbSize = std::mem::size_of::<WNDCLASSEXW>() as u32;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = Some(DefWindowProcW);
wc.hInstance = hinstance;
wc.lpszClassName = class_name.as_ptr();
RegisterClassExW(&wc);
let child = CreateWindowExW(
0,
class_name.as_ptr(),
std::ptr::null(),
WS_CHILD | WS_VISIBLE,
rect.x as i32,
rect.y as i32,
rect.width as i32,
rect.height as i32,
parent_hwnd,
std::ptr::null_mut(),
hinstance,
std::ptr::null_mut(),
);
if child.is_null() {
return Err(Error::Other("Failed to create child window".to_string()));
}
let handle = crate::plugin::WindowHandle::from_hwnd(child as *mut std::ffi::c_void);
if let Err(e) = plugin
.lock()
.map_err(|_| Error::Other("plugin lock poisoned".to_string()))?
.open_editor(handle)
{
DestroyWindow(child);
return Err(e);
}
ShowWindow(child, SW_SHOW);
Ok(Self { child })
}
}
pub fn set_rect(&self, rect: EditorRect) {
unsafe {
SetWindowPos(
self.child,
std::ptr::null_mut(),
rect.x as i32,
rect.y as i32,
rect.width as i32,
rect.height as i32,
SWP_NOZORDER,
);
}
}
}
impl Drop for WinEmbed {
fn drop(&mut self) {
unsafe {
DestroyWindow(self.child);
}
}
}
}
#[cfg(target_os = "linux")]
mod linux {
use super::*;
use xcb::{x, Xid, XidNew};
pub struct LinuxEmbed {
connection: xcb::Connection,
child: x::Window,
}
impl LinuxEmbed {
pub fn new(
plugin: &Arc<Mutex<Plugin>>,
parent: RawWindowHandle,
rect: EditorRect,
) -> Result<Self> {
let parent_id: u32 = match parent {
RawWindowHandle::Xcb(h) => h.window.get(),
RawWindowHandle::Xlib(h) => h.window as u32,
_ => {
return Err(Error::Other(
"expected an X11 (Xcb/Xlib) window handle for the parent".to_string(),
))
}
};
let (connection, screen_number) = xcb::Connection::connect(None)
.map_err(|e| Error::Other(format!("Failed to connect to X server: {e}")))?;
let visual = {
let setup = connection.get_setup();
let screen = setup
.roots()
.nth(screen_number as usize)
.ok_or_else(|| Error::Other("No X11 screen found".to_string()))?;
screen.root_visual()
};
let parent_win: x::Window = x::Window::new(parent_id);
let child = connection.generate_id();
connection
.send_and_check_request(&x::CreateWindow {
depth: x::COPY_FROM_PARENT as u8,
wid: child,
parent: parent_win,
x: rect.x as i16,
y: rect.y as i16,
width: (rect.width as u16).max(1),
height: (rect.height as u16).max(1),
border_width: 0,
class: x::WindowClass::InputOutput,
visual,
value_list: &[x::Cw::EventMask(x::EventMask::EXPOSURE)],
})
.map_err(|e| Error::Other(format!("Failed to create X11 child window: {e}")))?;
connection.send_request(&x::MapWindow { window: child });
let _ = connection.flush();
let handle = crate::plugin::WindowHandle::from_x11(child.resource_id());
if let Err(e) = plugin
.lock()
.map_err(|_| Error::Other("plugin lock poisoned".to_string()))?
.open_editor(handle)
{
connection.send_request(&x::DestroyWindow { window: child });
let _ = connection.flush();
return Err(e);
}
Ok(Self { connection, child })
}
pub fn set_rect(&self, rect: EditorRect) {
self.connection.send_request(&x::ConfigureWindow {
window: self.child,
value_list: &[
x::ConfigWindow::X(rect.x as i32),
x::ConfigWindow::Y(rect.y as i32),
x::ConfigWindow::Width((rect.width as u32).max(1)),
x::ConfigWindow::Height((rect.height as u32).max(1)),
],
});
let _ = self.connection.flush();
}
}
impl Drop for LinuxEmbed {
fn drop(&mut self) {
self.connection
.send_request(&x::DestroyWindow { window: self.child });
let _ = self.connection.flush();
}
}
}