Spawn a thread to fake do our XDND polling

We cannot integrate this into our event loop like we planned, because
Wine a) grabs the mouse pointer so we cannot do that, and b) blocks the
GUI thread. So instead we will spawn our own thread and do polling based
XDND. When Wine's tracker window gets destroyed, we know that the left
mouse button has been released.
This commit is contained in:
Robbert van der Helm
2021-07-10 19:28:40 +02:00
parent 42ce69943a
commit 091ab0f0df
4 changed files with 161 additions and 30 deletions
+105 -9
View File
@@ -19,11 +19,18 @@
#include <atomic>
#include <iostream>
#include "boost-fix.h"
#include <boost/container/small_vector.hpp>
#include "editor.h"
// As defined in `editor.cpp`
#define THROW_X11_ERROR(error) \
do { \
if (error) { \
free(error); \
throw std::runtime_error("X11 error in " + \
std::string(__PRETTY_FUNCTION__)); \
} \
} while (0)
/**
* The window class name Wine uses for its `DoDragDrop()` tracker window.
*
@@ -135,10 +142,6 @@ WineXdndProxy::Handle::Handle(Handle&& o) noexcept : proxy(o.proxy) {
instance_reference_count += 1;
}
void WineXdndProxy::Handle::handle_x11_events() const noexcept {
proxy->handle_x11_events();
}
WineXdndProxy::Handle WineXdndProxy::get_handle() {
// See the `instance` global above for an explanation on what's going on
// here.
@@ -149,8 +152,36 @@ WineXdndProxy::Handle WineXdndProxy::get_handle() {
return Handle(instance);
}
void WineXdndProxy::handle_x11_events() const noexcept {
// TODO
void WineXdndProxy::begin_xdnd(
const boost::container::small_vector_base<std::string>& file_paths,
HWND tracker_window) {
// When XDND starts, we need to start listening for mouse events so we can
// react when the mouse cursor hovers over a target that supports XDND. The
// actual file contents will be transferred over X11 selections. See the
// spec for a description of the entire process:
// https://www.freedesktop.org/wiki/Specifications/XDND/#atomsandproperties
xcb_set_selection_owner(x11_connection.get(), proxy_window.window,
xcb_xdnd_selection, XCB_CURRENT_TIME);
xcb_flush(x11_connection.get());
// Normally at this point you would grab the mouse pointer and track what
// windows it's moving over. Wine is already doing this, so as a hacky
// workaround we will instead just periodically poll the pointer position in
// `WineXdndProxy::handle_x11_events()`, and we'll consider the
// disappearance of `tracker_window` to indicate that the drag-and-drop has
// either been cancelled or it has succeeded.
dragged_file_paths.assign(file_paths.begin(), file_paths.end());
this->tracker_window = tracker_window;
// Because Wine is blocking the GUI thread, we need to do our XDND polling
// from another thread. Luckily the X11 API is thread safe.
xdnd_handler = Win32Thread([&]() { run_xdnd_loop(); });
}
void WineXdndProxy::end_xdnd() {
xcb_set_selection_owner(x11_connection.get(), XCB_NONE, xcb_xdnd_selection,
XCB_CURRENT_TIME);
xcb_flush(x11_connection.get());
}
/**
@@ -166,6 +197,62 @@ struct TrackerWindowInfo {
// ... 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()))
.data->root;
// We cannot just grab the pointer because Wine is already doing that, and
// it's also blocking the GUI thread. So instead we will periodically poll
// the mouse cursor position, and we will consider the disappearance of
// `tracker_window` to mean that the drag-and-drop operation has ended.
uint16_t last_pointer_x = ~0;
uint16_t last_pointer_y = ~0;
while (IsWindow(tracker_window)) {
usleep(1000);
// TODO: Can we somehow ignore plugin windows?
std::unique_ptr<xcb_generic_event_t> generic_event;
while (generic_event.reset(xcb_poll_for_event(x11_connection.get())),
generic_event != nullptr) {
const uint8_t event_type =
generic_event->response_type & xcb_event_type_mask;
switch (event_type) {
// TODO: Handle ConvertSelection
// TODO: Handle client messages
}
}
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) {
continue;
}
// TODO: Fetch the window under the mouse cursor, send messages to it
// according to the XDND protocol
last_pointer_x = query_pointer_reply->root_x;
last_pointer_y = query_pointer_reply->root_y;
}
// TODO: Check if the escape key is pressed to allow cancelling the drop,
// and either send the drop or leave message to the window that was
// under the pointer
end_xdnd();
}
void CALLBACK dnd_winevent_callback(HWINEVENTHOOK /*hWinEventHook*/,
DWORD event,
HWND hwnd,
@@ -288,4 +375,13 @@ void CALLBACK dnd_winevent_callback(HWINEVENTHOOK /*hWinEventHook*/,
<< std::endl;
return;
}
try {
instance->begin_xdnd(dragged_files, hwnd);
} catch (const std::exception& error) {
std::cerr << "XDND initialization failed:" << std::endl;
std::cerr << error.what() << std::endl;
}
}
#undef THROW_X11_ERROR