mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-08 12:30:12 +02:00
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:
@@ -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, ¬ify_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, ¬ify_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});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user