diff --git a/src/common/communication.h b/src/common/communication.h index 309d9147..46bc0528 100644 --- a/src/common/communication.h +++ b/src/common/communication.h @@ -16,20 +16,14 @@ #pragma once +#include #include -#include -#include -#include - #ifdef __WINE__ #include "../wine-host/boost-fix.h" #endif #include -#include "logging.h" -#include "serialization.h" - template using OutputAdapter = bitsery::OutputBufferAdapter; @@ -107,217 +101,3 @@ inline T read_object(Socket& socket) { T object; return read_object(socket, object); } - -/** - * Encodes the base behavior for reading from and writing to the `data` argument - * for event dispatch functions. This provides base functionality for these - * kinds of events. The `dispatch()` function will require some more specific - * structs. - */ -class DefaultDataConverter { - public: - virtual ~DefaultDataConverter(){}; - - /** - * Read data from the `data` void pointer into a an `EventPayload` value - * that can be serialized and conveys the meaning of the event. - * - * If this returns a nullopt, then the event won't be performed at all. Some - * plugins perform `audioMasterUpdateDisplay` host callbacks and apparently - * some hosts just outright crash when they receive these functions, so they - * have to be filtered out. Please let me know if there's some way to detect - * whether the host supports these callbacks before sending them! - */ - virtual std::optional read(const int /*opcode*/, - const intptr_t /*value*/, - const void* data) { - if (data == nullptr) { - return nullptr; - } - - // Assume buffers are zeroed out, this is probably not the case - const char* c_string = static_cast(data); - if (c_string[0] != 0) { - return std::string(c_string); - } else { - return WantsString{}; - } - } - - /** - * Write the reponse back to the data pointer. - */ - virtual void write(const int /*opcode*/, - void* data, - const EventResult& response) { - if (response.data.has_value()) { - char* output = static_cast(data); - - // For correctness we will copy the entire buffer and add a - // terminating null byte ourselves. In practice `response.data` will - // only ever contain C-style strings, but this would work with any - // other data format that can contain null bytes. - std::copy(response.data->begin(), response.data->end(), output); - output[response.data->size()] = 0; - } - } - - /** - * This function can override a callback's return value based on the opcode. - * This is used in one place to return a pointer to a `VstTime` object - * that's contantly being updated. - * - * @param opcode The opcode for the current event. - * @param original The original return value as returned by the callback - * function. - */ - virtual intptr_t return_value(const int /*opcode*/, - const intptr_t original) { - return original; - } -}; - -/** - * Serialize and send an event over a socket. This is used for both the host -> - * plugin 'dispatch' events and the plugin -> host 'audioMaster' host callbacks - * since they follow the same format. See one of those functions for details on - * the parameters and return value of this function. - * - * @param data_converter Some struct that knows how to read data from and write - * data back to the `data` void pointer. For host callbacks this parameter - * contains either a string or a null pointer while `dispatch()` calls might - * contain opcode specific structs. See the documentation for `EventPayload` - * for more information. The `DefaultDataConverter` defined above handles the - * basic behavior that's sufficient for hsot callbacks. - * @param logging A pair containing a logger instance and whether or not this is - * for sending `dispatch()` events or host callbacks. Optional since it - * doesn't have to be done on both sides. - * - * @relates passthrough_event - */ -template -intptr_t send_event(boost::asio::local::stream_protocol::socket& socket, - D& data_converter, - int opcode, - int index, - intptr_t value, - void* data, - float option, - std::optional> logging) { - // Encode the right payload type for this event. Check the documentation for - // `EventPayload` for more information. We have to skip some opcodes because - // some VST hsots will outright crash if they receive them, please let me - // know if there's a better way to do this. - const std::optional payload = - data_converter.read(opcode, value, data); - if (!payload.has_value()) { - return 1; - } - - if (logging.has_value()) { - auto [logger, is_dispatch] = logging.value(); - logger.log_event(is_dispatch, opcode, index, value, payload.value(), - option); - } - - const Event event{opcode, index, value, option, payload.value()}; - write_object(socket, event); - - const auto response = read_object(socket); - - if (logging.has_value()) { - auto [logger, is_dispatch] = logging.value(); - logger.log_event_response(is_dispatch, response.return_value, - response.data); - } - - data_converter.write(opcode, data, response); - - return data_converter.return_value(opcode, response.return_value); -} - -/** - * Receive an event from a socket and pass it through to some callback function. - * This is used for both the host -> plugin 'dispatch' events and the plugin -> - * host 'audioMaster' host callbacks. This callback function is either one of - * those functions. - * - * @param socket The socket to receive on and to send the response back to. - * @param plugin The `AEffect` instance that should be passed to the callback - * function. - * @param callback The function to call with the arguments received from the - * socket. - * @param logging A pair containing a logger instance and whether or not this is - * for sending `dispatch()` events or host callbacks. Optional since it - * doesn't have to be done on both sides. - * - * @relates send_event - */ -template -void passthrough_event(boost::asio::local::stream_protocol::socket& socket, - AEffect* plugin, - F callback, - std::optional> logging) { - auto event = read_object(socket); - if (logging.has_value()) { - auto [logger, is_dispatch] = logging.value(); - logger.log_event(is_dispatch, event.opcode, event.index, event.value, - event.payload, event.option); - } - - std::array string_buffer; - void* data = std::visit( - overload{ - [&](const std::nullptr_t&) -> void* { return nullptr; }, - [&](const std::string& s) -> void* { - return const_cast(s.c_str()); - }, - [&](DynamicVstEvents& events) -> void* { - return &events.as_c_events(); - }, - [&](WantsChunkBuffer&) -> void* { return string_buffer.data(); }, - [&](WantsVstTimeInfo&) -> void* { return nullptr; }, - [&](WantsString&) -> void* { return string_buffer.data(); }}, - event.payload); - - const intptr_t return_value = callback(plugin, event.opcode, event.index, - event.value, data, event.option); - - // Only write back data when needed, this depends on the event payload type - // XXX: Is it possbile here that we got passed a non empty buffer (i.e. - // because it was not zeroed out by the host) for an event that should - // report some data back? - const auto response_data = std::visit( - overload{ - [&](WantsChunkBuffer&) -> std::optional { - // In this case the plugin will have written its data stored in - // an array to which a pointer is stored in `data`, with the - // return value from the event determines how much data the - // plugin has written - return std::string(*static_cast(data), return_value); - }, - [&](WantsVstTimeInfo&) -> std::optional { - // Not sure why the VST API has twenty different ways of - // returning structs, but in this case the value returned from - // the callback function is actually a pointer to a - // `VstTimeInfo` struct! - // TODO: Maybe add a variant from these return types similar to - // `EventPayload`, even though this is as far as I'm aware - // the only non-string/buffer being returned. - return std::string(reinterpret_cast(return_value), - sizeof(VstTimeInfo)); - }, - [&](WantsString&) -> std::optional { - return std::string(static_cast(data)); - }, - [&](auto) -> std::optional { return std::nullopt; }}, - event.payload); - - if (logging.has_value()) { - auto [logger, is_dispatch] = logging.value(); - logger.log_event_response(is_dispatch, return_value, response_data); - } - - EventResult response{return_value, response_data}; - write_object(socket, response); -} diff --git a/src/common/events.h b/src/common/events.h new file mode 100644 index 00000000..5c033b68 --- /dev/null +++ b/src/common/events.h @@ -0,0 +1,234 @@ +// 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 "communication.h" +#include "logging.h" + +/** + * Encodes the base behavior for reading from and writing to the `data` argument + * for event dispatch functions. This provides base functionality for these + * kinds of events. The `dispatch()` function will require some more specific + * structs. + */ +class DefaultDataConverter { + public: + virtual ~DefaultDataConverter(){}; + + /** + * Read data from the `data` void pointer into a an `EventPayload` value + * that can be serialized and conveys the meaning of the event. + * + * If this returns a nullopt, then the event won't be performed at all. Some + * plugins perform `audioMasterUpdateDisplay` host callbacks and apparently + * some hosts just outright crash when they receive these functions, so they + * have to be filtered out. Please let me know if there's some way to detect + * whether the host supports these callbacks before sending them! + */ + virtual std::optional read(const int /*opcode*/, + const intptr_t /*value*/, + const void* data) { + if (data == nullptr) { + return nullptr; + } + + // Assume buffers are zeroed out, this is probably not the case + const char* c_string = static_cast(data); + if (c_string[0] != 0) { + return std::string(c_string); + } else { + return WantsString{}; + } + } + + /** + * Write the reponse back to the data pointer. + */ + virtual void write(const int /*opcode*/, + void* data, + const EventResult& response) { + if (response.data.has_value()) { + char* output = static_cast(data); + + // For correctness we will copy the entire buffer and add a + // terminating null byte ourselves. In practice `response.data` will + // only ever contain C-style strings, but this would work with any + // other data format that can contain null bytes. + std::copy(response.data->begin(), response.data->end(), output); + output[response.data->size()] = 0; + } + } + + /** + * This function can override a callback's return value based on the opcode. + * This is used in one place to return a pointer to a `VstTime` object + * that's contantly being updated. + * + * @param opcode The opcode for the current event. + * @param original The original return value as returned by the callback + * function. + */ + virtual intptr_t return_value(const int /*opcode*/, + const intptr_t original) { + return original; + } +}; + +/** + * Serialize and send an event over a socket. This is used for both the host -> + * plugin 'dispatch' events and the plugin -> host 'audioMaster' host callbacks + * since they follow the same format. See one of those functions for details on + * the parameters and return value of this function. + * + * @param data_converter Some struct that knows how to read data from and write + * data back to the `data` void pointer. For host callbacks this parameter + * contains either a string or a null pointer while `dispatch()` calls might + * contain opcode specific structs. See the documentation for `EventPayload` + * for more information. The `DefaultDataConverter` defined above handles the + * basic behavior that's sufficient for hsot callbacks. + * @param logging A pair containing a logger instance and whether or not this is + * for sending `dispatch()` events or host callbacks. Optional since it + * doesn't have to be done on both sides. + * + * @relates passthrough_event + */ +template +intptr_t send_event(boost::asio::local::stream_protocol::socket& socket, + D& data_converter, + int opcode, + int index, + intptr_t value, + void* data, + float option, + std::optional> logging) { + // Encode the right payload type for this event. Check the documentation for + // `EventPayload` for more information. We have to skip some opcodes because + // some VST hsots will outright crash if they receive them, please let me + // know if there's a better way to do this. + const std::optional payload = + data_converter.read(opcode, value, data); + if (!payload.has_value()) { + return 1; + } + + if (logging.has_value()) { + auto [logger, is_dispatch] = logging.value(); + logger.log_event(is_dispatch, opcode, index, value, payload.value(), + option); + } + + const Event event{opcode, index, value, option, payload.value()}; + write_object(socket, event); + + const auto response = read_object(socket); + + if (logging.has_value()) { + auto [logger, is_dispatch] = logging.value(); + logger.log_event_response(is_dispatch, response.return_value, + response.data); + } + + data_converter.write(opcode, data, response); + + return data_converter.return_value(opcode, response.return_value); +} + +/** + * Receive an event from a socket and pass it through to some callback function. + * This is used for both the host -> plugin 'dispatch' events and the plugin -> + * host 'audioMaster' host callbacks. This callback function is either one of + * those functions. + * + * @param socket The socket to receive on and to send the response back to. + * @param plugin The `AEffect` instance that should be passed to the callback + * function. + * @param callback The function to call with the arguments received from the + * socket. + * @param logging A pair containing a logger instance and whether or not this is + * for sending `dispatch()` events or host callbacks. Optional since it + * doesn't have to be done on both sides. + * + * @relates send_event + */ +template +void passthrough_event(boost::asio::local::stream_protocol::socket& socket, + AEffect* plugin, + F callback, + std::optional> logging) { + auto event = read_object(socket); + if (logging.has_value()) { + auto [logger, is_dispatch] = logging.value(); + logger.log_event(is_dispatch, event.opcode, event.index, event.value, + event.payload, event.option); + } + + std::array string_buffer; + void* data = std::visit( + overload{ + [&](const std::nullptr_t&) -> void* { return nullptr; }, + [&](const std::string& s) -> void* { + return const_cast(s.c_str()); + }, + [&](DynamicVstEvents& events) -> void* { + return &events.as_c_events(); + }, + [&](WantsChunkBuffer&) -> void* { return string_buffer.data(); }, + [&](WantsVstTimeInfo&) -> void* { return nullptr; }, + [&](WantsString&) -> void* { return string_buffer.data(); }}, + event.payload); + + const intptr_t return_value = callback(plugin, event.opcode, event.index, + event.value, data, event.option); + + // Only write back data when needed, this depends on the event payload type + // XXX: Is it possbile here that we got passed a non empty buffer (i.e. + // because it was not zeroed out by the host) for an event that should + // report some data back? + const auto response_data = std::visit( + overload{ + [&](WantsChunkBuffer&) -> std::optional { + // In this case the plugin will have written its data stored in + // an array to which a pointer is stored in `data`, with the + // return value from the event determines how much data the + // plugin has written + return std::string(*static_cast(data), return_value); + }, + [&](WantsVstTimeInfo&) -> std::optional { + // Not sure why the VST API has twenty different ways of + // returning structs, but in this case the value returned from + // the callback function is actually a pointer to a + // `VstTimeInfo` struct! + // TODO: Maybe add a variant from these return types similar to + // `EventPayload`, even though this is as far as I'm aware + // the only non-string/buffer being returned. + return std::string(reinterpret_cast(return_value), + sizeof(VstTimeInfo)); + }, + [&](WantsString&) -> std::optional { + return std::string(static_cast(data)); + }, + [&](auto) -> std::optional { return std::nullopt; }}, + event.payload); + + if (logging.has_value()) { + auto [logger, is_dispatch] = logging.value(); + logger.log_event_response(is_dispatch, return_value, response_data); + } + + EventResult response{return_value, response_data}; + write_object(socket, response); +} diff --git a/src/common/logging.h b/src/common/logging.h index 51167236..e0eca0e5 100644 --- a/src/common/logging.h +++ b/src/common/logging.h @@ -16,7 +16,6 @@ #pragma once -#include #include #include diff --git a/src/common/serialization.h b/src/common/serialization.h index f361a624..5765801c 100644 --- a/src/common/serialization.h +++ b/src/common/serialization.h @@ -48,7 +48,7 @@ constexpr size_t max_midi_events = 32; * in one of the dispatch functions. This is used to create buffers for plugins * to write strings to. */ -constexpr size_t max_string_length = 64; +[[maybe_unused]] constexpr size_t max_string_length = 64; /** * The size for a buffer in which we're receiving chunks. diff --git a/src/plugin/host-bridge.cpp b/src/plugin/host-bridge.cpp index e3cabce5..bc661bb7 100644 --- a/src/plugin/host-bridge.cpp +++ b/src/plugin/host-bridge.cpp @@ -25,6 +25,9 @@ #include #include +#include "../common/communication.h" +#include "../common/events.h" + namespace bp = boost::process; // I'd rather use std::filesystem instead, but Boost.Process depends on // boost::filesystem diff --git a/src/plugin/host-bridge.h b/src/plugin/host-bridge.h index 80e1a9a4..2698db8e 100644 --- a/src/plugin/host-bridge.h +++ b/src/plugin/host-bridge.h @@ -25,7 +25,6 @@ #include #include -#include "../common/communication.h" #include "../common/logging.h" /** diff --git a/src/wine-host/plugin-bridge.cpp b/src/wine-host/plugin-bridge.cpp index b6c7c15f..134b6aa5 100644 --- a/src/wine-host/plugin-bridge.cpp +++ b/src/wine-host/plugin-bridge.cpp @@ -17,6 +17,10 @@ #include "plugin-bridge.h" #include +#include + +#include "../common/communication.h" +#include "../common/events.h" namespace fs = boost::filesystem; diff --git a/src/wine-host/plugin-bridge.h b/src/wine-host/plugin-bridge.h index 980c07ee..9eb90007 100644 --- a/src/wine-host/plugin-bridge.h +++ b/src/wine-host/plugin-bridge.h @@ -16,8 +16,6 @@ #pragma once -// This included is redundant, but ccls will complain if this is not included -// before windowes.h even though it will compile just fine #include "boost-fix.h" #define NOMINMAX @@ -28,9 +26,9 @@ #include #include +#include #include -#include "../common/communication.h" #include "../common/logging.h" /**