Run certain GUI tasks from the host's run loop

This was a bit of a tricky one because it requires simulating mutual
recursion, but it's needed for REAPER as otherwide calls to
`IPlugFrame::resizeView()` and `IContextMenu::popup()` might cause
REAPER to segfault because its GUI is not thread safe.
This commit is contained in:
Robbert van der Helm
2021-01-18 14:19:31 +01:00
parent 5ad47c8c68
commit bb5471f2d9
5 changed files with 333 additions and 30 deletions
+102 -16
View File
@@ -16,6 +16,73 @@
#include "plug-view-proxy.h"
RunLoopTasks::RunLoopTasks(Steinberg::IPtr<Steinberg::IPlugFrame> plug_frame)
: run_loop(plug_frame) {
FUNKNOWN_CTOR
if (!run_loop) {
throw std::runtime_error(
"The host's 'IPlugFrame' object does not support 'IRunLoop'");
}
// This should be backed by eventfd instead, but Ardour doesn't allow that
std::array<int, 2> sockets;
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0,
sockets.data()) != 0) {
throw std::runtime_error("Failed to create a Unix domain socket");
}
socket_read_fd = sockets[0];
socket_write_fd = sockets[1];
if (run_loop->registerEventHandler(this, socket_read_fd) !=
Steinberg::kResultOk) {
throw std::runtime_error(
"Failed to register an event handler with the host's run loop");
}
}
RunLoopTasks::~RunLoopTasks() {
FUNKNOWN_DTOR
run_loop->unregisterEventHandler(this);
close(socket_read_fd);
close(socket_write_fd);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
IMPLEMENT_FUNKNOWN_METHODS(RunLoopTasks,
Steinberg::Linux::IEventHandler,
Steinberg::Linux::IEventHandler::iid)
#pragma GCC diagnostic pop
void RunLoopTasks::schedule(fu2::unique_function<void()> task) {
std::lock_guard eventfd_lock(tasks_mutex);
tasks.push_back(std::move(task));
uint8_t notify_value = 1;
write(socket_write_fd, &notify_value, sizeof(notify_value));
}
void PLUGIN_API
RunLoopTasks::onFDIsSet(Steinberg::Linux::FileDescriptor /*fd*/) {
std::lock_guard lock(tasks_mutex);
// Run all tasks that have been submitted to our queue from the host's
// calling thread (which will be the GUI thread)
for (auto& task : tasks) {
task();
// This should in theory stop the host from calling this function, but
// REAPER doesn't care. And funnily enough we only have to do all of
// this because of REAPER.
uint8_t notify_value;
read(socket_read_fd, &notify_value, sizeof(notify_value));
}
tasks.clear();
}
Vst3PlugViewProxyImpl::Vst3PlugViewProxyImpl(
Vst3PluginBridge& bridge,
Vst3PlugViewProxy::ConstructArgs&& args)
@@ -24,7 +91,7 @@ Vst3PlugViewProxyImpl::Vst3PlugViewProxyImpl(
Vst3PlugViewProxyImpl::~Vst3PlugViewProxyImpl() {
// Also drop the plug view smart pointer on the Wine side when this gets
// dropped
bridge.send_message(
send_mutually_recursive_message(
Vst3PlugViewProxy::Destruct{.owner_instance_id = owner_instance_id()});
}
@@ -42,8 +109,9 @@ Vst3PlugViewProxyImpl::isPlatformTypeSupported(Steinberg::FIDString type) {
if (type) {
// We'll swap the X11 window ID platform type string for the Win32 HWND
// equivalent on the Wine side
return bridge.send_message(YaPlugView::IsPlatformTypeSupported{
.owner_instance_id = owner_instance_id(), .type = type});
return send_mutually_recursive_message(
YaPlugView::IsPlatformTypeSupported{
.owner_instance_id = owner_instance_id(), .type = type});
} else {
bridge.logger.log(
"WARNING: Null pointer passed to "
@@ -57,7 +125,7 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::attached(void* parent,
if (parent && type) {
// We will embed the Wine Win32 window into the X11 window provided by
// the host
return bridge.send_message(YaPlugView::Attached{
return send_mutually_recursive_message(YaPlugView::Attached{
.owner_instance_id = owner_instance_id(),
.parent = reinterpret_cast<native_size_t>(parent),
.type = type});
@@ -69,19 +137,19 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::attached(void* parent,
}
tresult PLUGIN_API Vst3PlugViewProxyImpl::removed() {
return bridge.send_message(
return send_mutually_recursive_message(
YaPlugView::Removed{.owner_instance_id = owner_instance_id()});
}
tresult PLUGIN_API Vst3PlugViewProxyImpl::onWheel(float distance) {
return bridge.send_message(YaPlugView::OnWheel{
return send_mutually_recursive_message(YaPlugView::OnWheel{
.owner_instance_id = owner_instance_id(), .distance = distance});
}
tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyDown(char16 key,
int16 keyCode,
int16 modifiers) {
return bridge.send_message(
return send_mutually_recursive_message(
YaPlugView::OnKeyDown{.owner_instance_id = owner_instance_id(),
.key = key,
.key_code = keyCode,
@@ -91,7 +159,7 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyDown(char16 key,
tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyUp(char16 key,
int16 keyCode,
int16 modifiers) {
return bridge.send_message(
return send_mutually_recursive_message(
YaPlugView::OnKeyUp{.owner_instance_id = owner_instance_id(),
.key = key,
.key_code = keyCode,
@@ -101,7 +169,7 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyUp(char16 key,
tresult PLUGIN_API Vst3PlugViewProxyImpl::getSize(Steinberg::ViewRect* size) {
if (size) {
const GetSizeResponse response =
bridge.send_message(YaPlugView::GetSize{
send_mutually_recursive_message(YaPlugView::GetSize{
.owner_instance_id = owner_instance_id(), .size = *size});
*size = response.updated_size;
@@ -116,7 +184,7 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::getSize(Steinberg::ViewRect* size) {
tresult PLUGIN_API Vst3PlugViewProxyImpl::onSize(Steinberg::ViewRect* newSize) {
if (newSize) {
return bridge.send_message(YaPlugView::OnSize{
return send_mutually_recursive_message(YaPlugView::OnSize{
.owner_instance_id = owner_instance_id(), .new_size = *newSize});
} else {
bridge.logger.log(
@@ -126,7 +194,7 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::onSize(Steinberg::ViewRect* newSize) {
}
tresult PLUGIN_API Vst3PlugViewProxyImpl::onFocus(TBool state) {
return bridge.send_message(YaPlugView::OnFocus{
return send_mutually_recursive_message(YaPlugView::OnFocus{
.owner_instance_id = owner_instance_id(), .state = state});
}
@@ -138,7 +206,25 @@ Vst3PlugViewProxyImpl::setFrame(Steinberg::IPlugFrame* frame) {
// this component handler
plug_frame = frame;
return bridge.send_message(YaPlugView::SetFrame{
// REAPER's GUI is not thread safe, and if we don't call
// `IPlugFrame::resizeView()` or `IContextMenu::popup()` from a thread
// owned by REAPER then REAPER will eventually segfault We should thus
// try to call those functions from an `IRunLoop` event handler.
try {
run_loop_tasks = Steinberg::owned(new RunLoopTasks(plug_frame));
} catch (const std::runtime_error& e) {
// In case the host does not support `IRunLoop` or if we can't
// register an event handler, we'll throw during `RunLoopTasks`'
// constructor
run_loop_tasks = nullptr;
bridge.logger.log(
"The host does not support IRunLoop, falling back to naive GUI "
"function execution: " +
std::string(e.what()));
}
return send_mutually_recursive_message(YaPlugView::SetFrame{
.owner_instance_id = owner_instance_id(),
.plug_frame_args = Vst3PlugFrameProxy::ConstructArgs(
plug_frame, owner_instance_id())});
@@ -150,7 +236,7 @@ Vst3PlugViewProxyImpl::setFrame(Steinberg::IPlugFrame* frame) {
}
tresult PLUGIN_API Vst3PlugViewProxyImpl::canResize() {
return bridge.send_message(
return send_mutually_recursive_message(
YaPlugView::CanResize{.owner_instance_id = owner_instance_id()});
}
@@ -158,7 +244,7 @@ tresult PLUGIN_API
Vst3PlugViewProxyImpl::checkSizeConstraint(Steinberg::ViewRect* rect) {
if (rect) {
const CheckSizeConstraintResponse response =
bridge.send_message(YaPlugView::CheckSizeConstraint{
send_mutually_recursive_message(YaPlugView::CheckSizeConstraint{
.owner_instance_id = owner_instance_id(), .rect = *rect});
*rect = response.updated_rect;
@@ -177,7 +263,7 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::findParameter(
int32 yPos,
Steinberg::Vst::ParamID& resultTag /*out*/) {
const FindParameterResponse response =
bridge.send_message(YaParameterFinder::FindParameter{
send_mutually_recursive_message(YaParameterFinder::FindParameter{
.owner_instance_id = owner_instance_id(),
.x_pos = xPos,
.y_pos = yPos});
@@ -189,7 +275,7 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::findParameter(
tresult PLUGIN_API
Vst3PlugViewProxyImpl::setContentScaleFactor(ScaleFactor factor) {
return bridge.send_message(
return send_mutually_recursive_message(
YaPlugViewContentScaleSupport::SetContentScaleFactor{
.owner_instance_id = owner_instance_id(), .factor = factor});
}