// yabridge: a Wine plugin bridge
// Copyright (C) 2020-2022 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 "../vst3.h"
/**
* A RAII wrapper around `IRunLoop`'s event handlers so we can schedule tasks to
* be run in it. This is needed for REAPER, because function calls that involve
* GUI drawing (notable `IPlugFrame::resizeView()` and `IContextMenu::popup()`)
* have to be run from a thread owned by REAPER. If we don't do this, the
* `IPlugFrame::resizeView()` won't resize the actual window and both of these
* functions will eventually cause REAPER to segfault.
*/
class RunLoopTasks : public Steinberg::Linux::IEventHandler {
public:
/**
* Register an event handler in the host's run loop so we can schedule tasks
* to be run from there. This works very much like how we use Asio IO
* contexts everywhere else to run functions on other threads. All of this
* is backed by a dummy Unix domain socket, although REAPER will call the
* event handler regardless of whether the file descriptor is ready or not.
* eventfd would have made much more sense here, but Ardour doesn't support
* that.
*
* @throw std::runtime_error If the host does not support
* `Steinberg::Linux::IRunLoop`, or if registering the event handler was
* not successful. The caller should catch this and call back to not
* relying on the run loop.
*/
RunLoopTasks(Steinberg::IPtr plug_frame);
/**
* Unregister the event handler and close the file descriptor on cleanup.
*/
virtual ~RunLoopTasks();
DECLARE_FUNKNOWN_METHODS
/**
* Schedule a task to be run from the host's GUI thread in an `IRunLoop`
* event handler. This may block if the host is currently calling
* `onFDIsSet()`.
*
* @param task The task to execute. This can be used with
* `std::packaged_task` to run a computation that returns a value from the
* host's GUI thread.
*/
void schedule(fu2::unique_function task);
// From `IEventHandler`, required for REAPER because its GUI is not thread
// safe
void PLUGIN_API onFDIsSet(Steinberg::Linux::FileDescriptor fd) override;
private:
/**
* This pointer is cast from `plug_frame` once `IPlugView::setFrame()` has
* been called.
*/
Steinberg::FUnknownPtr run_loop_;
/**
* Tasks that should be executed in the next `IRunLoop` event handler call.
*
* @relates Vst3PlugViewProxyImpl::run_gui_task
*
* @see RunLoopTasks::schedule
*/
std::vector> tasks_;
std::mutex tasks_mutex_;
/**
* A dumy Unix domain socket file descriptor used to signal that there is a
* task ready. We'll pass this to the host's `IRunLoop` so it can tell when
* we have an event to handle.
*
* XXX: This should be backed by eventfd instead, but Ardour doesn't support
* that
*/
int socket_read_fd_ = -1;
/**
* The other side of `socket_read_fd`. We'll write to this when we want the
* hsot to call our event handler.
*/
int socket_write_fd_ = -1;
};
class Vst3PlugViewProxyImpl : public Vst3PlugViewProxy {
public:
Vst3PlugViewProxyImpl(Vst3PluginBridge& bridge,
Vst3PlugViewProxy::ConstructArgs&& args) noexcept;
/**
* When the reference count reaches zero and this destructor is called,
* we'll send a request to the Wine plugin host to destroy the corresponding
* object.
*/
~Vst3PlugViewProxyImpl() noexcept override;
/**
* We'll override the query interface to log queries for interfaces we do
* not (yet) support.
*/
tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid,
void** obj) override;
/**
* Run a task that's supposed to be run from the GUI thread.
* `IPlugFrame::resizeView()` and `IContextMenu::popup()` are the likely
* candidates here. This is needed for REAPER, as REAPER will segfault if
* you run those functions from a thread that's not owned by REAPER itself.
* If the `IPlugFrame` object passed to `IPlugView::setFrame()` supports
* `IRunLoop`, then we'll schedule `f` to be run from an even handler in the
* host's run loop. Otherwise `f` is run directly.
*
* This works similarly to
* `Vst3Bridge::do_mutual_recursion_or_handle_in_main_context`, except that
* we can post tasks to `run_loop_tasks_` instead of executing them directly
* in `main_context_` when no mutually recursive function calls are
* happening right now.
*
* @see Vst3HostBridge::send_mutually_recursive_message
*/
template
std::invoke_result_t run_gui_task(F&& fn) {
using Result = std::invoke_result_t;
// If `Vst3Bridge::send_mutually_recursive_message()` is currently being
// called (because the host is calling one of `IPlugView`'s methods from
// its GUI thread), then we'll call `fn` from that same thread.
// Otherwise we'll schedule the task to be run from an event handler
// registered to the host's run loop, if that exists. Finally if the
// host does not support `IRunLoop`, then we'll just run `fn` directly.
if (const auto result =
bridge_.maybe_run_on_mutual_recursion_thread(fn)) {
return *result;
}
if (run_loop_tasks_) {
std::packaged_task do_call(std::forward(fn));
std::future do_call_response = do_call.get_future();
run_loop_tasks_->schedule(std::move(do_call));
return do_call_response.get();
} else {
return fn();
}
}
// From `IPlugView`
tresult PLUGIN_API
isPlatformTypeSupported(Steinberg::FIDString type) override;
tresult PLUGIN_API attached(void* parent,
Steinberg::FIDString type) override;
tresult PLUGIN_API removed() override;
tresult PLUGIN_API onWheel(float distance) override;
tresult PLUGIN_API onKeyDown(char16 key,
int16 keyCode,
int16 modifiers) override;
tresult PLUGIN_API onKeyUp(char16 key,
int16 keyCode,
int16 modifiers) override;
tresult PLUGIN_API getSize(Steinberg::ViewRect* size) override;
tresult PLUGIN_API onSize(Steinberg::ViewRect* newSize) override;
tresult PLUGIN_API onFocus(TBool state) override;
tresult PLUGIN_API setFrame(Steinberg::IPlugFrame* frame) override;
tresult PLUGIN_API canResize() override;
tresult PLUGIN_API checkSizeConstraint(Steinberg::ViewRect* rect) override;
// From `IParameterFinder`
tresult PLUGIN_API
findParameter(int32 xPos,
int32 yPos,
Steinberg::Vst::ParamID& resultTag /*out*/) override;
// From `IPlugViewContentScaleSupport`
tresult PLUGIN_API setContentScaleFactor(ScaleFactor factor) override;
/**
* The `IPlugFrame` object passed by the host passed to us in
* `IPlugView::setFrame()`. When the plugin makes a callback on the
* `IPlugFrame` proxy object, we'll pass the call through to this object.
*/
Steinberg::IPtr plug_frame_;
private:
Vst3PluginBridge& bridge_;
/**
* If the host supports `IRunLoop`, we'll use this to run certain tasks from
* the host's GUI thread using a run loop event handler in
* `Vst3PlugViewProxyImpl::run_gui_task`.
*
* This will be an `std::nullopt` if the hostdoes not support `IRunLoop`.
*/
std::optional run_loop_tasks_;
// Caches
/**
* During resizing the host will likely constantly ask the plugin if it can
* be freely resized. Even if it is technically possible, I'm not aware of
* any plugins that change from not being able arbitrarily resizeable to
* being able to be resized like this. The reason why we might want to cache
* `IPlugView::canResize()` is because this function has to be run on the
* GUI thread, just like `IPlugView::onSize()` and
* `IPlugView::checkSizeConstraint`. Everything running in lockstep makes
* resizing a lot laggier than they would have to be, so as a compromise
* we'll remember this value for the duration of the resize.
*/
TimedValueCache can_resize_cache_;
std::mutex can_resize_cache_mutex_;
};