From c2e62b30cad97457f696abc88f9dd57c3a22554d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 11 Apr 2020 14:06:47 +0200 Subject: [PATCH 1/5] Strip out everything related to XEmbed We'll just go with the same workaroud LinVST uses isntead. It's not as pretty but it does work a lot better. --- src/wine-host/editor.cpp | 116 +++++--------------------------- src/wine-host/editor.h | 39 +++++------ src/wine-host/plugin-bridge.cpp | 4 +- 3 files changed, 32 insertions(+), 127 deletions(-) diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 19b27632..46583887 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -3,38 +3,11 @@ // The Win32 API requires you to hardcode identifiers for tiemrs constexpr size_t idle_timer_id = 1337; -constexpr char xembed_proeprty[] = "_XEMBED"; -constexpr char xembed_info_proeprty[] = "_XEMBED_INFO"; - -// Constants from the XEmbed spec -constexpr uint32_t xembed_protocol_version = 0; - -constexpr uint32_t xembed_embedded_notify_msg = 0; -constexpr uint32_t xembed_window_activate_msg = 1; -constexpr uint32_t xembed_focus_in_msg = 4; - -constexpr uint32_t xembed_focus_first = 1; - ATOM register_window_class(std::string window_class_name); Editor::Editor(std::string window_class_name) : window_class(register_window_class(window_class_name)), - x11_connection(xcb_connect(nullptr, nullptr), &xcb_disconnect) { - // We need a bunch of property atoms for the XEmbed protocol - xcb_generic_error_t* err; - - const auto xembed_cookie = xcb_intern_atom( - x11_connection.get(), 0, strlen(xembed_proeprty), xembed_proeprty); - const auto xembed_info_cookie = - xcb_intern_atom(x11_connection.get(), 0, strlen(xembed_info_proeprty), - xembed_info_proeprty); - - xcb_xembed = - xcb_intern_atom_reply(x11_connection.get(), xembed_cookie, &err)->atom; - xcb_xembed_info = - xcb_intern_atom_reply(x11_connection.get(), xembed_info_cookie, &err) - ->atom; -} + x11_connection(xcb_connect(nullptr, nullptr), &xcb_disconnect) {} HWND Editor::open(AEffect* effect, xcb_window_t parent_window_handle) { // Create a window without any decoratiosn for easy embedding. The @@ -46,7 +19,7 @@ HWND Editor::open(AEffect* effect, xcb_window_t parent_window_handle) { CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_ACCEPTFILES, reinterpret_cast(window_class), "yabridge plugin", WS_POPUP, CW_USEDEFAULT, - CW_USEDEFAULT, 2048, 2048, nullptr, nullptr, + CW_USEDEFAULT, 2560, 1440, nullptr, nullptr, GetModuleHandle(nullptr), this), &DestroyWindow); @@ -62,11 +35,23 @@ HWND Editor::open(AEffect* effect, xcb_window_t parent_window_handle) { // the plugin is not busy. SetTimer(win32_handle->get(), idle_timer_id, 100, nullptr); + // Embed the Win32 window into the window provided by the host. Instead of + // using the XEmbed protocol, we'll register a few events and manage the + // child window ourselves. This is a hack to work around the issue's + // described in `Editor`'s docstring'. + const size_t child_window = get_x11_handle().value(); + xcb_reparent_window(x11_connection.get(), child_window, parent_window, 0, + 0); + xcb_map_window(x11_connection.get(), child_window); + xcb_flush(x11_connection.get()); + const uint32_t event_mask = XCB_EVENT_MASK_VISIBILITY_CHANGE; xcb_change_window_attributes(x11_connection.get(), parent_window, XCB_CW_EVENT_MASK, &event_mask); xcb_flush(x11_connection.get()); + ShowWindow(win32_handle->get(), SW_SHOWNORMAL); + return win32_handle->get(); } @@ -78,38 +63,6 @@ void Editor::close() { // everything for us? } -bool Editor::xembed() { - if (!win32_handle.has_value()) { - return false; - } - - // This follows the embedding procedure specified in the XEmbed sped: - // https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html - // 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 = get_x11_handle().value(); - - 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, xembed_embedded_notify_msg, 0, - parent_window, xembed_protocol_version); - - 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); - xcb_flush(x11_connection.get()); - - ShowWindow(win32_handle->get(), SW_SHOWNORMAL); - - return true; -} - void Editor::handle_events() { // Process any remaining events, otherwise we won't be able to interact with // the window @@ -137,32 +90,13 @@ void Editor::handle_events() { } // Handle X11 events - // TODO: We don't listen for XEmbed property changes for mapping and - // unmapping the window as described in the spec. Is this - // something we should do? - // TODO: Also, increasing window size does not work reliably, any way we - // could make this work better? xcb_generic_event_t* event; while ((event = xcb_poll_for_event(x11_connection.get())) != nullptr) { // The most significant bit in an event's response type is used to // indicate whether the event source switch (event->response_type & ((1 << 7) - 1)) { case XCB_VISIBILITY_NOTIFY: { - xcb_visibility_notify_event_t visibility_event = - *reinterpret_cast( - event); - - // Wine's XEmbed implementation is a bit iffy when it comes - // to window size changes, including editor windows being - // increased in size by the DAW when first opening the - // editor. Restarting the XEmbed protocol on visibility and - // size changes is an easy workaround, but it's not 100% - // reliable. - if (visibility_event.window == parent_window && - visibility_event.state != - XCB_VISIBILITY_FULLY_OBSCURED) { - xembed(); - } + // TODO: Handle configuration changes } break; } @@ -180,26 +114,6 @@ std::optional Editor::get_x11_handle() { GetProp(win32_handle.value().get(), "__wine_x11_whole_window")); } -void Editor::send_xembed_event(const xcb_window_t& window, - const uint32_t message, - const uint32_t detail, - const uint32_t data1, - const uint32_t data2) { - xcb_client_message_event_t event; - event.response_type = XCB_CLIENT_MESSAGE; - event.type = xcb_xembed; - event.window = window; - event.format = 32; - event.data.data32[0] = XCB_CURRENT_TIME; - event.data.data32[1] = message; - event.data.data32[2] = detail; - event.data.data32[3] = data1; - event.data.data32[4] = data2; - - xcb_send_event(x11_connection.get(), false, window, XCB_EVENT_MASK_NO_EVENT, - reinterpret_cast(&event)); -} - LRESULT CALLBACK window_proc(HWND handle, UINT message, WPARAM wParam, diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index 9799ffe2..a0faf770 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -20,6 +20,18 @@ * A wrapper around the win32 windowing API to create and destroy editor * windows. A VST plugin can embed itself in that window, and we can then later * embed the window in a VST host provided X11 window. + * + * This was originally implemented using XEmbed. While that sounded like the + * right thing to do, there were a few minor issues with Wine's XEmbed + * implementation. The most important of which is that resizing GUIs sometimes + * works fine, but often fails to expand the embedded window's client area + * leaving part of the window inaccessible. There are also some a small number + * of plugins (such as Serum) that have rendering issues when using XEmbed but + * otherwise draw fine when running standalone or when just reparenting the + * window without using XEmbed. If anyone knows how to work around these two + * issues, please let me know and I'll switch to using XEmbed again. + * + * This workaround was inspired by LinVST. */ class Editor { public: @@ -30,8 +42,8 @@ class Editor { Editor(std::string window_class_name); /** - * Open a window and return a handle to the new Win32 window that can be - * used by the hosted VST plugin. + * Open a window, embed it into the DAW's parent window and return a handle + * to the new Win32 window that can be used by the hosted VST plugin. * * @param effect The plugin this window is being created for. Used to send * `effEditIdle` messages on a timer. @@ -48,14 +60,6 @@ class Editor { */ void handle_events(); - /** - * Embed the (open) window into the parent window. - * - * @return Whether the embedding was succesful. Will return false if the - * window is not open. - */ - bool xembed(); - // Needed to handle idle updates through a timer AEffect* plugin; @@ -71,18 +75,7 @@ class Editor { std::optional get_x11_handle(); /** - * Send an XEmbed message to a window. See the spec for more information. - * - * https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html#lifecycle - */ - void send_xembed_event(const xcb_window_t& window, - const uint32_t message, - const uint32_t detail, - const uint32_t data1, - const uint32_t data2); - - /** - * The Win32 window class registered for the window window. + * The Win32 window class registered for the windows window. */ ATOM window_class; @@ -95,6 +88,4 @@ class Editor { win32_handle; std::unique_ptr x11_connection; - xcb_atom_t xcb_xembed; - xcb_atom_t xcb_xembed_info; }; diff --git a/src/wine-host/plugin-bridge.cpp b/src/wine-host/plugin-bridge.cpp index 61cfbe3f..518e4722 100644 --- a/src/wine-host/plugin-bridge.cpp +++ b/src/wine-host/plugin-bridge.cpp @@ -241,8 +241,8 @@ intptr_t PluginBridge::dispatch_wrapper(AEffect* plugin, const auto x11_handle = reinterpret_cast(data); const auto win32_handle = editor.open(plugin, x11_handle); - // The actual XEmbed handling is done after the host's window has - // been set to the correct size, in `Editor::handle_events()` + // The created Win32 window has already been reparented to the host + // provided window return plugin->dispatcher(plugin, opcode, index, value, win32_handle, option); } break; From e1cc342bd02f3884300a09e1e19107cbc79816d0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 12 Apr 2020 18:22:08 +0200 Subject: [PATCH 2/5] Work around local<->global coordinate issues For reparented Wine windows. This is a similar approach as LinVST uses. --- src/wine-host/editor.cpp | 134 +++++++++++++++++++++++--------- src/wine-host/editor.h | 38 +++++++-- src/wine-host/plugin-bridge.cpp | 30 +++++-- 3 files changed, 152 insertions(+), 50 deletions(-) diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 46583887..bbe51fe4 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -3,13 +3,24 @@ // The Win32 API requires you to hardcode identifiers for tiemrs constexpr size_t idle_timer_id = 1337; +/** + * The most significant bit in an event's response type is used to indicate + * whether the event source. + */ +constexpr uint16_t event_type_mask = ((1 << 7) - 1); + +/** + * Return the X11 window handle for the window if it's currently open. + */ +xcb_window_t get_x11_handle(HWND win32_handle); + ATOM register_window_class(std::string window_class_name); Editor::Editor(std::string window_class_name) : window_class(register_window_class(window_class_name)), x11_connection(xcb_connect(nullptr, nullptr), &xcb_disconnect) {} -HWND Editor::open(AEffect* effect, xcb_window_t parent_window_handle) { +HWND Editor::open(AEffect* effect) { // 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 @@ -25,7 +36,6 @@ HWND Editor::open(AEffect* effect, xcb_window_t parent_window_handle) { // 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 @@ -35,26 +45,21 @@ HWND Editor::open(AEffect* effect, xcb_window_t parent_window_handle) { // the plugin is not busy. SetTimer(win32_handle->get(), idle_timer_id, 100, nullptr); - // Embed the Win32 window into the window provided by the host. Instead of - // using the XEmbed protocol, we'll register a few events and manage the - // child window ourselves. This is a hack to work around the issue's - // described in `Editor`'s docstring'. - const size_t child_window = get_x11_handle().value(); - xcb_reparent_window(x11_connection.get(), child_window, parent_window, 0, - 0); - xcb_map_window(x11_connection.get(), child_window); - xcb_flush(x11_connection.get()); - - const uint32_t event_mask = XCB_EVENT_MASK_VISIBILITY_CHANGE; - xcb_change_window_attributes(x11_connection.get(), parent_window, - XCB_CW_EVENT_MASK, &event_mask); - xcb_flush(x11_connection.get()); - - ShowWindow(win32_handle->get(), SW_SHOWNORMAL); - return win32_handle->get(); } +bool Editor::resize(const VstRect& new_size) { + if (!win32_handle.has_value()) { + return false; + } + + SetWindowPos(win32_handle->get(), HWND_TOP, new_size.left, new_size.top, + new_size.right - new_size.left, new_size.bottom - new_size.top, + 0); + + return true; +} + void Editor::close() { // RAII will destroy the window and tiemrs for us win32_handle = std::nullopt; @@ -63,6 +68,37 @@ void Editor::close() { // everything for us? } +bool Editor::embed_into(const size_t parent_window_handle) { + if (!win32_handle.has_value()) { + return false; + } + + child_window = get_x11_handle(win32_handle->get()); + parent_window = parent_window_handle; + + // See the X11 events part of `Editor::handle_events`. + // const uint32_t child_event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY; + // xcb_change_window_attributes(x11_connection.get(), child_window, + // XCB_CW_EVENT_MASK, &child_event_mask); + const uint32_t parent_event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY; + xcb_change_window_attributes(x11_connection.get(), parent_window, + XCB_CW_EVENT_MASK, &parent_event_mask); + xcb_flush(x11_connection.get()); + + // Embed the Win32 window into the window provided by the host. Instead of + // using the XEmbed protocol, we'll register a few events and manage the + // child window ourselves. This is a hack to work around the issue's + // described in `Editor`'s docstring'. + xcb_reparent_window(x11_connection.get(), child_window, parent_window, 0, + 0); + xcb_map_window(x11_connection.get(), child_window); + xcb_flush(x11_connection.get()); + + ShowWindow(win32_handle->get(), SW_SHOWNORMAL); + + return true; +} + void Editor::handle_events() { // Process any remaining events, otherwise we won't be able to interact with // the window @@ -90,30 +126,49 @@ void Editor::handle_events() { } // Handle X11 events - xcb_generic_event_t* event; - while ((event = xcb_poll_for_event(x11_connection.get())) != nullptr) { - // The most significant bit in an event's response type is used to - // indicate whether the event source - switch (event->response_type & ((1 << 7) - 1)) { - case XCB_VISIBILITY_NOTIFY: { - // TODO: Handle configuration changes + // TODO: Check if we should forward other events mostly to prevent + // unnecessary GUI processing in the background + xcb_generic_event_t* generic_event; + while ((generic_event = xcb_poll_for_event(x11_connection.get())) != + nullptr) { + switch (generic_event->response_type & event_type_mask) { + case XCB_CONFIGURE_NOTIFY: { + xcb_configure_notify_event_t event = + *reinterpret_cast( + generic_event); + if (event.window != parent_window) { + break; + } + + // We're purposely not using XEmbed. This has the + // consequence that wine still thinks that any X and Y + // coordinates are relative to the x11 window root instead + // of the parent window provided by the DAW, causing all + // sorts of GUI interactions to break. To alleviate this + // we'll just lie to Wine and tell it that it's located at + // the parent window's location. We'll only send the event + // instead of actually configuring the window. + xcb_configure_notify_event_t translated_event{}; + translated_event.response_type = XCB_CONFIGURE_NOTIFY; + translated_event.event = child_window; + translated_event.window = child_window; + translated_event.width = event.width; + translated_event.height = event.height; + translated_event.x = event.x; + translated_event.y = event.y; + + xcb_send_event(x11_connection.get(), false, child_window, + XCB_EVENT_MASK_STRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, + reinterpret_cast(&translated_event)); + xcb_flush(x11_connection.get()); } break; } - - free(event); + free(generic_event); } } } -std::optional Editor::get_x11_handle() { - if (!win32_handle.has_value()) { - return std::nullopt; - } - - return reinterpret_cast( - GetProp(win32_handle.value().get(), "__wine_x11_whole_window")); -} - LRESULT CALLBACK window_proc(HWND handle, UINT message, WPARAM wParam, @@ -155,6 +210,11 @@ LRESULT CALLBACK window_proc(HWND handle, return DefWindowProc(handle, message, wParam, lParam); } +xcb_window_t get_x11_handle(HWND win32_handle) { + return reinterpret_cast( + GetProp(win32_handle, "__wine_x11_whole_window")); +} + ATOM register_window_class(std::string window_class_name) { WNDCLASSEX window_class{}; diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index a0faf770..896293c5 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -22,7 +22,7 @@ * embed the window in a VST host provided X11 window. * * This was originally implemented using XEmbed. While that sounded like the - * right thing to do, there were a few minor issues with Wine's XEmbed + * right thing to do, there were a few small issues with Wine's XEmbed * implementation. The most important of which is that resizing GUIs sometimes * works fine, but often fails to expand the embedded window's client area * leaving part of the window inaccessible. There are also some a small number @@ -32,6 +32,9 @@ * issues, please let me know and I'll switch to using XEmbed again. * * This workaround was inspired by LinVST. + * + * TODO: Refactor this so we don't need to stage initialization and just add an + * `std::optional` to `PluginBridge` instead */ class Editor { public: @@ -47,11 +50,33 @@ class Editor { * * @param effect The plugin this window is being created for. Used to send * `effEditIdle` messages on a timer. + * + * @return The Win32 window handle of the newly created window. + */ + HWND open(AEffect* effect); + + void close(); + + /** + * Resize the window to match the given size, if open. + * + * @param new_size The rectangle with the plugin's current position. + * + * @return Whether the resizing was succesful. Will return false if the + * editor isn't open. + */ + bool resize(const VstRect& new_size); + + /** + * 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. + * + * @return Whether the embedding was succesful. Will return false if the + * window is not open. */ - HWND open(AEffect* effect, xcb_window_t parent_window_handle); - void close(); + bool embed_into(const size_t parent_window_handle); /** * Pump messages from the editor GUI's event loop until all events are @@ -63,16 +88,15 @@ class Editor { // Needed to handle idle updates through a timer AEffect* plugin; + private: /** * 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. + * The X11 window handle of the window belonging to `win32_handle`. */ - std::optional get_x11_handle(); + xcb_window_t child_window; /** * The Win32 window class registered for the windows window. diff --git a/src/wine-host/plugin-bridge.cpp b/src/wine-host/plugin-bridge.cpp index 518e4722..f34e6a2d 100644 --- a/src/wine-host/plugin-bridge.cpp +++ b/src/wine-host/plugin-bridge.cpp @@ -238,13 +238,20 @@ intptr_t PluginBridge::dispatch_wrapper(AEffect* plugin, option); } break; case effEditOpen: { - const auto x11_handle = reinterpret_cast(data); - const auto win32_handle = editor.open(plugin, x11_handle); + const auto win32_handle = editor.open(plugin); - // The created Win32 window has already been reparented to the host - // provided window - return plugin->dispatcher(plugin, opcode, index, value, - win32_handle, option); + const auto return_value = plugin->dispatcher( + plugin, opcode, index, value, win32_handle, option); + if (return_value == 0) { + return 0; + } + + // If opening the editor was succesful, reparent it to the window + // provided by the DAW + const auto x11_handle = reinterpret_cast(data); + editor.embed_into(x11_handle); + + return return_value; } break; case effEditClose: { const intptr_t return_value = @@ -291,6 +298,17 @@ class HostCallbackDataConverter : DefaultDataConverter { case audioMasterGetTime: return WantsVstTimeInfo{}; break; + case audioMasterSizeWindow: + // Plugins use this opcode to indicate that their editor should + // be resized, so we'll have to update the Wine window + // accordingly + // TODO: Can we just do this when handling XCB_CONFIGURE_NOTIFY + // instead? + editor.resize(VstRect{0, 0, static_cast(value), + static_cast(index)}); + + return DefaultDataConverter::read(opcode, index, value, data); + break; case audioMasterIOChanged: // This is a helpful event that indicates that the VST plugin's // `AEffect` struct has changed. Writing these results back is From 3850e39777db8fecc6241bdb9afb632bc76531df Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 12 Apr 2020 19:15:28 +0200 Subject: [PATCH 3/5] Resize the window together with ConfigureNotify This way we need less hacks and things can't get out of sync. --- src/wine-host/editor.cpp | 18 ++++++------------ src/wine-host/editor.h | 10 ---------- src/wine-host/plugin-bridge.cpp | 11 ----------- 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index bbe51fe4..bc353f55 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -48,18 +48,6 @@ HWND Editor::open(AEffect* effect) { return win32_handle->get(); } -bool Editor::resize(const VstRect& new_size) { - if (!win32_handle.has_value()) { - return false; - } - - SetWindowPos(win32_handle->get(), HWND_TOP, new_size.left, new_size.top, - new_size.right - new_size.left, new_size.bottom - new_size.top, - 0); - - return true; -} - void Editor::close() { // RAII will destroy the window and tiemrs for us win32_handle = std::nullopt; @@ -128,6 +116,7 @@ void Editor::handle_events() { // Handle X11 events // TODO: Check if we should forward other events mostly to prevent // unnecessary GUI processing in the background + // TODO: Check whether drag and drop works out of the box xcb_generic_event_t* generic_event; while ((generic_event = xcb_poll_for_event(x11_connection.get())) != nullptr) { @@ -140,6 +129,11 @@ void Editor::handle_events() { break; } + // The client area of the Win32 window doesn't expand + // automatically + SetWindowPos(win32_handle->get(), HWND_TOP, 0, 0, + event.width, event.height, 0); + // We're purposely not using XEmbed. This has the // consequence that wine still thinks that any X and Y // coordinates are relative to the x11 window root instead diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index 896293c5..14651cae 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -57,16 +57,6 @@ class Editor { void close(); - /** - * Resize the window to match the given size, if open. - * - * @param new_size The rectangle with the plugin's current position. - * - * @return Whether the resizing was succesful. Will return false if the - * editor isn't open. - */ - bool resize(const VstRect& new_size); - /** * Embed the (open) window into a parent window. * diff --git a/src/wine-host/plugin-bridge.cpp b/src/wine-host/plugin-bridge.cpp index f34e6a2d..ea0c8716 100644 --- a/src/wine-host/plugin-bridge.cpp +++ b/src/wine-host/plugin-bridge.cpp @@ -298,17 +298,6 @@ class HostCallbackDataConverter : DefaultDataConverter { case audioMasterGetTime: return WantsVstTimeInfo{}; break; - case audioMasterSizeWindow: - // Plugins use this opcode to indicate that their editor should - // be resized, so we'll have to update the Wine window - // accordingly - // TODO: Can we just do this when handling XCB_CONFIGURE_NOTIFY - // instead? - editor.resize(VstRect{0, 0, static_cast(value), - static_cast(index)}); - - return DefaultDataConverter::read(opcode, index, value, data); - break; case audioMasterIOChanged: // This is a helpful event that indicates that the VST plugin's // `AEffect` struct has changed. Writing these results back is From c0eafc85fd4453292463f69646209d39dc0e9327 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 12 Apr 2020 19:42:12 +0200 Subject: [PATCH 4/5] Re-introduce window resizing while draggin --- src/wine-host/editor.cpp | 18 ++++++++++++++---- src/wine-host/editor.h | 8 ++++++++ src/wine-host/plugin-bridge.cpp | 11 +++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index bc353f55..5a2c23dd 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -48,6 +48,16 @@ HWND Editor::open(AEffect* effect) { return win32_handle->get(); } +bool Editor::resize(const int width, const int height) { + if (!win32_handle.has_value()) { + return false; + } + + SetWindowPos(win32_handle->get(), HWND_TOP, 0, 0, width, height, 0); + + return true; +} + void Editor::close() { // RAII will destroy the window and tiemrs for us win32_handle = std::nullopt; @@ -129,10 +139,6 @@ void Editor::handle_events() { break; } - // The client area of the Win32 window doesn't expand - // automatically - SetWindowPos(win32_handle->get(), HWND_TOP, 0, 0, - event.width, event.height, 0); // We're purposely not using XEmbed. This has the // consequence that wine still thinks that any X and Y @@ -156,6 +162,10 @@ void Editor::handle_events() { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, reinterpret_cast(&translated_event)); xcb_flush(x11_connection.get()); + + // The client area of the Win32 window doesn't expand + // automatically + resize(event.width, event.height); } break; } free(generic_event); diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index 14651cae..43d9dbf4 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -57,6 +57,14 @@ class Editor { void close(); + /** + * Resize the window to match the given size, if open. + * + * @return Whether the resizing was succesful. Will return false if the + * editor isn't open. + */ + bool resize(const int width, const int height); + /** * Embed the (open) window into a parent window. * diff --git a/src/wine-host/plugin-bridge.cpp b/src/wine-host/plugin-bridge.cpp index ea0c8716..13b4ff0e 100644 --- a/src/wine-host/plugin-bridge.cpp +++ b/src/wine-host/plugin-bridge.cpp @@ -298,6 +298,17 @@ class HostCallbackDataConverter : DefaultDataConverter { case audioMasterGetTime: return WantsVstTimeInfo{}; break; + case audioMasterSizeWindow: + // Plugins use this opcode to indicate that their editor should + // be resized. This is handled implicitly when handling the + // ConfigureNotify X11 events but handling this here as well + // makes the resizing look much smoother. + // TODO: Check if this actually makes drag resizing feel better, + // otherwise just remove this + editor.resize(value, index); + + return DefaultDataConverter::read(opcode, index, value, data); + break; case audioMasterIOChanged: // This is a helpful event that indicates that the VST plugin's // `AEffect` struct has changed. Writing these results back is From d4dcc852055330b87b6315ed41c4f186fe458889 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 12 Apr 2020 19:42:30 +0200 Subject: [PATCH 5/5] Update readme to reflect current UGI situation --- README.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 41c0135a..457eae38 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,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: - - Small quality of life related GUI fixes. Wine's XEmbed implementation - doesn't always update the reparented window's client area when the window - gets resized. The current workaround works much better than not doing - anything at all, but it isn't fully reliably yet. - Fix implementation bugs: - - Serum's GUI has redrawing issues when using XEmbed, even though it doesn't - have this issue when running in a standalone window or when simply - reparenting the window without XEmbed. None of the other plugin's I've tried - have this issue, so it might have something to do with d2d1 or the way Serum - uses it. + - Valhalla DSP plugins have a GUI where either the dropdowns appear in the + wrong place or all mouse based events are calculated with incorrect + coordinates. This definitely has to do with the way embedding is handled but + none of the other plugins I've tested have this issue. - Add missing details if any to the architecture section. - Document what this has been tested on and what does or does not work. - Document wine32 support.