diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 7ea18191..76fdbce3 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -139,6 +139,79 @@ inline T read_object(Socket& socket) { return read_object(socket, buffer); } +/** + * Generate a unique base directory that can be used as a prefix for all Unix + * domain socket endpoints used in `Vst2PluginBridge`/`Vst2Bridge`. This will + * usually return `/run/user//yabridge--/`. + * + * Sockets for group hosts are handled separately. See + * `../plugin/utils.h:generate_group_endpoint` for more information on those. + * + * @param plugin_name The name of the plugin we're generating endpoints for. + * Used as a visual indication of what plugin is using this endpoint. + */ +boost::filesystem::path generate_endpoint_base(const std::string& plugin_name); + +/** + * Manages all the sockets used for communicating between the plugin and the + * Wine host. Every plugin will get its own directory (the socket endpoint base + * directory), and all socket endpoints are created within this directory. This + * is usually `/run/user//yabridge--/`. + */ +class Sockets { + public: + /** + * Sets up the the base directory for the sockets. Classes inheriting this + * should set up their sockets here. + * + * @param endpoint_base_dir The base directory that will be used for the + * Unix domain sockets. + * + * @see Sockets::connect + */ + Sockets(const boost::filesystem::path& endpoint_base_dir) + : base_dir(endpoint_base_dir) {} + + /** + * Shuts down and closes all sockets and then cleans up the directory + * containing the socket endpoints when yabridge shuts down if it still + * exists. + * + * @note Classes overriding this should call `close()` in their destructor. + */ + virtual ~Sockets() { + 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 + */ + virtual void connect() = 0; + + /** + * Shut down and close all sockets. Called during the destructor and also + * explicitly called when shutting down a plugin in a group host process. + * + * It should be safe to call this function more than once, and it should be + * called in the overridden class's destructor. + */ + virtual void close() = 0; + + /** + * The base directory for our socket endpoints. All `*_endpoint` variables + * below are files within this directory. + */ + const boost::filesystem::path base_dir; +}; + /** * A single, long-living socket */ @@ -154,7 +227,7 @@ class SocketHandler { * connections will be accepted when `connect()` gets called. This should * be set to `true` on the plugin side, and `false` on the Wine host side. * - * @see Vst2Sockets::connect + * @see Sockets::connect */ SocketHandler(boost::asio::io_context& io_context, boost::asio::local::stream_protocol::endpoint endpoint, @@ -304,16 +377,3 @@ class SocketHandler { */ std::optional acceptor; }; - -/** - * Generate a unique base directory that can be used as a prefix for all Unix - * domain socket endpoints used in `Vst2PluginBridge`/`Vst2Bridge`. This will - * usually return `/run/user//yabridge--/`. - * - * Sockets for group hosts are handled separately. See - * `../plugin/utils.h:generate_group_endpoint` for more information on those. - * - * @param plugin_name The name of the plugin we're generating endpoints for. - * Used as a visual indication of what plugin is using this endpoint. - */ -boost::filesystem::path generate_endpoint_base(const std::string& plugin_name); diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index 82218654..9706688c 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -124,7 +124,7 @@ class EventHandler { * connections will be accepted when `connect()` gets called. This should * be set to `true` on the plugin side, and `false` on the Wine host side. * - * @see Vst2Sockets::connect + * @see Sockets::connect */ EventHandler(boost::asio::io_context& io_context, boost::asio::local::stream_protocol::endpoint endpoint, @@ -448,7 +448,7 @@ class EventHandler { /** * This acceptor will be used once synchronously on the listening side - * during `Vst2Sockets::connect()`. When `EventHandler::receive_events()` is + * during `Sockets::connect()`. When `EventHandler::receive_events()` is * then called, we'll recreate the acceptor to asynchronously listen for new * incoming socket connections on `endpoint` using. This is important, * because on the case of `vst_host_callback` the acceptor is first accepts @@ -467,9 +467,7 @@ class EventHandler { /** * Manages all the sockets used for communicating between the plugin and the - * Wine host. Every plugin will get its own directory (the socket endpoint base - * directory), and all socket endpoints are created within this directory. This - * is usually `/run/user//yabridge--/`. + * Wine host when hosting a VST2 plugin. * * On the plugin side this class should be initialized with `listen` set to * `true` before launching the Wine VST host. This will start listening on the @@ -480,7 +478,7 @@ class EventHandler { * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ template -class Vst2Sockets { +class Vst2Sockets : public Sockets { public: /** * Sets up the sockets using the specified base directory. The sockets won't @@ -499,7 +497,7 @@ class Vst2Sockets { Vst2Sockets(boost::asio::io_context& io_context, const boost::filesystem::path& endpoint_base_dir, bool listen) - : base_dir(endpoint_base_dir), + : Sockets(endpoint_base_dir), host_vst_dispatch(io_context, (base_dir / "host_vst_dispatch.sock").string(), listen), @@ -517,36 +515,9 @@ class Vst2Sockets { (base_dir / "host_vst_control.sock").string(), listen) {} - /** - * Cleans up the directory containing the socket endpoints when yabridge - * shuts down if it still exists. - */ - ~Vst2Sockets() { - // Manually close all sockets so we break out of any blocking operations - // that may still be active - host_vst_dispatch.close(); - vst_host_callback.close(); - host_vst_parameters.close(); - host_vst_process_replacing.close(); - host_vst_control.close(); + ~Vst2Sockets() { close(); } - // 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 - 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() override { host_vst_dispatch.connect(); vst_host_callback.connect(); host_vst_parameters.connect(); @@ -554,11 +525,15 @@ class Vst2Sockets { host_vst_control.connect(); } - /** - * The base directory for our socket endpoints. All `*_endpoint` variables - * below are files within this directory. - */ - const boost::filesystem::path base_dir; + void close() override { + // Manually close all sockets so we break out of any blocking operations + // that may still be active + host_vst_dispatch.close(); + vst_host_callback.close(); + host_vst_parameters.close(); + host_vst_process_replacing.close(); + host_vst_control.close(); + } // The naming convention for these sockets is `__`. For // instance the socket named `host_vst_dispatch` forwards diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index b283450b..3af6ef9a 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -87,7 +87,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 Vst2Sockets& sockets) + const Sockets& sockets) : HostProcess(io_context, logger), plugin_arch(find_vst_architecture(plugin_path)), host_path(find_vst_host(plugin_arch, false)), @@ -134,7 +134,7 @@ void IndividualHost::terminate() { GroupHost::GroupHost(boost::asio::io_context& io_context, Logger& logger, fs::path plugin_path, - Vst2Sockets& sockets, + Sockets& sockets, std::string group_name) : HostProcess(io_context, logger), plugin_arch(find_vst_architecture(plugin_path)), @@ -251,8 +251,8 @@ bool GroupHost::running() { void GroupHost::terminate() { // There's no need to manually terminate group host processes as they will // shut down automatically after all plugins have exited. Manually closing - // the dispatch socket will cause the associated plugin to exit. - sockets.host_vst_dispatch.close(); + // the sockets will cause the associated plugin to exit. + sockets.close(); } bool pid_running(pid_t pid) { diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index 4130dc90..8ce79dad 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -25,10 +25,7 @@ #include #include -// TODO: Those host process implementation now directly uses the Vst2Sockets and -// thus requires `communication/vst2.h`. We should create a simple common -// interface for this instead. -#include "../common/communication/vst2.h" +#include "../common/communication/common.h" #include "../common/logging.h" #include "utils.h" @@ -130,7 +127,7 @@ class IndividualHost : public HostProcess { IndividualHost(boost::asio::io_context& io_context, Logger& logger, boost::filesystem::path plugin_path, - const Vst2Sockets& sockets); + const Sockets& sockets); PluginArchitecture architecture() override; boost::filesystem::path path() override; @@ -172,7 +169,7 @@ class GroupHost : public HostProcess { GroupHost(boost::asio::io_context& io_context, Logger& logger, boost::filesystem::path plugin_path, - Vst2Sockets& socket_endpoint, + Sockets& socket_endpoint, std::string group_name); PluginArchitecture architecture() override; @@ -206,7 +203,7 @@ class GroupHost : public HostProcess { * The associated sockets for the plugin we're hosting. This is used to * terminate the plugin. */ - Vst2Sockets& sockets; + Sockets& sockets; /** * A thread that waits for the group host to have started and then ask it to diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 983f9a0f..9ddfe3b0 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -59,7 +59,7 @@ class Vst2Bridge { * @param plugin_dll_path A (Unix style) path to the VST plugin .dll file to * load. * @param endpoint_base_dir The base directory used for the socket - * endpoints. See `Vst2Sockets` for more information. + * endpoints. See `Sockets` for more information. * * @note The object has to be constructed from the same thread that calls * `main_context.run()`.