From eebfceff560d2653502e5fc69a8c1892755c4eb3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 19 Mar 2020 00:58:03 +0100 Subject: [PATCH] Implement the rest of the GUI events The GUI is still not updating though. --- src/common/events.h | 6 ++ src/common/logging.cpp | 6 ++ src/common/serialization.h | 24 +++++- src/plugin/host-bridge.cpp | 134 ++++++++++++++++---------------- src/plugin/host-bridge.h | 5 ++ src/wine-host/plugin-bridge.cpp | 43 ++++++---- 6 files changed, 134 insertions(+), 84 deletions(-) diff --git a/src/common/events.h b/src/common/events.h index 08d36718..9a86507c 100644 --- a/src/common/events.h +++ b/src/common/events.h @@ -217,6 +217,7 @@ void passthrough_event(boost::asio::local::stream_protocol::socket& socket, return &events.as_c_events(); }, [&](WantsChunkBuffer&) -> void* { return string_buffer.data(); }, + [&](const WantsVstRect&) -> void* { return string_buffer.data(); }, [&](const WantsVstTimeInfo&) -> void* { return nullptr; }, [&](WantsString&) -> void* { return string_buffer.data(); }}, event.payload); @@ -264,6 +265,11 @@ void passthrough_event(boost::asio::local::stream_protocol::socket& socket, return std::string(*static_cast(data), return_value); }, + [&](WantsVstRect&) -> EventResposnePayload { + // The plugin has written a pointer to a VstRect struct + // into the data poitner + return **static_cast(data); + }, [&](WantsVstTimeInfo&) -> EventResposnePayload { // Not sure why the VST API has twenty different ways of // returning structs, but in this case the value returned diff --git a/src/common/logging.cpp b/src/common/logging.cpp index 5bd28058..4ab83351 100644 --- a/src/common/logging.cpp +++ b/src/common/logging.cpp @@ -175,6 +175,7 @@ void Logger::log_event(bool is_dispatch, [&](const WantsChunkBuffer&) { message << ""; }, + [&](const WantsVstRect&) { message << ""; }, [&](const WantsVstTimeInfo&) { message << ""; }, [&](const WantsString&) { message << ""; }}, payload); @@ -210,6 +211,11 @@ void Logger::log_event_response(bool is_dispatch, } }, [&](const AEffect&) { message << ", "; }, + [&](const VstRect& rect) { + message << ", {l: " << rect.left << ", t: " << rect.top + << ", r: " << rect.right + << ", b: " << rect.bottom << "}"; + }, [&](const VstTimeInfo&) { message << ", "; }}, payload); diff --git a/src/common/serialization.h b/src/common/serialization.h index d40e1976..a82cee57 100644 --- a/src/common/serialization.h +++ b/src/common/serialization.h @@ -83,6 +83,14 @@ void serialize(S& s, AEffect& plugin) { s.value4b(plugin.version); } +template +void serialize(S& s, VstRect& rect) { + s.value2b(rect.top); + s.value2b(rect.left); + s.value2b(rect.right); + s.value2b(rect.bottom); +} + template void serialize(S& s, VstTimeInfo& time_info) { s.value8b(time_info.samplePos); @@ -152,9 +160,15 @@ class alignas(16) DynamicVstEvents { */ struct WantsChunkBuffer {}; +/** + * Marker struct to indicate that the event handler will write a pointer to a + * `VstRect` struct into the void pointer. + */ +struct WantsVstRect {}; + /** * Marker struct to indicate that the event handler will return a pointer to a - * `VstTiemInfo` struct that should be returned transfered. + * `VstTimeInfo` struct that should be returned transfered. */ struct WantsVstTimeInfo {}; @@ -198,6 +212,7 @@ using EventPayload = std::variant; @@ -218,8 +233,8 @@ void serialize(S& s, EventPayload& payload) { events.events, max_midi_events, [](S& s, VstEvent& event) { s.container1b(event.dump); }); }, - [](S&, WantsChunkBuffer&) {}, [](S&, WantsVstTimeInfo&) {}, - [](S&, WantsString&) {}}); + [](S&, WantsChunkBuffer&) {}, [](S&, WantsVstRect&) {}, + [](S&, WantsVstTimeInfo&) {}, [](S&, WantsString&) {}}); } /** @@ -275,7 +290,7 @@ struct Event { * - An X11 window pointer for the editor window. */ using EventResposnePayload = - std::variant; + std::variant; template void serialize(S& s, EventResposnePayload& payload) { @@ -289,6 +304,7 @@ void serialize(S& s, EventResposnePayload& payload) { s.text1b(string, binary_buffer_size); }, [](S& s, AEffect& effect) { s.object(effect); }, + [](S& s, VstRect& rect) { s.object(rect); }, [](S& s, VstTimeInfo& time_info) { s.object(time_info); }}); } diff --git a/src/plugin/host-bridge.cpp b/src/plugin/host-bridge.cpp index cc66dcca..37bef267 100644 --- a/src/plugin/host-bridge.cpp +++ b/src/plugin/host-bridge.cpp @@ -172,8 +172,9 @@ HostBridge::HostBridge(audioMasterCallback host_callback) class DispatchDataConverter : DefaultDataConverter { public: - DispatchDataConverter(std::vector& chunk_data) - : chunk(chunk_data) {} + DispatchDataConverter(std::vector& chunk_data, + VstRect& editor_rectangle) + : chunk(chunk_data), rect(editor_rectangle) {} std::optional read(const int opcode, const intptr_t value, @@ -181,18 +182,8 @@ class DispatchDataConverter : DefaultDataConverter { // There are some events that need specific structs that we can't simply // serialize as a string because they might contain null bytes switch (opcode) { - // TODO: Add GUI support. These events are just disabled for now to - // ensure everything else works first. - case effEditTop: - case effEditIdle: - case effEditClose: case effEditGetRect: - std::cerr << "Got opcode " - << opcode_to_string(true, opcode) - .value_or(std::to_string(opcode)) - << "), ignoring..." << std::endl; - - return std::nullopt; + return WantsVstRect(); break; case effEditOpen: // The host will have passed us an X11 window handle in the void @@ -222,18 +213,31 @@ class DispatchDataConverter : DefaultDataConverter { void write(const int opcode, void* data, const EventResult& response) { switch (opcode) { - case effGetChunk: + case effEditGetRect: { + // Write back the (hopefully) updated editor dimensions + const auto new_rect = std::get(response.payload); + rect = new_rect; + + // TODO: Maybe the host expects this field to be always up to + // date, so if the editor resizes itself then the + // `VstRect` behind this pointer should change as well + // without any additional dispatch calls. If that's the + // case, then we can probably reuse + // `audioMasterSizeWindow`. + *static_cast(data) = ▭ + break; + } + case effGetChunk: { // Write the chunk data to some publically accessible place in // `HostBridge` and write a pointer to that struct to the data // pointer - { - std::string buffer = - std::get(response.payload); - chunk.assign(buffer.begin(), buffer.end()); - *static_cast(data) = chunk.data(); - } + std::string buffer = std::get(response.payload); + chunk.assign(buffer.begin(), buffer.end()); + + *static_cast(data) = chunk.data(); break; + } default: DefaultDataConverter::write(opcode, data, response); break; @@ -246,6 +250,7 @@ class DispatchDataConverter : DefaultDataConverter { private: std::vector& chunk; + VstRect& rect; }; /** @@ -258,62 +263,61 @@ intptr_t HostBridge::dispatch(AEffect* /*plugin*/, intptr_t value, void* data, float option) { - DispatchDataConverter converter(chunk_data); + DispatchDataConverter converter(chunk_data, editor_rectangle); // Some events need some extra handling // TODO: Handle GUI closing? switch (opcode) { break; - case effClose: + case effClose: { // TODO: Gracefully close the editor? // TODO: Check whether the sockets and the endpoint are closed // correctly - { - // Allow the plugin to handle its own shutdown. I've found a few - // plugins that work fine except for that they crash during - // shutdown. This shouldn't have any negative side effects since - // state has already been saved before this and all resources - // are cleaned up properly. Still not sure if this is a good way - // to handle this. - intptr_t return_value = 1; - try { - return_value = send_event( - host_vst_dispatch, dispatch_semaphore, converter, - std::pair(logger, true), opcode, index, - value, data, option); - } catch (const boost::system::system_error& a) { - // Thrown when the socket gets closed because the VST plugin - // loaded into the Wine process crashed during shutdown - logger.log("The plugin crashed during shutdown, ignoring"); - } - // Boost.Process will send SIGKILL to the Wien host for us when - // this class gets destroyed. Because the process is running a - // few threads Wine will say something about a segfault - // (probably related to `std::terminate`), but this doesn't seem - // to have any negative impact - - // The `stop()` method will cause the IO context to just drop - // all of its work and immediately and not throw any exceptions - // that would have been caused by pipes and sockets being closed - io_context.stop(); - - // `std::thread`s are not interruptable, and since we're doing - // blocking synchronous reads there's no way to interrupt them. - // If we don't detach them then the runtime will call - // `std::terminate` for us. The workaround here is to simply - // detach the threads and then close all sockets. This will - // cause them to throw exceptions which we then catch and - // ignore. Please let me know if there's a better way to handle - // this.q - host_callback_handler.detach(); - wine_io_handler.detach(); - - delete this; - - return return_value; + // Allow the plugin to handle its own shutdown. I've found a few + // plugins that work fine except for that they crash during + // shutdown. This shouldn't have any negative side effects since + // state has already been saved before this and all resources are + // cleaned up properly. Still not sure if this is a good way to + // handle this. + intptr_t return_value = 1; + try { + return_value = + send_event(host_vst_dispatch, dispatch_semaphore, converter, + std::pair(logger, true), opcode, + index, value, data, option); + } catch (const boost::system::system_error& a) { + // Thrown when the socket gets closed because the VST plugin + // loaded into the Wine process crashed during shutdown + logger.log("The plugin crashed during shutdown, ignoring"); } + + // Boost.Process will send SIGKILL to the Wien host for us when this + // class gets destroyed. Because the process is running a few + // threads Wine will say something about a segfault (probably + // related to `std::terminate`), but this doesn't seem to have any + // negative impact + + // The `stop()` method will cause the IO context to just drop + // all of its work and immediately and not throw any exceptions + // that would have been caused by pipes and sockets being closed + io_context.stop(); + + // `std::thread`s are not interruptable, and since we're doing + // blocking synchronous reads there's no way to interrupt them. If + // we don't detach them then the runtime will call `std::terminate` + // for us. The workaround here is to simply detach the threads and + // then close all sockets. This will cause them to throw exceptions + // which we then catch and ignore. Please let me know if there's a + // better way to handle this.q + host_callback_handler.detach(); + wine_io_handler.detach(); + + delete this; + + return return_value; break; + }; } // TODO: Maybe reuse buffers here when dealing with chunk data diff --git a/src/plugin/host-bridge.h b/src/plugin/host-bridge.h index b87fe711..8087360d 100644 --- a/src/plugin/host-bridge.h +++ b/src/plugin/host-bridge.h @@ -111,6 +111,11 @@ class HostBridge { * `effGetChunk` event. */ std::vector chunk_data; + /** + * The VST host will expect to be returned a pointer to a struct that stores + * the dimensions of the editor window. + */ + VstRect editor_rectangle; private: /** diff --git a/src/wine-host/plugin-bridge.cpp b/src/wine-host/plugin-bridge.cpp index c0b2437e..4aadf2f5 100644 --- a/src/wine-host/plugin-bridge.cpp +++ b/src/wine-host/plugin-bridge.cpp @@ -195,28 +195,41 @@ intptr_t PluginBridge::dispatch_wrapper(AEffect* plugin, intptr_t value, void* data, float option) { - // TODO: editEffClose // We have to intercept GUI open calls since we can't use // the X11 window handle passed by the host - if (opcode == effEditOpen) { - const auto win32_handle = editor.open(); + switch (opcode) { + case effEditOpen: { + const auto win32_handle = editor.open(); - // 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; + // 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(data); + editor.embed_into(x11_handle); + + return return_value; + break; } + case effEditClose: { + const intptr_t return_value = + plugin->dispatcher(plugin, opcode, index, value, data, option); - const auto x11_handle = reinterpret_cast(data); - editor.embed_into(x11_handle); + editor.close(); - return return_value; + return return_value; + break; + } + default: + return plugin->dispatcher(plugin, opcode, index, value, data, + option); + break; } - - return plugin->dispatcher(plugin, opcode, index, value, data, option); } void PluginBridge::wait() {