diff --git a/meson.build b/meson.build index ed88f111..360fab2e 100644 --- a/meson.build +++ b/meson.build @@ -94,7 +94,10 @@ host_sources = [ ] individual_host_sources = host_sources + ['src/wine-host/individual-host.cpp'] -group_host_sources = host_sources + ['src/wine-host/group-host.cpp'] +group_host_sources = host_sources + [ + 'src/wine-host/bridges/group.cpp', + 'src/wine-host/group-host.cpp', +] executable( individual_host_name_64bit, diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp new file mode 100644 index 00000000..64b1599e --- /dev/null +++ b/src/wine-host/bridges/group.cpp @@ -0,0 +1,44 @@ +// 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 "group.h" + +#include + +StdIoCapture::StdIoCapture(boost::asio::io_context& io_context, + int file_descriptor) + : pipe(io_context), + target_fd(file_descriptor), + original_fd_copy(dup(file_descriptor)) { + // We'll use the second element of these two file descriptors to reopen + // `file_descriptor`, and the first one to read the captured contents from + ::pipe(pipe_fd); + + // We've already created a copy of the original file descriptor, so we can + // reopen it using the newly created pipe + dup2(pipe_fd[1], target_fd); + close(pipe_fd[1]); + + pipe.assign(pipe_fd[0]); +} + +StdIoCapture::~StdIoCapture() { + // Restore the original file descriptor and close the pipe. The other wend + // was already closed in the constructor. + dup2(original_fd_copy, target_fd); + close(original_fd_copy); + close(pipe_fd[0]); +} diff --git a/src/wine-host/bridges/group.h b/src/wine-host/bridges/group.h new file mode 100644 index 00000000..89c6c866 --- /dev/null +++ b/src/wine-host/bridges/group.h @@ -0,0 +1,78 @@ +// 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 "../boost-fix.h" + +#include + +/** + * Encapsulate capturing the STDOUT or STDERR stream by opening a pipe and + * reopening the passed file descriptor as one of the ends of the newly opened + * pipe. This allows all output sent to be read from that pipe. This is needed + * to capture all (debug) output from Wine and the hosted plugins so we can + * prefix it with a timestamp and a group identifier and potentially write it to + * a log file. Since the host application is run independently of the yabridge + * instance that spawned it, this can't simply be done by the caller like we're + * doing for Wine output in individually hosted plugins. + */ +class StdIoCapture { + public: + /** + * Redirect all output sent to a file descriptor (e.g. `STDOUT_FILENO` or + * `STDERR_FILENO`) to a pipe. `StdIoCapture::pipe` can be used to read from + * this pipe. + * + * @param io_context The IO context to create the captured pipe stream on. + * @param file_descriptor The file descriptor to remap. + */ + StdIoCapture(boost::asio::io_context& io_context, int file_descriptor); + + StdIoCapture(const StdIoCapture&) = delete; + StdIoCapture& operator=(const StdIoCapture&) = delete; + + /** + * On cleanup, close the outgoing file descriptor from the pipe and restore + * the original file descriptor for the captured stream. + */ + ~StdIoCapture(); + + /** + * The pipe endpoint where all output from the original file descriptor gets + * redirected to. This can be read from like any other `Boost.Asio` stream. + */ + boost::asio::posix::stream_descriptor pipe; + + private: + /** + * The file descriptor of the stream we're capturing. + */ + const int target_fd; + + /** + * A copy of the original file descriptor. Will be used to undo + * the capture when this object gets destroyed. + */ + const int original_fd_copy; + + /** + * The two file descriptors generated by the `pipe()` function call. + * `pipe_fd[1]` is used to reopen/capture the passed file descriptor, and + * `pipe_fd[0]` can be used to read the captured output from. + */ + int pipe_fd[2]; +}; diff --git a/src/wine-host/group-host.cpp b/src/wine-host/group-host.cpp index be2b8b41..ff846183 100644 --- a/src/wine-host/group-host.cpp +++ b/src/wine-host/group-host.cpp @@ -16,8 +16,6 @@ #include "boost-fix.h" -#include -#include #include #include #include @@ -29,6 +27,7 @@ #include #include +#include "bridges/group.h" #include "bridges/vst2.h" // FIXME: `std::filesystem` is broken in wineg++, at least under Wine 5.8. Any @@ -97,25 +96,14 @@ int __cdecl main(int argc, char* argv[]) { // 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] "); + StdIoCapture stdout_redirect(io_context, STDOUT_FILENO); + StdIoCapture stderr_redirect(io_context, STDERR_FILENO); + log_lines(logger, stdout_redirect.pipe, stdout_buffer, "[STDOUT] "); + log_lines(logger, stderr_redirect.pipe, stderr_buffer, "[STDERR] "); + std::thread io_handler([&]() { io_context.run(); }); logger.log("Initializing yabridge group host version " +