mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-06-23 20:37:27 +02:00
Steal the window back when the WM reparents it
I've seen this happen once or twice. Earlier on (in #40) we could get away with just reparenting the window another time. But here both reparents succeed, and the issue still happens. But a reparent notify for `parent_window` did appear on system affected by this issue, which is very odd. Hopefully this will help.
This commit is contained in:
@@ -31,6 +31,10 @@ Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
like FrozenPlain **Obelisk** would result in a crash.
|
like FrozenPlain **Obelisk** would result in a crash.
|
||||||
- Fixed a regression from yabridge 3.4.0 where JUCE-based VST3 plugins might
|
- Fixed a regression from yabridge 3.4.0 where JUCE-based VST3 plugins might
|
||||||
cause **Ardour** or **Mixbus** to freeze in very specific circumstances.
|
cause **Ardour** or **Mixbus** to freeze in very specific circumstances.
|
||||||
|
- When the window manager somehow steals yabridge's window away from the host,
|
||||||
|
yabridge will now try to steal it back and reparent it to the host's window
|
||||||
|
again. This very rarely happened with some window managers, like XFWM, and
|
||||||
|
only in certain DAWs like **Ardour**.
|
||||||
- Worked around a **REAPER** bug that would cause REAPER to not process any
|
- Worked around a **REAPER** bug that would cause REAPER to not process any
|
||||||
keyboard input when the FX window is active but the mouse is outside of the
|
keyboard input when the FX window is active but the mouse is outside of the
|
||||||
window. We now use the same validation used in `xprop` and `xwininfo` to find
|
window. We now use the same validation used in `xprop` and `xwininfo` to find
|
||||||
|
|||||||
+69
-58
@@ -63,6 +63,12 @@ constexpr uint32_t parent_event_mask =
|
|||||||
host_event_mask | XCB_EVENT_MASK_FOCUS_CHANGE |
|
host_event_mask | XCB_EVENT_MASK_FOCUS_CHANGE |
|
||||||
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW;
|
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The X11 event mask for the Wine window. We'll use this to detect if the
|
||||||
|
* Window manager somehow steals the Wine window.
|
||||||
|
*/
|
||||||
|
constexpr uint32_t wine_event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -325,13 +331,18 @@ Editor::Editor(MainContext& main_context,
|
|||||||
// or resized, and when the user moves his mouse over the window because
|
// or resized, and when the user moves his mouse over the window because
|
||||||
// this is sometimes needed for plugin groups. We also listen for
|
// this is sometimes needed for plugin groups. We also listen for
|
||||||
// EnterNotify and LeaveNotify events on the Wine window so we can grab and
|
// EnterNotify and LeaveNotify events on the Wine window so we can grab and
|
||||||
// release input focus as necessary.
|
// release input focus as necessary. And lastly we'll look out for
|
||||||
|
// reparents, so we can make sure that the window does not get stolen by the
|
||||||
|
// window manager and that we correctly handle the host reparenting
|
||||||
|
// `parent_window` themselves.
|
||||||
// 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
|
||||||
xcb_change_window_attributes(x11_connection.get(), host_window,
|
xcb_change_window_attributes(x11_connection.get(), host_window,
|
||||||
XCB_CW_EVENT_MASK, &host_event_mask);
|
XCB_CW_EVENT_MASK, &host_event_mask);
|
||||||
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_change_window_attributes(x11_connection.get(), wine_window,
|
||||||
|
XCB_CW_EVENT_MASK, &wine_event_mask);
|
||||||
xcb_flush(x11_connection.get());
|
xcb_flush(x11_connection.get());
|
||||||
|
|
||||||
std::cerr << "DEBUG: host_window: " << host_window << std::endl;
|
std::cerr << "DEBUG: host_window: " << host_window << std::endl;
|
||||||
@@ -349,52 +360,6 @@ Editor::Editor(MainContext& main_context,
|
|||||||
// of using the XEmbed protocol, we'll register a few events and manage
|
// of using the XEmbed protocol, we'll register a few events and manage
|
||||||
// the child window ourselves. This is a hack to work around the issue's
|
// the child window ourselves. This is a hack to work around the issue's
|
||||||
// described in `Editor`'s docstring'.
|
// described in `Editor`'s docstring'.
|
||||||
auto do_reparent = [&]() {
|
|
||||||
const xcb_void_cookie_t reparent_cookie =
|
|
||||||
xcb_reparent_window_checked(x11_connection.get(), wine_window,
|
|
||||||
parent_window, 0, 0);
|
|
||||||
if (std::unique_ptr<xcb_generic_error_t> reparent_error(
|
|
||||||
xcb_request_check(x11_connection.get(), reparent_cookie));
|
|
||||||
reparent_error) {
|
|
||||||
std::cerr << "DEBUG: Reparent failed:" << std::endl;
|
|
||||||
std::cerr << "Error code: " << reparent_error->error_code
|
|
||||||
<< std::endl;
|
|
||||||
std::cerr << "Major code: " << reparent_error->major_code
|
|
||||||
<< std::endl;
|
|
||||||
std::cerr << "Minor code: " << reparent_error->minor_code
|
|
||||||
<< std::endl;
|
|
||||||
|
|
||||||
// Let's just check all of the reasons why the reparent could
|
|
||||||
// fail according to the spec in advance
|
|
||||||
xcb_generic_error_t* error = nullptr;
|
|
||||||
const xcb_query_pointer_cookie_t query_pointer_cookie =
|
|
||||||
xcb_query_pointer(x11_connection.get(), wine_window);
|
|
||||||
const std::unique_ptr<xcb_query_pointer_reply_t>
|
|
||||||
query_pointer_reply(xcb_query_pointer_reply(
|
|
||||||
x11_connection.get(), query_pointer_cookie, &error));
|
|
||||||
if (error) {
|
|
||||||
free(error);
|
|
||||||
std::cerr << "DEBUG: Could not query pointer location"
|
|
||||||
<< std::endl;
|
|
||||||
} else {
|
|
||||||
if (query_pointer_reply->same_screen) {
|
|
||||||
std::cerr
|
|
||||||
<< "DEBUG: Pointer is on the same screen as the "
|
|
||||||
"Wine window, good"
|
|
||||||
<< std::endl;
|
|
||||||
} else {
|
|
||||||
std::cerr
|
|
||||||
<< "DEBUG: Pointer is not on the same screen as "
|
|
||||||
"the Wine window, oh no"
|
|
||||||
<< std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::cerr << "DEBUG: Reparent succeeded" << std::endl;
|
|
||||||
}
|
|
||||||
xcb_flush(x11_connection.get());
|
|
||||||
};
|
|
||||||
|
|
||||||
do_reparent();
|
do_reparent();
|
||||||
|
|
||||||
// If we're using the double embedding option, then the child window
|
// If we're using the double embedding option, then the child window
|
||||||
@@ -414,17 +379,6 @@ Editor::Editor(MainContext& main_context,
|
|||||||
|
|
||||||
ShowWindow(win32_child_window->handle, SW_SHOWNORMAL);
|
ShowWindow(win32_child_window->handle, SW_SHOWNORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: I can't seem to figure why the initial reparent would fail on
|
|
||||||
// this particular i3 config in a way that I'm unable to
|
|
||||||
// reproduce, but if it doesn't work the first time, just keep
|
|
||||||
// trying!
|
|
||||||
//
|
|
||||||
// https://github.com/robbert-vdh/yabridge/issues/40
|
|
||||||
std::cerr
|
|
||||||
<< "DEBUG: Reparent 2 is allowed to fail if the first one succeeded"
|
|
||||||
<< std::endl;
|
|
||||||
do_reparent();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,6 +417,21 @@ void Editor::handle_x11_events() noexcept {
|
|||||||
<< event->event << std::endl;
|
<< event->event << std::endl;
|
||||||
|
|
||||||
redetect_host_window();
|
redetect_host_window();
|
||||||
|
|
||||||
|
// NOTE: Some window managers like to steal the window, so
|
||||||
|
// we must prevent that. This situation is easily
|
||||||
|
// recognized since the window will then cover the
|
||||||
|
// entire screen (since that's what the client area
|
||||||
|
// has been set to).
|
||||||
|
if (event->window == parent_window ||
|
||||||
|
(event->window == wine_window &&
|
||||||
|
event->parent != parent_window)) {
|
||||||
|
if (use_xembed) {
|
||||||
|
do_xembed();
|
||||||
|
} else {
|
||||||
|
do_reparent();
|
||||||
|
}
|
||||||
|
}
|
||||||
} break;
|
} break;
|
||||||
// We're listening for `ConfigureNotify` events on the host's
|
// We're listening for `ConfigureNotify` events on the host's
|
||||||
// window (i.e. the window that's actually going to get dragged
|
// window (i.e. the window that's actually going to get dragged
|
||||||
@@ -816,6 +785,48 @@ void Editor::send_xembed_message(xcb_window_t window,
|
|||||||
reinterpret_cast<char*>(&event));
|
reinterpret_cast<char*>(&event));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Editor::do_reparent() const {
|
||||||
|
// TODO: When rebasing this, we should keep in the error logging for when
|
||||||
|
// the reparent fails
|
||||||
|
const xcb_void_cookie_t reparent_cookie = xcb_reparent_window_checked(
|
||||||
|
x11_connection.get(), wine_window, parent_window, 0, 0);
|
||||||
|
if (std::unique_ptr<xcb_generic_error_t> reparent_error(
|
||||||
|
xcb_request_check(x11_connection.get(), reparent_cookie));
|
||||||
|
reparent_error) {
|
||||||
|
std::cerr << "DEBUG: Reparent failed:" << std::endl;
|
||||||
|
std::cerr << "Error code: " << reparent_error->error_code << std::endl;
|
||||||
|
std::cerr << "Major code: " << reparent_error->major_code << std::endl;
|
||||||
|
std::cerr << "Minor code: " << reparent_error->minor_code << std::endl;
|
||||||
|
|
||||||
|
// Let's just check all of the reasons why the reparent could
|
||||||
|
// fail according to the spec in advance
|
||||||
|
xcb_generic_error_t* error = nullptr;
|
||||||
|
const xcb_query_pointer_cookie_t query_pointer_cookie =
|
||||||
|
xcb_query_pointer(x11_connection.get(), wine_window);
|
||||||
|
const std::unique_ptr<xcb_query_pointer_reply_t> query_pointer_reply(
|
||||||
|
xcb_query_pointer_reply(x11_connection.get(), query_pointer_cookie,
|
||||||
|
&error));
|
||||||
|
if (error) {
|
||||||
|
free(error);
|
||||||
|
std::cerr << "DEBUG: Could not query pointer location" << std::endl;
|
||||||
|
} else {
|
||||||
|
if (query_pointer_reply->same_screen) {
|
||||||
|
std::cerr << "DEBUG: Pointer is on the same screen as the "
|
||||||
|
"Wine window, good"
|
||||||
|
<< std::endl;
|
||||||
|
} else {
|
||||||
|
std::cerr << "DEBUG: Pointer is not on the same screen as "
|
||||||
|
"the Wine window, oh no"
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cerr << "DEBUG: Reparent succeeded" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_flush(x11_connection.get());
|
||||||
|
}
|
||||||
|
|
||||||
void Editor::do_xembed() const {
|
void Editor::do_xembed() const {
|
||||||
if (!use_xembed) {
|
if (!use_xembed) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -245,6 +245,11 @@ class Editor {
|
|||||||
uint32_t data1,
|
uint32_t data1,
|
||||||
uint32_t data2) const noexcept;
|
uint32_t data2) const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reparent `wine_window` to `parent_window`. This includes the flush.
|
||||||
|
*/
|
||||||
|
void do_reparent() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the XEmbed procedure when `use_xembed` is enabled. This should be
|
* Start the XEmbed procedure when `use_xembed` is enabled. This should be
|
||||||
* rerun whenever visibility changes.
|
* rerun whenever visibility changes.
|
||||||
|
|||||||
Reference in New Issue
Block a user