@ -1,10 +1,12 @@
using System ;
using System.Collections.Generic ;
using System.Threading.Tasks ;
using Avalonia.Controls ;
using Avalonia.Input ;
using Avalonia.Input.Platform ;
using Avalonia.Platform ;
using Avalonia.Threading ;
using static Avalonia . X11 . Selections . DragDrop . XdndConstants ;
using static Avalonia . X11 . XLib ;
namespace Avalonia.X11.Selections.DragDrop ;
@ -29,7 +31,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
var cursorFactory = AvaloniaLocator . Current . GetService < ICursorFactory > ( ) ;
using var handler = new Handler ( platform , window . Handle , cursorFactory ) ;
using var handler = new Handler ( platform , window . Handle , dataTransfer . ToAsynchronous ( ) , allowedEffects , cursorFactory ) ;
await handler . Completion ;
return DragDropEffects . None ;
@ -39,21 +41,30 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
{
private readonly AvaloniaX11Platform _ platform ;
private readonly IntPtr _ sourceWindow ;
private readonly TaskCompletionSource _ completionSource = new ( ) ;
private readonly X11EventDispatcher . EventHandler ? _ originalEventHandler ;
private readonly IAsyncDataTransfer _d ataTransfer ;
private readonly DragDropEffects _ allowedEffects ;
private readonly TaskCompletionSource < DragDropEffects > _ completionSource = new ( ) ;
private X11EventDispatcher . EventHandler ? _ originalEventHandler ;
private bool _ pointerGrabbed ;
private XdndTargetInfo ? _l astTarget ;
public Task Completion
= > _ completionSource . Task ;
public Handler ( AvaloniaX11Platform platform , IntPtr sourceWindow , ICursorFactory ? cursorFactory )
private DragDropDataProvider ? _d ataProvider ;
private IntPtr _l astStatusAction ;
public Handler (
AvaloniaX11Platform platform ,
IntPtr sourceWindow ,
IAsyncDataTransfer dataTransfer ,
DragDropEffects allowedEffects ,
ICursorFactory ? cursorFactory )
{
_ platform = platform ;
_ sourceWindow = sourceWindow ;
_d ataTransfer = dataTransfer ;
_ allowedEffects = allowedEffects ;
if ( ! platform . Windows . TryGetValue ( sourceWindow , out _ originalEventHandler ) )
{
_ completionSource . TrySetResult ( ) ;
_ completionSource . TrySetResult ( DragDropEffects . None ) ;
return ;
}
@ -80,37 +91,167 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
{
Console . WriteLine ( $"Could not grab: {grabResult}" ) ;
_ completionSource . TrySetResult ( ) ;
_ completionSource . TrySetResult ( DragDropEffects . None ) ;
return ;
}
Console . WriteLine ( "Grabbed!" ) ;
_ pointerGrabbed = true ;
_ platform . Windows [ _ sourceWindow ] = OnEvent ;
}
public Task < DragDropEffects > Completion
= > _ completionSource . Task ;
private IntPtr [ ] DataFormatAtoms
{
get
{
return field ? ? = CalcDataFormatAtoms ( ) ;
IntPtr [ ] CalcDataFormatAtoms ( )
{
var atomValues = new List < IntPtr > ( _d ataTransfer . Formats . Count ) ;
foreach ( var format in _d ataTransfer . Formats )
{
foreach ( var atom in DataFormatHelper . ToAtoms ( format , _ platform . Info . Atoms ) )
atomValues . Add ( atom ) ;
}
return atomValues . ToArray ( ) ;
}
}
}
private DragDropDataProvider DataProvider
{
get
{
return _d ataProvider ? ? = CreateDataProvider ( ) ;
DragDropDataProvider CreateDataProvider ( )
{
var dataProvider = new DragDropDataProvider ( _ platform , _d ataTransfer ) ;
var formats = DataFormatAtoms ;
XChangeProperty (
_ platform . Display ,
dataProvider . Window ,
_ platform . Info . Atoms . XdndTypeList ,
_ platform . Info . Atoms . ATOM ,
3 2 ,
PropertyMode . Replace ,
formats ,
formats . Length ) ;
return dataProvider ;
}
}
}
private void OnEvent ( ref XEvent xev )
{
Console . WriteLine ( $"Event on drag window: {xev.type}" ) ;
if ( xev . type = = XEventName . MotionNotify & & _ pointerGrabbed )
OnMotionNotify ( in xev . MotionEvent ) ;
else if ( xev . type = = XEventName . ButtonRelease & & _ pointerGrabbed )
OnButtonRelease ( in xev . ButtonEvent ) ;
else if ( xev . type = = XEventName . ClientMessage )
{
ref var message = ref xev . ClientMessageEvent ;
var atoms = _ platform . Info . Atoms ;
if ( message . message_type = = atoms . XdndStatus )
OnXdndStatus ( in message ) ;
else if ( message . message_type = = atoms . XdndFinished )
OnXdndFinished ( in message ) ;
else
_ originalEventHandler ? . Invoke ( ref xev ) ;
}
else
_ originalEventHandler ? . Invoke ( ref xev ) ;
}
private void OnMotionNotify ( in XMotionEvent motion )
{
var target = FindXdndTarget ( motion . x_root , motion . y_root ) ;
if ( xev . type = = XEventName . MotionNotify )
if ( _l astTarget ! = target )
{
var m = xev . MotionEvent ;
var target = FindXdndTarget ( m . x_root , m . y_root ) ;
if ( _l astTarget is { } lastTarget )
SendXdndLeave ( lastTarget ) ;
_l astTarget = target ;
Console . WriteLine ( $"Motion on 0x{m.subwindow:x} at {m.x},{m.y} (root={m.x_root},{m.y_root}); XDND: win=0x{target?.MessageWindow:x}; version={target?.Version}" ) ;
if ( target is { } newTarget )
SendXdndEnter ( newTarget ) ;
}
else if ( xev . type = = XEventName . ButtonRelease )
if ( target is { } currentTarget )
{
Console . WriteLine ( "Completing" ) ;
_ completionSource . TrySetResult ( ) ;
var action = XdndActionHelper . EffectsToAction ( _ allowedEffects , _ platform . Info . Atoms ) ;
SendXdndPosition ( currentTarget , motion . x_root , motion . y_root , motion . time , action ) ;
}
Console . WriteLine ( $"Motion on 0x{motion.subwindow:x} at {motion.x},{motion.y} (root={motion.x_root},{motion.y_root}); XDND: win=0x{target?.MessageWindow:x}; version={target?.Version}" ) ;
}
private void OnButtonRelease ( in XButtonEvent button )
{
UngrabPointer ( ) ;
if ( _l astTarget is { } lastTarget )
SendXdndDrop ( lastTarget , button . time ) ;
}
private void OnXdndStatus ( in XClientMessageEvent message )
{
Console . WriteLine ( "Received XdndStatus" ) ;
if ( _l astTarget is null )
return ;
var targetWindow = message . ptr1 ;
if ( targetWindow = = 0 | | targetWindow ! = _d ataProvider ? . Window )
return ;
var accepted = ( message . ptr2 & 1 ) = = 1 ;
_l astStatusAction = accepted ? message . ptr5 : 0 ;
Console . WriteLine ( $"Status updated: {XdndActionHelper.ActionToEffects(_lastStatusAction, _platform.Info.Atoms)}" ) ;
}
private void OnXdndFinished ( in XClientMessageEvent message )
{
Console . WriteLine ( "Received XdndFinished" ) ;
if ( _l astTarget is not { } lastTarget )
return ;
var targetWindow = message . ptr1 ;
if ( targetWindow = = 0 | | targetWindow ! = _d ataProvider ? . Window )
return ;
_l astTarget = null ;
IntPtr action ;
if ( lastTarget . Version > = 5 )
{
var accepted = ( message . ptr2 & 1 ) = = 1 ;
action = accepted ? message . ptr3 : 0 ;
}
else
_ originalEventHandler ! ( ref xev ) ;
action = _l astStatusAction ;
var effects = XdndActionHelper . ActionToEffects ( action , _ platform . Info . Atoms ) ;
_ completionSource . TrySetResult ( effects ) ;
}
private XdndTargetInfo ? FindXdndTarget ( int rootX , int rootY )
private XdndTargetInfo ? FindXdndTarget ( int x , int y )
{
var display = _ platform . Display ;
var rootWindow = _ platform . Info . RootWindow ;
@ -121,7 +262,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
if ( TryGetXdndTargetInfo ( currentWindow ) is { } info )
return info ;
if ( ! XTranslateCoordinates ( display , rootWindow , currentWindow , rootX , rootY , out _ , out _ , out var childWindow ) )
if ( ! XTranslateCoordinates ( display , rootWindow , currentWindow , x , y , out _ , out _ , out var childWindow ) )
return null ;
currentWindow = childWindow ;
@ -162,13 +303,80 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
var atoms = _ platform . Info . Atoms ;
var version = XGetWindowPropertyAsIntPtr ( _ platform . Display , window , atoms . XdndAware , atoms . ATOM ) ? ? 0 ;
if ( version is < XdndConstants . MinXdndVersion or > byte . MaxValue )
if ( version is < MinXdndVersion or > byte . MaxValue )
version = 0 ;
return ( byte ) version ;
}
private void SendXdndEnter ( XdndTargetInfo target )
{
Console . WriteLine ( "Sending XdndEnter" ) ;
var version = Math . Min ( target . Version , XdndVersion ) ;
var formats = DataFormatAtoms ;
var hasMoreFormats = formats . Length > 3 ;
SendXdndMessage (
_ platform . Info . Atoms . XdndEnter ,
target ,
DataProvider . Window ,
( version < < 2 4 ) | ( hasMoreFormats ? 1 : 0 ) ,
formats . Length > = 1 ? formats [ 0 ] : 0 ,
formats . Length > = 2 ? formats [ 1 ] : 0 ,
formats . Length > = 3 ? formats [ 2 ] : 0 ) ;
}
private void SendXdndPosition ( XdndTargetInfo target , int x , int y , IntPtr timestamp , IntPtr action )
{
Console . WriteLine ( "Sending XdndPosition" ) ;
SendXdndMessage (
_ platform . Info . Atoms . XdndPosition ,
target ,
DataProvider . Window ,
0 ,
( x < < 1 6 ) | y ,
timestamp ,
action ) ;
}
private void SendXdndLeave ( XdndTargetInfo target )
{
Console . WriteLine ( "Sending XdndLeave" ) ;
SendXdndMessage (
_ platform . Info . Atoms . XdndLeave ,
target ,
DataProvider . Window ,
0 ,
0 ,
0 ,
0 ) ;
}
private void SendXdndDrop ( XdndTargetInfo target , IntPtr timestamp )
{
Console . WriteLine ( "Sending XdndDrop" ) ;
SendXdndMessage (
_ platform . Info . Atoms . XdndDrop ,
target ,
DataProvider . Window ,
0 ,
timestamp ,
0 ,
0 ) ;
}
private void SendXdndMessage (
IntPtr messageType ,
XdndTargetInfo target ,
IntPtr sourceWindow ,
IntPtr ptr2 ,
IntPtr ptr3 ,
IntPtr ptr4 ,
IntPtr ptr5 )
{
var evt = new XEvent
{
@ -177,13 +385,13 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
type = XEventName . ClientMessage ,
display = _ platform . Display ,
window = target . MessageWindow ,
message_type = _ platform . Info . Atoms . XdndEnter ,
message_type = messageType ,
format = 3 2 ,
ptr1 = target . Target Window,
ptr2 = 0 ,
ptr3 = 0 ,
ptr4 = 0 ,
ptr5 = 0
ptr1 = source Window,
ptr2 = ptr2 ,
ptr3 = ptr3 ,
ptr4 = ptr4 ,
ptr5 = ptr5
}
} ;
@ -191,19 +399,35 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
XFlush ( _ platform . Display ) ;
}
public void Dispose ( )
private void UngrabPointer ( )
{
_ pointerGrabbed = false ;
XUngrabPointer ( _ platform . Display , 0 ) ;
XFlush ( _ platform . Display ) ;
}
public void Dispose ( )
{
if ( _ pointerGrabbed )
UngrabPointer ( ) ;
if ( _ originalEventHandler is not null & & _ platform . Windows . ContainsKey ( _ sourceWindow ) )
{
_ platform . Windows [ _ sourceWindow ] = _ originalEventHandler ;
}
_ originalEventHandler = null ;
}
private readonly struct XdndTargetInfo ( byte version , IntPtr targetWindow , IntPtr messageWindow )
{
public readonly byte Version = version ;
public readonly IntPtr TargetWindow = targetWindow ;
public readonly IntPtr MessageWindow = messageWindow ;
_l astTarget = null ;
_l astStatusAction = 0 ;
_d ataProvider ? . Dispose ( ) ;
_d ataProvider = null ;
}
private readonly record struct XdndTargetInfo (
byte Version ,
IntPtr TargetWindow ,
IntPtr MessageWindow ) ;
}
}