@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks ;
using Avalonia.Input ;
using Avalonia.Input.Platform ;
using Avalonia.Logging ;
using Avalonia.Native.Interop ;
using Avalonia.Platform.Storage ;
using Avalonia.Platform.Storage.FileIO ;
@ -13,6 +14,7 @@ namespace Avalonia.Native
class ClipboardImpl : IClipboard , IDisposable
{
private IAvnClipboard _ native ;
// TODO hide native types behind IAvnClipboard abstraction, so managed side won't depend on macOS.
private const string NSPasteboardTypeString = "public.utf8-plain-text" ;
private const string NSFilenamesPboardType = "NSFilenamesPboardType" ;
@ -46,7 +48,7 @@ namespace Avalonia.Native
public IEnumerable < string > GetFormats ( )
{
var rv = new Lis t< string > ( ) ;
var rv = new HashSe t< string > ( ) ;
using ( var formats = _ native . ObtainFormats ( ) )
{
var cnt = formats . Count ;
@ -58,11 +60,11 @@ namespace Avalonia.Native
rv . Add ( DataFormats . Text ) ;
if ( fmt . String = = NSFilenamesPboardType )
{
#pragma warning disable CS0618 // Type or member is obsolete
rv . Add ( DataFormats . FileNames ) ;
#pragma warning restore CS0618 // Type or member is obsolete
rv . Add ( DataFormats . Files ) ;
rv . Add ( DataFormats . FileNames ) ;
rv . Add ( DataFormats . Files ) ;
}
else
rv . Add ( fmt . String ) ;
}
}
}
@ -91,32 +93,71 @@ namespace Avalonia.Native
public unsafe Task SetDataObjectAsync ( IDataObject data )
{
_ native . Clear ( ) ;
foreach ( var fmt in data . GetDataFormats ( ) )
// If there is multiple values with the same "to" format, prefer these that were not mapped.
var formats = data . GetDataFormats ( ) . Select ( f = >
{
string from , to ;
bool mapped ;
if ( f = = DataFormats . Text )
( from , to , mapped ) = ( f , NSPasteboardTypeString , true ) ;
else if ( f = = DataFormats . Files | | f = = DataFormats . FileNames )
( from , to , mapped ) = ( f , NSFilenamesPboardType , true ) ;
else ( from , to , mapped ) = ( f , f , false ) ;
return ( from , to , mapped ) ;
} )
. GroupBy ( p = > p . to )
. Select ( g = > g . OrderBy ( f = > f . mapped ) . First ( ) ) ;
foreach ( var ( fromFormat , toFormat , _ ) in formats )
{
var o = data . Get ( fmt ) ;
if ( o is string s )
_ native . SetText ( fmt , s ) ;
else if ( o is byte [ ] bytes )
fixed ( byte * pbytes = bytes )
_ native . SetBytes ( fmt , pbytes , bytes . Length ) ;
var o = data . Get ( fromFormat ) ;
switch ( o )
{
case string s :
_ native . SetText ( toFormat , s ) ;
break ;
case IEnumerable < IStorageItem > storageItems :
using ( var strings = new AvnStringArray ( storageItems
. Select ( s = > s . TryGetLocalPath ( ) )
. Where ( p = > p is not null ) ) )
{
_ native . SetStrings ( toFormat , strings ) ;
}
break ;
case IEnumerable < string > managedStrings :
using ( var strings = new AvnStringArray ( managedStrings ) )
{
_ native . SetStrings ( toFormat , strings ) ;
}
break ;
case byte [ ] bytes :
{
fixed ( byte * pbytes = bytes )
_ native . SetBytes ( toFormat , pbytes , bytes . Length ) ;
break ;
}
default :
Logger . TryGet ( LogEventLevel . Warning , LogArea . macOSPlatform ) ? . Log ( this ,
"Unsupported IDataObject value type: {0}" , o ? . GetType ( ) . FullName ? ? "(null)" ) ;
break ;
}
}
return Task . CompletedTask ;
}
public Task < string [ ] > GetFormatsAsync ( )
{
using ( var n = _ native . ObtainFormats ( ) )
return Task . FromResult ( n . ToStringArray ( ) ) ;
return Task . FromResult ( GetFormats ( ) . ToArray ( ) ) ;
}
public async Task < object > GetDataAsync ( string format )
{
if ( format = = DataFormats . Text )
if ( format = = DataFormats . Text | | format = = NSPasteboardTypeString )
return await GetTextAsync ( ) ;
#pragma warning disable CS0618 // Type or member is obsolete
if ( format = = DataFormats . FileNames )
if ( format = = DataFormats . FileNames | | format = = NSFilenamesPboardType )
return GetFileNames ( ) ;
#pragma warning restore CS0618 // Type or member is obsolete
if ( format = = DataFormats . Files )
return GetFiles ( ) ;
using ( var n = _ native . GetBytes ( format ) )
@ -146,17 +187,8 @@ namespace Avalonia.Native
public bool Contains ( string dataFormat ) = > Formats . Contains ( dataFormat ) ;
public object Get ( string dataFormat )
{
if ( dataFormat = = DataFormats . Text )
return _ clipboard . GetTextAsync ( ) . Result ;
if ( dataFormat = = DataFormats . Files )
return _ clipboard . GetFiles ( ) ;
#pragma warning disable CS0618
if ( dataFormat = = DataFormats . FileNames )
#pragma warning restore CS0618
return _ clipboard . GetFileNames ( ) ;
return null ;
}
public object Get ( string dataFormat ) = > _ clipboard . GetDataAsync ( dataFormat ) . GetAwaiter ( ) . GetResult ( ) ;
public Task SetFromDataObjectAsync ( IDataObject dataObject ) = > _ clipboard . SetDataObjectAsync ( dataObject ) ;
}
}