Completely run effEditIdle from a timer

Although it hasn't shown up, this will get rid of the possibility of
off-thread effEditIdle calls causing issues. And since we need some way
to run call this function while the event loop is running anyways, doing
it entirely from a timer similar to how hosts on Windows would do it
seems like the best solution.
This commit is contained in:
Robbert van der Helm
2021-01-04 15:50:17 +01:00
parent c554b9b4ab
commit 4ec8b01bcc
7 changed files with 109 additions and 15 deletions
+11 -5
View File
@@ -40,12 +40,10 @@ std::mutex current_bridge_instance_mutex;
/**
* Opcodes that should always be handled on the main thread because they may
* involve GUI operations.
*
* XXX: We removed effEditIdle from here and everything still seems to work
* fine. Verify that this didn't break any plugins.
*/
const std::set<int> unsafe_opcodes{effOpen, effClose, effEditGetRect,
effEditOpen, effEditClose, effEditTop};
effEditOpen, effEditClose, effEditIdle,
effEditTop};
intptr_t VST_CALL_CONV
host_callback_proxy(AEffect*, int, int, intptr_t, void*, float);
@@ -313,6 +311,11 @@ void Vst2Bridge::run() {
// https://github.com/Ardour/ardour/commit/f7cb1b0b481eeda755bdf8eb9fc5f90a81d2aa01.
// We should keep this in until Ardour 6.3 is no
// longer in distro repositories.
//
// Note that now that we run `effEditIdle`
// entirely off of a Win32 timer this will never
// get hit, but we'll keep it in for the sake of
// preserving correct behaviour.
if (opcode == effEditIdle && !editor) {
std::cerr << "WARNING: The host is calling "
"`effEditIdle()` while the "
@@ -379,7 +382,10 @@ intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin,
// provided by the host, and let the plugin embed itself into
// the Wine window
const auto x11_handle = reinterpret_cast<size_t>(data);
Editor& editor_instance = editor.emplace(config, x11_handle);
Editor& editor_instance =
editor.emplace(config, x11_handle, [plugin = this->plugin]() {
plugin->dispatcher(plugin, effEditIdle, 0, 0, nullptr, 0.0);
});
return plugin->dispatcher(plugin, opcode, index, value,
editor_instance.get_win32_handle(),
+42 -1
View File
@@ -18,6 +18,15 @@
#include <iostream>
/**
* The Win32 timer ID we'll use to periodically call the VST2 `effeditidle`
* function with. We have to do this on a timer because the function has to be
* called from the GUI thread, and it should also be called while the Win32
* event loop is being blocked (for instance when a plugin opens a dropdown
* menu).
*/
constexpr size_t idle_timer_id = 1337;
/**
* The most significant bit in an X11 event's response type is used to indicate
* the event source.
@@ -100,7 +109,9 @@ WindowClass::~WindowClass() {
UnregisterClass(reinterpret_cast<LPCSTR>(atom), GetModuleHandle(nullptr));
}
Editor::Editor(const Configuration& config, const size_t parent_window_handle)
Editor::Editor(const Configuration& config,
const size_t parent_window_handle,
std::optional<fu2::unique_function<void()>> timer_proc)
: use_xembed(config.editor_xembed),
x11_connection(xcb_connect(nullptr, nullptr), xcb_disconnect),
client_area(get_maximum_screen_dimensions(*x11_connection)),
@@ -128,6 +139,16 @@ Editor::Editor(const Configuration& config, const size_t parent_window_handle)
// window in `win32_child_handle`. If we do this before calling
// `ShowWindow()` on `win32_handle` we'll run into X11 errors.
win32_child_handle(std::nullopt),
idle_timer(
timer_proc
? Win32Timer(
win32_handle.get(),
idle_timer_id,
std::chrono::duration_cast<std::chrono::milliseconds>(
event_loop_interval)
.count())
: Win32Timer()),
idle_timer_proc(std::move(timer_proc)),
parent_window(parent_window_handle),
wine_window(get_x11_handle(win32_handle.get())),
topmost_window(find_topmost_window(*x11_connection, parent_window)) {
@@ -423,6 +444,12 @@ void Editor::set_input_focus(bool grab) const {
xcb_flush(x11_connection.get());
}
void Editor::maybe_run_timer_proc() {
if (idle_timer_proc) {
(*idle_timer_proc)();
}
}
void Editor::destroy_window_async(HWND window_handle) {
PostMessage(window_handle, WM_CLOSE, 0, 0);
}
@@ -566,6 +593,20 @@ LRESULT CALLBACK window_proc(HWND handle,
WINDOWPOS* info = reinterpret_cast<WINDOWPOS*>(lParam);
info->flags |= SWP_NOCOPYBITS | SWP_DEFERERASE;
} break;
case WM_TIMER: {
auto editor = reinterpret_cast<Editor*>(
GetWindowLongPtr(handle, GWLP_USERDATA));
if (!editor || wParam != idle_timer_id) {
break;
}
// We'll send idle messages on a timer for VST2 plugins. This way
// the plugin will get keep periodically updating its editor either
// when the host sends `effEditIdle` themself, or periodically when
// the GUI is being blocked by a dropdown or a message box.
editor->maybe_run_timer_proc();
return 0;
} break;
// In case the WM does not support the EWMH active window property,
// we'll fall back to grabbing focus when the user clicks on the window
// by listening to the generated `WM_PARENTNOTIFY` messages. Otherwise
+33 -1
View File
@@ -25,6 +25,7 @@
#define WINE_NOWINSOCK
#endif
#include <windows.h>
#include <function2/function2.hpp>
// Use the native version of xcb
#pragma push_macro("_WIN32")
@@ -99,10 +100,16 @@ class Editor {
* editor behaviours.
* @param parent_window_handle The X11 window handle passed by the VST host
* for the editor to embed itself into.
* @param timer_proc A function to run on a timer. This is used for VST2
* plugins to periodically call `effEditIdle` from the message loop
* thread, even when the GUI is blocked.
*
* @see win32_handle
*/
Editor(const Configuration& config, const size_t parent_window_handle);
Editor(
const Configuration& config,
const size_t parent_window_handle,
std::optional<fu2::unique_function<void()>> timer_proc = std::nullopt);
~Editor();
@@ -147,6 +154,14 @@ class Editor {
*/
void set_input_focus(bool grab) const;
/**
* Run the timer proc function passed to the constructor, if one was passed.
*
* @see idle_timer
* @see idle_timer_proc
*/
void maybe_run_timer_proc();
/**
* Whether to use XEmbed instead of yabridge's normal window embedded. Wine
* with XEmbed tends to cause rendering issues, so it's disabled by default.
@@ -236,6 +251,23 @@ class Editor {
#pragma GCC diagnostic pop
/**
* A timer we'll use to periodically run `idle_timer_proc`, if set. Thisi is
* only needed for VST2 plugins, as they expected the host to periodically
* send an idle event. We used to just pass through the calls from the host
* before yabridge 3.x, but doing it ourselves here makes things m much more
* manageable and we'd still need a timer anyways for when the GUI is
* blocked.
*/
Win32Timer idle_timer;
/**
* A function to call when the Win32 timer procs. This is used to
* periodically call `effEditIdle()` for VST2 plugins even if the GUI is
* being blocked.
*/
std::optional<fu2::unique_function<void()>> idle_timer_proc;
/**
* The window handle of the editor window created by the DAW.
*/
+6 -5
View File
@@ -50,6 +50,8 @@ Win32Thread::~Win32Thread() {
Win32Thread::Win32Thread() : handle(nullptr, nullptr) {}
Win32Timer::Win32Timer() {}
Win32Timer::Win32Timer(HWND window_handle,
size_t timer_id,
unsigned int interval_ms)
@@ -63,13 +65,12 @@ Win32Timer::~Win32Timer() {
}
}
Win32Timer::Win32Timer(Win32Timer&& o) : timer_id(o.timer_id) {
o.timer_id = std::nullopt;
}
Win32Timer::Win32Timer(Win32Timer&& o)
: window_handle(o.window_handle), timer_id(std::move(o.timer_id)) {}
Win32Timer& Win32Timer::operator=(Win32Timer&& o) {
timer_id = o.timer_id;
o.timer_id = std::nullopt;
window_handle = o.window_handle;
timer_id = std::move(o.timer_id);
return *this;
}
+2
View File
@@ -226,7 +226,9 @@ class Win32Thread {
*/
class Win32Timer {
public:
Win32Timer();
Win32Timer(HWND window_handle, size_t timer_id, unsigned int interval_ms);
~Win32Timer();
Win32Timer(const Win32Timer&) = delete;