// yabridge: a Wine VST bridge
// Copyright (C) 2020-2021 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 "configuration.h"
// tomlplusplus recently got some Windows fixes, but they cause compilation
// errors and we don't need them so we'll just disable them outright
#define TOML_WINDOWS_COMPAT 0
#include
#include
#include
#include "utils.h"
namespace fs = boost::filesystem;
Configuration::Configuration() noexcept {}
Configuration::Configuration(const fs::path& config_path,
const fs::path& yabridge_path)
: Configuration() {
// Will throw a `toml::parsing_error` if the file cannot be parsed. Better
// to throw here rather than failing silently since syntax errors would
// otherwise be impossible to spot. We'll also have to sort all tables by
// the location in the file since tomlplusplus internally uses ordered maps
// so otherwise we'll get the tables sorted by key instead.
toml::table table = toml::parse_file(config_path.string());
// I wasn't able to wade through the template soup to come up with a better
// way to sort this by location, so please feel free to correct this if you
// know of a better way! The source locations has to be stored inside of the
// vector itself because the `node.source()` on the copies stored in this
// vector won't contain the proper location after we've iterated through
// `table`.
std::vector>
sorted_tables{};
for (auto [pattern, node] : table) {
if (const toml::table* config = node.as_table()) {
sorted_tables.push_back(
std::make_tuple(pattern, config->source(), *config));
}
}
std::sort(sorted_tables.begin(), sorted_tables.end(),
[](const auto& a, const auto& b) {
const auto& [a_pattern, a_source, a_table] = a;
const auto& [b_pattern, b_source, b_table] = b;
return a_source.begin.line < b_source.begin.line;
});
const fs::path relative_path =
yabridge_path.lexically_relative(config_path.parent_path());
for (const auto& [pattern, source, table] : sorted_tables) {
// First try to match the glob pattern, allow matching an entire
// directory for ease of use. If none of the patterns in the file match
// the plugin path then everything will be left at the defaults.
if (fnmatch(pattern.c_str(), relative_path.c_str(),
FNM_PATHNAME | FNM_LEADING_DIR) != 0) {
continue;
}
matched_file = config_path;
matched_pattern = pattern;
// If the table is missing some fields then they will simply be left at
// their defaults. At this point I'd really wish C++ could do pattern
// matching.
for (const auto& [key, value] : table) {
if (key == "group") {
if (const auto parsed_value = value.as_string()) {
group = parsed_value->get();
} else {
invalid_options.push_back(key);
}
} else if (key == "disable_pipes") {
// This option can be either enabled or disable with a boolean,
// or it can be set to an absolute path
if (const auto parsed_value = value.as_boolean()) {
if (*parsed_value) {
disable_pipes = get_temporary_directory() /
"yabridge-plugin-output.log";
} else {
disable_pipes = std::nullopt;
}
} else if (const auto parsed_value = value.as_string()) {
disable_pipes = parsed_value->get();
} else {
invalid_options.push_back(key);
}
} else if (key == "editor_coordinate_hack") {
if (const auto parsed_value = value.as_boolean()) {
editor_coordinate_hack = parsed_value->get();
} else {
invalid_options.push_back(key);
}
} else if (key == "editor_force_dnd") {
if (const auto parsed_value = value.as_boolean()) {
editor_force_dnd = parsed_value->get();
} else {
invalid_options.push_back(key);
}
} else if (key == "editor_xembed") {
if (const auto parsed_value = value.as_boolean()) {
editor_xembed = parsed_value->get();
} else {
invalid_options.push_back(key);
}
} else if (key == "frame_rate") {
if (const auto parsed_value = value.as_floating_point()) {
frame_rate = parsed_value->get();
} else if (const auto parsed_value = value.as_integer()) {
// For usability's sake we want to be a bit more lax than a
// normal TOML file would be and accept both floating point
// values and integers here
frame_rate = parsed_value->get();
} else {
invalid_options.push_back(key);
}
} else if (key == "hide_daw") {
if (const auto parsed_value = value.as_boolean()) {
hide_daw = parsed_value->get();
} else {
invalid_options.push_back(key);
}
} else if (key == "vst3_no_scaling") {
if (const auto parsed_value = value.as_boolean()) {
vst3_no_scaling = parsed_value->get();
} else {
invalid_options.push_back(key);
}
} else if (key == "vst3_prefer_32bit") {
if (const auto parsed_value = value.as_boolean()) {
vst3_prefer_32bit = parsed_value->get();
} else {
invalid_options.push_back(key);
}
} else {
unknown_options.push_back(key);
}
}
break;
}
}
std::chrono::steady_clock::duration Configuration::event_loop_interval()
const noexcept {
return std::chrono::duration_cast(
std::chrono::milliseconds(1000) / frame_rate.value_or(60.0));
}