diff --git a/meson.build b/meson.build
index 5942e132..bade536b 100644
--- a/meson.build
+++ b/meson.build
@@ -92,6 +92,7 @@ shared_library(
'src/common/configuration.cpp',
'src/common/logging.cpp',
'src/common/serialization.cpp',
+ 'src/common/communication.cpp',
'src/common/utils.cpp',
'src/plugin/host-process.cpp',
'src/plugin/plugin.cpp',
@@ -116,6 +117,7 @@ host_sources = [
'src/common/configuration.cpp',
'src/common/logging.cpp',
'src/common/serialization.cpp',
+ 'src/common/communication.cpp',
'src/common/utils.cpp',
'src/wine-host/bridges/vst2.cpp',
'src/wine-host/editor.cpp',
diff --git a/src/common/communication.cpp b/src/common/communication.cpp
new file mode 100644
index 00000000..391eb709
--- /dev/null
+++ b/src/common/communication.cpp
@@ -0,0 +1,127 @@
+// 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 "communication.h"
+
+#include
+
+#include "utils.h"
+
+namespace fs = boost::filesystem;
+
+/**
+ * Used for generating random identifiers.
+ */
+constexpr char alphanumeric_characters[] =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+Sockets::Sockets(boost::asio::io_context& io_context,
+ const boost::filesystem::path& endpoint_base_dir,
+ bool listen)
+ : base_dir(endpoint_base_dir),
+ io_context(io_context),
+ host_vst_dispatch(io_context),
+ host_vst_dispatch_midi_events(io_context),
+ vst_host_callback(io_context),
+ host_vst_parameters(io_context),
+ host_vst_process_replacing(io_context),
+ host_vst_control(io_context),
+ host_vst_dispatch_endpoint(
+ (base_dir / "host_vst_dispatch.sock").string()),
+ host_vst_dispatch_midi_events_endpoint(
+ (base_dir / "host_vst_dispatch_midi_events.sock").string()),
+ vst_host_callback_endpoint(
+ (base_dir / "vst_host_callback.sock").string()),
+ 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_directory(base_dir);
+
+ acceptors = Acceptors{
+ .host_vst_dispatch{io_context, host_vst_dispatch_endpoint},
+ .host_vst_dispatch_midi_events{
+ io_context, host_vst_dispatch_midi_events_endpoint},
+ .vst_host_callback{io_context, vst_host_callback_endpoint},
+ .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() {
+ if (acceptors) {
+ acceptors->host_vst_dispatch.accept(host_vst_dispatch);
+ acceptors->host_vst_dispatch_midi_events.accept(
+ host_vst_dispatch_midi_events);
+ acceptors->vst_host_callback.accept(vst_host_callback);
+ 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_dispatch.connect(host_vst_dispatch_endpoint);
+ host_vst_dispatch_midi_events.connect(
+ host_vst_dispatch_midi_events_endpoint);
+ vst_host_callback.connect(vst_host_callback_endpoint);
+ 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();
+
+ std::random_device random_device;
+ std::mt19937 rng(random_device());
+ fs::path candidate_endpoint;
+ do {
+ std::string random_id;
+ std::sample(
+ alphanumeric_characters,
+ alphanumeric_characters + strlen(alphanumeric_characters) - 1,
+ std::back_inserter(random_id), 8, rng);
+
+ // We'll get rid of the file descriptors immediately after accepting the
+ // sockets, so putting them inside of a subdirectory would only leave
+ // behind an empty directory
+ std::ostringstream socket_name;
+ socket_name << "yabridge-" << plugin_name << "-" << random_id;
+
+ candidate_endpoint = temp_directory / socket_name.str();
+ } while (fs::exists(candidate_endpoint));
+
+ return candidate_endpoint;
+}
diff --git a/src/common/communication.h b/src/common/communication.h
index 22ac3516..21e5ee47 100644
--- a/src/common/communication.h
+++ b/src/common/communication.h
@@ -22,9 +22,11 @@
#ifdef __WINE__
#include "../wine-host/boost-fix.h"
#endif
+#include
#include
#include
#include
+#include
template
using OutputAdapter = bitsery::OutputBufferAdapter;
@@ -32,6 +34,151 @@ using OutputAdapter = bitsery::OutputBufferAdapter;
template
using InputAdapter = bitsery::InputBufferAdapter;
+/**
+ * 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--/`.
+ *
+ * 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
+ * sockets, and the call to `connect()` will then accept any incoming
+ * connections.
+ */
+class Sockets {
+ public:
+ /**
+ * Sets up the sockets using the specified base directory. The sockets won't
+ * be active until `connect()` gets called.
+ *
+ * @param io_context The IO context the sockets should be bound to. Relevant
+ * when doing asynchronous operations.
+ * @param endpoint_base_dir The base directory that will be used for the
+ * Unix domain sockets.
+ * @param listen If `true`, start listening on the sockets. Incoming
+ * 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 Sockets::connect
+ */
+ Sockets(boost::asio::io_context& io_context,
+ const boost::filesystem::path& endpoint_base_dir,
+ bool listen);
+
+ /**
+ * Cleans up the directory containing the socket endpoints when yabridge
+ * shuts down if it still exists.
+ */
+ ~Sockets();
+
+ /**
+ * 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();
+
+ /**
+ * The base directory for our socket endpoints. All `*_endpoint` variables
+ * below are files within this directory.
+ */
+ const boost::filesystem::path base_dir;
+
+ /**
+ * The IO context that the sockets will be created on. This is only relevant
+ * for asynchronous operations.
+ */
+ boost::asio::io_context& io_context;
+
+ // The naming convention for these sockets is `__`. For
+ // instance the socket named `host_vst_dispatch` forwards
+ // `AEffect.dispatch()` calls from the native VST host to the Windows VST
+ // plugin (through the Wine VST host).
+
+ /**
+ * The socket that forwards all `dispatcher()` calls from the VST host to
+ * the plugin.
+ */
+ boost::asio::local::stream_protocol::socket 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.
+ */
+ boost::asio::local::stream_protocol::socket host_vst_dispatch_midi_events;
+ /**
+ * The socket that forwards all `audioMaster()` calls from the Windows VST
+ * plugin to the host.
+ */
+ boost::asio::local::stream_protocol::socket vst_host_callback;
+ /**
+ * Used for both `getParameter` and `setParameter` since they mostly
+ * overlap.
+ */
+ boost::asio::local::stream_protocol::socket host_vst_parameters;
+ /**
+ * Used for processing audio usign the `process()`, `processReplacing()` and
+ * `processDoubleReplacing()` functions.
+ */
+ boost::asio::local::stream_protocol::socket host_vst_process_replacing;
+ /**
+ * A control socket that sends data that is not suitable for the other
+ * sockets. At the moment this is only used to, on startup, send the Windows
+ * VST plugin's `AEffect` object to the native VST plugin, and to then send
+ * the configuration (from `config`) back to the Wine host.
+ */
+ boost::asio::local::stream_protocol::socket host_vst_control;
+
+ private:
+ const boost::asio::local::stream_protocol::endpoint
+ host_vst_dispatch_endpoint;
+ const boost::asio::local::stream_protocol::endpoint
+ host_vst_dispatch_midi_events_endpoint;
+ const boost::asio::local::stream_protocol::endpoint
+ vst_host_callback_endpoint;
+ const boost::asio::local::stream_protocol::endpoint
+ host_vst_parameters_endpoint;
+ const boost::asio::local::stream_protocol::endpoint
+ host_vst_process_replacing_endpoint;
+ const boost::asio::local::stream_protocol::endpoint
+ host_vst_control_endpoint;
+
+ /**
+ * All of our socket acceptors. We have to create these before launching the
+ * Wine process.
+ */
+ struct Acceptors {
+ boost::asio::local::stream_protocol::acceptor host_vst_dispatch;
+ boost::asio::local::stream_protocol::acceptor
+ host_vst_dispatch_midi_events;
+ boost::asio::local::stream_protocol::acceptor vst_host_callback;
+ boost::asio::local::stream_protocol::acceptor host_vst_parameters;
+ boost::asio::local::stream_protocol::acceptor
+ host_vst_process_replacing;
+ boost::asio::local::stream_protocol::acceptor host_vst_control;
+ };
+
+ /**
+ * If the `listen` constructor argument was set to `true`, when we'll
+ * prepare a set of socket acceptors that listen on the socket endpoints.
+ */
+ std::optional acceptors;
+};
+
+/**
+ * Generate a unique base directory that can be used as a prefix for all Unix
+ * domain socket endpoints used in `PluginBridge`/`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);
+
/**
* Serialize an object using bitsery and write it to a socket. This will write
* both the size of the serialized object and the object itself over the socket.
diff --git a/src/common/serialization.cpp b/src/common/serialization.cpp
index 4a5e14d4..d49d480d 100644
--- a/src/common/serialization.cpp
+++ b/src/common/serialization.cpp
@@ -110,5 +110,6 @@ AEffect& update_aeffect(AEffect& plugin, const AEffect& updated_plugin) {
}
bool GroupRequest::operator==(const GroupRequest& rhs) const {
- return plugin_path == rhs.plugin_path && socket_path == rhs.socket_path;
+ return plugin_path == rhs.plugin_path &&
+ endpoint_base_dir == rhs.endpoint_base_dir;
}
diff --git a/src/common/serialization.h b/src/common/serialization.h
index 3679d528..c53612a5 100644
--- a/src/common/serialization.h
+++ b/src/common/serialization.h
@@ -598,14 +598,14 @@ struct AudioBuffers {
*/
struct GroupRequest {
std::string plugin_path;
- std::string socket_path;
+ std::string endpoint_base_dir;
bool operator==(const GroupRequest& rhs) const;
template
void serialize(S& s) {
s.text1b(plugin_path, 4096);
- s.text1b(socket_path, 4096);
+ s.text1b(endpoint_base_dir, 4096);
}
};
@@ -614,7 +614,8 @@ struct std::hash {
std::size_t operator()(GroupRequest const& params) const noexcept {
std::hash hasher{};
- return hasher(params.plugin_path) ^ (hasher(params.socket_path) << 1);
+ return hasher(params.plugin_path) ^
+ (hasher(params.endpoint_base_dir) << 1);
}
};
diff --git a/src/common/utils.cpp b/src/common/utils.cpp
index e62b5a0a..1b2d0e64 100644
--- a/src/common/utils.cpp
+++ b/src/common/utils.cpp
@@ -17,6 +17,19 @@
#include "utils.h"
#include
+#include
+
+namespace bp = boost::process;
+namespace fs = boost::filesystem;
+
+fs::path get_temporary_directory() {
+ bp::environment env = boost::this_process::environment();
+ if (!env["XDG_RUNTIME_DIR"].empty()) {
+ return env["XDG_RUNTIME_DIR"].to_string();
+ } else {
+ return fs::temp_directory_path();
+ }
+}
bool set_realtime_priority() {
sched_param params{.sched_priority = 5};
diff --git a/src/common/utils.h b/src/common/utils.h
index 7543294e..a21fb0dd 100644
--- a/src/common/utils.h
+++ b/src/common/utils.h
@@ -16,6 +16,17 @@
#pragma once
+#ifdef __WINE__
+#include "../wine-host/boost-fix.h"
+#endif
+#include
+
+/**
+ * Return the path to the directory for story temporary files. This will be
+ * `$XDG_RUNTIME_DIR` if set, and `/tmp` otherwise.
+ */
+boost::filesystem::path get_temporary_directory();
+
/**
* Set the scheduling policy to `SCHED_FIFO` with priority 10 for this process.
* We explicitly don't do this for wineserver itself since from my testing that
diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp
index 83d78c23..0473abb3 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,
- fs::path socket_endpoint)
+ const Sockets& sockets)
: HostProcess(io_context, logger),
plugin_arch(find_vst_architecture(plugin_path)),
host_path(find_vst_host(plugin_arch, false)),
@@ -93,7 +93,7 @@ IndividualHost::IndividualHost(boost::asio::io_context& io_context,
#else
plugin_path,
#endif
- socket_endpoint,
+ sockets.base_dir,
bp::env = set_wineprefix(),
bp::std_out = stdout_pipe,
bp::std_err = stderr_pipe
@@ -127,17 +127,15 @@ void IndividualHost::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)
+GroupHost::GroupHost(boost::asio::io_context& io_context,
+ Logger& logger,
+ fs::path plugin_path,
+ Sockets& sockets,
+ std::string group_name)
: 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) {
+ sockets(sockets) {
#ifdef WITH_WINEDBG
if (plugin_path.string().find(' ') != std::string::npos) {
logger.log("Warning: winedbg does not support paths containing spaces");
@@ -167,6 +165,7 @@ GroupHost::GroupHost(
wine_prefix = fs::path(host_env.at("HOME").to_string()) / ".wine";
}
+ const fs::path endpoint_base_dir = sockets.base_dir;
const fs::path group_socket_path =
generate_group_endpoint(group_name, wine_prefix, plugin_arch);
try {
@@ -175,9 +174,10 @@ GroupHost::GroupHost(
boost::asio::local::stream_protocol::socket group_socket(io_context);
group_socket.connect(group_socket_path.string());
- write_object(group_socket,
- GroupRequest{.plugin_path = plugin_path.string(),
- .socket_path = socket_endpoint.string()});
+ write_object(
+ group_socket,
+ GroupRequest{.plugin_path = plugin_path.string(),
+ .endpoint_base_dir = endpoint_base_dir.string()});
const auto response = read_object(group_socket);
host_pid = response.pid;
@@ -199,7 +199,7 @@ GroupHost::GroupHost(
// meantime.
group_host_connect_handler = std::jthread([&, group_socket_path,
plugin_path,
- socket_endpoint]() {
+ endpoint_base_dir]() {
using namespace std::literals::chrono_literals;
// TODO: Replace this polling with inotify
@@ -214,8 +214,9 @@ GroupHost::GroupHost(
write_object(
group_socket,
- GroupRequest{.plugin_path = plugin_path.string(),
- .socket_path = socket_endpoint.string()});
+ GroupRequest{
+ .plugin_path = plugin_path.string(),
+ .endpoint_base_dir = endpoint_base_dir.string()});
const auto response =
read_object(group_socket);
@@ -262,7 +263,7 @@ 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.shutdown(
+ sockets.host_vst_dispatch.shutdown(
boost::asio::local::stream_protocol::socket::shutdown_both);
- host_vst_dispatch.close();
+ sockets.host_vst_dispatch.close();
}
diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h
index 5330adf2..840b3a74 100644
--- a/src/plugin/host-process.h
+++ b/src/plugin/host-process.h
@@ -26,6 +26,7 @@
#include
#include "../common/logging.h"
+#include "../common/communication.h"
#include "utils.h"
/**
@@ -117,7 +118,7 @@ class IndividualHost : public HostProcess {
* 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
+ * @param sockets The socket endpoints that will be used for communication
* with the plugin.
*
* @throw std::runtime_error When `plugin_path` does not point to a valid
@@ -126,7 +127,7 @@ class IndividualHost : public HostProcess {
IndividualHost(boost::asio::io_context& io_context,
Logger& logger,
boost::filesystem::path plugin_path,
- boost::filesystem::path socket_endpoint);
+ const Sockets& sockets);
PluginArchitecture architecture() override;
boost::filesystem::path path() override;
@@ -160,19 +161,16 @@ class GroupHost : public HostProcess {
* 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 sockets The socket endpoints that will be used for communication
+ * with the plugin. When the plugin shuts down, we'll terminate the
+ * dispatch socket contained in this object.
* @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);
+ Sockets& socket_endpoint,
+ std::string group_name);
PluginArchitecture architecture() override;
boost::filesystem::path path() override;
@@ -191,10 +189,10 @@ class GroupHost : public HostProcess {
pid_t host_pid;
/**
- * The associated dispatch socket for the plugin we're hosting. This is used
- * to terminate the plugin.
+ * The associated sockets for the plugin we're hosting. This is used to
+ * terminate the plugin.
*/
- boost::asio::local::stream_protocol::socket& host_vst_dispatch;
+ 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.cpp b/src/plugin/plugin-bridge.cpp
index 886abeec..0c0c3003 100644
--- a/src/plugin/plugin-bridge.cpp
+++ b/src/plugin/plugin-bridge.cpp
@@ -54,32 +54,26 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback)
// bridge will crash otherwise
plugin(),
io_context(),
- socket_endpoint(generate_plugin_endpoint().string()),
- socket_acceptor(io_context, socket_endpoint),
- host_vst_dispatch(io_context),
- host_vst_dispatch_midi_events(io_context),
- vst_host_callback(io_context),
- host_vst_parameters(io_context),
- host_vst_process_replacing(io_context),
- host_vst_control(io_context),
+ sockets(io_context,
+ generate_endpoint_base(
+ vst_plugin_path.filename().replace_extension("").string()),
+ true),
host_callback_function(host_callback),
logger(Logger::create_from_environment(
- create_logger_prefix(socket_endpoint.path()))),
+ create_logger_prefix(sockets.base_dir))),
wine_version(get_wine_version()),
- vst_host(
- config.group
- ? std::unique_ptr(
- std::make_unique(io_context,
- logger,
- vst_plugin_path,
- socket_endpoint.path(),
- *config.group,
- host_vst_dispatch))
- : std::unique_ptr(
- std::make_unique(io_context,
+ vst_host(config.group
+ ? std::unique_ptr(
+ std::make_unique(io_context,
logger,
vst_plugin_path,
- socket_endpoint.path()))),
+ sockets,
+ *config.group))
+ : std::unique_ptr(
+ std::make_unique(io_context,
+ logger,
+ vst_plugin_path,
+ sockets))),
has_realtime_priority(set_realtime_priority()),
wine_io_handler([&]() { io_context.run(); }) {
log_init_message();
@@ -107,24 +101,13 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback)
});
#endif
- // It's very important that these sockets are connected to in the same
- // order in the Wine VST host
- socket_acceptor.accept(host_vst_dispatch);
- socket_acceptor.accept(host_vst_dispatch_midi_events);
- socket_acceptor.accept(vst_host_callback);
- socket_acceptor.accept(host_vst_parameters);
- socket_acceptor.accept(host_vst_process_replacing);
- socket_acceptor.accept(host_vst_control);
-
+ // This will block until all sockets have been connected to by the Wine VST
+ // host
+ sockets.connect();
#ifndef WITH_WINEDBG
host_guard_handler.request_stop();
#endif
- // There's no need to keep the socket endpoint file around after accepting
- // all the sockets, and RAII won't clean these files up for us
- socket_acceptor.close();
- fs::remove(socket_endpoint.path());
-
// Set up all pointers for our `AEffect` struct. We will fill this with data
// from the VST plugin loaded in Wine at the end of this constructor.
plugin.ptr3 = this;
@@ -144,8 +127,8 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback)
// TODO: Think of a nicer way to structure this and the similar
// handler in `Vst2Bridge::handle_dispatch_midi_events`
receive_event(
- vst_host_callback, std::pair(logger, false),
- [&](Event& event) {
+ sockets.vst_host_callback,
+ std::pair(logger, false), [&](Event& event) {
// MIDI events sent from the plugin back to the host are
// a special case here. They have to sent during the
// `processReplacing()` function or else the host will
@@ -181,13 +164,14 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback)
// call these during its initialization. Any further updates will be sent
// over the `dispatcher()` socket. This would happen whenever the plugin
// calls `audioMasterIOChanged()` and after the host calls `effOpen()`.
- const auto initialization_data = read_object(host_vst_control);
+ const auto initialization_data =
+ read_object(sockets.host_vst_control);
const auto initialized_plugin =
std::get(initialization_data.payload);
// After receiving the `AEffect` values we'll want to send the configuration
// back to complete the startup process
- write_object(host_vst_control, config);
+ write_object(sockets.host_vst_control, config);
update_aeffect(plugin, initialized_plugin);
}
@@ -452,10 +436,10 @@ intptr_t PluginBridge::dispatch(AEffect* /*plugin*/,
intptr_t return_value = 0;
try {
// TODO: Add some kind of timeout?
- return_value =
- send_event(host_vst_dispatch, dispatch_mutex, converter,
- std::pair(logger, true), opcode,
- index, value, data, option);
+ return_value = send_event(
+ sockets.host_vst_dispatch, dispatch_mutex, 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
@@ -478,7 +462,7 @@ intptr_t PluginBridge::dispatch(AEffect* /*plugin*/,
// thread and socket to pass MIDI events. Otherwise plugins will
// stop receiving MIDI data when they have an open dropdowns or
// message box.
- return send_event(host_vst_dispatch_midi_events,
+ return send_event(sockets.host_vst_dispatch_midi_events,
dispatch_midi_events_mutex, converter,
std::pair(logger, true), opcode,
index, value, data, option);
@@ -538,7 +522,7 @@ intptr_t PluginBridge::dispatch(AEffect* /*plugin*/,
// and loading plugin state it's much better to have bitsery or our
// receiving function temporarily allocate a large enough buffer rather than
// to have a bunch of allocated memory sitting around doing nothing.
- return send_event(host_vst_dispatch, dispatch_mutex, converter,
+ return send_event(sockets.host_vst_dispatch, dispatch_mutex, converter,
std::pair(logger, true), opcode, index,
value, data, option);
}
@@ -555,11 +539,11 @@ void PluginBridge::do_process(T** inputs, T** outputs, int sample_frames) {
}
const AudioBuffers request{input_buffers, sample_frames};
- write_object(host_vst_process_replacing, request, process_buffer);
+ write_object(sockets.host_vst_process_replacing, request, process_buffer);
// Write the results back to the `outputs` arrays
- const auto response =
- read_object(host_vst_process_replacing, process_buffer);
+ const auto response = read_object(
+ sockets.host_vst_process_replacing, process_buffer);
const auto& response_buffers =
std::get>>(response.buffers);
@@ -608,8 +592,8 @@ float PluginBridge::get_parameter(AEffect* /*plugin*/, int index) {
// called at the same time since they share the same socket
{
std::lock_guard lock(parameters_mutex);
- write_object(host_vst_parameters, request);
- response = read_object(host_vst_parameters);
+ write_object(sockets.host_vst_parameters, request);
+ response = read_object(sockets.host_vst_parameters);
}
logger.log_get_parameter_response(*response.value);
@@ -625,9 +609,9 @@ void PluginBridge::set_parameter(AEffect* /*plugin*/, int index, float value) {
{
std::lock_guard lock(parameters_mutex);
- write_object(host_vst_parameters, request);
+ write_object(sockets.host_vst_parameters, request);
- response = read_object(host_vst_parameters);
+ response = read_object(sockets.host_vst_parameters);
}
logger.log_set_parameter_response();
@@ -647,7 +631,8 @@ void PluginBridge::log_init_message() {
<< std::endl;
init_msg << "realtime: '" << (has_realtime_priority ? "yes" : "no")
<< "'" << std::endl;
- init_msg << "socket: '" << socket_endpoint.path() << "'" << std::endl;
+ init_msg << "sockets: '" << sockets.base_dir.string() << "'"
+ << std::endl;
init_msg << "wine prefix: '";
// If the Wine prefix is manually overridden, then this should be made
diff --git a/src/plugin/plugin-bridge.h b/src/plugin/plugin-bridge.h
index 6b3fc57e..ad0d62a2 100644
--- a/src/plugin/plugin-bridge.h
+++ b/src/plugin/plugin-bridge.h
@@ -25,6 +25,7 @@
#include "../common/configuration.h"
#include "../common/logging.h"
+#include "../common/communication.h"
#include "host-process.h"
/**
@@ -123,45 +124,7 @@ class PluginBridge {
void log_init_message();
boost::asio::io_context io_context;
- boost::asio::local::stream_protocol::endpoint socket_endpoint;
- boost::asio::local::stream_protocol::acceptor socket_acceptor;
-
- // The naming convention for these sockets is `__`. For
- // instance the socket named `host_vst_dispatch` forwards
- // `AEffect.dispatch()` calls from the native VST host to the Windows VST
- // plugin (through the Wine VST host).
-
- /**
- * The socket that forwards all `dispatcher()` calls from the VST host to
- * the plugin.
- */
- boost::asio::local::stream_protocol::socket 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.
- */
- boost::asio::local::stream_protocol::socket host_vst_dispatch_midi_events;
- /**
- * The socket that forwards all `audioMaster()` calls from the Windows VST
- * plugin to the host.
- */
- boost::asio::local::stream_protocol::socket vst_host_callback;
- /**
- * Used for both `getParameter` and `setParameter` since they mostly
- * overlap.
- */
- boost::asio::local::stream_protocol::socket host_vst_parameters;
- boost::asio::local::stream_protocol::socket host_vst_process_replacing;
-
- /**
- * A control socket that sends data that is not suitable for the other
- * sockets. At the moment this is only used to, on startup, send the Windows
- * VST plugin's `AEffect` object to the native VST plugin, and to then send
- * the configuration (from `config`) back to the Wine host.
- */
- boost::asio::local::stream_protocol::socket host_vst_control;
+ Sockets sockets;
/**
* The thread that handles host callbacks.
diff --git a/src/plugin/utils.cpp b/src/plugin/utils.cpp
index 651bbaa5..86ca0e82 100644
--- a/src/plugin/utils.cpp
+++ b/src/plugin/utils.cpp
@@ -22,37 +22,27 @@
#include
#include
#include
-#include
#include
// Generated inside of the build directory
#include
#include "../common/configuration.h"
+#include "../common/utils.h"
namespace bp = boost::process;
namespace fs = boost::filesystem;
-/**
- * Used for generating random identifiers.
- */
-constexpr char alphanumeric_characters[] =
- "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-
-std::string create_logger_prefix(const fs::path& socket_path) {
- // Use the socket filename as the logger prefix, but strip the `yabridge-`
- // part since that's redundant
- std::string socket_name =
- socket_path.filename().replace_extension().string();
+std::string create_logger_prefix(const fs::path& endpoint_base_dir) {
+ // Use the name of the base directory used for our sockets as the logger
+ // prefix, but strip the `yabridge-` part since that's redundant
+ std::string endpoint_name = endpoint_base_dir.filename().string();
constexpr std::string_view socket_prefix("yabridge-");
- assert(socket_name.starts_with(socket_prefix));
- socket_name = socket_name.substr(socket_prefix.size());
+ assert(endpoint_name.starts_with(socket_prefix));
+ endpoint_name = endpoint_name.substr(socket_prefix.size());
- std::ostringstream prefix;
- prefix << "[" << socket_name << "] ";
-
- return prefix.str();
+ return "[" + endpoint_name + "] ";
}
std::optional find_wineprefix() {
@@ -183,39 +173,7 @@ boost::filesystem::path generate_group_endpoint(
}
socket_name << ".sock";
- return fs::temp_directory_path() / socket_name.str();
-}
-
-fs::path generate_plugin_endpoint() {
- const auto plugin_name =
- find_vst_plugin().filename().replace_extension("").string();
-
- std::random_device random_device;
- std::mt19937 rng(random_device());
- fs::path candidate_endpoint;
- do {
- std::string random_id;
- std::sample(
- alphanumeric_characters,
- alphanumeric_characters + strlen(alphanumeric_characters) - 1,
- std::back_inserter(random_id), 8, rng);
-
- // We'll get rid of the file descriptors immediately after accepting the
- // sockets, so putting them inside of a subdirectory would only leave
- // behind an empty directory
- std::ostringstream socket_name;
- socket_name << "yabridge-" << plugin_name << "-" << random_id
- << ".sock";
-
- candidate_endpoint = fs::temp_directory_path() / socket_name.str();
- } while (fs::exists(candidate_endpoint));
-
- // TODO: Should probably try creating the endpoint right here and catch any
- // exceptions since this could technically result in a race condition
- // when two instances of yabridge decide to use the same endpoint name
- // at the same time
-
- return candidate_endpoint;
+ return get_temporary_directory() / socket_name.str();
}
fs::path get_this_file_location() {
@@ -243,7 +201,7 @@ std::string get_wine_version() {
bp::environment env = boost::this_process::environment();
if (!env["WINELOADER"].empty()) {
- wine_command = env.get("WINELOADER");
+ wine_command = env["WINELOADER"].to_string();
}
bp::ipstream output;
@@ -271,13 +229,13 @@ std::string get_wine_version() {
std::string join_quoted_strings(std::vector& strings) {
bool is_first = true;
- std::ostringstream joined_strigns{};
+ std::ostringstream joined_strings{};
for (const auto& option : strings) {
- joined_strigns << (is_first ? "'" : ", '") << option << "'";
+ joined_strings << (is_first ? "'" : ", '") << option << "'";
is_first = false;
}
- return joined_strigns.str();
+ return joined_strings.str();
}
Configuration load_config_for(const fs::path& yabridge_path) {
diff --git a/src/plugin/utils.h b/src/plugin/utils.h
index a9d3fb92..8e2d6fbb 100644
--- a/src/plugin/utils.h
+++ b/src/plugin/utils.h
@@ -46,15 +46,17 @@ class patched_async_pipe : public boost::process::async_pipe {
enum class PluginArchitecture { vst_32, vst_64 };
/**
- * Create a logger prefix based on the unique socket path for easy
- * identification. The socket path contains both the plugin's name and a unique
- * identifier.
+ * Create a logger prefix based on the endpoint base directory used for the
+ * sockets for easy identification. This will result in a prefix of the form
+ * `[-] `.
*
- * @param socket_path The path to the socket endpoint in use.
+ * @param endpoint_base_dir A directory name generated by
+ * `generate_endpoint_base()`.
*
* @return A prefix string for log messages.
*/
-std::string create_logger_prefix(const boost::filesystem::path& socket_path);
+std::string create_logger_prefix(
+ const boost::filesystem::path& endpoint_base_dir);
/**
* Determine the architecture of a VST plugin (or rather, a .dll file) based on
@@ -117,10 +119,11 @@ std::optional find_wineprefix();
/**
* Generate the group socket endpoint name used based on the name of the group,
* the Wine prefix in use and the plugin architecture. The resulting format is
- * `/tmp/yabridge-group---.sock`. In
- * this socket name the `wine_prefix_id` is a numerical hash based on the Wine
- * prefix in use. This way the same group name can be used for multiple Wine
- * prefixes and for both 32 and 64 bit plugins without clashes.
+ * in the form
+ * `/run/user//yabridge-group---.sock`.
+ * In this socket name the `wine_prefix_id` is a numerical hash based on the
+ * Wine prefix in use. This way the same group name can be used for multiple
+ * Wine prefixes and for both 32 and 64 bit plugins without clashes.
*
* @param group_name The name of the plugin group.
* @param wine_prefix The name of the Wine prefix in use. This should be
@@ -140,16 +143,6 @@ boost::filesystem::path generate_group_endpoint(
const boost::filesystem::path& wine_prefix,
const PluginArchitecture architecture);
-/**
- * Generate a unique name for the Unix domain socket endpoint based on the VST
- * plugin's name. This will also generate the parent directory if it does not
- * yet exist since we're using this in the constructor's initializer list.
- *
- * @return A path to a not yet existing Unix domain socket endpoint.
- * @throw std::runtime_error If no matching .dll file could be found.
- */
-boost::filesystem::path generate_plugin_endpoint();
-
/**
* Return a path to this `.so` file. This can be used to find out from where
* this link to or copy of `libyabridge.so` was loaded.
diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp
index bb01b6bb..b50fdbe4 100644
--- a/src/wine-host/bridges/group.cpp
+++ b/src/wine-host/bridges/group.cpp
@@ -209,10 +209,12 @@ void GroupBridge::accept_requests() {
// this has to be done on the same thread that's handling messages,
// and all window messages have to be handled from the same thread.
logger.log("Received request to host '" + request.plugin_path +
- "' using socket '" + request.socket_path + "'");
+ "' using socket endpoint base directory '" +
+ request.endpoint_base_dir + "'");
try {
auto bridge = std::make_unique(
- plugin_context, request.plugin_path, request.socket_path);
+ plugin_context, request.plugin_path,
+ request.endpoint_base_dir);
logger.log("Finished initializing '" + request.plugin_path +
"'");
diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp
index e2647cb3..7d5ad91f 100644
--- a/src/wine-host/bridges/vst2.cpp
+++ b/src/wine-host/bridges/vst2.cpp
@@ -67,16 +67,10 @@ Vst2Bridge& get_bridge_instance(const AEffect* plugin) {
Vst2Bridge::Vst2Bridge(boost::asio::io_context& main_context,
std::string plugin_dll_path,
- std::string socket_endpoint_path)
+ std::string endpoint_base_dir)
: io_context(main_context),
plugin_handle(LoadLibrary(plugin_dll_path.c_str()), FreeLibrary),
- socket_endpoint(socket_endpoint_path),
- host_vst_dispatch(io_context),
- host_vst_dispatch_midi_events(io_context),
- vst_host_callback(io_context),
- host_vst_parameters(io_context),
- host_vst_process_replacing(io_context),
- host_vst_control(io_context) {
+ sockets(io_context, endpoint_base_dir, false) {
// Got to love these C APIs
if (!plugin_handle) {
throw std::runtime_error("Could not load the Windows .dll file at '" +
@@ -101,14 +95,7 @@ Vst2Bridge::Vst2Bridge(boost::asio::io_context& main_context,
"'.");
}
- // It's very important that these sockets are accepted to in the same order
- // in the Linux plugin
- host_vst_dispatch.connect(socket_endpoint);
- host_vst_dispatch_midi_events.connect(socket_endpoint);
- vst_host_callback.connect(socket_endpoint);
- host_vst_parameters.connect(socket_endpoint);
- host_vst_process_replacing.connect(socket_endpoint);
- host_vst_control.connect(socket_endpoint);
+ sockets.connect();
// Initialize after communication has been set up
// We'll try to do the same `get_bridge_isntance` trick as in
@@ -132,13 +119,14 @@ Vst2Bridge::Vst2Bridge(boost::asio::io_context& main_context,
// of this object will be sent over the `dispatcher()` socket. This would be
// done after the host calls `effOpen()`, and when the plugin calls
// `audioMasterIOChanged()`.
- write_object(host_vst_control, EventResult{.return_value = 0,
- .payload = *plugin,
- .value_payload = std::nullopt});
+ write_object(sockets.host_vst_control,
+ EventResult{.return_value = 0,
+ .payload = *plugin,
+ .value_payload = std::nullopt});
// After sending the AEffect struct we'll receive this instance's
// configuration as a response
- config = read_object(host_vst_control);
+ config = read_object(sockets.host_vst_control);
// This works functionally identically to the `handle_dispatch()` function,
// but this socket will only handle MIDI events and it will handle them
@@ -160,7 +148,7 @@ void Vst2Bridge::handle_dispatch() {
while (true) {
try {
receive_event(
- host_vst_dispatch, std::nullopt,
+ sockets.host_vst_dispatch, std::nullopt,
passthrough_event(
plugin,
[&](AEffect* plugin, int opcode, int index, intptr_t value,
@@ -194,7 +182,8 @@ void Vst2Bridge::handle_dispatch_midi_events() {
while (true) {
try {
receive_event(
- host_vst_dispatch_midi_events, std::nullopt, [&](Event& event) {
+ sockets.host_vst_dispatch_midi_events, std::nullopt,
+ [&](Event& event) {
if (BOOST_LIKELY(event.opcode == effProcessEvents)) {
// For 99% of the plugins we can just call
// `effProcessReplacing()` and be done with it, but a
@@ -255,19 +244,19 @@ void Vst2Bridge::handle_parameters() {
// through on this socket since they have a lot of overlap. The
// presence of the `value` field tells us which one we're dealing
// with.
- auto request = read_object(host_vst_parameters);
+ auto request = read_object(sockets.host_vst_parameters);
if (request.value) {
// `setParameter`
plugin->setParameter(plugin, request.index, *request.value);
ParameterResult response{std::nullopt};
- write_object(host_vst_parameters, response);
+ write_object(sockets.host_vst_parameters, response);
} else {
// `getParameter`
float value = plugin->getParameter(plugin, request.index);
ParameterResult response{value};
- write_object(host_vst_parameters, response);
+ write_object(sockets.host_vst_parameters, response);
}
} catch (const boost::system::system_error&) {
// The plugin has cut off communications, so we can shut down this
@@ -288,8 +277,8 @@ void Vst2Bridge::handle_process_replacing() {
while (true) {
try {
- auto request = read_object(host_vst_process_replacing,
- process_buffer);
+ auto request = read_object(
+ sockets.host_vst_process_replacing, process_buffer);
// Let the plugin process the MIDI events that were received since
// the last buffer, and then clean up those events. This approach
// should not be needed but Kontakt only stores pointers to rather
@@ -348,8 +337,8 @@ void Vst2Bridge::handle_process_replacing() {
AudioBuffers response{output_buffers_single_precision,
request.sample_frames};
- write_object(host_vst_process_replacing, response,
- process_buffer);
+ write_object(sockets.host_vst_process_replacing,
+ response, process_buffer);
},
[&](std::vector>& input_buffers) {
// Exactly the same as the above, but for double
@@ -373,8 +362,8 @@ void Vst2Bridge::handle_process_replacing() {
AudioBuffers response{output_buffers_double_precision,
request.sample_frames};
- write_object(host_vst_process_replacing, response,
- process_buffer);
+ write_object(sockets.host_vst_process_replacing,
+ response, process_buffer);
}},
request.buffers);
@@ -419,7 +408,7 @@ intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin,
// When hosting multiple plugins in a group process, all plugins
// should get a unique window class
const std::string window_class =
- "yabridge plugin " + socket_endpoint.path();
+ "yabridge plugin " + sockets.base_dir.string();
Editor& editor_instance = editor.emplace(
config, window_class, x11_handle, plugin);
@@ -581,7 +570,7 @@ intptr_t Vst2Bridge::host_callback(AEffect* effect,
}
HostCallbackDataConverter converter(effect, time_info);
- return send_event(vst_host_callback, host_callback_mutex, converter,
+ return send_event(sockets.vst_host_callback, host_callback_mutex, converter,
std::nullopt, opcode, index, value, data, option);
}
diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h
index 3d0815af..162907e4 100644
--- a/src/wine-host/bridges/vst2.h
+++ b/src/wine-host/bridges/vst2.h
@@ -31,6 +31,7 @@
#include "../../common/configuration.h"
#include "../../common/logging.h"
+#include "../../common/communication.h"
#include "../editor.h"
#include "../utils.h"
@@ -46,7 +47,7 @@ struct EditorOpening {};
* plugin and provides host callback function for the plugin to talk back.
*
* @remark Because of Win32 API limitations, all window handling has to be done
- * from the same thread. Most plugins won't have any issues when using
+ * from a single thread. Most plugins won't have any issues when using
* multiple message loops, but the Melda plugins for instance will only update
* their GUIs from the message loop of the thread that created the first
* instance. This is why we pass an IO context to this class so everything
@@ -64,8 +65,8 @@ class Vst2Bridge {
* also be run from this context.
* @param plugin_dll_path A (Unix style) path to the VST plugin .dll file to
* load.
- * @param socket_endpoint_path A (Unix style) path to the Unix socket
- * endpoint the native VST plugin created to communicate over.
+ * @param endpoint_base_dir The base directory used for the socket
+ * endpoints. See `Sockets` for more information.
*
* @note The object has to be constructed from the same thread that calls
* `main_context.run()`.
@@ -75,7 +76,7 @@ class Vst2Bridge {
*/
Vst2Bridge(boost::asio::io_context& main_context,
std::string plugin_dll_path,
- std::string socket_endpoint_path);
+ std::string endpoint_base_dir);
/**
* Returns true if the message loop should be skipped. This happens when the
@@ -189,47 +190,9 @@ class Vst2Bridge {
AEffect* plugin;
/**
- * The UNIX domain socket endpoint used for communicating to this specific
- * bridged plugin.
+ * All sockets used for communicating with this specific plugin.
*/
- boost::asio::local::stream_protocol::endpoint socket_endpoint;
-
- // The naming convention for these sockets is `__`. For
- // instance the socket named `host_vst_dispatch` forwards
- // `AEffect.dispatch()` calls from the native VST host to the Windows VST
- // plugin (through the Wine VST host).
-
- /**
- * The socket that forwards all `dispatcher()` calls from the VST host to
- * the plugin.
- */
- boost::asio::local::stream_protocol::socket 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.
- */
- boost::asio::local::stream_protocol::socket host_vst_dispatch_midi_events;
- /**
- * The socket that forwards all `audioMaster()` calls from the Windows VST
- * plugin to the host.
- */
- boost::asio::local::stream_protocol::socket vst_host_callback;
- /**
- * Used for both `getParameter` and `setParameter` since they mostly
- * overlap.
- */
- boost::asio::local::stream_protocol::socket host_vst_parameters;
- boost::asio::local::stream_protocol::socket host_vst_process_replacing;
-
- /**
- * A control socket that sends data that is not suitable for the other
- * sockets. At the moment this is only used to, on startup, send the Windows
- * VST plugin's `AEffect` object to the native VST plugin, and to then send
- * the configuration (from `config`) back to the Wine host.
- */
- boost::asio::local::stream_protocol::socket host_vst_control;
+ Sockets sockets;
/**
* The thread that specifically handles `effProcessEvents` opcodes so the
diff --git a/src/wine-host/individual-host.cpp b/src/wine-host/individual-host.cpp
index 48e4f847..3f93e46b 100644
--- a/src/wine-host/individual-host.cpp
+++ b/src/wine-host/individual-host.cpp
@@ -39,7 +39,7 @@ void async_handle_events(boost::asio::steady_timer& timer, Vst2Bridge& bridge);
/**
* This is the default VST host application. It will load the specified VST2
- * plugin, and then connect back to the `libyabridge.so` instace that spawned
+ * plugin, and then connect back to the `libyabridge.so` instance that spawned
* this over the socket.
*
* The explicit calling convention is needed to work around a bug introduced in
@@ -48,9 +48,9 @@ void async_handle_events(boost::asio::steady_timer& timer, Vst2Bridge& bridge);
int __cdecl main(int argc, char* argv[]) {
set_realtime_priority();
- // We pass the name of the VST plugin .dll file to load and the Unix domain
- // socket to connect to in plugin/bridge.cpp as the first two arguments of
- // this process.
+ // We pass the name of the VST plugin .dll file to load and the base
+ // directory for the Unix domain socket endpoints to connect to as the first
+ // two arguments of this process in plugin/bridge.cpp.
if (argc < 3) {
std::cerr << "Usage: "
#ifdef __i386__
@@ -58,7 +58,7 @@ int __cdecl main(int argc, char* argv[]) {
#else
<< yabridge_individual_host_name
#endif
- << " " << std::endl;
+ << " " << std::endl;
return 1;
}