diff --git a/src/wine-host/xdnd-proxy.cpp b/src/wine-host/xdnd-proxy.cpp index df6b2c1f..36c15f83 100644 --- a/src/wine-host/xdnd-proxy.cpp +++ b/src/wine-host/xdnd-proxy.cpp @@ -194,9 +194,9 @@ WineXdndProxy::Handle WineXdndProxy::get_handle() { return Handle(instance); } -void WineXdndProxy::begin_xdnd(const boost::container::small_vector_base< - boost::filesystem::path>& file_paths, - HWND tracker_window) { +void WineXdndProxy::begin_xdnd( + const boost::container::small_vector_base& + file_paths) { if (file_paths.empty()) { 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 // 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. - 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. + // workaround we will just poll the mouse position every millisecond until + // the left mouse button gets released. Because Wine is also 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(); }); } @@ -301,11 +297,12 @@ void WineXdndProxy::run_xdnd_loop() { // 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. + // the mouse cursor position, and we will end the drag once the left mouse + // button gets released. + bool left_mouse_button_held = true; std::optional last_pointer_x; std::optional last_pointer_y; - while (IsWindow(tracker_window)) { + while (left_mouse_button_held) { usleep(1000); std::unique_ptr generic_event; @@ -360,9 +357,19 @@ void WineXdndProxy::run_xdnd_loop() { // coordinates. const std::unique_ptr 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)) { + if (!xdnd_window_query) { + continue; + } + + // 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; } @@ -431,7 +438,7 @@ void WineXdndProxy::run_xdnd_loop() { // After the loop has finished we either: // 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 // TODO: Check if the escape key is pressed to allow cancelling the drop. In // that case we should call `maybe_leave_last_window()` @@ -440,10 +447,10 @@ void WineXdndProxy::run_xdnd_loop() { return; } - // After the tracker window has disappeared we will try to send the drop to - // the last window we hovered over, if it was a valid XDND aware window. We - // should however wait with this until the window has accepted our - // `XdndPosition` message with an `XdndStatus` + // After the left mouse button has been released we will try to send the + // drop to the last window we hovered over, if it was a valid XDND aware + // window. We should however wait with this until the window has accepted + // our `XdndPosition` message with an `XdndStatus` bool drop_finished = false; const std::chrono::steady_clock::time_point wait_start = std::chrono::steady_clock::now(); @@ -801,7 +808,7 @@ void CALLBACK dnd_winevent_callback(HWINEVENTHOOK /*hWinEventHook*/, } try { - instance->begin_xdnd(dragged_files, hwnd); + instance->begin_xdnd(dragged_files); } catch (const std::exception& error) { std::cerr << "XDND initialization failed:" << std::endl; std::cerr << error.what() << std::endl; diff --git a/src/wine-host/xdnd-proxy.h b/src/wine-host/xdnd-proxy.h index b933d41c..b90396e2 100644 --- a/src/wine-host/xdnd-proxy.h +++ b/src/wine-host/xdnd-proxy.h @@ -66,7 +66,7 @@ class ProxyWindow { /** * A simple wrapper that registers a WinEvents hook to listen for new windows * being created, and handles XDND client messages to achieve the behaviour - * described in `WineXdndProxy::init_proxy()`. + * described in `WineXdndProxy::get_handle()`. */ class WineXdndProxy { protected: @@ -88,7 +88,7 @@ class WineXdndProxy { protected: /** * Before calling this, the reference count should be increased by one - * in `WineXdndProxy::init_proxy()`. + * in `WineXdndProxy::get_handle()`. */ Handle(WineXdndProxy* proxy); @@ -114,14 +114,14 @@ class WineXdndProxy { /** * 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 - * proxy windows created by the Wine server. When a drag and drop operation - * is started, we will initiate the XDND protocol with the same file. This - * will allow us to drag files from Wine windows to X11 applications, - * something that's normally not possible. Calling this function more than - * once doesn't have any effect, but this should still be called at least - * once from every plugin host instance. Because the actual data is stored - * in a COM object, we can only handle drag-and-drop coming form this - * process. + * tracker windows created by the Wine server. When a drag and drop + * operation is started, we will initiate the XDND protocol with the files + * referenced by that tracker window. This will allow us to drag files from + * Wine windows to X11 applications, something that's normally not possible. + * Calling this function more than once doesn't have any effect, but this + * should still be called at least once from every plugin host instance. + * Because the actual data is stored in a COM object, we can only handle + * drag-and-drop coming form this process. * * 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 @@ -136,9 +136,9 @@ class WineXdndProxy { * Initiate the XDDN protocol by taking ownership of the `XdndSelection` * selection and setting up the event listeners. */ - void begin_xdnd(const boost::container::small_vector_base< - boost::filesystem::path>& file_paths, - HWND tracker_window); + void begin_xdnd( + const boost::container::small_vector_base& + file_paths); /** * Release ownership of the selection stop listening for X11 events. @@ -147,11 +147,11 @@ class WineXdndProxy { private: /** - * From another thread, constantly poll the mouse position until - * `tracker_window` disappears, and then perform the drop if the mouse - * cursor was last positioned over an XDND aware window. This is a - * workaround for us not being able to grab the mouse cursor since Wine is - * already doing that. + * From another thread, constantly poll the mouse position until the left + * mouse button gets released, and then perform the drop if the mouse cursor + * was last positioned over an XDND aware window. This is a workaround for + * us not being able to grab the mouse cursor since Wine is already doing + * that. */ void run_xdnd_loop(); @@ -240,18 +240,6 @@ class WineXdndProxy { */ 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 * when the drag-and-drop operation starts Wine will be blocking the GUI