mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-06-12 07:12:42 +02:00
Fix focus handling when reopening REAPER FX window
REAPER initializes the plugin's editor first before reparenting the parent window to the FX window, so our `topmost_window` didn't actually refer to the FX window.
This commit is contained in:
+10
-7
@@ -70,6 +70,16 @@ Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
plugins would at runtime change their query interface to support more VST3
|
plugins would at runtime change their query interface to support more VST3
|
||||||
interfaces, including the mandatory edit controller interface. Yabridge now
|
interfaces, including the mandatory edit controller interface. Yabridge now
|
||||||
requeries the supported interfaces at a later stage to work around this.
|
requeries the supported interfaces at a later stage to work around this.
|
||||||
|
- Fixed VST2 plugins in **Ardour** not receiving all transport information,
|
||||||
|
breaking host sync and LFOs in certain plugins. This was a regression from
|
||||||
|
yabridge 3.2.0.
|
||||||
|
- Fixed input focus handling being broken **REAPER** after reopning a closed FX
|
||||||
|
window. Now moving the mouse cursor outside of the plugin's GUI will always
|
||||||
|
release input focus, even after closing the window.
|
||||||
|
- Fixed _Insert Piz Here_'s _midiLooper_ crashing in **REAPER** when the plugin
|
||||||
|
tries to use REAPER's [host function
|
||||||
|
API](https://www.reaper.fm/sdk/vst/vst_ext.php#vst_host). This currently isn't
|
||||||
|
supported by yabridge. We now explicitly ignore these requests.
|
||||||
- Fixed JUCE VST3 plugins like Tokyo Dawn Records' _SlickEQ M_ causing the host
|
- Fixed JUCE VST3 plugins like Tokyo Dawn Records' _SlickEQ M_ causing the host
|
||||||
to freeze when they send a parameter change from the audio thread using the
|
to freeze when they send a parameter change from the audio thread using the
|
||||||
wrong VST3 API while the plugin is also trying to resize the window from the
|
wrong VST3 API while the plugin is also trying to resize the window from the
|
||||||
@@ -77,13 +87,6 @@ Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
the Smart Ops panel after having used it once. To fix this, yabridge's
|
the Smart Ops panel after having used it once. To fix this, yabridge's
|
||||||
Wine-side VST3 mutual recursion mechanism now only operates when invoked from
|
Wine-side VST3 mutual recursion mechanism now only operates when invoked from
|
||||||
the GUI thread.
|
the GUI thread.
|
||||||
- Fixed VST2 plugins in **Ardour** not receiving all transport information,
|
|
||||||
breaking host sync and LFOs in certain plugins. This was a regression from
|
|
||||||
yabridge 3.2.0.
|
|
||||||
- Fixed _Insert Piz Here_'s _midiLooper_ crashing in **REAPER** when the plugin
|
|
||||||
tries to use REAPER's [host function
|
|
||||||
API](https://www.reaper.fm/sdk/vst/vst_ext.php#vst_host). This currently isn't
|
|
||||||
supported by yabridge. We now explicitly ignore these requests.
|
|
||||||
- Fixed yabridge's logging seeking the STDERR stream to position 0 every time it
|
- Fixed yabridge's logging seeking the STDERR stream to position 0 every time it
|
||||||
writes a log message. This would be noticeable when piping the host's STDERR
|
writes a log message. This would be noticeable when piping the host's STDERR
|
||||||
stream to a file and `YABRIDGE_DEBUG_LEVEL` wasn't set.
|
stream to a file and `YABRIDGE_DEBUG_LEVEL` wasn't set.
|
||||||
|
|||||||
@@ -1171,7 +1171,7 @@ void Vst3Bridge::run() {
|
|||||||
void Vst3Bridge::handle_x11_events() noexcept {
|
void Vst3Bridge::handle_x11_events() noexcept {
|
||||||
std::lock_guard lock(object_instances_mutex);
|
std::lock_guard lock(object_instances_mutex);
|
||||||
|
|
||||||
for (const auto& [instance_id, object] : object_instances) {
|
for (auto& [instance_id, object] : object_instances) {
|
||||||
if (object.editor) {
|
if (object.editor) {
|
||||||
object.editor->handle_x11_events();
|
object.editor->handle_x11_events();
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-12
@@ -48,6 +48,21 @@ using namespace std::literals::chrono_literals;
|
|||||||
*/
|
*/
|
||||||
constexpr size_t idle_timer_id = 1337;
|
constexpr size_t idle_timer_id = 1337;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The X11 event mask for the topmost window, which in most DAWs except for
|
||||||
|
* Ardour and REAPER will be the same as `parent_window`.
|
||||||
|
*/
|
||||||
|
constexpr uint32_t topmost_event_mask =
|
||||||
|
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_VISIBILITY_CHANGE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The X11 event mask for the parent window. We need this structure notify here
|
||||||
|
* as well to detect reparents.
|
||||||
|
*/
|
||||||
|
constexpr uint32_t parent_event_mask =
|
||||||
|
topmost_event_mask | XCB_EVENT_MASK_FOCUS_CHANGE |
|
||||||
|
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the X11 property on the root window used to denote the active
|
* The name of the X11 property on the root window used to denote the active
|
||||||
* window in EWMH compliant window managers.
|
* window in EWMH compliant window managers.
|
||||||
@@ -278,13 +293,8 @@ Editor::Editor(MainContext& main_context,
|
|||||||
// release input focus as necessary.
|
// release input focus as necessary.
|
||||||
// If we do enable XEmbed support, we'll also listen for visibility changes
|
// If we do enable XEmbed support, we'll also listen for visibility changes
|
||||||
// and trigger the embedding when the window becomes visible
|
// and trigger the embedding when the window becomes visible
|
||||||
const uint32_t topmost_event_mask =
|
|
||||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_VISIBILITY_CHANGE;
|
|
||||||
xcb_change_window_attributes(x11_connection.get(), topmost_window,
|
xcb_change_window_attributes(x11_connection.get(), topmost_window,
|
||||||
XCB_CW_EVENT_MASK, &topmost_event_mask);
|
XCB_CW_EVENT_MASK, &topmost_event_mask);
|
||||||
const uint32_t parent_event_mask =
|
|
||||||
XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW |
|
|
||||||
XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_VISIBILITY_CHANGE;
|
|
||||||
xcb_change_window_attributes(x11_connection.get(), parent_window,
|
xcb_change_window_attributes(x11_connection.get(), parent_window,
|
||||||
XCB_CW_EVENT_MASK, &parent_event_mask);
|
XCB_CW_EVENT_MASK, &parent_event_mask);
|
||||||
xcb_flush(x11_connection.get());
|
xcb_flush(x11_connection.get());
|
||||||
@@ -334,7 +344,7 @@ Editor::Editor(MainContext& main_context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Editor::handle_x11_events() const noexcept {
|
void Editor::handle_x11_events() noexcept {
|
||||||
// NOTE: Ardour will unmap the window instead of closing the editor. When
|
// NOTE: Ardour will unmap the window instead of closing the editor. When
|
||||||
// the window is unmapped `wine_window` doesn't exist and any X11
|
// the window is unmapped `wine_window` doesn't exist and any X11
|
||||||
// function calls involving it will fail. All functions called from
|
// function calls involving it will fail. All functions called from
|
||||||
@@ -346,6 +356,17 @@ void Editor::handle_x11_events() const noexcept {
|
|||||||
const uint8_t event_type =
|
const uint8_t event_type =
|
||||||
generic_event->response_type & xcb_event_type_mask;
|
generic_event->response_type & xcb_event_type_mask;
|
||||||
switch (event_type) {
|
switch (event_type) {
|
||||||
|
// NOTE: When reopening a closed editor window in REAPER, REAPER
|
||||||
|
// will initialize the editor first, and only then will it
|
||||||
|
// reparent `parent_window` to a new FX window. This means
|
||||||
|
// that `topmost_window` will be the same as
|
||||||
|
// `parent_window` in REAPER if you reopen a plugin GUI,
|
||||||
|
// which breaks our input focus handling. To work around
|
||||||
|
// this, we will just check if the topmost window has
|
||||||
|
// changed whenever the parent window gets reparented.
|
||||||
|
case XCB_REPARENT_NOTIFY: {
|
||||||
|
redetect_topmost_window();
|
||||||
|
} break;
|
||||||
// We're listening for `ConfigureNotify` events on the topmost
|
// We're listening for `ConfigureNotify` events on the topmost
|
||||||
// window before the root window, i.e. the window that's
|
// window before the root window, i.e. the window that's
|
||||||
// actually going to get dragged around the by the user. In most
|
// actually going to get dragged around the by the user. In most
|
||||||
@@ -354,19 +375,19 @@ void Editor::handle_x11_events() const noexcept {
|
|||||||
// window, the local coordinates should be updated. The
|
// window, the local coordinates should be updated. The
|
||||||
// additional `EnterWindow` check is sometimes necessary for
|
// additional `EnterWindow` check is sometimes necessary for
|
||||||
// using multiple editor windows within a single plugin group.
|
// using multiple editor windows within a single plugin group.
|
||||||
case XCB_CONFIGURE_NOTIFY:
|
case XCB_CONFIGURE_NOTIFY: {
|
||||||
if (!use_xembed) {
|
if (!use_xembed) {
|
||||||
fix_local_coordinates();
|
fix_local_coordinates();
|
||||||
}
|
}
|
||||||
break;
|
} break;
|
||||||
// Start the XEmbed procedure when the window becomes visible,
|
// Start the XEmbed procedure when the window becomes visible,
|
||||||
// since most hosts will only show the window after the plugin
|
// since most hosts will only show the window after the plugin
|
||||||
// has embedded itself into it.
|
// has embedded itself into it.
|
||||||
case XCB_VISIBILITY_NOTIFY:
|
case XCB_VISIBILITY_NOTIFY: {
|
||||||
if (use_xembed) {
|
if (use_xembed) {
|
||||||
do_xembed();
|
do_xembed();
|
||||||
}
|
}
|
||||||
break;
|
} break;
|
||||||
// We want to grab keyboard input focus when the user hovers
|
// We want to grab keyboard input focus when the user hovers
|
||||||
// over our embedded Wine window AND that window is a child of
|
// over our embedded Wine window AND that window is a child of
|
||||||
// the currently active window. This ensures that the behavior
|
// the currently active window. This ensures that the behavior
|
||||||
@@ -376,7 +397,7 @@ void Editor::handle_x11_events() const noexcept {
|
|||||||
// for when returning to the main plugin window after closing a
|
// for when returning to the main plugin window after closing a
|
||||||
// dialog, since that often won't trigger an `EnterNotify'.
|
// dialog, since that often won't trigger an `EnterNotify'.
|
||||||
case XCB_ENTER_NOTIFY:
|
case XCB_ENTER_NOTIFY:
|
||||||
case XCB_FOCUS_IN:
|
case XCB_FOCUS_IN: {
|
||||||
if (!use_xembed) {
|
if (!use_xembed) {
|
||||||
fix_local_coordinates();
|
fix_local_coordinates();
|
||||||
}
|
}
|
||||||
@@ -389,7 +410,7 @@ void Editor::handle_x11_events() const noexcept {
|
|||||||
is_wine_window_active()) {
|
is_wine_window_active()) {
|
||||||
set_input_focus(true);
|
set_input_focus(true);
|
||||||
}
|
}
|
||||||
break;
|
} break;
|
||||||
// When the user moves their mouse away from the Wine window
|
// When the user moves their mouse away from the Wine window
|
||||||
// _while the window provided by the host it is contained in is
|
// _while the window provided by the host it is contained in is
|
||||||
// still active_, we will give back keyboard focus to that
|
// still active_, we will give back keyboard focus to that
|
||||||
@@ -566,6 +587,35 @@ bool Editor::is_wine_window_active() const {
|
|||||||
return is_child_window_or_same(*x11_connection, wine_window, active_window);
|
return is_child_window_or_same(*x11_connection, wine_window, active_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Editor::redetect_topmost_window() noexcept {
|
||||||
|
const xcb_window_t new_topmost_window =
|
||||||
|
find_ancestor_windows(*x11_connection, parent_window).back();
|
||||||
|
if (new_topmost_window == topmost_window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to readjust the event masks for the new topmost window, keeping
|
||||||
|
// the (very probable) possibility in mind that the old topmost window is
|
||||||
|
// the same as the parent window or that the parent window now is the
|
||||||
|
// topmost window.
|
||||||
|
if (topmost_window != parent_window) {
|
||||||
|
constexpr uint32_t no_event_mask = XCB_EVENT_MASK_NO_EVENT;
|
||||||
|
xcb_change_window_attributes(x11_connection.get(), topmost_window,
|
||||||
|
XCB_CW_EVENT_MASK, &no_event_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_topmost_window == parent_window) {
|
||||||
|
xcb_change_window_attributes(x11_connection.get(), new_topmost_window,
|
||||||
|
XCB_CW_EVENT_MASK, &parent_event_mask);
|
||||||
|
} else {
|
||||||
|
xcb_change_window_attributes(x11_connection.get(), new_topmost_window,
|
||||||
|
XCB_CW_EVENT_MASK, &topmost_event_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
topmost_window = new_topmost_window;
|
||||||
|
xcb_flush(x11_connection.get());
|
||||||
|
}
|
||||||
|
|
||||||
bool Editor::supports_ewmh_active_window() const {
|
bool Editor::supports_ewmh_active_window() const {
|
||||||
if (supports_ewmh_active_window_cache) {
|
if (supports_ewmh_active_window_cache) {
|
||||||
return *supports_ewmh_active_window_cache;
|
return *supports_ewmh_active_window_cache;
|
||||||
|
|||||||
+14
-2
@@ -163,7 +163,7 @@ class Editor {
|
|||||||
/**
|
/**
|
||||||
* Handle X11 events sent to the window our editor is embedded in.
|
* Handle X11 events sent to the window our editor is embedded in.
|
||||||
*/
|
*/
|
||||||
void handle_x11_events() const noexcept;
|
void handle_x11_events() noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Win32 window handle so it can be passed to an `effEditOpen()`
|
* Get the Win32 window handle so it can be passed to an `effEditOpen()`
|
||||||
@@ -225,6 +225,12 @@ class Editor {
|
|||||||
*/
|
*/
|
||||||
bool is_wine_window_active() const;
|
bool is_wine_window_active() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After `parent_window` gets reparented, we may need to redetect the
|
||||||
|
* topmost window and adjust the events we're subscribed to accordingly.
|
||||||
|
*/
|
||||||
|
void redetect_topmost_window() noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send an XEmbed message to a window. This does not include a flush. See
|
* Send an XEmbed message to a window. This does not include a flush. See
|
||||||
* the spec for more information:
|
* the spec for more information:
|
||||||
@@ -313,8 +319,14 @@ class Editor {
|
|||||||
* REAPER) embed `parent_window` into another window. We have to listen for
|
* REAPER) embed `parent_window` into another window. We have to listen for
|
||||||
* configuration changes on this topmost window to know when the window is
|
* configuration changes on this topmost window to know when the window is
|
||||||
* being dragged around.
|
* being dragged around.
|
||||||
|
*
|
||||||
|
* NOTE: When reopening a REAPER FX window that has previously been closed,
|
||||||
|
* REAPER will initialize the first plugin's editor first before
|
||||||
|
* opening the window. This means that the topmost FX window doesn't
|
||||||
|
* actually exist yet at that point, so we need to redetect this
|
||||||
|
* later.
|
||||||
*/
|
*/
|
||||||
const xcb_window_t topmost_window;
|
xcb_window_t topmost_window;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The atom corresponding to `_NET_ACTIVE_WINDOW`.
|
* The atom corresponding to `_NET_ACTIVE_WINDOW`.
|
||||||
|
|||||||
Reference in New Issue
Block a user