diff --git a/CHANGELOG.md b/CHANGELOG.md index 77403c00..5e79b31c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,11 +15,20 @@ Versioning](https://semver.org/spec/v2.0.0.html). ### Fixed -- Changed the filter in the Wine->X11 drag-and-drop implementation for - distinguishing between Wine windows and other windows (so that we don't - interfere with Wine->Wine drag-and-drop) to be more specific. Before this - change we might use our own XDND implementation when dragging from a plugin to - a standalone Wine application running within the same Wine prefix. +- Changed how input focus releasing works by more specifically ignoring events + where the mouse pointer is still hovering over a Wine window instead of + ignoring a wider class of events. This should fix some edge cases where input + focus would not be given back to the host, or where dropdown menus could close + immediately when moving your mouse outsdie of them. The first case would in + practice only happen when using a touchscreen or drawing tablet, since that + would require the mouse to instantly move from the plugin GUI to another + window without going over the window's borders. +- Similarly, the filter in the Wine->X11 drag-and-drop implementation for + distinguishing between Wine windows and other windows (so that we won't + interfere with Wine's own internal drag-and-drop mechanism) has also been made + more specific. Before this change we might use our own XDND implementation + when dragging from a plugin to a standalone Wine application running within + the same Wine prefix. ## [3.5.0] - 2021-06-23 diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 471ec094..fc640e1d 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -597,13 +597,52 @@ void Editor::handle_x11_events() noexcept { const auto event = reinterpret_cast( generic_event.get()); + + // HACK: We need to do a `WindowFromPoint()` query inside of + // `is_cursor_in_wine_window()`, and + // `GetCursorPos()`'s value only updates once every + // 100 milliseconds: + // https://github.com/wine-mirror/wine/blob/25271032dfb3f126a8b0dff2adb9b96a7d09241d/dlls/user32/input.c#L345 + // + // To avoid this, we will use the X11 cursor position. + // For this to work we will need to translate X11 root + // window coordinates into Wine virtual screen + // coordinates, like so: + // https://github.com/wine-mirror/wine/tree/25271032dfb3f126a8b0dff2adb9b96a7d09241d/dlls/winex11.drv/display.c + // + // This function is sadly not exposed, so instead we + // will get the root window cursor position, and then + // add to that the difference between `wine_window`'s + // root-relative X11 position and its Win32 position. + // The alternative is sleeping for 100 milliseconds, + // but this is faster. + const std::optional windows_pointer_pos = + get_current_pointer_position(); + logger.log_editor_trace([&]() { - return "DEBUG: LeaveNotify for window " + - std::to_string(event->child) + " (wine window " + - (is_wine_window_active() ? "active" - : "inactive") + - ", detail " + std::to_string(event->detail) + - ")"; + std::ostringstream message; + message << "DEBUG: LeaveNotify for window " + << event->child; + message << " (wine window " + << (is_wine_window_active() ? "active" + : "inactive"); + message << ", detail: " + << static_cast(event->detail); + message << ", pointer pos: "; + if (windows_pointer_pos) { + message << windows_pointer_pos->x << ", " + << windows_pointer_pos->y; + } else { + message << ""; + } + message + << ", pointer " + << (is_cursor_in_wine_window(windows_pointer_pos) + ? "is" + : "is not") + << " in Wine window)"; + + return message.str(); }); // This extra check for the `NonlinearVirtual` detail is @@ -617,9 +656,9 @@ void Editor::handle_x11_events() noexcept { // these fake dropdowns would immediately close when // hovering over them. if (event->child == wrapper_window.window && - event->detail != XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL && supports_ewmh_active_window() && - is_wine_window_active()) { + is_wine_window_active() && + !is_cursor_in_wine_window(windows_pointer_pos)) { set_input_focus(false); } } break; @@ -815,6 +854,40 @@ void Editor::maybe_run_timer_proc() { } } +std::optional Editor::get_current_pointer_position() const { + 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 query_pointer_reply( + xcb_query_pointer_reply(x11_connection.get(), query_pointer_cookie, + &error)); + if (error) { + free(error); + return std::nullopt; + } + + // We know the mouse coordinates relative to the root window, and we know + // the mouse coordinates relative to `wine_window`, so we can skip a request + // by calculating Wine window's coordinates ourself. + const uint16_t x11_x_pos = + query_pointer_reply->root_x - query_pointer_reply->win_x; + const uint16_t x11_y_pos = + query_pointer_reply->root_y - query_pointer_reply->win_y; + + // We need to offset the root-relative pointer position with the difference + // between `wine_window`'s X11 and Win32 coordinates. Wine sadly does not + // expose a function that just lets us translate X11 coordinates into + // Windows coordinates. + RECT win32_pos{}; + if (!GetWindowRect(get_win32_handle(), &win32_pos)) { + return std::nullopt; + } + + return POINT{ + .x = query_pointer_reply->root_x + (win32_pos.left - x11_x_pos), + .y = query_pointer_reply->root_y + (win32_pos.top - x11_y_pos)}; +} + bool Editor::is_wine_window_active() const { if (!supports_ewmh_active_window()) { return false; diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index f0da55e2..658aaa82 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -262,6 +262,18 @@ class Editor { const bool use_xembed; private: + /** + * Get the current cursor position, in Win32 screen coordinates. This is + * needed for our `LeaveNotify` handling because `GetCursorPos()` only + * updates once every 100 ms. This takes the X11 mouse cursor position, and + * then adds to that the difference between `wine_window`'s X11 coordinates + * and its Win32 coordinates. This is kind of a workaround for Wine's + * X11drv's `root_to_virtual_screen()` function not being exposed. + * + * If we cannot obtain the X11 cursor position, then this returns a nullopt. + */ + std::optional get_current_pointer_position() const; + /** * Returns `true` if the currently active window (as per * `_NET_ACTIVE_WINDOW`) contains `wine_window`. If the window manager does