Delay the XEmbed messages

This works, but we now have the same issues with flickering and resizing
found in some other implementations such as Airwave.
This commit is contained in:
Robbert van der Helm
2020-03-30 00:47:46 +02:00
parent b3254b1c5a
commit fa045fb770
4 changed files with 63 additions and 42 deletions
+5 -4
View File
@@ -7,10 +7,11 @@ Yet Another way to use Windows VST2 plugins in Linux VST hosts.
There are a few things that should be done before releasing this, including:
- Implement missing features:
- GUIs. Wine's XEmbed implementation is causing X11 draw calls to fail so
there's probably something right. Right now GUIs do work if you disable the
XEmbed messages or skip reparenting altogether, but that's of course not
ideal.
- GUIs. The current basic implementation with XEmbed suffers from flickering
during redrwas (in Serum, depends on the plugin) and it has the usual
problems with resizing. Reparenting without XEmbed works great, but it
breaks a lot of GUI elements because the plugin still thinks it's in the top
left corner of the screen. If that could be fixed that would be ideal.
- Fix implementation bugs:
- Closing Serum's editor takes a full second to execute `DestroyWindow`. After
fixing XEmbed it might be possible to at least make it feel responsive by
+43 -19
View File
@@ -36,7 +36,7 @@ Editor::Editor(std::string window_class_name)
->atom;
}
HWND Editor::open(AEffect* effect) {
HWND Editor::open(AEffect* effect, xcb_window_t parent_window_handle) {
// Create a window without any decoratiosn for easy embedding. The
// combination of `WS_EX_TOOLWINDOW` and `WS_POPUP` causes the window to be
// drawn without any decorations (making resizes behave as you'd expect) and
@@ -52,6 +52,22 @@ HWND Editor::open(AEffect* effect) {
// Needed to send update messages on a timer
plugin = effect;
parent_window = parent_window_handle;
// The Win32 API will block the `DispatchMessage` call when opening e.g. a
// dropdown, but it will still allow timers to be run so the GUI can still
// update in the background. Because of this we send `effEditIdle` to the
// plugin on a timer. The refresh rate is purposely fairly low since we
// we'll also trigger this manually in `Editor::handle_events()` whenever
// the plugin is not busy.
SetTimer(win32_handle->get(), idle_timer_id, 100, nullptr);
// We'll only start the xembed procedure after the host has givne the window
// the correct size, otherwise Wine can't draw correctly
const uint32_t event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
xcb_change_window_attributes(x11_connection.get(), parent_window,
XCB_CW_EVENT_MASK, &event_mask);
xcb_flush(x11_connection.get());
return win32_handle->get();
}
@@ -64,7 +80,7 @@ void Editor::close() {
// everything for us?
}
// TODO: I feel like this should only have to be done once
// TODO: I feel like this shouldn't necessary with xembed
bool Editor::resize(const VstRect& new_size) {
if (!win32_handle.has_value()) {
return false;
@@ -77,7 +93,7 @@ bool Editor::resize(const VstRect& new_size) {
return true;
}
bool Editor::embed_into(const size_t parent_window_handle) {
bool Editor::xembed() {
if (!win32_handle.has_value()) {
return false;
}
@@ -87,31 +103,24 @@ bool Editor::embed_into(const size_t parent_window_handle) {
// under 'Embedding life cycle
// Sadly there's doesn't seem to be any implementation of this available as
// a library
const size_t child_window_handle = get_x11_handle().value();
const size_t child_window = get_x11_handle().value();
xcb_reparent_window(x11_connection.get(), child_window_handle,
parent_window_handle, 0, 0);
xcb_reparent_window(x11_connection.get(), child_window, parent_window, 0,
0);
// Tell the window from Wine it's embedded into the window provided by the
// host
send_xembed_event(child_window_handle, xembed_embedded_notify_msg, 0,
parent_window_handle, xembed_protocol_version);
send_xembed_event(child_window, xembed_embedded_notify_msg, 0,
parent_window, xembed_protocol_version);
send_xembed_event(child_window_handle, xembed_focus_in_msg,
xembed_focus_first, 0, 0);
send_xembed_event(child_window_handle, xembed_window_activate_msg, 0, 0, 0);
send_xembed_event(child_window, xembed_focus_in_msg, xembed_focus_first, 0,
0);
send_xembed_event(child_window, xembed_window_activate_msg, 0, 0, 0);
xcb_map_window(x11_connection.get(), child_window_handle);
xcb_map_window(x11_connection.get(), child_window);
xcb_flush(x11_connection.get());
ShowWindow(win32_handle->get(), SW_SHOWNORMAL);
// The Win32 API will block the `DispatchMessage` call when opening e.g. a
// dropdown, but it will still allow timers to be run so the GUI can still
// update in the background. Because of this we send `effEditIdle` to the
// plugin on a timer. The refresh rate is purposely fairly low since we
// we'll also trigger this manually in `Editor::handle_events()` whenever
// the plugin is not busy.
SetTimer(win32_handle->get(), idle_timer_id, 100, nullptr);
return true;
}
@@ -141,6 +150,21 @@ void Editor::handle_events() {
if (!gui_was_updated) {
SendMessage(win32_handle->get(), WM_TIMER, idle_timer_id, 0);
}
// Handle X11 events
xcb_generic_event_t* event;
while ((event = xcb_poll_for_event(x11_connection.get())) != nullptr) {
if ((event->response_type & ~0x80) == XCB_CONFIGURE_NOTIFY) {
xcb_configure_notify_event_t configuration =
*reinterpret_cast<xcb_configure_notify_event_t*>(event);
// TODO: Only has to be done once, and the problems mentioned in
// the readme are still here.
xembed();
}
free(event);
}
}
}
+10 -6
View File
@@ -35,8 +35,10 @@ class Editor {
*
* @param effect The plugin this window is being created for. Used to send
* `effEditIdle` messages on a timer.
* @param parent_window_handle The X11 window handle passed by the VST host
* for the editor to embed itself into.
*/
HWND open(AEffect* effect);
HWND open(AEffect* effect, xcb_window_t parent_window_handle);
void close();
/**
@@ -57,19 +59,21 @@ class Editor {
void handle_events();
/**
* Embed the (open) window into a parent window.
*
* @param parent_window_handle The X11 window handle passed by the VST host
* for the editor to embed itself into.
* Embed the (open) window into the parent window.
*
* @return Whether the embedding was succesful. Will return false if the
* window is not open.
*/
bool embed_into(const size_t parent_window_handle);
bool xembed();
// Needed to handle idle updates through a timer
AEffect* plugin;
/**
* The window handle of the editor window created by the DAW.
*/
xcb_window_t parent_window;
private:
/**
* Return the X11 window handle for the window if it's currently open.
+5 -13
View File
@@ -238,21 +238,13 @@ intptr_t PluginBridge::dispatch_wrapper(AEffect* plugin,
option);
} break;
case effEditOpen: {
const auto win32_handle = editor.open(plugin);
// The plugin will return 0 if it can not open its
// editor window (or if it does not support it, but in
// that case the DAW should be hiding the option)
const intptr_t return_value = plugin->dispatcher(
plugin, opcode, index, value, win32_handle, option);
if (return_value == 0) {
return 0;
}
const auto x11_handle = reinterpret_cast<size_t>(data);
editor.embed_into(x11_handle);
const auto win32_handle = editor.open(plugin, x11_handle);
return return_value;
// The actual XEmbed handling is done after the host's window has
// been set to the correct size, in `Editor::handle_events()`
return plugin->dispatcher(plugin, opcode, index, value,
win32_handle, option);
} break;
case effEditClose: {
const intptr_t return_value =