diff --git a/src/common/communication.cpp b/src/common/communication.cpp index 543202ae..1ad60440 100644 --- a/src/common/communication.cpp +++ b/src/common/communication.cpp @@ -81,100 +81,6 @@ intptr_t DefaultDataConverter::return_value(const int /*opcode*/, return original; } -EventHandler::EventHandler( - boost::asio::io_context& io_context, - boost::asio::local::stream_protocol::endpoint endpoint, - bool listen) - : io_context(io_context), endpoint(endpoint), socket(io_context) { - if (listen) { - fs::create_directories(fs::path(endpoint.path()).parent_path()); - acceptor.emplace(io_context, endpoint); - } -} - -void EventHandler::connect() { - if (acceptor) { - acceptor->accept(socket); - - // As mentioned in `acceptor's` docstring, this acceptor will be - // recreated in `receive()` on another context, and potentially on the - // other side of the connection in the case of `vst_host_callback` - acceptor.reset(); - fs::remove(endpoint.path()); - } else { - socket.connect(endpoint); - } -} - -void EventHandler::close() { - socket.shutdown(boost::asio::local::stream_protocol::socket::shutdown_both); - socket.close(); -} - -Sockets::Sockets(boost::asio::io_context& io_context, - const boost::filesystem::path& endpoint_base_dir, - bool listen) - : base_dir(endpoint_base_dir), - host_vst_dispatch(io_context, - (base_dir / "host_vst_dispatch.sock").string(), - listen), - host_vst_dispatch_midi_events( - io_context, - (base_dir / "host_vst_dispatch_midi_events.sock").string(), - listen), - vst_host_callback(io_context, - (base_dir / "vst_host_callback.sock").string(), - listen), - host_vst_parameters(io_context), - host_vst_process_replacing(io_context), - host_vst_control(io_context), - host_vst_parameters_endpoint( - (base_dir / "host_vst_parameters.sock").string()), - host_vst_process_replacing_endpoint( - (base_dir / "host_vst_process_replacing.sock").string()), - host_vst_control_endpoint((base_dir / "host_vst_control.sock").string()) { - if (listen) { - fs::create_directories(base_dir); - - acceptors = Acceptors{ - .host_vst_parameters{io_context, host_vst_parameters_endpoint}, - .host_vst_process_replacing{io_context, - host_vst_process_replacing_endpoint}, - .host_vst_control{io_context, host_vst_control_endpoint}, - }; - } -} - -Sockets::~Sockets() { - // Only clean if we're the ones who have created these files, although it - // should not cause any harm to also do this on the Wine side - if (acceptors) { - try { - fs::remove_all(base_dir); - } catch (const fs::filesystem_error&) { - // There should not be any filesystem errors since only one side - // removes the files, but if we somehow can't delete the file then - // we can just silently ignore this - } - } -} - -void Sockets::connect() { - host_vst_dispatch.connect(); - host_vst_dispatch_midi_events.connect(); - vst_host_callback.connect(); - if (acceptors) { - acceptors->host_vst_parameters.accept(host_vst_parameters); - acceptors->host_vst_process_replacing.accept( - host_vst_process_replacing); - acceptors->host_vst_control.accept(host_vst_control); - } else { - host_vst_parameters.connect(host_vst_parameters_endpoint); - host_vst_process_replacing.connect(host_vst_process_replacing_endpoint); - host_vst_control.connect(host_vst_control_endpoint); - } -} - boost::filesystem::path generate_endpoint_base(const std::string& plugin_name) { fs::path temp_directory = get_temporary_directory(); diff --git a/src/common/communication.h b/src/common/communication.h index 5664f551..4df0966a 100644 --- a/src/common/communication.h +++ b/src/common/communication.h @@ -18,7 +18,6 @@ #include #include -#include #include #include @@ -204,7 +203,11 @@ class DefaultDataConverter { * above. Similarly, the `EventHandler::receive()` method first sets up * asynchronous listeners for the socket endpoint, and then block and handle * events until the main socket is closed. + * + * @tparam Thread The thread implementation to use. On the Linux side this + * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ +template class EventHandler { public: /** @@ -223,20 +226,44 @@ class EventHandler { */ EventHandler(boost::asio::io_context& io_context, boost::asio::local::stream_protocol::endpoint endpoint, - bool listen); + bool listen) + : io_context(io_context), endpoint(endpoint), socket(io_context) { + if (listen) { + boost::filesystem::create_directories( + boost::filesystem::path(endpoint.path()).parent_path()); + acceptor.emplace(io_context, endpoint); + } + } /** * Depending on the value of the `listen` argument passed to the * constructor, either accept connections made to the sockets on the Linux * side or connect to the sockets on the Wine side */ - void connect(); + void connect() { + if (acceptor) { + acceptor->accept(socket); + + // As mentioned in `acceptor's` docstring, this acceptor will be + // recreated in `receive()` on another context, and potentially on + // the other side of the connection in the case of + // `vst_host_callback` + acceptor.reset(); + boost::filesystem::remove(endpoint.path()); + } else { + socket.connect(endpoint); + } + } /** * Close the socket. Both sides that are actively listening will be thrown a * `boost::system_error` when this happens. */ - void close(); + void close() { + socket.shutdown( + boost::asio::local::stream_protocol::socket::shutdown_both); + socket.close(); + } /** * Serialize and send an event over a socket. This is used for both the host @@ -384,7 +411,7 @@ class EventHandler { // This works the exact same was as `active_plugins` and // `next_plugin_id` in `GroupBridge` - std::map active_secondary_requests{}; + std::map active_secondary_requests{}; std::atomic_size_t next_request_id{}; std::mutex active_secondary_requests_mutex{}; accept_requests( @@ -395,7 +422,7 @@ class EventHandler { // We have to make sure to keep moving these sockets into the // threads that will handle them std::lock_guard lock(active_secondary_requests_mutex); - active_secondary_requests[request_id] = std::jthread( + active_secondary_requests[request_id] = Thread( [&, request_id](boost::asio::local::stream_protocol::socket secondary_socket) { // TODO: Factor this out @@ -427,15 +454,14 @@ class EventHandler { active_secondary_requests_mutex); // The join is implicit because we're using - // std::jthread + // std::jthread/Win32Thread active_secondary_requests.erase(request_id); }); }, std::move(secondary_socket)); }); - std::jthread secondary_requests_handler( - [&]() { secondary_context.run(); }); + Thread secondary_requests_handler([&]() { secondary_context.run(); }); while (true) { try { @@ -555,7 +581,11 @@ class EventHandler { * `true` before launching the Wine VST host. This will start listening on the * sockets, and the call to `connect()` will then accept any incoming * connections. + * + * @tparam Thread The thread implementation to use. On the Linux side this + * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ +template class Sockets { public: /** @@ -574,20 +604,78 @@ class Sockets { */ Sockets(boost::asio::io_context& io_context, const boost::filesystem::path& endpoint_base_dir, - bool listen); + bool listen) + : base_dir(endpoint_base_dir), + host_vst_dispatch(io_context, + (base_dir / "host_vst_dispatch.sock").string(), + listen), + host_vst_dispatch_midi_events( + io_context, + (base_dir / "host_vst_dispatch_midi_events.sock").string(), + listen), + vst_host_callback(io_context, + (base_dir / "vst_host_callback.sock").string(), + listen), + host_vst_parameters(io_context), + host_vst_process_replacing(io_context), + host_vst_control(io_context), + host_vst_parameters_endpoint( + (base_dir / "host_vst_parameters.sock").string()), + host_vst_process_replacing_endpoint( + (base_dir / "host_vst_process_replacing.sock").string()), + host_vst_control_endpoint( + (base_dir / "host_vst_control.sock").string()) { + if (listen) { + boost::filesystem::create_directories(base_dir); + + acceptors = Acceptors{ + .host_vst_parameters{io_context, host_vst_parameters_endpoint}, + .host_vst_process_replacing{ + io_context, host_vst_process_replacing_endpoint}, + .host_vst_control{io_context, host_vst_control_endpoint}, + }; + } + } /** * Cleans up the directory containing the socket endpoints when yabridge * shuts down if it still exists. */ - ~Sockets(); + ~Sockets() { + // Only clean if we're the ones who have created these files, although + // it should not cause any harm to also do this on the Wine side + if (acceptors) { + try { + boost::filesystem::remove_all(base_dir); + } catch (const boost::filesystem::filesystem_error&) { + // There should not be any filesystem errors since only one side + // removes the files, but if we somehow can't delete the file + // then we can just silently ignore this + } + } + } /** * Depending on the value of the `listen` argument passed to the * constructor, either accept connections made to the sockets on the Linux * side or connect to the sockets on the Wine side */ - void connect(); + void connect() { + host_vst_dispatch.connect(); + host_vst_dispatch_midi_events.connect(); + vst_host_callback.connect(); + if (acceptors) { + acceptors->host_vst_parameters.accept(host_vst_parameters); + acceptors->host_vst_process_replacing.accept( + host_vst_process_replacing); + acceptors->host_vst_control.accept(host_vst_control); + } else { + host_vst_parameters.connect(host_vst_parameters_endpoint); + host_vst_process_replacing.connect( + host_vst_process_replacing_endpoint); + host_vst_control.connect(host_vst_control_endpoint); + } + } /** * The base directory for our socket endpoints. All `*_endpoint` variables @@ -604,19 +692,19 @@ class Sockets { * The socket that forwards all `dispatcher()` calls from the VST host to * the plugin. */ - EventHandler host_vst_dispatch; + EventHandler host_vst_dispatch; /** * Used specifically for the `effProcessEvents` opcode. This is needed * because the Win32 API is designed to block during certain GUI * interactions such as resizing a window or opening a dropdown. Without * this MIDI input would just stop working at times. */ - EventHandler host_vst_dispatch_midi_events; + EventHandler host_vst_dispatch_midi_events; /** * The socket that forwards all `audioMaster()` calls from the Windows VST * plugin to the host. */ - EventHandler vst_host_callback; + EventHandler vst_host_callback; /** * Used for both `getParameter` and `setParameter` since they mostly * overlap. diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index fa46d623..49c0492a 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -83,7 +83,7 @@ void HostProcess::async_log_pipe_lines(patched_async_pipe& pipe, IndividualHost::IndividualHost(boost::asio::io_context& io_context, Logger& logger, fs::path plugin_path, - const Sockets& sockets) + const Sockets& sockets) : HostProcess(io_context, logger), plugin_arch(find_vst_architecture(plugin_path)), host_path(find_vst_host(plugin_arch, false)), @@ -130,7 +130,7 @@ void IndividualHost::terminate() { GroupHost::GroupHost(boost::asio::io_context& io_context, Logger& logger, fs::path plugin_path, - Sockets& sockets, + Sockets& sockets, std::string group_name) : HostProcess(io_context, logger), plugin_arch(find_vst_architecture(plugin_path)), diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index 840b3a74..5826c987 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -25,8 +25,8 @@ #include #include -#include "../common/logging.h" #include "../common/communication.h" +#include "../common/logging.h" #include "utils.h" /** @@ -127,7 +127,7 @@ class IndividualHost : public HostProcess { IndividualHost(boost::asio::io_context& io_context, Logger& logger, boost::filesystem::path plugin_path, - const Sockets& sockets); + const Sockets& sockets); PluginArchitecture architecture() override; boost::filesystem::path path() override; @@ -169,7 +169,7 @@ class GroupHost : public HostProcess { GroupHost(boost::asio::io_context& io_context, Logger& logger, boost::filesystem::path plugin_path, - Sockets& socket_endpoint, + Sockets& socket_endpoint, std::string group_name); PluginArchitecture architecture() override; @@ -192,7 +192,7 @@ class GroupHost : public HostProcess { * The associated sockets for the plugin we're hosting. This is used to * terminate the plugin. */ - Sockets& sockets; + Sockets& sockets; /** * A thread that waits for the group host to have started and then ask it to diff --git a/src/plugin/plugin-bridge.h b/src/plugin/plugin-bridge.h index e62fc750..468c8fd6 100644 --- a/src/plugin/plugin-bridge.h +++ b/src/plugin/plugin-bridge.h @@ -124,7 +124,7 @@ class PluginBridge { void log_init_message(); boost::asio::io_context io_context; - Sockets sockets; + Sockets sockets; /** * The thread that handles host callbacks. diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 55e1cd3d..ba02d8d0 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -189,7 +189,7 @@ class Vst2Bridge { /** * All sockets used for communicating with this specific plugin. */ - Sockets sockets; + Sockets sockets; /** * The thread that specifically handles `effProcessEvents` opcodes so the