diff --git a/meson.build b/meson.build index 83da7c3a..16fde6c0 100644 --- a/meson.build +++ b/meson.build @@ -65,6 +65,7 @@ shared_library( 'src/common/logging.cpp', 'src/common/serialization.cpp', 'src/plugin/configuration.cpp', + 'src/plugin/host-process.cpp', 'src/plugin/plugin.cpp', 'src/plugin/plugin-bridge.cpp', 'src/plugin/utils.cpp', diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp new file mode 100644 index 00000000..d4184d4d --- /dev/null +++ b/src/plugin/host-process.cpp @@ -0,0 +1,254 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "host-process.h" + +#include +#include +#include +#include + +#include "../common/communication.h" + +namespace bp = boost::process; +namespace fs = boost::filesystem; + +/** + * Simple helper function around `boost::process::child` that launches the host + * application (`*.exe`) wrapped in winedbg if compiling with + * `-Duse-winedbg=true`. Keep in mind that winedbg does not handle arguments + * containing spaces, so most Windows paths will be split up into multiple + * arugments. + */ +template +bp::child launch_host(fs::path host_path, Args&&... args) { + return bp::child( +#ifdef USE_WINEDBG + // This is set up for KDE Plasma. Other desktop environments and + // window managers require some slight modifications to spawn a + // detached terminal emulator. + "/usr/bin/kstart5", "konsole", "--", "-e", "winedbg", "--gdb", + host_path.string() + ".so", +#else + host_path, +#endif + std::forward(args)...); +} + +HostProcess::HostProcess(boost::asio::io_context& io_context, Logger& logger) + : stdout_pipe(io_context), stderr_pipe(io_context), logger(logger) { + // Print the Wine host's STDOUT and STDERR streams to the log file. This + // should be done before trying to accept the sockets as otherwise we will + // miss all output. + async_log_pipe_lines(stdout_pipe, stdout_buffer, "[Wine STDOUT] "); + async_log_pipe_lines(stderr_pipe, stderr_buffer, "[Wine STDERR] "); +} + +void HostProcess::async_log_pipe_lines(patched_async_pipe& pipe, + boost::asio::streambuf& buffer, + std::string prefix) { + boost::asio::async_read_until( + pipe, buffer, '\n', + [&, prefix](const boost::system::error_code& error, size_t) { + // When we get an error code then that likely means that the pipe + // has been clsoed and we have reached the end of the file + if (error.failed()) { + return; + } + + std::string line; + std::getline(std::istream(&buffer), line); + logger.log(prefix + line); + + async_log_pipe_lines(pipe, buffer, prefix); + }); +} + +IndividualHost::IndividualHost(boost::asio::io_context& io_context, + Logger& logger, + fs::path plugin_path, + fs::path socket_endpoint) + : HostProcess(io_context, logger), + plugin_arch(find_vst_architecture(plugin_path)), + host_path(find_vst_host(plugin_arch, false)), + host(launch_host(host_path, +#ifdef USE_WINEDBG + plugin_path.filename(), +#else + plugin_path, +#endif + socket_endpoint, + bp::env = set_wineprefix(), + bp::std_out = stdout_pipe, + bp::std_err = stderr_pipe +#ifdef USE_WINEDBG + , // winedbg has no reliable way to escape spaces, so + // we'll start the process in the plugin's directory + bp::start_dir = plugin_path.parent_path() +#endif + )) { +#ifdef USE_WINEDBG + if (plugin_path.string().find(' ') != std::string::npos) { + logger.log("Warning: winedbg does not support paths containing spaces"); + } +#endif +} + +PluginArchitecture IndividualHost::architecture() { + return plugin_arch; +} + +fs::path IndividualHost::path() { + return host_path; +} + +bool IndividualHost::running() { + return host.running(); +} + +void IndividualHost::terminate() { + host.terminate(); + host.wait(); +} + +GroupHost::GroupHost( + boost::asio::io_context& io_context, + Logger& logger, + fs::path plugin_path, + fs::path socket_endpoint, + std::string group_name, + boost::asio::local::stream_protocol::socket& host_vst_dispatch) + : HostProcess(io_context, logger), + plugin_arch(find_vst_architecture(plugin_path)), + host_path(find_vst_host(plugin_arch, true)), + host_vst_dispatch(host_vst_dispatch) { +#ifdef USE_WINEDBG + if (plugin_path.string().find(' ') != std::string::npos) { + logger.log("Warning: winedbg does not support paths containing spaces"); + } +#endif + + // When using plugin groups, we'll first try to connect to an existing group + // host process and ask it to host our plugin. If no such process exists, + // then we'll start a new process. In the event that two yabridge instances + // simultaneously try to start a new group process for the same group, then + // the last process to connect to the socket will terminate gracefully and + // the first process will handle the connections for both yabridge + // instances. + const bp::environment host_env = set_wineprefix(); + fs::path wine_prefix = host_env.at("WINEPREFIX").to_string(); + if (host_env.at("WINEPREFIX").empty()) { + // Fall back to `~/.wine` if this has not been set or detected. This + // would happen if the plugin's .dll file is not inside of a Wine + // prefix. If this happens, then the Wine instance will be launched in + // the default Wine prefix, so we should reflect that here. + wine_prefix = fs::path(host_env.at("HOME").to_string()) / ".wine"; + } + + const fs::path group_socket_path = + generate_group_endpoint(group_name, wine_prefix, plugin_arch); + try { + // Request the existing group host process to host our plugin, and store + // the PID of that process so we'll know if it has crashed + boost::asio::local::stream_protocol::socket group_socket(io_context); + group_socket.connect(group_socket_path.string()); + + write_object(group_socket, GroupRequest{plugin_path.string(), + socket_endpoint.string()}); + const auto response = read_object(group_socket); + + host_pid = response.pid; + } catch (const boost::system::system_error&) { + // In case we could not connect to the socket, then we'll start a + // new group host process. This process is detached immediately + // because it should run independently of this yabridge instance as + // it will likely outlive it. + bp::child group_host = + launch_host(host_path, group_socket_path, bp::env = host_env, + bp::std_out = stdout_pipe, bp::std_err = stderr_pipe); + host_pid = group_host.id(); + group_host.detach(); + + // We now want to connect to the socket the in the exact same way as + // above. The only problem is that it may take some time for the + // process to start depending on Wine's current state. We'll defer + // this to a thread so we can finish the rest of the startup in the + // meantime. + group_host_connect_handler = std::thread([&, group_socket_path, + plugin_path, + socket_endpoint]() { + using namespace std::literals::chrono_literals; + + // TODO: Replace this polling with inotify + while (running()) { + std::this_thread::sleep_for(20ms); + + try { + // This is the exact same connection sequence as above + boost::asio::local::stream_protocol::socket group_socket( + io_context); + group_socket.connect(group_socket_path.string()); + + write_object(group_socket, + GroupRequest{plugin_path.string(), + socket_endpoint.string()}); + const auto response = + read_object(group_socket); + + // If two group processes started at the same time, than the + // first one will be the one to respond to the host request + host_pid = response.pid; + return; + } catch (const boost::system::system_error&) { + // Keep trying to connect until either connection gets + // accepted or the group host crashes + } + } + }); + } +} + +GroupHost::~GroupHost() { + // This thread was briefly used to issue the host request if we had to start + // a new group host process + if (group_host_connect_handler.joinable()) { + group_host_connect_handler.join(); + } +} + +PluginArchitecture GroupHost::architecture() { + return plugin_arch; +} + +fs::path GroupHost::path() { + return host_path; +} + +bool GroupHost::running() { + // With regular individually hosted plugins we can simply check whether the + // process is still running, however Boost.Process does not allow you to do + // the same thing for a process that's not a direct child if this process. + // When using plugin groups we'll have to manually check whether the PID + // returned by the group host process is still active. + return kill(host_pid, 0) == 0; +} + +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. + host_vst_dispatch.close(); +} diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h new file mode 100644 index 00000000..04a542fe --- /dev/null +++ b/src/plugin/host-process.h @@ -0,0 +1,208 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include +#include +#include +#include + +#include "../common/logging.h" +#include "utils.h" + +/** + * Encapsulates the behavior of launching a host process or connecting to an + * existing one. This is needed because plugins groups require slightly + * different handling. All derived classes are set up to pipe their STDOUT and + * STDERR streams to the provided IO context instance. + */ +class HostProcess { + public: + virtual ~HostProcess(){}; + + /** + * Return the architecture of the plugin we are loading, i.e. whether it is + * 32-bit or 64-bit. + */ + virtual PluginArchitecture architecture() = 0; + + /** + * Return the full path to the host application in use. The host application + * is chosen depending on the architecture of the plugin's DLL file and on + * the hosting mode. + */ + virtual boost::filesystem::path path() = 0; + + /** + * Return true if the host process is still running. Used during startup to + * abort connecting to sockets if the Wine process has crashed. + */ + virtual bool running() = 0; + + /** + * Kill the process or cause the plugin that's being hosted to exit. + */ + virtual void terminate() = 0; + + protected: + /** + * Initialize the host process by setting up the STDIO redirection. + * + * @param io_context The IO context that the STDIO redurection will be + * handled on. + * @param logger The `Logger` instance the redirected STDIO streams will be + * written to. + */ + HostProcess(boost::asio::io_context& io_context, Logger& logger); + + /** + * The STDOUT stream of the Wine process we can forward to the logger. + */ + patched_async_pipe stdout_pipe; + /** + * The STDERR stream of the Wine process we can forward to the logger. + */ + patched_async_pipe stderr_pipe; + + private: + /** + * Write output from an async pipe to the log on a line by line basis. + * Useful for logging the Wine process's STDOUT and STDERR streams. + * + * @param pipe The pipe to read from. + * @param buffer The stream buffer to write to. + * @param prefix Text to prepend to the line before writing to the log. + */ + void async_log_pipe_lines(patched_async_pipe& pipe, + boost::asio::streambuf& buffer, + std::string prefix = ""); + + /** + * The logger the Wine output will be written to. + */ + Logger& logger; + + boost::asio::streambuf stdout_buffer; + boost::asio::streambuf stderr_buffer; +}; + +/** + * Launch a group host process for hosting a single plugin. + */ +class IndividualHost : public HostProcess { + public: + /** + * Start a host process that loads the plugin and connects back to this + * yabridge instance over the specified socket. + * + * @param io_context The IO context that the STDIO redurection will be + * handled on. + * @param logger The `Logger` instance the redirected STDIO streams will be + * written to. + * @param socket_endpoint The endpoint that should be used to communicate + * with the plugin. + * + * @throw std::runtime_error When `plugin_path` does not point to a valid + * 32-bit or 64-bit .dll file. + */ + IndividualHost(boost::asio::io_context& io_context, + Logger& logger, + boost::filesystem::path plugin_path, + boost::filesystem::path socket_endpoint); + + PluginArchitecture architecture() override; + boost::filesystem::path path() override; + bool running() override; + void terminate() override; + + private: + PluginArchitecture plugin_arch; + boost::filesystem::path host_path; + boost::process::child host; +}; + +/** + * Either launch a new group host process, or connect to an existing one. This + * will first try to connect to the plugin group's socket (determined based on + * group name, Wine prefix and architecture). If that fails, it will launch a + * new, detached group host process. This will likely outlive this plugin + * instance if multiple instances of yabridge using the same plugin group are in + * use. In the event that two yabridge instances are initialized at the same + * time and both instances spawn their own group host process, then the later + * one will simply terminate gracefully after it fails to listen on the socket. + */ +class GroupHost : public HostProcess { + public: + /** + * Start a new group host process or connect to an existing one. The actual + * host request is deferred until the process has actually started using a + * thread. + * + * @param io_context The IO context that the STDIO redurection will be + * handled on. + * @param logger The `Logger` instance the redirected STDIO streams will be + * written to. + * @param socket_endpoint The endpoint that should be used to communicate + * with the plugin. + * @param group_name The name of the plugin group. + * @param host_vst_dispatch The socket used to communicate + * `AEffect::dispatcher()` events with this plugin. Will be closed as to + * shut down the plugin. + */ + GroupHost(boost::asio::io_context& io_context, + Logger& logger, + boost::filesystem::path plugin_path, + boost::filesystem::path socket_endpoint, + std::string group_name, + boost::asio::local::stream_protocol::socket& host_vst_dispatch); + + ~GroupHost(); + + PluginArchitecture architecture() override; + boost::filesystem::path path() override; + bool running() override; + void terminate() override; + + private: + PluginArchitecture plugin_arch; + boost::filesystem::path host_path; + + /** + * The PID of the vst host process. Needed for checking whether the group + * host is still active if we are connecting to an already running group + * host instance. + */ + pid_t host_pid; + + /** + * The associated dispatch socket for the plugin we're hosting. This is used + * to terminate the plugin. + */ + boost::asio::local::stream_protocol::socket& host_vst_dispatch; + + /** + * A thread that waits for the group host to have started and then ask it to + * host our plugin. This is used to defer the request since it may take a + * little while until the group host process is up and running. This way we + * don't have to delay the rest of the initialization process. + * + * TODO: Replace the polling with inotify to prevent delays and to reduce + * wasting resources + */ + std::thread group_host_connect_handler; +}; diff --git a/src/plugin/plugin-bridge.cpp b/src/plugin/plugin-bridge.cpp index d9fb243a..de1596ab 100644 --- a/src/plugin/plugin-bridge.cpp +++ b/src/plugin/plugin-bridge.cpp @@ -16,12 +16,6 @@ #include "plugin-bridge.h" -#include -#include -#include -#include -#include - // Generated inside of build directory #include #include @@ -29,7 +23,6 @@ #include "../common/communication.h" #include "../common/events.h" -namespace bp = boost::process; // I'd rather use std::filesystem instead, but Boost.Process depends on // boost::filesystem namespace fs = boost::filesystem; @@ -49,15 +42,9 @@ PluginBridge& get_bridge_instance(const AEffect& plugin) { return *static_cast(plugin.ptr3); } -// TODO: It would be nice to have a better way to encapsulate the small -// differences in behavior when using plugin groups, i.e. everywhere where -// we check for `config.group.has_value()` - PluginBridge::PluginBridge(audioMasterCallback host_callback) : config(Configuration::load_for(get_this_file_location())), vst_plugin_path(find_vst_plugin()), - vst_plugin_arch(find_vst_architecture(vst_plugin_path)), - vst_host_path(find_vst_host(vst_plugin_arch, config.group.has_value())), // All the fields should be zero initialized because // `Vst2PluginInstance::vstAudioMasterCallback` from Bitwig's plugin // bridge will crash otherwise @@ -74,17 +61,22 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback) logger(Logger::create_from_environment( create_logger_prefix(socket_endpoint.path()))), wine_version(get_wine_version()), - wine_stdout(io_context), - wine_stderr(io_context) { + vst_host( + config.group.has_value() + ? std::unique_ptr( + std::make_unique(io_context, + logger, + vst_plugin_path, + socket_endpoint.path(), + config.group.value(), + host_vst_dispatch)) + : std::unique_ptr( + std::make_unique(io_context, + logger, + vst_plugin_path, + socket_endpoint.path()))), + wine_io_handler([&]() { io_context.run(); }) { log_init_message(); - launch_vst_host(); - - // Print the Wine host's STDOUT and STDERR streams to the log file. This - // should be done before trying to accept the sockets as otherwise we will - // miss all output. - async_log_pipe_lines(wine_stdout, wine_stdout_buffer, "[Wine STDOUT] "); - async_log_pipe_lines(wine_stderr, wine_stderr_buffer, "[Wine STDERR] "); - wine_io_handler = std::thread([&]() { io_context.run(); }); #ifndef USE_WINEDBG // If the Wine process fails to start, then nothing will connect to the @@ -100,27 +92,11 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback) if (finished_accepting_sockets) { return; } - - // When using regular individually hosted plugins we can simply - // check whether the process is still running, but Boost.Process - // does not allow you to do the same thing for a process that's not - // a child if this process. When using plugin groups we'll have to - // manually check whether the PID returned by the group host process - // is still active. - if (config.group.has_value()) { - if (kill(vst_host_pid, 0) != 0) { - logger.log( - "The group host process has exited unexpectedly. Check " - "the output above for more information."); - std::terminate(); - } - } else { - if (!vst_host.running()) { - logger.log( - "The Wine process failed to start. Check the output " - "above for more information."); - std::terminate(); - } + if (!vst_host->running()) { + logger.log( + "The Wine host process has exited unexpectedly. Check the " + "output above for more information."); + std::terminate(); } std::this_thread::sleep_for(1s); @@ -467,15 +443,7 @@ intptr_t PluginBridge::dispatch(AEffect* /*plugin*/, logger.log("The plugin crashed during shutdown, ignoring"); } - // Don't terminate group host processes. They will shut down - // automatically after all plugins have exited. - if (!config.group.has_value()) { - vst_host.terminate(); - } else { - // Manually the dispatch socket will cause the host process to - // terminate - host_vst_dispatch.close(); - } + vst_host->terminate(); // The `stop()` method will cause the IO context to just drop all of // its work immediately and not throw any exceptions that would have @@ -484,10 +452,6 @@ intptr_t PluginBridge::dispatch(AEffect* /*plugin*/, // These threads should now be finished because we've forcefully // terminated the Wine process, interupting their socket operations - if (group_host_connect_handler.joinable()) { - // This thread is only used when using plugin groups - group_host_connect_handler.join(); - } host_callback_handler.join(); wine_io_handler.join(); @@ -614,152 +578,13 @@ void PluginBridge::set_parameter(AEffect* /*plugin*/, int index, float value) { assert(!response.value.has_value()); } -void PluginBridge::async_log_pipe_lines(patched_async_pipe& pipe, - boost::asio::streambuf& buffer, - std::string prefix) { - boost::asio::async_read_until( - pipe, buffer, '\n', - [&, prefix](const boost::system::error_code& error, size_t) { - // When we get an error code then that likely means that the pipe - // has been clsoed and we have reached the end of the file - if (error.failed()) { - return; - } - - std::string line; - std::getline(std::istream(&buffer), line); - logger.log(prefix + line); - - async_log_pipe_lines(pipe, buffer, prefix); - }); -} - -void PluginBridge::launch_vst_host() { - const bp::environment host_env = set_wineprefix(); - -#ifndef USE_WINEDBG - const std::vector host_command{vst_host_path.string()}; -#else - // This is set up for KDE Plasma. Other desktop environments and window - // managers require some slight modifications to spawn a detached terminal - // emulator. - const std::vector host_command{"/usr/bin/kstart5", - "konsole", - "--", - "-e", - "winedbg", - "--gdb", - vst_host_path.string() + ".so"}; -#endif - -#ifndef USE_WINEDBG - const fs::path plugin_path = vst_plugin_path; - const fs::path starting_dir = fs::current_path(); -#else - // winedbg has no reliable way to escape spaces, so we'll start the process - // in the plugin's directory - const fs::path plugin_path = vst_plugin_path.filename(); - const fs::path starting_dir = vst_plugin_path.parent_path(); - - if (plugin_path.string().find(' ') != std::string::npos) { - logger.log("Warning: winedbg does not support paths containing spaces"); - } -#endif - const fs::path socket_path = socket_endpoint.path(); - - if (!config.group.has_value()) { - vst_host = - bp::child(host_command, plugin_path, socket_path, - bp::env = host_env, bp::std_out = wine_stdout, - bp::std_err = wine_stderr, bp::start_dir = starting_dir); - return; - } - - // When using plugin groups, we'll first try to connect to an existing group - // host process and ask it to host our plugin. If no such process exists, - // then we'll start a new process. In the event that two yabridge instances - // simultaneously try to start a new group process for the same group, then - // the last process to connect to the socket will terminate gracefully and - // the first process will handle the connections for both yabridge - // instances. - fs::path wine_prefix = host_env.at("WINEPREFIX").to_string(); - if (host_env.at("WINEPREFIX").empty()) { - // Fall back to `~/.wine` if this has not been set or detected. This - // would happen if the plugin's .dll file is not inside of a Wine - // prefix. If this happens, then the Wine instance will be launched in - // the default Wine prefix, so we should reflect that here. - wine_prefix = fs::path(host_env.at("HOME").to_string()) / ".wine"; - } - - const fs::path group_socket_path = generate_group_endpoint( - config.group.value(), wine_prefix, vst_plugin_arch); - - try { - // Request the existing group host process to host our plugin, and store - // the PID of that process so we'll know if it has crashed - boost::asio::local::stream_protocol::socket group_socket(io_context); - group_socket.connect(group_socket_path.string()); - - write_object(group_socket, - GroupRequest{plugin_path.string(), socket_path.string()}); - const auto response = read_object(group_socket); - - vst_host_pid = response.pid; - } catch (const boost::system::system_error&) { - // In case we could not connect to the socket, then we'll start a - // new group host process. This process is detached immediately - // because it should run independently of this yabridge instance as - // it will likely outlive it. - vst_host = - bp::child(host_command, group_socket_path, bp::env = host_env, - bp::std_out = wine_stdout, bp::std_err = wine_stderr, - bp::start_dir = starting_dir); - vst_host_pid = vst_host.id(); - vst_host.detach(); - - // We now want to connect to the socket the in the exact same way as - // above. The only problem is that it may take some time for the - // process to start depending on Wine's current state. We'll defer - // this to a thread so we can finish the rest of the startup in the - // meantime. - group_host_connect_handler = std::thread([&, group_socket_path, - plugin_path, socket_path]() { - using namespace std::literals::chrono_literals; - - // TODO: Replace this polling with inotify when encapsulating - // the different host launch behaviors - while (vst_host.running()) { - std::this_thread::sleep_for(20ms); - - try { - // This is the exact same connection sequence as above - boost::asio::local::stream_protocol::socket group_socket( - io_context); - group_socket.connect(group_socket_path.string()); - - write_object(group_socket, - GroupRequest{plugin_path.string(), - socket_path.string()}); - const auto response = - read_object(group_socket); - - // If two group processes started at the same time, than the - // first one will be the one to respond to the host request - vst_host_pid = response.pid; - return; - } catch (const boost::system::system_error&) { - } - } - }); - } -} - void PluginBridge::log_init_message() { std::stringstream init_msg; init_msg << "Initializing yabridge version " << yabridge_git_version << std::endl; - init_msg << "host: '" << vst_host_path.string() << "'" << std::endl; + init_msg << "host: '" << vst_host->path().string() << "'" + << std::endl; init_msg << "plugin: '" << vst_plugin_path.string() << "'" << std::endl; init_msg << "socket: '" << socket_endpoint.path() << "'" << std::endl; @@ -782,7 +607,7 @@ void PluginBridge::log_init_message() { } else { init_msg << "individually"; } - if (vst_plugin_arch == PluginArchitecture::vst_32) { + if (vst_host->architecture() == PluginArchitecture::vst_32) { init_msg << ", 32-bit"; } else { init_msg << ", 64-bit"; diff --git a/src/plugin/plugin-bridge.h b/src/plugin/plugin-bridge.h index a4709b35..ccc51cc1 100644 --- a/src/plugin/plugin-bridge.h +++ b/src/plugin/plugin-bridge.h @@ -20,14 +20,12 @@ #include #include -#include -#include #include #include #include "../common/logging.h" #include "configuration.h" -#include "utils.h" +#include "host-process.h" /** * This handles the communication between the Linux native VST plugin and the @@ -86,16 +84,6 @@ class PluginBridge { * The path to the .dll being loaded in the Wine VST host. */ const boost::filesystem::path vst_plugin_path; - /** - * Whether the plugin is 64-bit or 32-bit. - */ - const PluginArchitecture vst_plugin_arch; - /** - * The path to the host application (i.e. a path to either - * `yabridge-host.exe` or `yabridge-host-32.exe`). The host application will - * be chosen depending on the architecture of the VST plugin .dll file. - */ - const boost::filesystem::path vst_host_path; /** * This AEffect struct will be populated using the data passed by the Wine @@ -105,31 +93,6 @@ class PluginBridge { AEffect plugin; private: - /** - * Write output from an async pipe to the log on a line by line basis. - * Useful for logging the Wine process's STDOUT and STDERR streams. - * - * @param pipe The pipe to read from. - * @param buffer The stream buffer to write to. - * @param prefix Text to prepend to the line before writing to the log. - */ - void async_log_pipe_lines(patched_async_pipe& pipe, - boost::asio::streambuf& buffer, - std::string prefix = ""); - - /** - * Launch the Wine VST host to host the plugin. When using plugin groups, - * this will first try to connect to the plugin group's socket (determined - * based on group name, Wine prefix and architecture). If that fails, it - * will launch a new, detached group host process. This will likely outlive - * this plugin instance if multiple instances of yabridge using the same - * plugin group are in use. In the event that two yabridge instances are - * initialized at the same time and both instances spawn their own group - * host process, then the later one will simply terminate gracefully after - * it fails to listen on the socket. - */ - void launch_vst_host(); - /** * Format and log all relevant debug information during initialization. */ @@ -212,49 +175,17 @@ class PluginBridge { */ const std::string wine_version; - boost::asio::streambuf wine_stdout_buffer; - boost::asio::streambuf wine_stderr_buffer; - /** - * The STDOUT stream of the Wine process we can forward to the logger. - */ - patched_async_pipe wine_stdout; - /** - * The STDERR stream of the Wine process we can forward to the logger. - */ - patched_async_pipe wine_stderr; - /** - * Runs the Boost.Asio `io_context` thread for logging the Wine process - * STDOUT and STDERR messages. - */ - std::thread wine_io_handler; - /** * The Wine process hosting the Windows VST plugin. * * @see launch_vst_host */ - boost::process::child vst_host; + std::unique_ptr vst_host; /** - * The PID of the vst host process. Needed for checking whether the group - * host is still active if we are connecting to an already running group - * host instance. - * - * TODO: Remove this after encapsulating the minor differences in individual - * and group host handling + * Runs the Boost.Asio `io_context` thread for logging the Wine process + * STDOUT and STDERR messages. */ - pid_t vst_host_pid; - /** - * A thread that waits for the group host to have started and then ask it to - * host our plugin. This is used to defer the request since it may take a - * little while until the group host process is up and running. This way we - * don't have to delay the rest of the initialization process. - * - * TODO: Remove this after encapsulating the minor differences in individual - * and group host handling - * TODO: Replace this with inotify to prevent delays and to reduce wasting - * resources - */ - std::thread group_host_connect_handler; + std::thread wine_io_handler; /** * A scratch buffer for sending and receiving data during `process` and diff --git a/src/wine-host/bridges/group.h b/src/wine-host/bridges/group.h index 034f859e..053a7d11 100644 --- a/src/wine-host/bridges/group.h +++ b/src/wine-host/bridges/group.h @@ -191,7 +191,7 @@ class GroupBridge { * Continuously read from a pipe and write the output to the log file. Used * with the IO streams captured by `stdout_redirect` and `stderr_redirect`. * - * TODO: Merge this with `PluginBridge::async_log_pipe_lines` + * TODO: Merge this with `HostProcess::async_log_pipe_lines` * * @param pipe The pipe to read from. * @param buffer The stream buffer to write to.