-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update GTK and FreeDesktop implementations
- Loading branch information
Showing
5 changed files
with
263 additions
and
140 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,102 +1,171 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Avalonia.Controls; | ||
using Avalonia.Controls.Platform; | ||
using Avalonia.Logging; | ||
using Avalonia.Platform; | ||
using Avalonia.Platform.Storage; | ||
using Avalonia.Platform.Storage.FileIO; | ||
|
||
using Tmds.DBus; | ||
|
||
namespace Avalonia.FreeDesktop | ||
{ | ||
internal class DBusSystemDialog : ISystemDialogImpl | ||
internal class DBusSystemDialog : BclStorageProvider | ||
{ | ||
private readonly IFileChooser _fileChooser; | ||
|
||
internal static DBusSystemDialog? TryCreate() | ||
private static readonly Lazy<IFileChooser?> s_fileChooser = new(() => | ||
{ | ||
var fileChooser = DBusHelper.Connection?.CreateProxy<IFileChooser>("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); | ||
if (fileChooser is null) | ||
return null; | ||
try | ||
{ | ||
fileChooser.GetVersionAsync().GetAwaiter().GetResult(); | ||
return new DBusSystemDialog(fileChooser); | ||
_ = fileChooser.GetVersionAsync(); | ||
return fileChooser; | ||
} | ||
catch (Exception e) | ||
{ | ||
Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform)?.Log(null, $"Unable to connect to org.freedesktop.portal.Desktop: {e.Message}"); | ||
return null; | ||
} | ||
}); | ||
|
||
internal static DBusSystemDialog? TryCreate(IPlatformHandle handle) | ||
{ | ||
return handle.HandleDescriptor == "XID" && s_fileChooser.Value is { } fileChooser | ||
? new DBusSystemDialog(fileChooser, handle) : null; | ||
} | ||
|
||
private DBusSystemDialog(IFileChooser fileChooser) | ||
private readonly IFileChooser _fileChooser; | ||
private readonly IPlatformHandle _handle; | ||
|
||
private DBusSystemDialog(IFileChooser fileChooser, IPlatformHandle handle) | ||
{ | ||
_fileChooser = fileChooser; | ||
_handle = handle; | ||
} | ||
|
||
public async Task<string[]?> ShowFileDialogAsync(FileDialog dialog, Window parent) | ||
public override bool CanOpen => true; | ||
|
||
public override bool CanSave => true; | ||
|
||
public override bool CanPickFolder => true; | ||
|
||
public override async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options) | ||
{ | ||
var parentWindow = $"x11:{parent.PlatformImpl!.Handle.Handle.ToString("X")}"; | ||
var parentWindow = $"x11:{_handle.Handle:X}"; | ||
ObjectPath objectPath; | ||
var options = new Dictionary<string, object>(); | ||
if (dialog.Filters is not null) | ||
options.Add("filters", ParseFilters(dialog)); | ||
var chooserOptions = new Dictionary<string, object>(); | ||
var filters = ParseFilters(options.FileTypeFilter); | ||
if (filters.Any()) | ||
{ | ||
chooserOptions.Add("filters", filters); | ||
} | ||
|
||
chooserOptions.Add("multiple", options.AllowMultiple); | ||
|
||
objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); | ||
|
||
var request = DBusHelper.Connection!.CreateProxy<IRequest>("org.freedesktop.portal.Request", objectPath); | ||
var tsc = new TaskCompletionSource<string[]?>(); | ||
using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException); | ||
var uris = await tsc.Task ?? Array.Empty<string>(); | ||
|
||
return uris.Select(path => new BclStorageFile(new FileInfo(new Uri(path).AbsolutePath))).ToList(); | ||
} | ||
|
||
switch (dialog) | ||
public override async Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options) | ||
{ | ||
var parentWindow = $"x11:{_handle.Handle:X}"; | ||
ObjectPath objectPath; | ||
var chooserOptions = new Dictionary<string, object>(); | ||
var filters = ParseFilters(options.FileTypeChoices); | ||
if (filters.Any()) | ||
{ | ||
case OpenFileDialog openFileDialog: | ||
options.Add("multiple", openFileDialog.AllowMultiple); | ||
objectPath = await _fileChooser.OpenFileAsync(parentWindow, openFileDialog.Title ?? string.Empty, options); | ||
break; | ||
case SaveFileDialog saveFileDialog: | ||
if (saveFileDialog.InitialFileName is not null) | ||
options.Add("current_name", saveFileDialog.InitialFileName); | ||
if (saveFileDialog.Directory is not null) | ||
options.Add("current_folder", Encoding.UTF8.GetBytes(saveFileDialog.Directory)); | ||
objectPath = await _fileChooser.SaveFileAsync(parentWindow, saveFileDialog.Title ?? string.Empty, options); | ||
break; | ||
chooserOptions.Add("filters", filters); | ||
} | ||
|
||
if (options.SuggestedFileName is { } currentName) | ||
chooserOptions.Add("current_name", currentName); | ||
if (options.SuggestedStartLocation?.TryGetUri(out var currentFolder) == true) | ||
chooserOptions.Add("current_folder", Encoding.UTF8.GetBytes(currentFolder.ToString())); | ||
objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); | ||
|
||
var request = DBusHelper.Connection!.CreateProxy<IRequest>("org.freedesktop.portal.Request", objectPath); | ||
var tsc = new TaskCompletionSource<string[]?>(); | ||
using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException); | ||
var uris = await tsc.Task; | ||
if (uris is null) | ||
var path = uris?.FirstOrDefault() is { } filePath ? new Uri(filePath).AbsolutePath : null; | ||
|
||
if (path is null) | ||
{ | ||
return null; | ||
for (var i = 0; i < uris.Length; i++) | ||
uris[i] = new Uri(uris[i]).AbsolutePath; | ||
return uris; | ||
} | ||
else | ||
{ | ||
// 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, null); | ||
|
||
return new BclStorageFile(new FileInfo(path)); | ||
} | ||
} | ||
|
||
public async Task<string?> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent) | ||
public override async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options) | ||
{ | ||
var parentWindow = $"x11:{parent.PlatformImpl!.Handle.Handle.ToString("X")}"; | ||
var options = new Dictionary<string, object> | ||
var parentWindow = $"x11:{_handle.Handle:X}"; | ||
var chooserOptions = new Dictionary<string, object> | ||
{ | ||
{ "directory", true } | ||
{ "directory", true }, | ||
{ "multiple", options.AllowMultiple } | ||
}; | ||
var objectPath = await _fileChooser.OpenFileAsync(parentWindow, dialog.Title ?? string.Empty, options); | ||
var objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); | ||
var request = DBusHelper.Connection!.CreateProxy<IRequest>("org.freedesktop.portal.Request", objectPath); | ||
var tsc = new TaskCompletionSource<string[]?>(); | ||
using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException); | ||
var uris = await tsc.Task; | ||
if (uris is null) | ||
return null; | ||
return uris.Length != 1 ? string.Empty : new Uri(uris[0]).AbsolutePath; | ||
var uris = await tsc.Task ?? Array.Empty<string>(); | ||
|
||
return uris | ||
.Select(path => new Uri(path).AbsolutePath) | ||
// WSL2 freedesktop allows to select files as well in directory picker, filter it out. | ||
.Where(Directory.Exists) | ||
.Select(path => new BclStorageFolder(new DirectoryInfo(path))).ToList(); | ||
} | ||
|
||
private static (string name, (uint style, string extension)[])[] ParseFilters(FileDialog dialog) | ||
private static (string name, (uint style, string extension)[])[] ParseFilters(IReadOnlyList<FilePickerFileType>? fileTypes) | ||
{ | ||
var filters = new (string name, (uint style, string extension)[])[dialog.Filters!.Count]; | ||
for (var i = 0; i < filters.Length; i++) | ||
// Example: [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])] | ||
|
||
if (fileTypes is null) | ||
{ | ||
var extensions = dialog.Filters[i].Extensions.Select(static x => (0u, x)).ToArray(); | ||
filters[i] = (dialog.Filters[i].Name ?? string.Empty, extensions); | ||
return Array.Empty<(string name, (uint style, string extension)[])>(); | ||
} | ||
|
||
var filters = new List<(string name, (uint style, string extension)[])>(); | ||
foreach (var fileType in fileTypes) | ||
{ | ||
const uint globStyle = 0u; | ||
const uint mimeStyle = 1u; | ||
|
||
var extensions = Enumerable.Empty<(uint, string)>(); | ||
|
||
if (fileType.Patterns is { } patterns) | ||
{ | ||
extensions = extensions.Concat(patterns.Select(static x => (globStyle, x))); | ||
} | ||
else if (fileType.MimeTypes is { } mimeTypes) | ||
{ | ||
extensions = extensions.Concat(mimeTypes.Select(static x => (mimeStyle, x))); | ||
} | ||
|
||
if (extensions.Any()) | ||
{ | ||
filters.Add((fileType.Name, extensions.ToArray())); | ||
} | ||
} | ||
|
||
return filters; | ||
return filters.ToArray(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.