Continue polling while LMB is held

Instead of while the tracker window is alive. Technically the plugin may
clean up their files after the tracker window is gone, but in practice I
haven't seen it happen.
This commit is contained in:
Robbert van der Helm
2021-07-11 18:31:37 +02:00
parent 1c84c4a02a
commit ca5a7b2b96
2 changed files with 48 additions and 53 deletions
+30 -23
View File
@@ -194,9 +194,9 @@ WineXdndProxy::Handle WineXdndProxy::get_handle() {
return Handle(instance); return Handle(instance);
} }
void WineXdndProxy::begin_xdnd(const boost::container::small_vector_base< void WineXdndProxy::begin_xdnd(
boost::filesystem::path>& file_paths, const boost::container::small_vector_base<boost::filesystem::path>&
HWND tracker_window) { file_paths) {
if (file_paths.empty()) { if (file_paths.empty()) {
throw std::runtime_error("Cannot drag-and-drop without any files"); throw std::runtime_error("Cannot drag-and-drop without any files");
} }
@@ -232,14 +232,10 @@ void WineXdndProxy::begin_xdnd(const boost::container::small_vector_base<
// Normally at this point you would grab the mouse pointer and track what // 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 // 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 // workaround we will just poll the mouse position every millisecond until
// `WineXdndProxy::handle_x11_events()`, and we'll consider the // the left mouse button gets released. Because Wine is also blocking the
// disappearance of `tracker_window` to indicate that the drag-and-drop has // GUI thread, we need to do our XDND polling from another thread. Luckily
// either been cancelled or it has succeeded. // the X11 API is thread safe.
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(); }); xdnd_handler = Win32Thread([&]() { run_xdnd_loop(); });
} }
@@ -301,11 +297,12 @@ void WineXdndProxy::run_xdnd_loop() {
// We cannot just grab the pointer because Wine is already doing that, and // 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 // it's also blocking the GUI thread. So instead we will periodically poll
// the mouse cursor position, and we will consider the disappearance of // the mouse cursor position, and we will end the drag once the left mouse
// `tracker_window` to mean that the drag-and-drop operation has ended. // button gets released.
bool left_mouse_button_held = true;
std::optional<uint16_t> last_pointer_x; std::optional<uint16_t> last_pointer_x;
std::optional<uint16_t> last_pointer_y; std::optional<uint16_t> last_pointer_y;
while (IsWindow(tracker_window)) { while (left_mouse_button_held) {
usleep(1000); usleep(1000);
std::unique_ptr<xcb_generic_event_t> generic_event; std::unique_ptr<xcb_generic_event_t> generic_event;
@@ -360,9 +357,19 @@ void WineXdndProxy::run_xdnd_loop() {
// coordinates. // coordinates.
const std::unique_ptr<xcb_query_pointer_reply_t> xdnd_window_query = const std::unique_ptr<xcb_query_pointer_reply_t> xdnd_window_query =
query_xdnd_aware_window_at_pointer(root_window); query_xdnd_aware_window_at_pointer(root_window);
if (!xdnd_window_query || if (!xdnd_window_query) {
(xdnd_window_query->root_x == last_pointer_x && continue;
xdnd_window_query->root_y == last_pointer_y)) { }
// We will stop the dragging operation as soon as the left mouse button
// gets released
// NOTE: In soem cases Wine's own drag-and-drop operation ends
// prematurely. This seems to often happen with JUCE plugins. We
// will still continue with the dragging operation, although at
// that point the mouse pointer isn't grabbed by anything anymore.
left_mouse_button_held = xdnd_window_query->mask & XCB_BUTTON_MASK_1;
if (xdnd_window_query->root_x == last_pointer_x &&
xdnd_window_query->root_y == last_pointer_y) {
continue; continue;
} }
@@ -431,7 +438,7 @@ void WineXdndProxy::run_xdnd_loop() {
// After the loop has finished we either: // After the loop has finished we either:
// 1) Finish the drop, if `last_xdnd_window` is a valid XDND window // 1) Finish the drop, if `last_xdnd_window` is a valid XDND window
// 2) Cancel the drop, if the escape key is held, or // 2) Cancel the drop, if the escape key is being held, or
// 3) Don't do antyhing, if `last_xdnd_window` is a nullopt // 3) Don't do antyhing, if `last_xdnd_window` is a nullopt
// TODO: Check if the escape key is pressed to allow cancelling the drop. In // TODO: Check if the escape key is pressed to allow cancelling the drop. In
// that case we should call `maybe_leave_last_window()` // that case we should call `maybe_leave_last_window()`
@@ -440,10 +447,10 @@ void WineXdndProxy::run_xdnd_loop() {
return; return;
} }
// After the tracker window has disappeared we will try to send the drop to // After the left mouse button has been released we will try to send the
// the last window we hovered over, if it was a valid XDND aware window. We // drop to the last window we hovered over, if it was a valid XDND aware
// should however wait with this until the window has accepted our // window. We should however wait with this until the window has accepted
// `XdndPosition` message with an `XdndStatus` // our `XdndPosition` message with an `XdndStatus`
bool drop_finished = false; bool drop_finished = false;
const std::chrono::steady_clock::time_point wait_start = const std::chrono::steady_clock::time_point wait_start =
std::chrono::steady_clock::now(); std::chrono::steady_clock::now();
@@ -801,7 +808,7 @@ void CALLBACK dnd_winevent_callback(HWINEVENTHOOK /*hWinEventHook*/,
} }
try { try {
instance->begin_xdnd(dragged_files, hwnd); instance->begin_xdnd(dragged_files);
} catch (const std::exception& error) { } catch (const std::exception& error) {
std::cerr << "XDND initialization failed:" << std::endl; std::cerr << "XDND initialization failed:" << std::endl;
std::cerr << error.what() << std::endl; std::cerr << error.what() << std::endl;
+18 -30
View File
@@ -66,7 +66,7 @@ class ProxyWindow {
/** /**
* A simple wrapper that registers a WinEvents hook to listen for new windows * A simple wrapper that registers a WinEvents hook to listen for new windows
* being created, and handles XDND client messages to achieve the behaviour * being created, and handles XDND client messages to achieve the behaviour
* described in `WineXdndProxy::init_proxy()`. * described in `WineXdndProxy::get_handle()`.
*/ */
class WineXdndProxy { class WineXdndProxy {
protected: protected:
@@ -88,7 +88,7 @@ class WineXdndProxy {
protected: protected:
/** /**
* Before calling this, the reference count should be increased by one * Before calling this, the reference count should be increased by one
* in `WineXdndProxy::init_proxy()`. * in `WineXdndProxy::get_handle()`.
*/ */
Handle(WineXdndProxy* proxy); Handle(WineXdndProxy* proxy);
@@ -114,14 +114,14 @@ class WineXdndProxy {
/** /**
* Initialize the Wine->X11 drag-and-drop proxy. Calling this will hook into * Initialize the Wine->X11 drag-and-drop proxy. Calling this will hook into
* Wine's OLE drag and drop system by listening for the creation of special * Wine's OLE drag and drop system by listening for the creation of special
* proxy windows created by the Wine server. When a drag and drop operation * tracker windows created by the Wine server. When a drag and drop
* is started, we will initiate the XDND protocol with the same file. This * operation is started, we will initiate the XDND protocol with the files
* will allow us to drag files from Wine windows to X11 applications, * referenced by that tracker window. This will allow us to drag files from
* something that's normally not possible. Calling this function more than * Wine windows to X11 applications, something that's normally not possible.
* once doesn't have any effect, but this should still be called at least * Calling this function more than once doesn't have any effect, but this
* once from every plugin host instance. Because the actual data is stored * should still be called at least once from every plugin host instance.
* in a COM object, we can only handle drag-and-drop coming form this * Because the actual data is stored in a COM object, we can only handle
* process. * drag-and-drop coming form this process.
* *
* This is sort of a singleton but not quite, as the `WineXdndProxy` is only * This is sort of a singleton but not quite, as the `WineXdndProxy` is only
* alive for as long as there are open editors in this process. This is done * alive for as long as there are open editors in this process. This is done
@@ -136,9 +136,9 @@ class WineXdndProxy {
* Initiate the XDDN protocol by taking ownership of the `XdndSelection` * Initiate the XDDN protocol by taking ownership of the `XdndSelection`
* selection and setting up the event listeners. * selection and setting up the event listeners.
*/ */
void begin_xdnd(const boost::container::small_vector_base< void begin_xdnd(
boost::filesystem::path>& file_paths, const boost::container::small_vector_base<boost::filesystem::path>&
HWND tracker_window); file_paths);
/** /**
* Release ownership of the selection stop listening for X11 events. * Release ownership of the selection stop listening for X11 events.
@@ -147,11 +147,11 @@ class WineXdndProxy {
private: private:
/** /**
* From another thread, constantly poll the mouse position until * From another thread, constantly poll the mouse position until the left
* `tracker_window` disappears, and then perform the drop if the mouse * mouse button gets released, and then perform the drop if the mouse cursor
* cursor was last positioned over an XDND aware window. This is a * was last positioned over an XDND aware window. This is a workaround for
* workaround for us not being able to grab the mouse cursor since Wine is * us not being able to grab the mouse cursor since Wine is already doing
* already doing that. * that.
*/ */
void run_xdnd_loop(); void run_xdnd_loop();
@@ -240,18 +240,6 @@ class WineXdndProxy {
*/ */
std::string dragged_files_uri_list; std::string dragged_files_uri_list;
/**
* Wine's tracker window for tracking the drag-and-drop operation. Normally
* you would grab the mouse pointer when the drag-and-drop operation starts
* so you can track what windows you are hovering over, but we cannot do
* that because Wine is already doing just that. So instead we will
* periodically poll the mouse position from another thread, and we'll
* consider the disappearance of this window to mean that the drop has
* either succeeded or cancelled (depending on whether or not Escape is
* pressed).
*/
HWND tracker_window;
/** /**
* We need to poll for mouse position changes from another thread, because * We need to poll for mouse position changes from another thread, because
* when the drag-and-drop operation starts Wine will be blocking the GUI * when the drag-and-drop operation starts Wine will be blocking the GUI