Handle Wayland dragging actions ending prematurely

This commit is contained in:
2025-04-19 11:07:47 +03:00
parent e6f90898cb
commit a2414f596a
2 changed files with 77 additions and 36 deletions

View File

@@ -51,26 +51,26 @@ namespace FlaxEditor.GUI.Docking
// Update rectangles // Update rectangles
UpdateRects(Platform.MousePosition); UpdateRects(Platform.MousePosition);
// Ensure the dragged window stays on top of every other window
window.IsAlwaysOnTop = true;
_dragSourceWindow = dragSourceWindow; _dragSourceWindow = dragSourceWindow;
if (_dragSourceWindow != null) // Detaching a tab from existing window if (_dragSourceWindow != null) // Detaching a tab from existing window
{ {
_dragOffset = new Float2(window.Size.X / 2, 10.0f); _dragOffset = new Float2(window.Size.X / 2, 10.0f);
_dragSourceWindow.MouseUp += OnMouseUp; // The mouse up event is sent to the source window on Windows
// TODO: when detaching tab in floating window (not main window), the drag source window is still main window? // TODO: when detaching tab in floating window (not main window), the drag source window is still main window?
var dragSourceWindowWayland = toMove.MasterPanel?.RootWindow.Window ?? Editor.Instance.Windows.MainWindow; var dragSourceWindowWayland = toMove.MasterPanel?.RootWindow.Window ?? Editor.Instance.Windows.MainWindow;
window.DoDragDrop(window.Title, _dragOffset, dragSourceWindowWayland); window.DoDragDrop(window.Title, _dragOffset, dragSourceWindowWayland);
_dragSourceWindow.MouseUp += OnMouseUp; // The mouse up event is sent to the source window on Windows
} }
else else
{ {
_dragOffset = window.MousePosition; _dragOffset = window.MousePosition;
window.DoDragDrop(window.Title, _dragOffset, window); window.DoDragDrop(window.Title, _dragOffset, window);
} }
// Ensure the dragged window stays on top of every other window
window.IsAlwaysOnTop = true;
} }
/// <summary> /// <summary>

View File

