mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-10 04:30:12 +02:00
Find the first XDND aware window under the cursor
This commit is contained in:
+106
-29
@@ -184,19 +184,6 @@ void WineXdndProxy::end_xdnd() {
|
|||||||
xcb_flush(x11_connection.get());
|
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() {
|
void WineXdndProxy::run_xdnd_loop() {
|
||||||
const xcb_window_t root_window =
|
const xcb_window_t root_window =
|
||||||
xcb_setup_roots_iterator(xcb_get_setup(x11_connection.get()))
|
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;
|
// We'll try to find the first window under the pointer (starting form
|
||||||
const xcb_query_pointer_cookie_t query_pointer_cookie =
|
// the root) until we find a window that supports XDND. The returned
|
||||||
xcb_query_pointer(x11_connection.get(), root_window);
|
// child window may not support XDND so we need to check that
|
||||||
const std::unique_ptr<xcb_query_pointer_reply_t> query_pointer_reply(
|
// separately, as we still need to keep track of the pointer
|
||||||
xcb_query_pointer_reply(x11_connection.get(), query_pointer_cookie,
|
// coordinates.
|
||||||
&error));
|
const std::unique_ptr<xcb_query_pointer_reply_t> xdnd_window_query =
|
||||||
if (error) {
|
query_xdnd_aware_window_at_pointer(root_window);
|
||||||
free(error);
|
if (!xdnd_window_query ||
|
||||||
continue;
|
(xdnd_window_query->root_x == last_pointer_x &&
|
||||||
}
|
xdnd_window_query->root_y == last_pointer_y)) {
|
||||||
if (query_pointer_reply->root_x == last_pointer_x &&
|
|
||||||
query_pointer_reply->root_y == last_pointer_y) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
last_pointer_x = query_pointer_reply->root_x;
|
|
||||||
last_pointer_y = query_pointer_reply->root_y;
|
|
||||||
last_window.reset();
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +242,7 @@ void WineXdndProxy::run_xdnd_loop() {
|
|||||||
|
|
||||||
// TODO: Fetch the window under the mouse cursor, send messages to it
|
// TODO: Fetch the window under the mouse cursor, send messages to it
|
||||||
// according to the XDND protocol
|
// 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,
|
// TODO: Check if the escape key is pressed to allow cancelling the drop,
|
||||||
@@ -267,6 +252,98 @@ void WineXdndProxy::run_xdnd_loop() {
|
|||||||
end_xdnd();
|
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*/,
|
void CALLBACK dnd_winevent_callback(HWINEVENTHOOK /*hWinEventHook*/,
|
||||||
DWORD event,
|
DWORD event,
|
||||||
HWND hwnd,
|
HWND hwnd,
|
||||||
|
|||||||
@@ -155,6 +155,48 @@ class WineXdndProxy {
|
|||||||
*/
|
*/
|
||||||
void run_xdnd_loop();
|
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
|
* 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
|
* multiple open editors in a single process (e.g. when using VST3 plugins
|
||||||
|
|||||||
Reference in New Issue
Block a user