mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-06-15 16:03:55 +02:00
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:
+1
-3
@@ -44,9 +44,7 @@ TODO: Add an updated screenshot with some fancy VST3-only plugins to the readme
|
|||||||
Most hosts already do something similar themselves, so this may not make any
|
Most hosts already do something similar themselves, so this may not make any
|
||||||
difference in responsiveness.
|
difference in responsiveness.
|
||||||
- VST2 editor idle events are now handled slightly differently. This should
|
- VST2 editor idle events are now handled slightly differently. This should
|
||||||
result in even more responsive GUIs and I have not come across any plugins
|
result in even more responsive GUIs for VST2 plugins.
|
||||||
where this change introduced issues, but please let me know if it does break
|
|
||||||
anything for you.
|
|
||||||
- Changed part of the build process considering [this Wine
|
- Changed part of the build process considering [this Wine
|
||||||
bug](https://bugs.winehq.org/show_bug.cgi?id=49138). Building with Wine 5.7
|
bug](https://bugs.winehq.org/show_bug.cgi?id=49138). Building with Wine 5.7
|
||||||
and 5.8 required a change, but that change now breaks builds using Wine 6.0
|
and 5.8 required a change, but that change now breaks builds using Wine 6.0
|
||||||
|
|||||||
@@ -398,6 +398,20 @@ intptr_t Vst2PluginBridge::dispatch(AEffect* /*plugin*/,
|
|||||||
|
|
||||||
return return_value;
|
return return_value;
|
||||||
}; break;
|
}; break;
|
||||||
|
case effEditIdle: {
|
||||||
|
// This is the only place where we'll deviate from yabridge's
|
||||||
|
// 'one-to-one passthrough' philosophy. While in practice we can
|
||||||
|
// just pass through `effEditIdle` and we have been doing so until
|
||||||
|
// yabridge 3.x, in reality it's much more practical to just run
|
||||||
|
// this on a Win32 timer. We would either need to run `effEditIdle`
|
||||||
|
// from a non-GUI thread (which could cause issues), or we would
|
||||||
|
// need a timer anyways to proc the function when the GUI is being
|
||||||
|
// blocked by for instance an open dropdown.
|
||||||
|
logger.log_event(true, opcode, index, value, nullptr, option,
|
||||||
|
std::nullopt);
|
||||||
|
logger.log_event_response(true, opcode, 0, nullptr, std::nullopt);
|
||||||
|
return 0;
|
||||||
|
}; break;
|
||||||
case effCanDo: {
|
case effCanDo: {
|
||||||
const std::string query(static_cast<const char*>(data));
|
const std::string query(static_cast<const char*>(data));
|
||||||
|
|
||||||
|
|||||||
@@ -40,12 +40,10 @@ std::mutex current_bridge_instance_mutex;
|
|||||||
/**
|
/**
|
||||||
* Opcodes that should always be handled on the main thread because they may
|
* Opcodes that should always be handled on the main thread because they may
|
||||||
* involve GUI operations.
|
* 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,
|
const std::set<int> unsafe_opcodes{effOpen, effClose, effEditGetRect,
|
||||||
effEditOpen, effEditClose, effEditTop};
|
effEditOpen, effEditClose, effEditIdle,
|
||||||
|
effEditTop};
|
||||||
|
|
||||||
intptr_t VST_CALL_CONV
|
intptr_t VST_CALL_CONV
|
||||||
host_callback_proxy(AEffect*, int, int, intptr_t, void*, float);
|
host_callback_proxy(AEffect*, int, int, intptr_t, void*, float);
|
||||||
@@ -313,6 +311,11 @@ void Vst2Bridge::run() {
|
|||||||
// https://github.com/Ardour/ardour/commit/f7cb1b0b481eeda755bdf8eb9fc5f90a81d2aa01.
|
// https://github.com/Ardour/ardour/commit/f7cb1b0b481eeda755bdf8eb9fc5f90a81d2aa01.
|
||||||
// We should keep this in until Ardour 6.3 is no
|
// We should keep this in until Ardour 6.3 is no
|
||||||
// longer in distro repositories.
|
// 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) {
|
if (opcode == effEditIdle && !editor) {
|
||||||
std::cerr << "WARNING: The host is calling "
|
std::cerr << "WARNING: The host is calling "
|
||||||
"`effEditIdle()` while the "
|
"`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
|
// provided by the host, and let the plugin embed itself into
|
||||||
// the Wine window
|
// the Wine window
|
||||||
const auto x11_handle = reinterpret_cast<size_t>(data);
|
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,
|
return plugin->dispatcher(plugin, opcode, index, value,
|
||||||
editor_instance.get_win32_handle(),
|
editor_instance.get_win32_handle(),
|
||||||
|
|||||||
@@ -18,6 +18,15 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#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 most significant bit in an X11 event's response type is used to indicate
|
||||||
* the event source.
|
* the event source.
|
||||||
@@ -100,7 +109,9 @@ WindowClass::~WindowClass() {
|
|||||||
UnregisterClass(reinterpret_cast<LPCSTR>(atom), GetModuleHandle(nullptr));
|
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),
|
: use_xembed(config.editor_xembed),
|
||||||
x11_connection(xcb_connect(nullptr, nullptr), xcb_disconnect),
|
x11_connection(xcb_connect(nullptr, nullptr), xcb_disconnect),
|
||||||
client_area(get_maximum_screen_dimensions(*x11_connection)),
|
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
|
// window in `win32_child_handle`. If we do this before calling
|
||||||
// `ShowWindow()` on `win32_handle` we'll run into X11 errors.
|
// `ShowWindow()` on `win32_handle` we'll run into X11 errors.
|
||||||
win32_child_handle(std::nullopt),
|
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),
|
parent_window(parent_window_handle),
|
||||||
wine_window(get_x11_handle(win32_handle.get())),
|
wine_window(get_x11_handle(win32_handle.get())),
|
||||||
topmost_window(find_topmost_window(*x11_connection, parent_window)) {
|
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());
|
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) {
|
void Editor::destroy_window_async(HWND window_handle) {
|
||||||
PostMessage(window_handle, WM_CLOSE, 0, 0);
|
PostMessage(window_handle, WM_CLOSE, 0, 0);
|
||||||
}
|
}
|
||||||
@@ -566,6 +593,20 @@ LRESULT CALLBACK window_proc(HWND handle,
|
|||||||
WINDOWPOS* info = reinterpret_cast<WINDOWPOS*>(lParam);
|
WINDOWPOS* info = reinterpret_cast<WINDOWPOS*>(lParam);
|
||||||
info->flags |= SWP_NOCOPYBITS | SWP_DEFERERASE;
|
info->flags |= SWP_NOCOPYBITS | SWP_DEFERERASE;
|
||||||
} break;
|
} 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,
|
// 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
|
// we'll fall back to grabbing focus when the user clicks on the window
|
||||||
// by listening to the generated `WM_PARENTNOTIFY` messages. Otherwise
|
// by listening to the generated `WM_PARENTNOTIFY` messages. Otherwise
|
||||||
|
|||||||
+33
-1
@@ -25,6 +25,7 @@
|
|||||||
#define WINE_NOWINSOCK
|
#define WINE_NOWINSOCK
|
||||||
#endif
|
#endif
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <function2/function2.hpp>
|
||||||
|
|
||||||
// Use the native version of xcb
|
// Use the native version of xcb
|
||||||
#pragma push_macro("_WIN32")
|
#pragma push_macro("_WIN32")
|
||||||
@@ -99,10 +100,16 @@ class Editor {
|
|||||||
* editor behaviours.
|
* editor behaviours.
|
||||||
* @param parent_window_handle The X11 window handle passed by the VST host
|
* @param parent_window_handle The X11 window handle passed by the VST host
|
||||||
* for the editor to embed itself into.
|
* 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
|
* @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();
|
~Editor();
|
||||||
|
|
||||||
@@ -147,6 +154,14 @@ class Editor {
|
|||||||
*/
|
*/
|
||||||
void set_input_focus(bool grab) const;
|
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
|
* 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.
|
* with XEmbed tends to cause rendering issues, so it's disabled by default.
|
||||||
@@ -236,6 +251,23 @@ class Editor {
|
|||||||
|
|
||||||
#pragma GCC diagnostic pop
|
#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.
|
* The window handle of the editor window created by the DAW.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ Win32Thread::~Win32Thread() {
|
|||||||
|
|
||||||
Win32Thread::Win32Thread() : handle(nullptr, nullptr) {}
|
Win32Thread::Win32Thread() : handle(nullptr, nullptr) {}
|
||||||
|
|
||||||
|
Win32Timer::Win32Timer() {}
|
||||||
|
|
||||||
Win32Timer::Win32Timer(HWND window_handle,
|
Win32Timer::Win32Timer(HWND window_handle,
|
||||||
size_t timer_id,
|
size_t timer_id,
|
||||||
unsigned int interval_ms)
|
unsigned int interval_ms)
|
||||||
@@ -63,13 +65,12 @@ Win32Timer::~Win32Timer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Win32Timer::Win32Timer(Win32Timer&& o) : timer_id(o.timer_id) {
|
Win32Timer::Win32Timer(Win32Timer&& o)
|
||||||
o.timer_id = std::nullopt;
|
: window_handle(o.window_handle), timer_id(std::move(o.timer_id)) {}
|
||||||
}
|
|
||||||
|
|
||||||
Win32Timer& Win32Timer::operator=(Win32Timer&& o) {
|
Win32Timer& Win32Timer::operator=(Win32Timer&& o) {
|
||||||
timer_id = o.timer_id;
|
window_handle = o.window_handle;
|
||||||
o.timer_id = std::nullopt;
|
timer_id = std::move(o.timer_id);
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,7 +226,9 @@ class Win32Thread {
|
|||||||
*/
|
*/
|
||||||
class Win32Timer {
|
class Win32Timer {
|
||||||
public:
|
public:
|
||||||
|
Win32Timer();
|
||||||
Win32Timer(HWND window_handle, size_t timer_id, unsigned int interval_ms);
|
Win32Timer(HWND window_handle, size_t timer_id, unsigned int interval_ms);
|
||||||
|
|
||||||
~Win32Timer();
|
~Win32Timer();
|
||||||
|
|
||||||
Win32Timer(const Win32Timer&) = delete;
|
Win32Timer(const Win32Timer&) = delete;
|
||||||
|
|||||||
Reference in New Issue
Block a user