Browse Source

Xdnd: wait for XdndStatus before sending nez XdndPosition

pull/20926/head
Julien Lebosquain 6 months ago
parent
commit
39287d581d
No known key found for this signature in database GPG Key ID: 1833CAD10ACC46FD
  1. 8
      src/Avalonia.X11/Selections/DragDrop/DragDropTimeoutManager.cs
  2. 158
      src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs

8
src/Avalonia.X11/Selections/DragDrop/DragDropTimeoutManager.cs

@ -19,11 +19,17 @@ internal sealed class DragDropTimeoutManager : IDisposable
Timeout.InfiniteTimeSpan);
}
public void Stop()
=> ChangeTimer(Timeout.InfiniteTimeSpan);
public void Restart()
=> ChangeTimer(_timeout);
private void ChangeTimer(TimeSpan dueTime)
{
try
{
_timer.Change(_timeout, Timeout.InfiniteTimeSpan);
_timer.Change(dueTime, Timeout.InfiniteTimeSpan);
}
catch (ObjectDisposedException)
{

158
src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs

@ -55,9 +55,8 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
private readonly IntPtr[] _formatAtoms;
private X11WindowInfo? _originalSourceWindowInfo;
private bool _pointerGrabbed;
private XdndTargetInfo? _lastTarget;
private DragDropEffects _currentEffects;
private bool _targetCanAcceptDrop;
private TargetState _targetState;
private DragDropTimeoutManager? _timeoutManager;
public Handler(
@ -109,7 +108,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
_pointerGrabbed = true;
// Replace the window event handler with our own during the drag operation
// Replace the window event handler with our own during the drag operation.
_originalSourceWindowInfo = sourceWindowInfo;
_platform.Windows[_sourceWindow] = new X11WindowInfo(OnEvent, sourceWindowInfo.Window);
@ -117,6 +116,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
_formatAtoms = DataFormatHelper.ToAtoms(dataTransfer.Formats, atoms);
_dataProvider.SetOwner(_sourceWindow);
// Publish our formats.
XChangeProperty(
_platform.Display,
_sourceWindow,
@ -131,6 +131,10 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
public Task<DragDropEffects> Completion
=> _completionSource.Task;
private DragDropTimeoutManager TimeoutManager
=> _timeoutManager ??=
new DragDropTimeoutManager(SelectionHelper.Timeout, () => Complete(DragDropEffects.None));
private void OnEvent(ref XEvent evt)
{
if (evt.type == XEventName.MotionNotify && _pointerGrabbed)
@ -161,14 +165,19 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
private void OnMotionNotify(in XMotionEvent motion)
{
// Drop pending, ignore any queued position change.
if (_targetState.PendingDrop is not null)
return;
var rootPosition = new PixelPoint(motion.x_root, motion.y_root);
var target = FindXdndTarget(rootPosition);
var modifiers = motion.state.ToRawInputModifiers();
var effectiveAllowedEffects = GetEffectiveAllowedEffects(modifiers.ToKeyModifiers());
if (_lastTarget != target)
// Handle new target.
if (_targetState.Target != target)
{
if (_lastTarget is { } lastTarget)
if (_targetState.Target is { } lastTarget)
{
if (lastTarget.InProcessWindow is { } window)
ProcessRawDragEvent(window, RawDragEventType.DragLeave, rootPosition, modifiers);
@ -176,8 +185,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
SendXdndLeave(lastTarget);
}
_lastTarget = target;
_targetCanAcceptDrop = false;
_targetState = new TargetState(target);
UpdateCurrentEffects(effectiveAllowedEffects);
if (target is { } newTarget)
@ -185,7 +193,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
if (newTarget.InProcessWindow is { } window)
{
var effects = ProcessRawDragEvent(window, RawDragEventType.DragEnter, rootPosition, modifiers);
_targetCanAcceptDrop = effects != DragDropEffects.None;
_targetState.AllowsDrop = effects != DragDropEffects.None;
UpdateCurrentEffects(effects);
}
else
@ -193,18 +201,29 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
}
}
// Update current target.
if (target is { } currentTarget)
{
if (currentTarget.InProcessWindow is { } window)
{
var effects = ProcessRawDragEvent(window, RawDragEventType.DragOver, rootPosition, modifiers);
_targetCanAcceptDrop = effects != DragDropEffects.None;
_targetState.AllowsDrop = effects != DragDropEffects.None;
UpdateCurrentEffects(effects);
}
else
{
var action = XdndActionHelper.EffectsToAction(effectiveAllowedEffects, _platform.Info.Atoms);
SendXdndPosition(currentTarget, rootPosition, motion.time, action);
var positionRequest = new PositionRequest(rootPosition, motion.time, action);
if (_targetState.IsWaitingForStatus)
{
// We already sent a position previously and are waiting for a response. Don't flood.
_targetState.PendingPosition = positionRequest;
}
else
{
SendPositionRequest(positionRequest, currentTarget);
}
}
}
}
@ -213,7 +232,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
{
UngrabPointer();
if (_lastTarget is not { } lastTarget)
if (_targetState.Target is not { } lastTarget || _targetState.PendingDrop is not null)
return;
if (lastTarget.InProcessWindow is not null)
@ -221,7 +240,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
var rootPosition = new PixelPoint(button.x_root, button.y_root);
var modifiers = button.state.ToRawInputModifiers();
if (_targetCanAcceptDrop)
if (_targetState.AllowsDrop)
{
var effects = ProcessRawDragEvent(lastTarget.InProcessWindow, RawDragEventType.Drop, rootPosition, modifiers);
UpdateCurrentEffects(effects);
@ -232,53 +251,68 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
UpdateCurrentEffects(DragDropEffects.None);
}
_lastTarget = null;
_targetCanAcceptDrop = false;
_targetState = default;
Complete(_currentEffects);
}
else
{
if (!_targetCanAcceptDrop)
var dropRequest = new DropRequest(button.time);
if (_targetState.IsWaitingForStatus || _targetState.PendingPosition is not null)
{
SendXdndLeave(lastTarget);
Complete(DragDropEffects.None);
return;
// We're still waiting for a XdndStatus response for the last position, defer the drop.
_targetState.PendingDrop = dropRequest;
}
Debug.Assert(_timeoutManager is null);
_timeoutManager = new DragDropTimeoutManager(SelectionHelper.Timeout, () =>
else if (_targetState.AllowsDrop)
{
DisposeTimeoutManager();
SendDropRequest(dropRequest, lastTarget);
}
else
{
_targetState = default;
UpdateCurrentEffects(DragDropEffects.None);
SendXdndLeave(lastTarget);
Complete(DragDropEffects.None);
});
_dataProvider.Activity = _timeoutManager.Restart;
SendXdndDrop(lastTarget, button.time);
}
}
}
private void OnXdndStatus(in XClientMessageEvent message)
{
if (_lastTarget is not { } lastTarget || message.ptr1 != lastTarget.TargetWindow)
if (_targetState.Target is not { } lastTarget || message.ptr1 != lastTarget.TargetWindow)
return;
TimeoutManager.Stop();
var accepted = (message.ptr2 & 1) == 1;
var action = accepted ? message.ptr5 : 0;
var effects = XdndActionHelper.ActionToEffects(action, _platform.Info.Atoms);
_targetCanAcceptDrop = accepted;
_targetState.IsWaitingForStatus = false;
_targetState.AllowsDrop = action != 0;
UpdateCurrentEffects(effects & _allowedEffects);
// If we have any pending XdndPosition or XdndDrop to send, now is the time.
if (_targetState.PendingPosition is { } position)
{
_targetState.PendingPosition = null;
SendPositionRequest(position, lastTarget);
}
else if (_targetState.PendingDrop is { } drop)
{
SendDropRequest(drop, lastTarget);
}
}
private void OnXdndFinished(in XClientMessageEvent message)
{
if (_lastTarget is not { } lastTarget || message.ptr1 != lastTarget.TargetWindow)
if (_targetState.Target is not { } lastTarget || message.ptr1 != lastTarget.TargetWindow)
return;
_lastTarget = null;
_targetCanAcceptDrop = false;
DisposeTimeoutManager();
_targetState = default;
_dataProvider.Activity = null;
_timeoutManager?.Stop();
if (lastTarget.Version >= 5)
{
@ -385,14 +419,10 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
}
private void SendXdndLeave(XdndTargetInfo target)
{
SendXdndMessage(_platform.Info.Atoms.XdndLeave, target.MessageWindow, 0, 0, 0, 0);
}
=> SendXdndMessage(_platform.Info.Atoms.XdndLeave, target.MessageWindow, 0, 0, 0, 0);
private void SendXdndDrop(XdndTargetInfo target, IntPtr timestamp)
{
SendXdndMessage(_platform.Info.Atoms.XdndDrop, target.MessageWindow, 0, timestamp, 0, 0);
}
=> SendXdndMessage(_platform.Info.Atoms.XdndDrop, target.MessageWindow, 0, timestamp, 0, 0);
private void SendXdndMessage(
IntPtr messageType,
@ -423,6 +453,21 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
XFlush(_platform.Display);
}
private void SendPositionRequest(PositionRequest request, XdndTargetInfo target)
{
_targetState.IsWaitingForStatus = true;
TimeoutManager.Restart();
SendXdndPosition(target, request.Position, request.Timestamp, request.Action);
}
private void SendDropRequest(DropRequest dropRequest, XdndTargetInfo target)
{
TimeoutManager.Restart();
_dataProvider.Activity = TimeoutManager.Restart;
SendXdndDrop(target, dropRequest.Timestamp);
}
private DragDropEffects ProcessRawDragEvent(
X11Window targetWindow,
RawDragEventType eventType,
@ -507,13 +552,6 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
private void Complete(DragDropEffects resultEffects)
=> _completionSource.TrySetResult(resultEffects);
private void DisposeTimeoutManager()
{
_dataProvider.Activity = null;
_timeoutManager?.Dispose();
_timeoutManager = null;
}
public void Dispose()
{
if (_pointerGrabbed)
@ -526,11 +564,10 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
_originalSourceWindowInfo = null;
}
_lastTarget = null;
_targetCanAcceptDrop = false;
_targetState = default;
_currentEffects = DragDropEffects.None;
DisposeTimeoutManager();
_timeoutManager?.Dispose();
if (_dataProvider.GetOwner() == _sourceWindow)
_dataProvider.SetOwner(0);
@ -543,5 +580,26 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
IntPtr TargetWindow,
IntPtr MessageWindow,
X11Window? InProcessWindow);
private readonly struct PositionRequest(PixelPoint position, IntPtr timestamp, IntPtr action)
{
public readonly PixelPoint Position = position;
public readonly IntPtr Timestamp = timestamp;
public readonly IntPtr Action = action;
}
private readonly struct DropRequest(IntPtr timestamp)
{
public readonly IntPtr Timestamp = timestamp;
}
private struct TargetState(XdndTargetInfo? target)
{
public readonly XdndTargetInfo? Target = target;
public bool AllowsDrop;
public bool IsWaitingForStatus; // XdndPosition sent, but no XdndStatus received yet
public PositionRequest? PendingPosition; // Position not sent via XdndPosition yet
public DropRequest? PendingDrop; // Drop not sent via XdndDrop yet
}
}
}

Loading…
Cancel
Save