@ -17,12 +17,13 @@ namespace Avalonia.FreeDesktop
{
{
internal static async Task < IStorageProvider ? > TryCreateAsync ( IPlatformHandle handle )
internal static async Task < IStorageProvider ? > TryCreateAsync ( IPlatformHandle handle )
{
{
if ( DBusHelper . DefaultConnection is not { } conn )
if ( DBusHelper . DefaultConnection is not { } conn )
return null ;
return null ;
using var restoreContext = AvaloniaSynchronizationContext . Ensure ( DispatcherPriority . Input ) ;
using var restoreContext = AvaloniaSynchronizationContext . Ensure ( DispatcherPriority . Input ) ;
var dbusFileChooser = new OrgFreedesktopPortalFileChooserProxy ( conn , "org.freedesktop.portal.Desktop" , "/org/freedesktop/portal/desktop" ) ;
var dbusFileChooser = new OrgFreedesktopPortalFileChooserProxy ( conn , "org.freedesktop.portal.Desktop" ,
"/org/freedesktop/portal/desktop" ) ;
uint version ;
uint version ;
try
try
{
{
@ -41,7 +42,8 @@ namespace Avalonia.FreeDesktop
private readonly IPlatformHandle _ handle ;
private readonly IPlatformHandle _ handle ;
private readonly uint _ version ;
private readonly uint _ version ;
private DBusSystemDialog ( Connection connection , IPlatformHandle handle , OrgFreedesktopPortalFileChooserProxy fileChooser , uint version )
private DBusSystemDialog ( Connection connection , IPlatformHandle handle ,
OrgFreedesktopPortalFileChooserProxy fileChooser , uint version )
{
{
_ connection = connection ;
_ connection = connection ;
_f ileChooser = fileChooser ;
_f ileChooser = fileChooser ;
@ -64,14 +66,15 @@ namespace Avalonia.FreeDesktop
if ( TryParseFilters ( options . FileTypeFilter , out var filters ) )
if ( TryParseFilters ( options . FileTypeFilter , out var filters ) )
chooserOptions . Add ( "filters" , filters ) ;
chooserOptions . Add ( "filters" , filters ) ;
if ( options . SuggestedStartLocation ? . TryGetLocalPath ( ) is { } folderPath )
if ( options . SuggestedStartLocation ? . TryGetLocalPath ( ) is { } folderPath )
chooserOptions . Add ( "current_folder" , VariantValue . Array ( Encoding . UTF8 . GetBytes ( folderPath + "\0" ) ) ) ;
chooserOptions . Add ( "current_folder" , VariantValue . Array ( Encoding . UTF8 . GetBytes ( folderPath + "\0" ) ) ) ;
chooserOptions . Add ( "multiple" , VariantValue . Bool ( options . AllowMultiple ) ) ;
chooserOptions . Add ( "multiple" , VariantValue . Bool ( options . AllowMultiple ) ) ;
objectPath = await _f ileChooser . OpenFileAsync ( parentWindow , options . Title ? ? string . Empty , chooserOptions ) ;
objectPath = await _f ileChooser . OpenFileAsync ( parentWindow , options . Title ? ? string . Empty , chooserOptions ) ;
var request = new OrgFreedesktopPortalRequestProxy ( _ connection , "org.freedesktop.portal.Desktop" , objectPath ) ;
var request =
new OrgFreedesktopPortalRequestProxy ( _ connection , "org.freedesktop.portal.Desktop" , objectPath ) ;
var tsc = new TaskCompletionSource < string [ ] ? > ( ) ;
var tsc = new TaskCompletionSource < string [ ] ? > ( ) ;
using var disposable = await request . WatchResponseAsync ( ( e , x ) = >
using var disposable = await request . WatchResponseAsync ( ( e , x ) = >
{
{
@ -86,6 +89,19 @@ namespace Avalonia.FreeDesktop
}
}
public override async Task < IStorageFile ? > SaveFilePickerAsync ( FilePickerSaveOptions options )
public override async Task < IStorageFile ? > SaveFilePickerAsync ( FilePickerSaveOptions options )
{
var ( file , _ ) = await SaveFilePickerCoreAsync ( options ) . ConfigureAwait ( false ) ;
return file ;
}
public override async Task < SaveFilePickerResult > SaveFilePickerWithResultAsync ( FilePickerSaveOptions options )
{
var ( file , selectedType ) = await SaveFilePickerCoreAsync ( options ) . ConfigureAwait ( false ) ;
return new SaveFilePickerResult ( file ) { SelectedFileType = selectedType } ;
}
private async Task < ( IStorageFile ? file , FilePickerFileType ? selectedType ) > SaveFilePickerCoreAsync (
FilePickerSaveOptions options )
{
{
var parentWindow = $"x11:{_handle.Handle:X}" ;
var parentWindow = $"x11:{_handle.Handle:X}" ;
ObjectPath objectPath ;
ObjectPath objectPath ;
@ -95,11 +111,13 @@ namespace Avalonia.FreeDesktop
if ( options . SuggestedFileName is { } currentName )
if ( options . SuggestedFileName is { } currentName )
chooserOptions . Add ( "current_name" , VariantValue . String ( currentName ) ) ;
chooserOptions . Add ( "current_name" , VariantValue . String ( currentName ) ) ;
if ( options . SuggestedStartLocation ? . TryGetLocalPath ( ) is { } folderPath )
if ( options . SuggestedStartLocation ? . TryGetLocalPath ( ) is { } folderPath )
chooserOptions . Add ( "current_folder" , VariantValue . Array ( Encoding . UTF8 . GetBytes ( folderPath + "\0" ) ) ) ;
chooserOptions . Add ( "current_folder" , VariantValue . Array ( Encoding . UTF8 . GetBytes ( folderPath + "\0" ) ) ) ;
objectPath = await _f ileChooser . SaveFileAsync ( parentWindow , options . Title ? ? string . Empty , chooserOptions ) ;
objectPath = await _f ileChooser . SaveFileAsync ( parentWindow , options . Title ? ? string . Empty , chooserOptions )
var request = new OrgFreedesktopPortalRequestProxy ( _ connection , "org.freedesktop.portal.Desktop" , objectPath ) ;
. ConfigureAwait ( false ) ;
var request =
new OrgFreedesktopPortalRequestProxy ( _ connection , "org.freedesktop.portal.Desktop" , objectPath ) ;
var tsc = new TaskCompletionSource < string [ ] ? > ( ) ;
var tsc = new TaskCompletionSource < string [ ] ? > ( ) ;
FilePickerFileType ? selectedType = null ;
FilePickerFileType ? selectedType = null ;
using var disposable = await request . WatchResponseAsync ( ( e , x ) = >
using var disposable = await request . WatchResponseAsync ( ( e , x ) = >
@ -113,35 +131,39 @@ namespace Avalonia.FreeDesktop
if ( x . Results . TryGetValue ( "current_filter" , out var currentFilter ) )
if ( x . Results . TryGetValue ( "current_filter" , out var currentFilter ) )
{
{
var name = currentFilter . GetItem ( 0 ) . GetString ( ) ;
var name = currentFilter . GetItem ( 0 ) . GetString ( ) ;
selectedType = new FilePickerFileType ( name ) ;
var patterns = new List < string > ( ) ;
var patterns = new List < string > ( ) ;
var mimeTypes = new List < string > ( ) ;
var mimeTypes = new List < string > ( ) ;
var types = currentFilter . GetItem ( 1 ) . GetArray < VariantValue > ( ) ;
var types = currentFilter . GetItem ( 1 ) . GetArray < VariantValue > ( ) ;
foreach ( var t in types )
foreach ( var t in types )
{
{
if ( t . GetItem ( 0 ) . GetUInt32 ( ) = = 1 )
if ( t . GetItem ( 0 ) . GetUInt32 ( ) = = 1 )
mimeTypes . Add ( t . GetItem ( 1 ) . GetString ( ) ) ;
mimeTypes . Add ( t . GetItem ( 1 ) . GetString ( ) ) ;
else
else
patterns . Add ( t . GetItem ( 1 ) . GetString ( ) ) ;
patterns . Add ( t . GetItem ( 1 ) . GetString ( ) ) ;
}
}
selectedType . Patterns = patterns ;
// Reuse the file type objects from options
selectedType . MimeTypes = mimeTypes ;
// so the consuming code can match exactly the
// file type selected instead of spawning one.
selectedType = options . FileTypeChoices ? . FirstOrDefault ( type = > type . Name = = name & & (
( type . MimeTypes ? . All ( y = > mimeTypes . Contains ( y ) ) ? ? false ) | |
( type . Patterns ? . All ( y = > patterns . Contains ( y ) ) ? ? false ) ) )
? ? new FilePickerFileType ( name ) { MimeTypes = mimeTypes , Patterns = patterns } ;
}
}
tsc . TrySetResult ( x . Results [ "uris" ] . GetArray < string > ( ) ) ;
tsc . TrySetResult ( x . Results [ "uris" ] . GetArray < string > ( ) ) ;
}
}
} ) ;
} ) . ConfigureAwait ( false ) ;
var uris = await tsc . Task ;
var uris = await tsc . Task . ConfigureAwait ( false ) ;
var path = uris ? . FirstOrDefault ( ) is { } filePath ? new Uri ( filePath ) . LocalPath : null ;
var path = uris ? . FirstOrDefault ( ) is { } filePath ? new Uri ( filePath ) . LocalPath : null ;
if ( path is null )
if ( path is null )
return null ;
return ( null , selectedType ) ;
// WSL2 freedesktop automatically adds extension from selected file type, but we can't pass "default ext". So apply it manually.
// WSL2 freedesktop automatically adds extension from selected file type, but we can't pass "default ext". So apply it manually.
path = StorageProviderHelpers . NameWithExtension ( path , options . DefaultExtension , selectedType ) ;
path = StorageProviderHelpers . NameWithExtension ( path , options . DefaultExtension , selectedType ) ;
return new BclStorageFile ( new FileInfo ( path ) ) ;
return ( new BclStorageFile ( new FileInfo ( path ) ) , selectedType ) ;
}
}
public override async Task < IReadOnlyList < IStorageFolder > > OpenFolderPickerAsync ( FolderPickerOpenOptions options )
public override async Task < IReadOnlyList < IStorageFolder > > OpenFolderPickerAsync ( FolderPickerOpenOptions options )
@ -152,8 +174,7 @@ namespace Avalonia.FreeDesktop
var parentWindow = $"x11:{_handle.Handle:X}" ;
var parentWindow = $"x11:{_handle.Handle:X}" ;
var chooserOptions = new Dictionary < string , VariantValue >
var chooserOptions = new Dictionary < string , VariantValue >
{
{
{ "directory" , VariantValue . Bool ( true ) } ,
{ "directory" , VariantValue . Bool ( true ) } , { "multiple" , VariantValue . Bool ( options . AllowMultiple ) }
{ "multiple" , VariantValue . Bool ( options . AllowMultiple ) }
} ;
} ;
if ( options . SuggestedFileName is { } currentName )
if ( options . SuggestedFileName is { } currentName )
@ -161,8 +182,10 @@ namespace Avalonia.FreeDesktop
if ( options . SuggestedStartLocation ? . TryGetLocalPath ( ) is { } folderPath )
if ( options . SuggestedStartLocation ? . TryGetLocalPath ( ) is { } folderPath )
chooserOptions . Add ( "current_folder" , VariantValue . Array ( Encoding . UTF8 . GetBytes ( folderPath + "\0" ) ) ) ;
chooserOptions . Add ( "current_folder" , VariantValue . Array ( Encoding . UTF8 . GetBytes ( folderPath + "\0" ) ) ) ;
var objectPath = await _f ileChooser . OpenFileAsync ( parentWindow , options . Title ? ? string . Empty , chooserOptions ) ;
var objectPath =
var request = new OrgFreedesktopPortalRequestProxy ( _ connection , "org.freedesktop.portal.Desktop" , objectPath ) ;
await _f ileChooser . OpenFileAsync ( parentWindow , options . Title ? ? string . Empty , chooserOptions ) ;
var request =
new OrgFreedesktopPortalRequestProxy ( _ connection , "org.freedesktop.portal.Desktop" , objectPath ) ;
var tsc = new TaskCompletionSource < string [ ] ? > ( ) ;
var tsc = new TaskCompletionSource < string [ ] ? > ( ) ;
using var disposable = await request . WatchResponseAsync ( ( e , x ) = >
using var disposable = await request . WatchResponseAsync ( ( e , x ) = >
{
{
@ -200,7 +223,8 @@ namespace Avalonia.FreeDesktop
if ( fileType . Patterns ? . Count > 0 )
if ( fileType . Patterns ? . Count > 0 )
extensions . AddRange ( fileType . Patterns . Select ( static pattern = > Struct . Create ( GlobStyle , pattern ) ) ) ;
extensions . AddRange ( fileType . Patterns . Select ( static pattern = > Struct . Create ( GlobStyle , pattern ) ) ) ;
else if ( fileType . MimeTypes ? . Count > 0 )
else if ( fileType . MimeTypes ? . Count > 0 )
extensions . AddRange ( fileType . MimeTypes . Select ( static mimeType = > Struct . Create ( MimeStyle , mimeType ) ) ) ;
extensions . AddRange (
fileType . MimeTypes . Select ( static mimeType = > Struct . Create ( MimeStyle , mimeType ) ) ) ;
else
else
continue ;
continue ;