diff --git a/src/wine-host/group-host.cpp b/src/wine-host/group-host.cpp
index afd3f03c..76a7450d 100644
--- a/src/wine-host/group-host.cpp
+++ b/src/wine-host/group-host.cpp
@@ -14,7 +14,16 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+#include "boost-fix.h"
+
+#include
+#include
+#include
+#include
+#include
#include
+#include
+#include
// Generated inside of build directory
#include
@@ -22,6 +31,27 @@
#include "wine-bridge.h"
+// FIXME: `std::filesystem` is broken in wineg++, at least under Wine 5.8. Any
+// path operation will thrown an encoding related error.
+namespace fs = boost::filesystem;
+
+// TODO: Move most plumbing to another file
+
+/**
+ * Create a logger prefix containing the group name based on the socket path.
+ */
+std::string create_logger_prefix(const fs::path& socket_path);
+
+/**
+ * Continuously Read from a stream and write the lines to a logger instance.
+ *
+ * TODO: Merge this with the other similar function in `PluginBridge`
+ */
+void log_lines(Logger& logger,
+ boost::asio::posix::stream_descriptor& pipe,
+ boost::asio::streambuf& buffer,
+ std::string prefix);
+
/**
* This works very similar to the host application defined in
* `individual-host.cpp`, but instead of just loading a single plugin this will
@@ -39,34 +69,55 @@ int __cdecl main(int argc, char* argv[]) {
// Instead of directly hosting a plugin, this process will receive a UNIX
// domain socket endpoint path that it should listen on to allow yabridge
// instances to spawn plugins in this process.
- if (argc < 3) {
+ if (argc < 2) {
std::cerr << "Usage: "
#ifdef __i386__
<< yabridge_group_host_name_32bit
#else
<< yabridge_group_host_name
#endif
- << " " << std::endl;
+ << " " << std::endl;
return 1;
}
- const std::string group_name(argv[1]);
- const std::string group_socket_endpoint_path(argv[2]);
+ const std::string group_socket_endpoint_path(argv[1]);
// TODO: Before doing anything, try listening on the socket and fail
// silently (or log a message?) if another application is already
// listening on the socket. This way we don't need any complicated
// inter-process synchronization to ensure that there is a single
// active group host listening for this group.
- // TODO: We should somehow try and redirect this process's STDOUT and STDERR
- // streams to the logger so we can forward Wine's debug messages to
- // the log even the yabridge plugin instance that initially spawned
- // this group host process has exited. The only way I can think of
- // doing this would be using some kind of in memory file and the
- // `dup2()` system call.
- Logger logger = Logger::create_from_environment(group_name);
+ // Has to be initialized before redirecting the STDIO streams
+ Logger logger = Logger::create_from_environment(
+ create_logger_prefix(group_socket_endpoint_path));
+
+ // Redirect this process's STDOUT and STDERR streams to a pipe so we can
+ // process the output and redirect it to a logger. Needed to capture Wine
+ // debug output, since this process will likely outlive the yabridge
+ // instance that originally spawned it.
+ int stdout_pipe[2];
+ pipe(stdout_pipe);
+ dup2(stdout_pipe[1], STDOUT_FILENO);
+ close(stdout_pipe[1]);
+
+ int stderr_pipe[2];
+ pipe(stderr_pipe);
+ dup2(stderr_pipe[1], STDERR_FILENO);
+ close(stderr_pipe[1]);
+
+ boost::asio::io_context io_context;
+ boost::asio::streambuf stdout_buffer;
+ boost::asio::streambuf stderr_buffer;
+ boost::asio::posix::stream_descriptor stdout_redirect(io_context,
+ stdout_pipe[0]);
+ boost::asio::posix::stream_descriptor stderr_redirect(io_context,
+ stderr_pipe[0]);
+ log_lines(logger, stdout_redirect, stdout_buffer, "[STDOUT] ");
+ log_lines(logger, stderr_redirect, stderr_buffer, "[STDERR] ");
+ std::thread io_handler([&]() { io_context.run(); });
+
logger.log("Initializing yabridge group host version " +
std::string(yabridge_git_version)
#ifdef __i386__
@@ -74,8 +125,53 @@ int __cdecl main(int argc, char* argv[]) {
#endif
);
+ // TODO: Remove debug prints
+ printf("This should be caught now!\n");
+ std::cerr << "This too!" << std::endl;
+
// TODO: After initializing, listen for connections and spawn plugins
// the exact same way as what happens in `individual-host.cpp`
// TODO: Allow this process to exit when the last plugin exits. Make sure
// that that doesn't cause any race conditions.
+
+ // TODO: This usleep() is just to ensure that the second print to stderr
+ // also gets processed before stopping the IO context since we're
+ // immediately stopping it after starting. This would not needed in
+ // normal use.
+ usleep(1000);
+ io_context.stop();
+ io_handler.join();
+}
+
+void log_lines(Logger& logger,
+ boost::asio::posix::stream_descriptor& pipe,
+ boost::asio::streambuf& buffer,
+ std::string prefix) {
+ boost::asio::async_read_until(pipe, buffer, '\n',
+ [&, prefix](const auto&, size_t) {
+ std::string line;
+ std::getline(std::istream(&buffer), line);
+ logger.log(prefix + line);
+
+ log_lines(logger, pipe, buffer, prefix);
+ });
+}
+
+std::string create_logger_prefix(const fs::path& socket_path) {
+ // The group socket filename will be in the format
+ // '/tmp/yabridge-group---.sock',
+ // where Wine prefix ID is just Wine prefix ran through `std::hash` to
+ // prevent collisions without needing complicated filenames. We want to
+ // extract the group name.
+ std::string socket_name =
+ socket_path.filename().replace_extension().string();
+
+ std::smatch group_match;
+ std::regex group_regexp("^yabridge-group-(.*)-[^-]+-[^-]+$",
+ std::regex::ECMAScript);
+ if (std::regex_match(socket_name, group_match, group_regexp)) {
+ socket_name = group_match[1].str();
+ }
+
+ return "[" + socket_name + "] ";
}