Find the first XDND aware window under the cursor

This commit is contained in:
Robbert van der Helm
2021-07-10 23:08:37 +02:00
parent 82f7a8f799
commit 2e153304dd
2 changed files with 148 additions and 29 deletions
+106 -29
View File
@@ -184,19 +184,6 @@ void WineXdndProxy::end_xdnd() {
xcb_flush(x11_connection.get());
}
/**
* Part of the struct Wine uses to keep track of the data during an OLE
* drag-and-drop operation. We only really care about the first field that
* contains the actual data.
*
* https://github.com/wine-mirror/wine/blob/d10887b8f56792ebcca717ccc28a289f7bcaf107/dlls/ole32/ole2.c#L54-L73
*/
struct TrackerWindowInfo {
IDataObject* dataObject;
IDropSource* dropSource;
// ... more fields that we don't need
};
void WineXdndProxy::run_xdnd_loop() {
const xcb_window_t root_window =
xcb_setup_roots_iterator(xcb_get_setup(x11_connection.get()))
@@ -224,25 +211,23 @@ void WineXdndProxy::run_xdnd_loop() {
}
}
xcb_generic_error_t* error = nullptr;
const xcb_query_pointer_cookie_t query_pointer_cookie =
xcb_query_pointer(x11_connection.get(), root_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);
continue;
}
if (query_pointer_reply->root_x == last_pointer_x &&
query_pointer_reply->root_y == last_pointer_y) {
// We'll try to find the first window under the pointer (starting form
// the root) until we find a window that supports XDND. The returned
// child window may not support XDND so we need to check that
// separately, as we still need to keep track of the pointer
// coordinates.
const std::unique_ptr<xcb_query_pointer_reply_t> xdnd_window_query =
query_xdnd_aware_window_at_pointer(root_window);
if (!xdnd_window_query ||
(xdnd_window_query->root_x == last_pointer_x &&
xdnd_window_query->root_y == last_pointer_y)) {
continue;
}
last_pointer_x = query_pointer_reply->root_x;
last_pointer_y = query_pointer_reply->root_y;
last_window.reset();
if (query_pointer_reply->child == XCB_NONE) {
last_pointer_x = xdnd_window_query->root_x;
last_pointer_y = xdnd_window_query->root_y;
if (!is_xdnd_aware(xdnd_window_query->child)) {
continue;
}
@@ -257,7 +242,7 @@ void WineXdndProxy::run_xdnd_loop() {
// TODO: Fetch the window under the mouse cursor, send messages to it
// according to the XDND protocol
last_window = query_pointer_reply->child;
last_window = xdnd_window_query->child;
}
// TODO: Check if the escape key is pressed to allow cancelling the drop,
@@ -267,6 +252,98 @@ void WineXdndProxy::run_xdnd_loop() {
end_xdnd();
}
std::unique_ptr<xcb_query_pointer_reply_t>
WineXdndProxy::query_xdnd_aware_window_at_pointer(
xcb_window_t window) const noexcept {
xcb_generic_error_t* error = nullptr;
xcb_query_pointer_cookie_t query_pointer_cookie;
std::unique_ptr<xcb_query_pointer_reply_t> query_pointer_reply = nullptr;
while (true) {
query_pointer_cookie = xcb_query_pointer(x11_connection.get(), window);
query_pointer_reply.reset(xcb_query_pointer_reply(
x11_connection.get(), query_pointer_cookie, &error));
if (error) {
free(error);
break;
}
// We want to find the first XDND aware window under the mouse pointer,
// if there is any
if (query_pointer_reply->child == XCB_NONE ||
is_xdnd_aware(query_pointer_reply->child)) {
break;
}
window = query_pointer_reply->child;
}
return query_pointer_reply;
}
bool WineXdndProxy::is_xdnd_aware(xcb_window_t window) const noexcept {
// Respect `XdndProxy`, if that's set
window = get_xdnd_proxy(window).value_or(window);
xcb_generic_error_t* error = nullptr;
const xcb_get_property_cookie_t property_cookie =
xcb_get_property(x11_connection.get(), false, window,
xcb_xdnd_aware_property, XCB_ATOM_ATOM, 0, 1);
const std::unique_ptr<xcb_get_property_reply_t> property_reply(
xcb_get_property_reply(x11_connection.get(), property_cookie, &error));
if (error) {
free(error);
return false;
}
// Since the spec dates from 2002, we won't even bother checking the
// supported version
return property_reply->type != XCB_NONE &&
*static_cast<xcb_atom_t*>(
xcb_get_property_value(property_reply.get())) != 0;
}
std::optional<xcb_window_t> WineXdndProxy::get_xdnd_proxy(
xcb_window_t window) const noexcept {
xcb_generic_error_t* error = nullptr;
const xcb_get_property_cookie_t property_cookie =
xcb_get_property(x11_connection.get(), false, window,
xcb_xdnd_proxy_property, XCB_ATOM_WINDOW, 0, 1);
const std::unique_ptr<xcb_get_property_reply_t> property_reply(
xcb_get_property_reply(x11_connection.get(), property_cookie, &error));
if (error) {
free(error);
return std::nullopt;
}
if (property_reply->type == XCB_NONE) {
return std::nullopt;
} else {
return *static_cast<xcb_window_t*>(
xcb_get_property_value(property_reply.get()));
}
}
void WineXdndProxy::send_xdnd_message(const xcb_window_t& /*window*/,
const uint32_t /*message*/,
const uint32_t /*detail*/,
const uint32_t /*data1*/,
const uint32_t /*data2*/) const noexcept {
// TODO: Implement
}
/**
* Part of the struct Wine uses to keep track of the data during an OLE
* drag-and-drop operation. We only really care about the first field that
* contains the actual data.
*
* https://github.com/wine-mirror/wine/blob/d10887b8f56792ebcca717ccc28a289f7bcaf107/dlls/ole32/ole2.c#L54-L73
*/
struct TrackerWindowInfo {
IDataObject* dataObject;
IDropSource* dropSource;
// ... more fields that we don't need
};
void CALLBACK dnd_winevent_callback(HWINEVENTHOOK /*hWinEventHook*/,
DWORD event,
HWND hwnd,
+42
View File
@@ -155,6 +155,48 @@ class WineXdndProxy {
*/
void run_xdnd_loop();
/**
* Find the first XDND aware X11 window at the current mouse cursor,
* starting at `window` and iteratively descending into its children until
* we reach the bottommost child where the mouse cursor is in. This respects
* `XdndProxy`. If no XdndAware window was found, then this will contain the
* deepest query so we still have access to the pointer coordinates. That
* means you will still need to check `is_xdnd_aware(result->child)` after
* the fact.
*
* This will return a null pointer if an X11 error was thrown.
*/
std::unique_ptr<xcb_query_pointer_reply_t>
query_xdnd_aware_window_at_pointer(xcb_window_t window) const noexcept;
/**
* Check whether a window is XDND-aware, respecting `XdndProxy`. We should
* be checking the supported version as well and change our handling
* accordingly, but the XDND spec was last updated in 2002 so we'll just
* assume this won't cause any issues.
*/
bool is_xdnd_aware(xcb_window_t window) const noexcept;
/**
* Return the XDND proxy window for `window` as specified in the `XdndProxy`
* property. Returns a nullopt if `window` doesn't have that property set.
*/
std::optional<xcb_window_t> get_xdnd_proxy(
xcb_window_t window) const noexcept;
/**
* Send an XDND message to a window, respecting `XdndProxy` (i.e. window
* should always be the window under the cursor). This does not include a
* flush. See the spec for more information:
*
* https://www.freedesktop.org/wiki/Specifications/XDND/#clientmessages
*/
void send_xdnd_message(const xcb_window_t& window,
const uint32_t message,
const uint32_t detail,
const uint32_t data1,
const uint32_t data2) const noexcept;
/**
* We need a dedicated X11 connection for our proxy because we can have
* multiple open editors in a single process (e.g. when using VST3 plugins