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.