// 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 "../common/configuration.h" /** * Boost 1.72 was released with a known breaking bug caused by a missing * typedef: https://github.com/boostorg/process/issues/116. * * Luckily this is easy to fix since it's not really possible to downgrade Boost * as it would break other applications. * * Check if this is still needed for other distros after Arch starts packaging * Boost 1.73. */ class patched_async_pipe : public boost::process::async_pipe { public: using boost::process::async_pipe::async_pipe; typedef typename handle_type::executor_type executor_type; }; /** * A tag to differentiate between 32 and 64-bit plugins, used to determine which * host application to use. */ enum class PluginArchitecture { vst_32, vst_64 }; /** * 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 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& endpoint_base_dir); /** * Determine the architecture of a VST plugin (or rather, a .dll file) based on * it's header values. * * See https://docs.microsoft.com/en-us/windows/win32/debug/pe-format for more * information on the PE32 format. * * @param plugin_path The path to the .dll file we're going to check. * * @return The detected architecture. * @throw std::runtime_error If the file is not a .dll file. */ PluginArchitecture find_vst_architecture(boost::filesystem::path); /** * Finds the Wine VST host (either `yabridge-host.exe` or `yabridge-host.exe` * depending on the plugin). For this we will search in two places: * * 1. Alongside libyabridge.so if the file got symlinked. This is useful * when developing, as you can simply symlink the the libyabridge.so * file in the build directory without having to install anything to * /usr. * 2. In the regular search path. * * @param plugin_arch The architecture of the plugin, either 64-bit or 32-bit. * Used to determine which host application to use, if available. * @param use_plugin_groups Whether the plugin is using plugin groups and we * should be looking for the group host instead of the individual plugin host. * * @return The a path to the VST host, if found. * @throw std::runtime_error If the Wine VST host could not be found. */ boost::filesystem::path find_vst_host(PluginArchitecture plugin_arch, bool use_plugin_groups); /** * Find the VST plugin .dll file that corresponds to this copy of * `libyabridge.so`. This should be the same as the name of this file but with a * `.dll` file extension instead of `.so`. In case this file does not exist and * the `.so` file is a symlink, we'll also repeat this check for the file it * links to. This is to support the workflow described in issue #3 where you use * symlinks to copies of `libyabridge.so`. * * @return The a path to the accompanying VST plugin .dll file. * @throw std::runtime_error If no matching .dll file could be found. */ boost::filesystem::path find_vst_plugin(); /** * Locate the Wine prefix this file is located in, if it is inside of a wine * prefix. This is done by locating the first parent directory that contains a * directory named `dosdevices`. * * @return Either the path to the Wine prefix (containing the `drive_c?` * directory), or `std::nullopt` if it is not inside of a wine prefix. */ 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 * 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 * obtained by first calling `set_wineprefix()` to allow the user to override * this, and then falling back to `$HOME/.wine` if the environment variable is * unset. Otherwise plugins run from outwide of a Wine prefix will not be * groupable with those run from within `~/.wine` even though they both run * under the same prefix. * @param architecture The architecture the plugin is using, since 64-bit * processes can't host 32-bit plugins and the other way around. * * @return A socket endpoint path that corresponds to the format described * above. */ boost::filesystem::path generate_group_endpoint( const std::string& group_name, const boost::filesystem::path& wine_prefix, const PluginArchitecture architecture); /** * 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. */ boost::filesystem::path get_this_file_location(); /** * Return the installed Wine version. This is obtained by from `wine --version` * and then stripping the `wine-` prefix. This respects the `WINELOADER` * environment variable used in the scripts generated by winegcc. * * This will *not* throw when Wine can not be found, but will instead return * ''. This way the user will still get some useful log files. */ std::string get_wine_version(); /** * Join a vector of strings with commas while wrapping the strings in quotes. * For example, `join_quoted_strings(std::vector{"string", "another * string", "also a string"})` outputs `"'string', 'another string', 'also a * string'"`. This is used to format the initialisation message. */ std::string join_quoted_strings(std::vector& strings); /** * Load the configuration that belongs to a copy of or symlink to * `libyabridge.so`. If no configuration file could be found then this will * return an empty configuration object with default settings. See the docstrong * on the `Configuration` class for more details on how to choose the config * file to load. * * This function will take any optional compile-time features that have not been * enabled into account. * * @param yabridge_path The path to the .so file that's being loaded.by the VST * host. This will be used both for the starting location of the search and to * determine which section in the config file to use. * * @return Either a configuration object populated with values from matched glob * pattern within the found configuration file, or an empty configuration * object if no configuration file could be found or if the plugin could not * be matched to any of the glob patterns in the configuration file. * * @see Configuration */ Configuration load_config_for(const boost::filesystem::path& yabridge_path); /** * Locate the Wine prefix and set the `WINEPREFIX` environment variable if * found. This way it's also possible to run .dll files outside of a Wine prefix * using the user's default prefix. */ boost::process::environment set_wineprefix(); /** * Starting from the starting file or directory, go up in the directory * hierarchy until we find a file named `filename`. * * @param filename The name of the file we're looking for. This can also be a * directory name since directories are also files. * @param starting_from The directory to start searching in. If this is a file, * then start searching in the directory the file is located in. * @param predicate The predicate to use to check if the path matches a file. * Needed as an easy way to limit the search to directories only since C++17 * does not have any built in coroutines or generators. * * @return The path to the *file* found, or `std::nullopt` if the file could not * be found. */ template std::optional find_dominating_file( const std::string& filename, boost::filesystem::path starting_dir, F predicate = boost::filesystem::exists) { while (starting_dir != "") { const boost::filesystem::path candidate = starting_dir / filename; if (predicate(candidate)) { return candidate; } starting_dir = starting_dir.parent_path(); } return std::nullopt; }