Skip to content

Commit bf65d4e

Browse files
committed
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.
1 parent 0f75461 commit bf65d4e

File tree

3 files changed

+74
-58
lines changed

3 files changed

+74
-58
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ Versioning](https://semver.org/spec/v2.0.0.html).
3131
like FrozenPlain **Obelisk** would result in a crash.
3232
- Fixed a regression from yabridge 3.4.0 where JUCE-based VST3 plugins might
3333
cause **Ardour** or **Mixbus** to freeze in very specific circumstances.
34+
- When the window manager somehow steals yabridge's window away from the host,
35+
yabridge will now try to steal it back and reparent it to the host's window
36+
again. This very rarely happened with some window managers, like XFWM, and
37+
only in certain DAWs like **Ardour**.
3438
- Worked around a **REAPER** bug that would cause REAPER to not process any
3539
keyboard input when the FX window is active but the mouse is outside of the
3640
window. We now use the same validation used in `xprop` and `xwininfo` to find

src/wine-host/editor.cpp

+65-58
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ constexpr uint32_t parent_event_mask =
6363
host_event_mask | XCB_EVENT_MASK_FOCUS_CHANGE |
6464
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW;
6565

66+
/**
67+
* The X11 event mask for the Wine window. We'll use this to detect if the
68+
* Window manager somehow steals the Wine window.
69+
*/
70+
constexpr uint32_t wine_event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
71+
6672
/**
6773
* The name of the X11 property on the root window used to denote the active
6874
* window in EWMH compliant window managers.
@@ -325,13 +331,18 @@ Editor::Editor(MainContext& main_context,
325331
// or resized, and when the user moves his mouse over the window because
326332
// this is sometimes needed for plugin groups. We also listen for
327333
// EnterNotify and LeaveNotify events on the Wine window so we can grab and
328-
// release input focus as necessary.
334+
// release input focus as necessary. And lastly we'll look out for
335+
// reparents, so we can make sure that the window does not get stolen by the
336+
// window manager and that we correctly handle the host reparenting
337+
// `parent_window` themselves.
329338
// If we do enable XEmbed support, we'll also listen for visibility changes
330339
// and trigger the embedding when the window becomes visible
331340
xcb_change_window_attributes(x11_connection.get(), host_window,
332341
XCB_CW_EVENT_MASK, &host_event_mask);
333342
xcb_change_window_attributes(x11_connection.get(), parent_window,
334343
XCB_CW_EVENT_MASK, &parent_event_mask);
344+
xcb_change_window_attributes(x11_connection.get(), wine_window,
345+
XCB_CW_EVENT_MASK, &wine_event_mask);
335346
xcb_flush(x11_connection.get());
336347

337348
std::cerr << "DEBUG: host_window: " << host_window << std::endl;
@@ -349,52 +360,6 @@ Editor::Editor(MainContext& main_context,
349360
// of using the XEmbed protocol, we'll register a few events and manage
350361
// the child window ourselves. This is a hack to work around the issue's
351362
// described in `Editor`'s docstring'.
352-
auto do_reparent = [&]() {
353-
const xcb_void_cookie_t reparent_cookie =
354-
xcb_reparent_window_checked(x11_connection.get(), wine_window,
355-
parent_window, 0, 0);
356-
if (std::unique_ptr<xcb_generic_error_t> reparent_error(
357-
xcb_request_check(x11_connection.get(), reparent_cookie));
358-
reparent_error) {
359-
std::cerr << "DEBUG: Reparent failed:" << std::endl;
360-
std::cerr << "Error code: " << reparent_error->error_code
361-
<< std::endl;
362-
std::cerr << "Major code: " << reparent_error->major_code
363-
<< std::endl;
364-
std::cerr << "Minor code: " << reparent_error->minor_code
365-
<< std::endl;
366-
367-
// Let's just check all of the reasons why the reparent could
368-
// fail according to the spec in advance
369-
xcb_generic_error_t* error = nullptr;
370-
const xcb_query_pointer_cookie_t query_pointer_cookie =
371-
xcb_query_pointer(x11_connection.get(), wine_window);
372-
const std::unique_ptr<xcb_query_pointer_reply_t>
373-
query_pointer_reply(xcb_query_pointer_reply(
374-
x11_connection.get(), query_pointer_cookie, &error));
375-
if (error) {
376-
free(error);
377-
std::cerr << "DEBUG: Could not query pointer location"
378-
<< std::endl;
379-
} else {
380-
if (query_pointer_reply->same_screen) {
381-
std::cerr
382-
<< "DEBUG: Pointer is on the same screen as the "
383-
"Wine window, good"
384-
<< std::endl;
385-
} else {
386-
std::cerr
387-
<< "DEBUG: Pointer is not on the same screen as "
388-
"the Wine window, oh no"
389-
<< std::endl;
390-
}
391-
}
392-
} else {
393-
std::cerr << "DEBUG: Reparent succeeded" << std::endl;
394-
}
395-
xcb_flush(x11_connection.get());
396-
};
397-
398363
do_reparent();
399364

400365
// If we're using the double embedding option, then the child window
@@ -414,17 +379,6 @@ Editor::Editor(MainContext& main_context,
414379

415380
ShowWindow(win32_child_window->handle, SW_SHOWNORMAL);
416381
}
417-
418-
// HACK: I can't seem to figure why the initial reparent would fail on
419-
// this particular i3 config in a way that I'm unable to
420-
// reproduce, but if it doesn't work the first time, just keep
421-
// trying!
422-
//
423-
// https://github.com/robbert-vdh/yabridge/issues/40
424-
std::cerr
425-
<< "DEBUG: Reparent 2 is allowed to fail if the first one succeeded"
426-
<< std::endl;
427-
do_reparent();
428382
}
429383
}
430384

@@ -463,6 +417,17 @@ void Editor::handle_x11_events() noexcept {
463417
<< event->event << std::endl;
464418

465419
redetect_host_window();
420+
421+
// NOTE: Some window managers like to steal the window, so
422+
// we must prevent that. This situation is easily
423+
// recognized since the window will then cover the
424+
// entire screen (since that's what the client area
425+
// has been set to).
426+
if (event->window == parent_window ||
427+
(event->window == wine_window &&
428+
event->parent != parent_window)) {
429+
do_reparent();
430+
}
466431
} break;
467432
// We're listening for `ConfigureNotify` events on the host's
468433
// window (i.e. the window that's actually going to get dragged
@@ -816,6 +781,48 @@ void Editor::send_xembed_message(xcb_window_t window,
816781
reinterpret_cast<char*>(&event));
817782
}
818783

784+
void Editor::do_reparent() const {
785+
// TODO: When rebasing this, we should keep in the error logging for when
786+
// the reparent fails
787+
const xcb_void_cookie_t reparent_cookie = xcb_reparent_window_checked(
788+
x11_connection.get(), wine_window, parent_window, 0, 0);
789+
if (std::unique_ptr<xcb_generic_error_t> reparent_error(
790+
xcb_request_check(x11_connection.get(), reparent_cookie));
791+
reparent_error) {
792+
std::cerr << "DEBUG: Reparent failed:" << std::endl;
793+
std::cerr << "Error code: " << reparent_error->error_code << std::endl;
794+
std::cerr << "Major code: " << reparent_error->major_code << std::endl;
795+
std::cerr << "Minor code: " << reparent_error->minor_code << std::endl;
796+
797+
// Let's just check all of the reasons why the reparent could
798+
// fail according to the spec in advance
799+
xcb_generic_error_t* error = nullptr;
800+
const xcb_query_pointer_cookie_t query_pointer_cookie =
801+
xcb_query_pointer(x11_connection.get(), wine_window);
802+
const std::unique_ptr<xcb_query_pointer_reply_t> query_pointer_reply(
803+
xcb_query_pointer_reply(x11_connection.get(), query_pointer_cookie,
804+
&error));
805+
if (error) {
806+
free(error);
807+
std::cerr << "DEBUG: Could not query pointer location" << std::endl;
808+
} else {
809+
if (query_pointer_reply->same_screen) {
810+
std::cerr << "DEBUG: Pointer is on the same screen as the "
811+
"Wine window, good"
812+
<< std::endl;
813+
} else {
814+
std::cerr << "DEBUG: Pointer is not on the same screen as "
815+
"the Wine window, oh no"
816+
<< std::endl;
817+
}
818+
}
819+
} else {
820+
std::cerr << "DEBUG: Reparent succeeded" << std::endl;
821+
}
822+
823+
xcb_flush(x11_connection.get());
824+
}
825+
819826
void Editor::do_xembed() const {
820827
if (!use_xembed) {
821828
return;

src/wine-host/editor.h

+5
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,11 @@ class Editor {
245245
uint32_t data1,
246246
uint32_t data2) const noexcept;
247247

248+
/**
249+
* Reparent `wine_window` to `parent_window`. This includes the flush.
250+
*/
251+
void do_reparent() const;
252+
248253
/**
249254
* Start the XEmbed procedure when `use_xembed` is enabled. This should be
250255
* rerun whenever visibility changes.

0 commit comments

Comments
 (0)