use crate::consts::plugins_x11::{
CLIENT_MESSAGE, DESTROY_NOTIFY, EXPOSURE_MASK, STRUCTURE_NOTIFY_MASK, XEMBED_EMBEDDED_NOTIFY,
XEMBED_FOCUS_CURRENT, XEMBED_FOCUS_IN, XEMBED_WINDOW_ACTIVATE,
};
use std::ffi::{CString, c_char, c_int, c_long, c_uchar, c_uint, c_ulong, c_void};
use std::sync::OnceLock;
static X11_THREADS_INIT: OnceLock<bool> = OnceLock::new();
#[repr(C)]
#[derive(Copy, Clone)]
pub union XEvent {
pub type_: c_int,
pub xclient: XClientMessageEvent,
pub xconfigure: XConfigureEvent,
pub xdestroywindow: XDestroyWindowEvent,
pub xunmap: XUnmapEvent,
pad: [c_long; 24],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct XClientMessageData {
pub longs: [c_long; 5],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct XClientMessageEvent {
pub type_: c_int,
pub serial: c_ulong,
pub send_event: c_int,
pub display: *mut c_void,
pub window: c_ulong,
pub message_type: c_ulong,
pub format: c_int,
pub data: XClientMessageData,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct XConfigureEvent {
type_: c_int,
serial: c_ulong,
send_event: c_int,
display: *mut c_void,
event: c_ulong,
window: c_ulong,
x: c_int,
y: c_int,
width: c_int,
height: c_int,
border_width: c_int,
above: c_ulong,
override_redirect: c_int,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct XDestroyWindowEvent {
pub type_: c_int,
pub serial: c_ulong,
pub send_event: c_int,
pub display: *mut c_void,
pub event: c_ulong,
pub window: c_ulong,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct XUnmapEvent {
pub type_: c_int,
pub serial: c_ulong,
pub send_event: c_int,
pub display: *mut c_void,
pub event: c_ulong,
pub window: c_ulong,
pub from_configure: c_int,
}
#[repr(C)]
pub struct XErrorEvent {
type_: c_int,
display: *mut c_void,
resourceid: c_ulong,
serial: c_ulong,
error_code: c_uchar,
request_code: c_uchar,
minor_code: c_uchar,
}
type XErrorHandler = unsafe extern "C" fn(*mut c_void, *mut XErrorEvent) -> c_int;
unsafe extern "C" fn vst3_x11_error_handler(
_display: *mut c_void,
event: *mut XErrorEvent,
) -> c_int {
if event.is_null() {
return 0;
}
let ev = unsafe { &*event };
if ev.error_code == 3 {
return 0;
}
0
}
struct X11ErrorHandlerGuard {
previous: Option<XErrorHandler>,
}
impl X11ErrorHandlerGuard {
fn install() -> Self {
let previous = unsafe { XSetErrorHandler(Some(vst3_x11_error_handler)) };
Self { previous }
}
}
impl Drop for X11ErrorHandlerGuard {
fn drop(&mut self) {
unsafe {
let _ = XSetErrorHandler(self.previous);
}
}
}
#[link(name = "X11")]
unsafe extern "C" {
pub fn XInitThreads() -> c_int;
pub fn XOpenDisplay(display_name: *const c_char) -> *mut c_void;
pub fn XCloseDisplay(display: *mut c_void) -> c_int;
pub fn XDefaultScreen(display: *mut c_void) -> c_int;
pub fn XRootWindow(display: *mut c_void, screen_number: c_int) -> c_ulong;
pub fn XBlackPixel(display: *mut c_void, screen_number: c_int) -> c_ulong;
pub fn XWhitePixel(display: *mut c_void, screen_number: c_int) -> c_ulong;
pub fn XCreateSimpleWindow(
display: *mut c_void,
parent: c_ulong,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
border_width: c_uint,
border: c_ulong,
background: c_ulong,
) -> c_ulong;
pub fn XStoreName(display: *mut c_void, window: c_ulong, window_name: *const c_char) -> c_int;
pub fn XSelectInput(display: *mut c_void, window: c_ulong, event_mask: c_long) -> c_int;
pub fn XInternAtom(
display: *mut c_void,
atom_name: *const c_char,
only_if_exists: c_int,
) -> c_ulong;
pub fn XSetWMProtocols(
display: *mut c_void,
window: c_ulong,
protocols: *mut c_ulong,
count: c_int,
) -> c_int;
pub fn XChangeProperty(
display: *mut c_void,
window: c_ulong,
property: c_ulong,
type_: c_ulong,
format: c_int,
mode: c_int,
data: *const c_uchar,
nelements: c_int,
) -> c_int;
pub fn XMapRaised(display: *mut c_void, window: c_ulong) -> c_int;
pub fn XMapSubwindows(display: *mut c_void, window: c_ulong) -> c_int;
pub fn XResizeWindow(
display: *mut c_void,
window: c_ulong,
width: c_uint,
height: c_uint,
) -> c_int;
fn XMoveResizeWindow(
display: *mut c_void,
window: c_ulong,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> c_int;
pub fn XDestroyWindow(display: *mut c_void, window: c_ulong) -> c_int;
pub fn XQueryTree(
display: *mut c_void,
window: c_ulong,
root_return: *mut c_ulong,
parent_return: *mut c_ulong,
children_return: *mut *mut c_ulong,
nchildren_return: *mut c_uint,
) -> c_int;
pub fn XFree(data: *mut c_void) -> c_int;
pub fn XSync(display: *mut c_void, discard: c_int) -> c_int;
pub fn XPending(display: *mut c_void) -> c_int;
pub fn XNextEvent(display: *mut c_void, event_return: *mut XEvent) -> c_int;
pub fn XFlush(display: *mut c_void) -> c_int;
pub fn XSendEvent(
display: *mut c_void,
w: c_ulong,
propagate: c_int,
event_mask: c_long,
event_send: *mut XEvent,
) -> c_int;
pub fn XSetErrorHandler(handler: Option<XErrorHandler>) -> Option<XErrorHandler>;
}
fn ensure_x11_threads() -> bool {
*X11_THREADS_INIT.get_or_init(|| unsafe { XInitThreads() != 0 })
}
fn first_child(display: *mut c_void, window: c_ulong) -> Option<c_ulong> {
let mut root: c_ulong = 0;
let mut parent: c_ulong = 0;
let mut children_ptr: *mut c_ulong = std::ptr::null_mut();
let mut nchildren: c_uint = 0;
let ok = unsafe {
XQueryTree(
display,
window,
&mut root,
&mut parent,
&mut children_ptr,
&mut nchildren,
)
};
if ok == 0 || children_ptr.is_null() || nchildren == 0 {
return None;
}
let child = unsafe { *children_ptr };
unsafe {
let _ = XFree(children_ptr.cast::<c_void>());
}
Some(child)
}
pub fn set_dialog_window_type(display: *mut c_void, window: c_ulong) {
if display.is_null() || window == 0 {
return;
}
let wm_type_name = CString::new("_NET_WM_WINDOW_TYPE").unwrap_or_default();
let dialog_name = CString::new("_NET_WM_WINDOW_TYPE_DIALOG").unwrap_or_default();
if wm_type_name.as_bytes().is_empty() || dialog_name.as_bytes().is_empty() {
return;
}
unsafe {
let wm_type = XInternAtom(display, wm_type_name.as_ptr(), 0);
let dialog = XInternAtom(display, dialog_name.as_ptr(), 0);
if wm_type == 0 || dialog == 0 {
return;
}
XChangeProperty(
display,
window,
wm_type,
4,
32,
0,
&dialog as *const c_ulong as *const c_uchar,
1,
);
}
}
fn map_and_resize_children(display: *mut c_void, parent: c_ulong, width: i32, height: i32) {
let mut root: c_ulong = 0;
let mut parent_ret: c_ulong = 0;
let mut children_ptr: *mut c_ulong = std::ptr::null_mut();
let mut nchildren: c_uint = 0;
let ok = unsafe {
XQueryTree(
display,
parent,
&mut root,
&mut parent_ret,
&mut children_ptr,
&mut nchildren,
)
};
if ok != 0 && !children_ptr.is_null() && nchildren > 0 {
for i in 0..nchildren as usize {
let child = unsafe { *children_ptr.add(i) };
unsafe {
let _ = XMoveResizeWindow(display, child, 0, 0, width as c_uint, height as c_uint);
let _ = XMapRaised(display, child);
}
}
unsafe {
let _ = XFree(children_ptr.cast::<c_void>());
}
}
unsafe {
let _ = XFlush(display);
}
}
fn send_xembed_message(
display: *mut c_void,
child: c_ulong,
message: c_long,
detail: c_long,
data2: c_long,
) {
let atom_name = match CString::new("_XEMBED") {
Ok(v) => v,
Err(_) => return,
};
let xembed_atom = unsafe { XInternAtom(display, atom_name.as_ptr(), 0) };
if xembed_atom == 0 {
return;
}
let mut event = XEvent { pad: [0; 24] };
event.xclient = XClientMessageEvent {
type_: CLIENT_MESSAGE,
serial: 0,
send_event: 1,
display,
window: child,
message_type: xembed_atom,
format: 32,
data: XClientMessageData {
longs: [0, message, detail, data2, 0],
},
};
let _ = unsafe { XSendEvent(display, child, 0, 0, &mut event) };
unsafe {
let _ = XFlush(display);
}
}
pub fn open_editor_with_processor(
processor: std::sync::Arc<maolan_engine::vst3::Vst3Processor>,
title: String,
) -> Result<Option<maolan_engine::vst3::Vst3PluginState>, String> {
let result = run_vst3_x11_editor_with_processor(processor, title);
result.map(|_| None)
}
fn run_vst3_x11_editor_with_processor(
processor: std::sync::Arc<maolan_engine::vst3::Vst3Processor>,
title: String,
) -> Result<(), String> {
let _ = ensure_x11_threads();
processor.ui_begin_session();
let display = unsafe { XOpenDisplay(std::ptr::null()) };
if display.is_null() {
processor.ui_end_session();
return Err("Failed to open X display for VST3 editor".to_string());
}
let _error_handler_guard = X11ErrorHandlerGuard::install();
let screen = unsafe { XDefaultScreen(display) };
let root = unsafe { XRootWindow(display, screen) };
let black = unsafe { XBlackPixel(display, screen) };
let white = unsafe { XWhitePixel(display, screen) };
let window = unsafe { XCreateSimpleWindow(display, root, 120, 120, 900, 600, 1, black, white) };
if window == 0 {
unsafe {
let _ = XCloseDisplay(display);
}
processor.ui_end_session();
return Err("Failed to create X11 window for VST3 editor".to_string());
}
let embed_window =
unsafe { XCreateSimpleWindow(display, window, 0, 0, 900, 600, 0, black, white) };
if embed_window == 0 {
unsafe {
let _ = XDestroyWindow(display, window);
let _ = XCloseDisplay(display);
}
processor.ui_end_session();
return Err("Failed to create X11 embed window for VST3 editor".to_string());
}
set_dialog_window_type(display, window);
let platform_type = "X11EmbedWindowID";
if let Err(e) = processor.gui_create(platform_type) {
unsafe {
let _ = XDestroyWindow(display, window);
let _ = XCloseDisplay(display);
}
processor.ui_end_session();
return Err(e);
}
let (width, height) = match processor.gui_get_size() {
Ok((w, h)) => (w.max(320), h.max(240)),
Err(_) => (900, 600),
};
let title_c = CString::new(title).map_err(|e| e.to_string())?;
let (wm_delete, wm_protocols) = unsafe {
let _ = XStoreName(display, window, title_c.as_ptr());
let _ = XResizeWindow(display, window, width as c_uint, height as c_uint);
let _ = XResizeWindow(display, embed_window, width as c_uint, height as c_uint);
let _ = XSelectInput(
display,
window,
(STRUCTURE_NOTIFY_MASK | EXPOSURE_MASK) as c_long,
);
let _ = XSelectInput(
display,
embed_window,
(STRUCTURE_NOTIFY_MASK | EXPOSURE_MASK) as c_long,
);
let wm_delete_atom_name = CString::new("WM_DELETE_WINDOW").map_err(|e| e.to_string())?;
let wm_delete = XInternAtom(display, wm_delete_atom_name.as_ptr(), 0);
let wm_protocols_atom_name = CString::new("WM_PROTOCOLS").map_err(|e| e.to_string())?;
let wm_protocols = XInternAtom(display, wm_protocols_atom_name.as_ptr(), 0);
if wm_delete != 0 {
let mut protocols = [wm_delete];
let _ = XSetWMProtocols(display, window, protocols.as_mut_ptr(), 1);
}
(wm_delete, wm_protocols)
};
if let Err(e) = processor.gui_set_parent(embed_window as usize, platform_type) {
unsafe {
let _ = XDestroyWindow(display, window);
let _ = XCloseDisplay(display);
}
processor.gui_destroy();
processor.ui_end_session();
return Err(e);
}
let _ = processor.gui_on_size(width, height);
if let Err(e) = processor.gui_show() {
unsafe {
let _ = XDestroyWindow(display, window);
let _ = XCloseDisplay(display);
}
processor.gui_destroy();
processor.ui_end_session();
return Err(e);
}
unsafe {
let _ = XMapSubwindows(display, embed_window);
let _ = XMapRaised(display, embed_window);
let _ = XMapRaised(display, window);
let _ = XFlush(display);
}
map_and_resize_children(display, embed_window, width, height);
if let Some(child) = first_child(display, embed_window) {
send_xembed_message(
display,
child,
XEMBED_EMBEDDED_NOTIFY,
0,
embed_window as c_long,
);
send_xembed_message(display, child, XEMBED_WINDOW_ACTIVATE, 0, 0);
send_xembed_message(display, child, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0);
}
loop {
processor.gui_on_main_thread();
if processor.ui_should_close() {
break;
}
let pending = unsafe { XPending(display) };
if pending <= 0 {
std::thread::sleep(std::time::Duration::from_millis(16));
continue;
}
let mut event = XEvent { pad: [0; 24] };
unsafe {
let _ = XNextEvent(display, &mut event);
}
let event_type = unsafe { event.type_ };
if event_type == DESTROY_NOTIFY {
break;
}
if event_type == 22 {
let cfg = unsafe { event.xconfigure };
let w = cfg.width.max(1);
let h = cfg.height.max(1);
if cfg.window == window {
let _ = processor.gui_on_size(w, h);
map_and_resize_children(display, embed_window, w, h);
} else if cfg.window == embed_window {
map_and_resize_children(display, embed_window, w, h);
}
}
if event_type == CLIENT_MESSAGE {
let msg = unsafe { event.xclient };
if wm_delete != 0
&& wm_protocols != 0
&& msg.window == window
&& msg.message_type == wm_protocols
&& msg.format == 32
&& (msg.data.longs[0] as c_ulong) == wm_delete
{
unsafe {
let _ = XDestroyWindow(display, window);
let _ = XSync(display, 0);
}
}
}
}
processor.gui_hide();
processor.gui_destroy();
unsafe {
let _ = XSync(display, 0);
let _ = XDestroyWindow(display, window);
let _ = XSync(display, 0);
let _ = XCloseDisplay(display);
}
processor.ui_end_session();
Ok(())
}