@ -1,8 +1,10 @@
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Runtime.InteropServices ;
using System.Text ;
using System.Threading.Tasks ;
using Avalonia.Input ;
using Avalonia.Input.Platform ;
using static Avalonia . X11 . XLib ;
namespace Avalonia.X11
@ -10,12 +12,14 @@ namespace Avalonia.X11
class X11Clipboard : IClipboard
{
private readonly X11Info _ x11 ;
private string _ storedString ;
private IDataObject _ storedDataObject ;
private IntPtr _ handle ;
private TaskCompletionSource < IntPtr [ ] > _ requestedFormatsTcs ;
private TaskCompletionSource < string > _ requestedText Tcs;
private TaskCompletionSource < object > _ requestedData Tcs;
private readonly IntPtr [ ] _ textAtoms ;
private readonly IntPtr _ avaloniaSaveTargetsAtom ;
private readonly Dictionary < string , IntPtr > _f ormatAtoms = new Dictionary < string , IntPtr > ( ) ;
private readonly Dictionary < IntPtr , string > _ atomFormats = new Dictionary < IntPtr , string > ( ) ;
public X11Clipboard ( AvaloniaX11Platform platform )
{
@ -31,6 +35,11 @@ namespace Avalonia.X11
} . Where ( a = > a ! = IntPtr . Zero ) . ToArray ( ) ;
}
bool IsStringAtom ( IntPtr atom )
{
return _ textAtoms . Contains ( atom ) ;
}
Encoding GetStringEncoding ( IntPtr atom )
{
return ( atom = = _ x11 . Atoms . XA_STRING
@ -75,21 +84,31 @@ namespace Avalonia.X11
Encoding textEnc ;
if ( target = = _ x11 . Atoms . TARGETS )
{
var atoms = _ textAtoms ;
atoms = atoms . Concat ( new [ ] { _ x11 . Atoms . TARGETS , _ x11 . Atoms . MULTIPLE } )
. ToArray ( ) ;
var atoms = new HashSet < IntPtr > { _ x11 . Atoms . TARGETS , _ x11 . Atoms . MULTIPLE } ;
foreach ( var fmt in _ storedDataObject . GetDataFormats ( ) )
{
if ( fmt = = DataFormats . Text )
foreach ( var ta in _ textAtoms )
atoms . Add ( ta ) ;
else
atoms . Add ( _ x11 . Atoms . GetAtom ( fmt ) ) ;
}
XChangeProperty ( _ x11 . Display , window , property ,
_ x11 . Atoms . XA_ATOM , 3 2 , PropertyMode . Replace , atoms , atoms . Length ) ;
_ x11 . Atoms . XA_ATOM , 3 2 , PropertyMode . Replace , atoms . ToArray ( ) , atoms . Count ) ;
return property ;
}
else if ( target = = _ x11 . Atoms . SAVE_TARGETS & & _ x11 . Atoms . SAVE_TARGETS ! = IntPtr . Zero )
{
return property ;
}
else if ( ( textEnc = GetStringEncoding ( target ) ) ! = null )
else if ( ( textEnc = GetStringEncoding ( target ) ) ! = null
& & _ storedDataObject ? . Contains ( DataFormats . Text ) = = true )
{
var data = textEnc . GetBytes ( _ storedString ? ? "" ) ;
var text = _ storedDataObject . GetText ( ) ;
if ( text = = null )
return IntPtr . Zero ;
var data = textEnc . GetBytes ( text ) ;
fixed ( void * pdata = data )
XChangeProperty ( _ x11 . Display , window , property , target , 8 ,
PropertyMode . Replace ,
@ -121,6 +140,23 @@ namespace Avalonia.X11
return property ;
}
else if ( _ storedDataObject ? . Contains ( _ x11 . Atoms . GetAtomName ( target ) ) = = true )
{
var objValue = _ storedDataObject . Get ( _ x11 . Atoms . GetAtomName ( target ) ) ;
if ( ! ( objValue is byte [ ] bytes ) )
{
if ( objValue is string s )
bytes = Encoding . UTF8 . GetBytes ( s ) ;
else
return IntPtr . Zero ;
}
XChangeProperty ( _ x11 . Display , window , property , target , 8 ,
PropertyMode . Replace ,
bytes , bytes . Length ) ;
return property ;
}
else
return IntPtr . Zero ;
}
@ -131,15 +167,15 @@ namespace Avalonia.X11
if ( sel . property = = IntPtr . Zero )
{
_ requestedFormatsTcs ? . TrySetResult ( null ) ;
_ requestedText Tcs ? . TrySetResult ( null ) ;
_ requestedData Tcs ? . TrySetResult ( null ) ;
}
XGetWindowProperty ( _ x11 . Display , _ handle , sel . property , IntPtr . Zero , new IntPtr ( 0x7fffffff ) , true , ( IntPtr ) Atom . AnyPropertyType ,
out var actualAtom , out var actualFormat , out var nitems , out var bytes_after , out var prop ) ;
out var actualType Atom , out var actualFormat , out var nitems , out var bytes_after , out var prop ) ;
Encoding textEnc = null ;
if ( nitems = = IntPtr . Zero )
{
_ requestedFormatsTcs ? . TrySetResult ( null ) ;
_ requestedText Tcs ? . TrySetResult ( null ) ;
_ requestedData Tcs ? . TrySetResult ( null ) ;
}
else
{
@ -154,10 +190,24 @@ namespace Avalonia.X11
_ requestedFormatsTcs ? . TrySetResult ( formats ) ;
}
}
else if ( ( textEnc = GetStringEncoding ( sel . property ) ) ! = null )
else if ( ( textEnc = GetStringEncoding ( actualTypeAtom ) ) ! = null )
{
var text = textEnc . GetString ( ( byte * ) prop . ToPointer ( ) , nitems . ToInt32 ( ) ) ;
_ requestedTextTcs ? . TrySetResult ( text ) ;
_ requestedDataTcs ? . TrySetResult ( text ) ;
}
else
{
if ( actualTypeAtom = = _ x11 . Atoms . INCR )
{
// TODO: Actually implement that monstrosity
_ requestedDataTcs . TrySetResult ( null ) ;
}
else
{
var data = new byte [ ( int ) nitems * ( actualFormat / 8 ) ] ;
Marshal . Copy ( prop , data , 0 , data . Length ) ;
_ requestedDataTcs ? . TrySetResult ( data ) ;
}
}
}
@ -174,17 +224,19 @@ namespace Avalonia.X11
return _ requestedFormatsTcs . Task ;
}
Task < string > SendText Request( IntPtr format )
Task < object > SendData Request( IntPtr format )
{
if ( _ requestedText Tcs = = null | | _ requestedFormatsTcs . Task . IsCompleted )
_ requestedText Tcs = new TaskCompletionSource < string > ( ) ;
if ( _ requestedData Tcs = = null | | _ requestedFormatsTcs . Task . IsCompleted )
_ requestedData Tcs = new TaskCompletionSource < object > ( ) ;
XConvertSelection ( _ x11 . Display , _ x11 . Atoms . CLIPBOARD , format , format , _ handle , IntPtr . Zero ) ;
return _ requestedText Tcs . Task ;
return _ requestedData Tcs . Task ;
}
bool HasOwner = > XGetSelectionOwner ( _ x11 . Display , _ x11 . Atoms . CLIPBOARD ) ! = IntPtr . Zero ;
public async Task < string > GetTextAsync ( )
{
if ( XGetSelectionOwner ( _ x11 . Display , _ x11 . Atoms . CLIPBOARD ) = = IntPtr . Zero )
if ( ! HasOwner )
return null ;
var res = await SendFormatRequest ( ) ;
var target = _ x11 . Atoms . UTF8_STRING ;
@ -199,7 +251,7 @@ namespace Avalonia.X11
}
}
return await SendText Request( target ) ;
return ( string ) await SendData Request( target ) ;
}
void StoreAtomsInClipboardManager ( IntPtr [ ] atoms )
@ -220,15 +272,52 @@ namespace Avalonia.X11
public Task SetTextAsync ( string text )
{
_ storedString = text ;
var data = new DataObject ( ) ;
data . Set ( DataFormats . Text , text ) ;
return SetDataObjectAsync ( data ) ;
}
public Task ClearAsync ( )
{
return SetTextAsync ( null ) ;
}
public Task SetDataObjectAsync ( IDataObject data )
{
_ storedDataObject = data ;
XSetSelectionOwner ( _ x11 . Display , _ x11 . Atoms . CLIPBOARD , _ handle , IntPtr . Zero ) ;
StoreAtomsInClipboardManager ( _ textAtoms ) ;
return Task . CompletedTask ;
}
public Task ClearAsync ( )
public async Task < string [ ] > GetFormats Async( )
{
return SetTextAsync ( null ) ;
if ( ! HasOwner )
return null ;
var res = await SendFormatRequest ( ) ;
if ( res = = null )
return null ;
var rv = new List < string > ( ) ;
if ( _ textAtoms . Any ( res . Contains ) )
rv . Add ( DataFormats . Text ) ;
foreach ( var t in res )
rv . Add ( _ x11 . Atoms . GetAtomName ( t ) ) ;
return rv . ToArray ( ) ;
}
public async Task < object > GetDataAsync ( string format )
{
if ( ! HasOwner )
return null ;
if ( format = = DataFormats . Text )
return await GetTextAsync ( ) ;
var formatAtom = _ x11 . Atoms . GetAtom ( format ) ;
var res = await SendFormatRequest ( ) ;
if ( ! res . Contains ( formatAtom ) )
return null ;
return await SendDataRequest ( formatAtom ) ;
}
}
}