Rename topmost_window to host_window

Since apparently to keep REAPER happy we shouldn't take the _very_
topmost window.
This commit is contained in:
Robbert van der Helm
2021-07-17 19:11:29 +02:00
parent e4f2e8c27f
commit b99f03cf64
2 changed files with 66 additions and 59 deletions
+46 -47
View File
@@ -49,10 +49,10 @@ using namespace std::literals::chrono_literals;
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`.
* The X11 event mask for the host window, which in most DAWs except for Ardour
* and REAPER will be the same as `parent_window`.
*/
constexpr uint32_t topmost_event_mask =
constexpr uint32_t host_event_mask =
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_VISIBILITY_CHANGE;
/**
@@ -60,7 +60,7 @@ constexpr uint32_t topmost_event_mask =
* as well to detect reparents.
*/
constexpr uint32_t parent_event_mask =
topmost_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;
/**
@@ -93,14 +93,14 @@ static const HCURSOR arrow_cursor = LoadCursor(nullptr, IDC_ARROW);
* Find the the ancestors for the given window. This returns a list of window
* IDs that starts wit h`starting_at`, and then iteratively contains the parent
* of the previous window in the list until we reach the root window. The
* topmost window (i.e. the window that will show up in the user's window
* manager) will be the last window in this list.
* topmost window (i.e. the window closest to the root in the window stack) will
* be the last window in this list.
*
* @param x11_connection The X11 connection to use.
* @param starting_at The window we want to know the ancestor windows of.
*
* @return A non-empty list containing `starting_at` and all of its ancestor
* windows `starting_at`.
* windows `starting_at`.
*/
boost::container::small_vector<xcb_window_t, 8> find_ancestor_windows(
xcb_connection_t& x11_connection,
@@ -244,7 +244,7 @@ Editor::Editor(MainContext& main_context,
idle_timer_proc(std::move(timer_proc)),
parent_window(parent_window_handle),
wine_window(get_x11_handle(win32_window.handle)),
topmost_window(
host_window(
find_ancestor_windows(*x11_connection, parent_window).back()) {
// Used for input focus grabbing to only grab focus when the window is
// active. In case the atom does not exist or the WM does not support this
@@ -293,8 +293,8 @@ Editor::Editor(MainContext& main_context,
// release input focus as necessary.
// If we do enable XEmbed support, we'll also listen for visibility changes
// and trigger the embedding when the window becomes visible
xcb_change_window_attributes(x11_connection.get(), topmost_window,
XCB_CW_EVENT_MASK, &topmost_event_mask);
xcb_change_window_attributes(x11_connection.get(), host_window,
XCB_CW_EVENT_MASK, &host_event_mask);
xcb_change_window_attributes(x11_connection.get(), parent_window,
XCB_CW_EVENT_MASK, &parent_event_mask);
xcb_flush(x11_connection.get());
@@ -359,22 +359,22 @@ void Editor::handle_x11_events() noexcept {
// 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.
// that `host_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 host's window has changed whenever the
// parent window gets reparented.
case XCB_REPARENT_NOTIFY: {
redetect_topmost_window();
redetect_host_window();
} break;
// We're listening for `ConfigureNotify` events on the topmost
// window before the root window, i.e. the window that's
// actually going to get dragged around the by the user. In most
// cases this is the same as `parent_window`. When either this
// window gets moved, or when the user moves his mouse over our
// window, the local coordinates should be updated. The
// additional `EnterWindow` check is sometimes necessary for
// using multiple editor windows within a single plugin group.
// We're listening for `ConfigureNotify` events on the host's
// window (i.e. the window that's actually going to get dragged
// around the by the user). In most cases this is the same as
// `parent_window`. When either this window gets moved, or when
// the user moves his mouse over our window, the local
// coordinates should be updated. The additional `EnterWindow`
// check is sometimes necessary for using multiple editor
// windows within a single plugin group.
case XCB_CONFIGURE_NOTIFY: {
if (!use_xembed) {
fix_local_coordinates();
@@ -511,7 +511,7 @@ void Editor::fix_local_coordinates() const {
}
void Editor::set_input_focus(bool grab) const {
const xcb_window_t focus_target = grab ? parent_window : topmost_window;
const xcb_window_t focus_target = grab ? parent_window : host_window;
xcb_generic_error_t* error = nullptr;
const xcb_get_input_focus_cookie_t focus_cookie =
@@ -522,13 +522,13 @@ void Editor::set_input_focus(bool grab) const {
// Calling `set_input_focus(true)` can trigger another `FocusIn` event,
// which will then once again call `set_input_focus(true)`. To work around
// this we prevent unnecessary repeat keyboard focus grabs.
// One thing that slightly complicates this is the use of unmapped input
// proxy windows. When `topmost_window` gets foccused, some hosts will
// reassign input focus to such a proxy window. To avoid fighting over
// focus, when grabbing focus we don't just check whether `current_focus`
// and `focus_target` are the same window but we'll also allow
// `current_focus` to be a child of `focus_target`.
// this we prevent unnecessary repeat keyboard focus grabs. One thing that
// slightly complicates this is the use of unmapped input proxy windows.
// When `host_window` gets foccused, some hosts will reassign input focus to
// such a proxy window. To avoid fighting over focus, when grabbing focus we
// don't just check whether `current_focus` and `focus_target` are the same
// window but we'll also allow `current_focus` to be a child of
// `focus_target`.
const xcb_window_t current_focus = focus_reply->focus;
if (current_focus == focus_target ||
(grab && is_child_window_or_same(*x11_connection, current_focus,
@@ -537,7 +537,7 @@ void Editor::set_input_focus(bool grab) const {
}
// Explicitly request input focus when the user interacts with the window.
// Without this `topmost_window` will capture all keyboard events in most
// Without this, `host_window` will capture all keyboard events in most
// hosts. Ideally we would just do this whenever the child window calls
// `SetFocus()` (or no handling should be necessary), but as far as I'm
// aware there is no way to do this. Right now we will grab input focus when
@@ -587,32 +587,31 @@ bool Editor::is_wine_window_active() const {
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 =
void Editor::redetect_host_window() noexcept {
const xcb_window_t new_host_window =
find_ancestor_windows(*x11_connection, parent_window).back();
if (new_topmost_window == topmost_window) {
if (new_host_window == host_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) {
// We need to readjust the event masks for the new host window, keeping the
// (very probable) possibility in mind that the old host window is the same
// as the parent window or that the parent window now is the host window.
if (host_window != parent_window) {
constexpr uint32_t no_event_mask = XCB_EVENT_MASK_NO_EVENT;
xcb_change_window_attributes(x11_connection.get(), topmost_window,
xcb_change_window_attributes(x11_connection.get(), host_window,
XCB_CW_EVENT_MASK, &no_event_mask);
}
if (new_topmost_window == parent_window) {
xcb_change_window_attributes(x11_connection.get(), new_topmost_window,
if (new_host_window == parent_window) {
xcb_change_window_attributes(x11_connection.get(), new_host_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);
xcb_change_window_attributes(x11_connection.get(), new_host_window,
XCB_CW_EVENT_MASK, &host_event_mask);
}
topmost_window = new_topmost_window;
host_window = new_host_window;
xcb_flush(x11_connection.get());
}
+20 -12
View File
@@ -53,7 +53,8 @@ constexpr uint8_t xcb_event_type_mask = 0b0111'1111;
/**
* The name of the X11 property that indicates whether a window supports
* drag-and-drop. If the `editor_force_dnd` option is enabled we'll remove this
* property from `topmost_window` to work around a bug in REAPER.
* property from all of `parent_window`'s ancestors to work around a bug in
* REAPER.
*/
constexpr char xdnd_aware_property_name[] = "XdndAware";
@@ -197,7 +198,7 @@ class Editor {
* details on when this is used.
*
* @param grab Whether to grab input focus (if `true`) or to give back input
* focus to `topmost_window` (if `false`).
* focus to `host_window` (if `false`).
*/
void set_input_focus(bool grab) const;
@@ -226,10 +227,11 @@ class Editor {
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.
* After `parent_window` gets reparented, we may need to redetect which
* toplevel-ish window the host is using and adjust the events we're
* subscribed to accordingly.
*/
void redetect_topmost_window() noexcept;
void redetect_host_window() noexcept;
/**
* Send an XEmbed message to a window. This does not include a flush. See
@@ -313,20 +315,26 @@ class Editor {
*/
const xcb_window_t wine_window;
/**
* The X11 window that's at the top of the window tree starting from
* `parent_window`, i.e. a direct child of the root window. In most cases
* this is going to be the same as `parent_window`, but some DAWs (such as
* REAPER) embed `parent_window` into another window. We have to listen for
* configuration changes on this topmost window to know when the window is
* being dragged around.
* The toplevel X11 window `parent_window` is contained in, or
* `parent_window` if the host doesn't do any fancy window embedding. We'll
* find this by looking for the topmost ancestor window of `parent_window`
* that has `WM_STATE` set. This is similar to how `xprop` and `xwininfo`
* select windows. In most cases this is going to be the same as
* `parent_window`, but some DAWs (such as REAPER) embed `parent_window`
* into another window. We have to listen for configuration changes on this
* topmost window to know when the window is being dragged around, and when
* returning keyboard focus to the host we'll focus this window.
*
* 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.
* NOTE: Taking the very topmost window is not an option, because for some
* reason REAPER will only process keyboard input for that window when
* the mouse is within the window.
*/
xcb_window_t topmost_window;
xcb_window_t host_window;
/**
* The atom corresponding to `_NET_ACTIVE_WINDOW`.