@@ -80,8 +80,8 @@ public:
namespace WaylandImpl namespace WaylandImpl
{ {
wl_display* WaylandDisplay = nullptr; wl_display* WaylandDisplay = nullptr;
int64 DragOverFlag = 0;
uint32 GrabSerial = 0; int64 Serial = 0;
wl_pointer_listener PointerListener = wl_pointer_listener PointerListener =
{ {
[](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { }, // Enter event [](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { }, // Enter event
@@ -91,7 +91,9 @@ namespace WaylandImpl
{ {
// Store the serial for upcoming drag-and-drop action // Store the serial for upcoming drag-and-drop action
if (state == 1) if (state == 1)
GrabSerial = serial; Platform::AtomicStore(&Serial, serial);
else
Platform::AtomicStore(&Serial, 0);
}, },
[](void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { }, // Axis event [](void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { }, // Axis event
[](void* data, wl_pointer* wl_pointer) { }, // Frame event [](void* data, wl_pointer* wl_pointer) { }, // Frame event
@@ -184,7 +186,6 @@ namespace WaylandImpl
}, },
}; };
int64 DragOverFlag = 0;
wl_data_source_listener DataSourceListener = wl_data_source_listener DataSourceListener =
{ {
[](void* data, wl_data_source* source, const char* mime_type) { }, // Target event [](void* data, wl_data_source* source, const char* mime_type) { }, // Target event
@@ -202,20 +203,20 @@ namespace WaylandImpl
}, },
[](void* data, wl_data_source* source) // Cancelled event [](void* data, wl_data_source* source) // Cancelled event
{ {
// Clipboard: other application has replaced the content in clipboard // Clipboard: other application has replaced the content in clipboad
wl_data_source_destroy(source);
IGuiData* inputData = static_cast<IGuiData*>(data); IGuiData* inputData = static_cast<IGuiData*>(data);
Platform::AtomicStore(&WaylandImpl::DragOverFlag, 1); Platform::AtomicStore(&WaylandImpl::DragOverFlag, 1);
wl_data_source_destroy(source);
}, },
[](void* data, wl_data_source* source) { }, // DnD drop performed event [](void* data, wl_data_source* source) { }, // DnD drop performed event
[](void* data, wl_data_source* source) // DnD Finished event [](void* data, wl_data_source* source) // DnD Finished event
{ {
// The destination has finally accepted the last given dnd_action // The destination has finally accepted the last given dnd_action
wl_data_source_destroy(source);
IGuiData* inputData = static_cast<IGuiData*>(data); IGuiData* inputData = static_cast<IGuiData*>(data);
Platform::AtomicStore(&WaylandImpl::DragOverFlag, 1); Platform::AtomicStore(&WaylandImpl::DragOverFlag, 1);
wl_data_source_destroy(source);
}, },
[](void* data, wl_data_source* source, uint32_t dnd_action) { }, // Action event [](void* data, wl_data_source* source, uint32_t dnd_action) { }, // Action event
}; };
@@ -236,12 +237,12 @@ namespace WaylandImpl
SDLWindow* Window = nullptr; SDLWindow* Window = nullptr;
SDLWindow* DragSourceWindow = nullptr; SDLWindow* DragSourceWindow = nullptr;
Float2 DragOffset = Float2::Zero; Float2 DragOffset = Float2::Zero;
uint32 DragSerial = 0;
// [ThreadPoolTask] // [ThreadPoolTask]
bool Run() override bool Run() override
{ {
bool dragWindow = DraggingWindow; bool dragWindow = DraggingWindow;
uint32 grabSerial = GrabSerial;
if (EventQueue == nullptr) if (EventQueue == nullptr)
{ {
@@ -294,27 +295,25 @@ namespace WaylandImpl
auto dragStartWindow = DragSourceWindow != nullptr ? DragSourceWindow->GetSDLWindow() : draggedWindow; auto dragStartWindow = DragSourceWindow != nullptr ? DragSourceWindow->GetSDLWindow() : draggedWindow;
wl_surface* originSurface = static_cast<wl_surface*>(SDL_GetPointerProperty(SDL_GetWindowProperties(dragStartWindow), SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr)); wl_surface* originSurface = static_cast<wl_surface*>(SDL_GetPointerProperty(SDL_GetWindowProperties(dragStartWindow), SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr));
wl_surface* iconSurface = nullptr; wl_surface* iconSurface = nullptr;
wl_data_device_start_drag(WrappedDataDevice, dataSource, originSurface, iconSurface, grabSerial); wl_data_device_start_drag(WrappedDataDevice, dataSource, originSurface, iconSurface, DragSerial);
Platform::AtomicStore(&StartFlag, 1); Platform::AtomicStore(&StartFlag, 1);
xdg_toplevel_drag_v1* toplevelDrag = nullptr; xdg_toplevel_drag_v1* toplevelDrag = nullptr;
xdg_toplevel* wrappedToplevel = nullptr; xdg_toplevel* wrappedToplevel = nullptr;
while (Platform::AtomicRead(&ExitFlag) == 0) // Start dispatching events to keep data offers alive
while (Platform::AtomicRead(&ExitFlag) == 0 && Platform::AtomicRead(&Serial) == DragSerial && Platform::AtomicRead(&DragOverFlag) == 0)
{ {
// Start dispatching events to keep data offers alive
if (wl_display_dispatch_queue(WaylandDisplay, EventQueue) == -1)
LOG(Warning, "wl_display_dispatch_queue failed, errno: {}", errno);
if (wl_display_roundtrip_queue(WaylandDisplay, EventQueue) == -1)
LOG(Warning, "wl_display_roundtrip_queue failed, errno: {}", errno);
// Wait until window has showed up
if (DragManager != nullptr && wrappedToplevel == nullptr && dragWindow && Platform::AtomicRead(&WaitFlag) != 0) if (DragManager != nullptr && wrappedToplevel == nullptr && dragWindow && Platform::AtomicRead(&WaitFlag) != 0)
{ {
// Wait until the dragged window has showed up
auto toplevel = static_cast<xdg_toplevel*>(SDL_GetPointerProperty(SDL_GetWindowProperties(draggedWindow), SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, nullptr)); auto toplevel = static_cast<xdg_toplevel*>(SDL_GetPointerProperty(SDL_GetWindowProperties(draggedWindow), SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, nullptr));
if (toplevel != nullptr) if (toplevel != nullptr)
{ {
if (Platform::AtomicRead(&DragOverFlag) == 1 || Platform::AtomicRead(&Serial) != DragSerial)
break;
// Attach the window to the ongoing drag operation // Attach the window to the ongoing drag operation
wrappedToplevel = static_cast<xdg_toplevel*>(wl_proxy_create_wrapper(toplevel)); wrappedToplevel = static_cast<xdg_toplevel*>(wl_proxy_create_wrapper(toplevel));
wl_proxy_set_queue(reinterpret_cast<wl_proxy*>(wrappedToplevel), EventQueue); wl_proxy_set_queue(reinterpret_cast<wl_proxy*>(wrappedToplevel), EventQueue);
@@ -324,17 +323,27 @@ namespace WaylandImpl
xdg_toplevel_drag_v1_attach(toplevelDrag, wrappedToplevel, static_cast<int32>(scaledOffset.X), static_cast<int32>(scaledOffset.Y)); xdg_toplevel_drag_v1_attach(toplevelDrag, wrappedToplevel, static_cast<int32>(scaledOffset.X), static_cast<int32>(scaledOffset.Y));
} }
} }
}
if (wl_display_roundtrip_queue(WaylandDisplay, EventQueue) == -1) if (wl_display_roundtrip_queue(WaylandDisplay, EventQueue) == -1)
LOG(Warning, "wl_display_roundtrip_queue failed, errno: {}", errno); LOG(Warning, "wl_display_roundtrip_queue failed, errno: {}", errno);
}
if (toplevelDrag != nullptr) if (toplevelDrag != nullptr)
{ {
// Wait for pending operations to finish
while (Platform::AtomicRead(&DragOverFlag) == 0 && Platform::AtomicRead(&Serial) == DragSerial)
{
if (wl_display_dispatch_queue_pending(WaylandDisplay, EventQueue) == -1)
LOG(Warning, "wl_display_dispatch_queue_pending failed, errno: {}", errno);
Platform::Sleep(1);
}
wl_proxy_wrapper_destroy(wrappedToplevel); wl_proxy_wrapper_destroy(wrappedToplevel);
xdg_toplevel_drag_v1_destroy(toplevelDrag); xdg_toplevel_drag_v1_destroy(toplevelDrag);
toplevelDrag = nullptr; toplevelDrag = nullptr;
} }
Platform::AtomicStore(&DragOverFlag, 1);
if (wrappedDataSource != nullptr) if (wrappedDataSource != nullptr)
wl_proxy_wrapper_destroy(wrappedDataSource); wl_proxy_wrapper_destroy(wrappedDataSource);
@@ -555,11 +564,28 @@ DragDropEffect Window::DoDragDrop(const StringView& data)
DragDropEffect Window::DoDragDropWayland(const StringView& data, Window* dragSourceWindow, Float2 dragOffset) DragDropEffect Window::DoDragDropWayland(const StringView& data, Window* dragSourceWindow, Float2 dragOffset)
{ {
// For drag-and-drop, we need to run another event queue in a separate thread to avoid racing issues // HACK: For drag-and-drop, we need to run another event queue in a separate thread to avoid racing issues
// while SDL is dispatching the main Wayland event queue when receiving the data offer from us. // while SDL is dispatching the main Wayland event queue when receiving the data offer from us.
Engine::OnDraw(); Engine::OnDraw();
if (WaylandImpl::DraggingActive)
LOG(Fatal, "Previous drag and drop operation was not finished");
// Read the latest serial code from mouse event, and check if we are still holding the mouse before committing
auto dragSerial = Platform::AtomicRead(&WaylandImpl::Serial);
SDLPlatform::Tick(); // Handle events to check for serial changes
if (!Input::Mouse->GetButton(MouseButton::Left) ||
dragSerial == 0 ||
dragSerial != Platform::AtomicRead(&WaylandImpl::Serial))
{
// The mouse up event was ignored earlier, release the button now
Input::Mouse->OnMouseUp(Platform::GetMousePosition(), MouseButton::Left, this);
return DragDropEffect::None;
}
WaylandImpl::DraggingActive = true; WaylandImpl::DraggingActive = true;
WaylandImpl::DraggingData = StringView(data.Get(), data.Length()); WaylandImpl::DraggingData = StringView(data.Get(), data.Length());
WaylandImpl::DragOverFlag = 0; WaylandImpl::DragOverFlag = 0;
@@ -568,22 +594,31 @@ DragDropEffect Window::DoDragDropWayland(const StringView& data, Window* dragSou
task->Window = this; task->Window = this;
task->DragSourceWindow = dragSourceWindow; // Needs to be the parent window when dragging a tab to window task->DragSourceWindow = dragSourceWindow; // Needs to be the parent window when dragging a tab to window
task->DragOffset = dragOffset; task->DragOffset = dragOffset;
task->DragSerial = dragSerial;
Task::StartNew(task); Task::StartNew(task);
while (task->GetState() == TaskState::Queued)
Platform::Sleep(1);
while (Platform::AtomicRead(&task->StartFlag) == 0) while (Platform::AtomicRead(&task->StartFlag) == 0)
{
SDLPlatform::Tick();
Platform::Sleep(1); Platform::Sleep(1);
}
while (Platform::AtomicRead(&WaylandImpl::DragOverFlag) == 0) while (Platform::AtomicRead(&WaylandImpl::DragOverFlag) == 0)
{ {
SDLPlatform::Tick(); SDLPlatform::Tick();
Engine::OnUpdate();//Scripting::Update(); // For docking updates Engine::OnUpdate(); // For docking updates
Engine::OnDraw(); Engine::OnDraw();
// The window needs to be finished showing up before we can start dragging it // The window needs to be finished showing up before we can start dragging it
if (IsVisible() && Platform::AtomicRead(&task->WaitFlag) == 0) if (IsVisible() && Platform::AtomicRead(&task->WaitFlag) == 0)
Platform::AtomicStore(&task->WaitFlag, 1); Platform::AtomicStore(&task->WaitFlag, 1);
if (!WaylandImpl::DraggingWindow && !Input::Mouse->GetButton(MouseButton::Left))
{
// Abort in case the dragging was interrupted before receiving any data offers
Platform::AtomicStore(&task->ExitFlag, 1);
break;
}
Platform::Sleep(1); Platform::Sleep(1);
} }
@@ -592,7 +627,11 @@ DragDropEffect Window::DoDragDropWayland(const StringView& data, Window* dragSou
Input::Mouse->OnMouseUp(Platform::GetMousePosition(), MouseButton::Left, this); Input::Mouse->OnMouseUp(Platform::GetMousePosition(), MouseButton::Left, this);
Platform::AtomicStore(&task->ExitFlag, 1); Platform::AtomicStore(&task->ExitFlag, 1);
task->Wait(); while (task->GetState() != TaskState::Finished)
{
SDLPlatform::Tick();
Platform::Sleep(1);
}
WaylandImpl::DraggingActive = false; WaylandImpl::DraggingActive = false;
WaylandImpl::DraggingData = nullptr; WaylandImpl::DraggingData = nullptr;
@@ -1014,7 +1053,8 @@ bool SDLWindow::HandleEventInternal(SDL_Event& event)
if (event.type == SDL_EVENT_DROP_BEGIN) if (event.type == SDL_EVENT_DROP_BEGIN)
{ {
// We don't know the type of dragged data at this point, so call the events for both types // We don't know the type of dragged data at this point, so call the events for both types
OnDragEnter(&filesData, mousePos, effect); if (!WaylandImpl::DraggingActive)
OnDragEnter(&filesData, mousePos, effect);
if (effect == DragDropEffect::None) if (effect == DragDropEffect::None)
OnDragEnter(&textData, mousePos, effect); OnDragEnter(&textData, mousePos, effect);
} }
@@ -1023,7 +1063,8 @@ bool SDLWindow::HandleEventInternal(SDL_Event& event)
Input::Mouse->OnMouseMove(ClientToScreen(mousePos), this); Input::Mouse->OnMouseMove(ClientToScreen(mousePos), this);
// We don't know the type of dragged data at this point, so call the events for both types // We don't know the type of dragged data at this point, so call the events for both types
OnDragOver(&filesData, mousePos, effect); if (!WaylandImpl::DraggingActive)
OnDragOver(&filesData, mousePos, effect);
if (effect == DragDropEffect::None) if (effect == DragDropEffect::None)
OnDragOver(&textData, mousePos, effect); OnDragOver(&textData, mousePos, effect);
} }