diff --git a/Avalonia.sln b/Avalonia.sln index 071d0457b8..4999719676 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -559,6 +559,7 @@ Global {2B390431-288C-435C-BB6B-A374033BD8D1} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {EABE2161-989B-42BF-BD8D-1E34B20C21F1} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} + {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/src/Avalonia.FreeDesktop/DBusHelper.cs b/src/Avalonia.FreeDesktop/DBusHelper.cs index 9f9d75b411..ef99838208 100644 --- a/src/Avalonia.FreeDesktop/DBusHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusHelper.cs @@ -24,8 +24,7 @@ namespace Avalonia.FreeDesktop if (_ctx is not null) _ctx?.Post(d, state); else - lock (_lock) - d(state); + d(state); } } diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs index c17d5b993c..7974069184 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs @@ -15,27 +15,26 @@ namespace Avalonia.FreeDesktop { internal class DBusSystemDialog : BclStorageProvider { - private static readonly Lazy s_fileChooser = new(() => + private static readonly Lazy s_fileChooser = new(() => DBusHelper.Connection? + .CreateProxy("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop")); + + internal static async Task TryCreate(IPlatformHandle handle) { - var fileChooser = DBusHelper.Connection?.CreateProxy("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); - if (fileChooser is null) - return null; - try + if (handle.HandleDescriptor == "XID" && s_fileChooser.Value is { } 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; + try + { + await fileChooser.GetVersionAsync(); + return new DBusSystemDialog(fileChooser, handle); + } + 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; + return null; } private readonly IFileChooser _fileChooser; diff --git a/src/Avalonia.X11/NativeDialogs/CompositeStorageProvider.cs b/src/Avalonia.X11/NativeDialogs/CompositeStorageProvider.cs new file mode 100644 index 0000000000..5fe0f46b14 --- /dev/null +++ b/src/Avalonia.X11/NativeDialogs/CompositeStorageProvider.cs @@ -0,0 +1,64 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Avalonia.Platform.Storage; + +namespace Avalonia.X11.NativeDialogs; + +internal class CompositeStorageProvider : IStorageProvider +{ + private readonly IEnumerable>> _factories; + public CompositeStorageProvider(IEnumerable>> factories) + { + _factories = factories; + } + + public bool CanOpen => true; + public bool CanSave => true; + public bool CanPickFolder => true; + + private async Task EnsureStorageProvider() + { + foreach (var factory in _factories) + { + var provider = await factory(); + if (provider is not null) + { + return provider; + } + } + + throw new InvalidOperationException("Neither DBus nor GTK are available on the system"); + } + + public async Task> OpenFilePickerAsync(FilePickerOpenOptions options) + { + var provider = await EnsureStorageProvider().ConfigureAwait(false); + return await provider.OpenFilePickerAsync(options).ConfigureAwait(false); + } + + public async Task SaveFilePickerAsync(FilePickerSaveOptions options) + { + var provider = await EnsureStorageProvider().ConfigureAwait(false); + return await provider.SaveFilePickerAsync(options).ConfigureAwait(false); + } + + public async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) + { + var provider = await EnsureStorageProvider().ConfigureAwait(false); + return await provider.OpenFolderPickerAsync(options).ConfigureAwait(false); + } + + public async Task OpenFileBookmarkAsync(string bookmark) + { + var provider = await EnsureStorageProvider().ConfigureAwait(false); + return await provider.OpenFileBookmarkAsync(bookmark).ConfigureAwait(false); + } + + public async Task OpenFolderBookmarkAsync(string bookmark) + { + var provider = await EnsureStorageProvider().ConfigureAwait(false); + return await provider.OpenFolderBookmarkAsync(bookmark).ConfigureAwait(false); + } +} diff --git a/src/Avalonia.X11/NativeDialogs/Gtk.cs b/src/Avalonia.X11/NativeDialogs/Gtk.cs index c9e482db86..ae04c072a5 100644 --- a/src/Avalonia.X11/NativeDialogs/Gtk.cs +++ b/src/Avalonia.X11/NativeDialogs/Gtk.cs @@ -264,8 +264,6 @@ namespace Avalonia.X11.NativeDialogs public static Task StartGtk() { return StartGtkCore(); - lock (s_startGtkLock) - return s_startGtkTask ??= StartGtkCore(); } private static void GtkThread(TaskCompletionSource tcs) diff --git a/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs b/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs index 89d08a3974..89aa0340b5 100644 --- a/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs +++ b/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs @@ -17,10 +17,10 @@ namespace Avalonia.X11.NativeDialogs { internal class GtkSystemDialog : BclStorageProvider { - private Task? _initialized; + private static Task? _initialized; private readonly X11Window _window; - public GtkSystemDialog(X11Window window) + private GtkSystemDialog(X11Window window) { _window = window; } @@ -31,10 +31,15 @@ namespace Avalonia.X11.NativeDialogs public override bool CanPickFolder => true; - public override async Task> OpenFilePickerAsync(FilePickerOpenOptions options) + internal static async Task TryCreate(X11Window window) { - await EnsureInitialized(); + _initialized ??= StartGtk(); + + return await _initialized ? new GtkSystemDialog(window) : null; + } + public override async Task> OpenFilePickerAsync(FilePickerOpenOptions options) + { return await await RunOnGlibThread(async () => { var res = await ShowDialog(options.Title, _window, GtkFileChooserAction.Open, @@ -46,8 +51,6 @@ namespace Avalonia.X11.NativeDialogs public override async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) { - await EnsureInitialized(); - return await await RunOnGlibThread(async () => { var res = await ShowDialog(options.Title, _window, GtkFileChooserAction.SelectFolder, @@ -59,8 +62,6 @@ namespace Avalonia.X11.NativeDialogs public override async Task SaveFilePickerAsync(FilePickerSaveOptions options) { - await EnsureInitialized(); - return await await RunOnGlibThread(async () => { var res = await ShowDialog(options.Title, _window, GtkFileChooserAction.Save, @@ -225,19 +226,6 @@ namespace Avalonia.X11.NativeDialogs return tcs.Task; } - private async Task EnsureInitialized() - { - if (_initialized == null) - { - _initialized = StartGtk(); - } - - if (!(await _initialized)) - { - throw new Exception("Unable to initialize GTK on separate thread"); - } - } - private static void UpdateParent(IntPtr chooser, IWindowImpl parentWindow) { var xid = parentWindow.Handle.Handle; diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index edb320d4f0..7043c60ae7 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -216,16 +216,16 @@ namespace Avalonia public bool OverlayPopups { get; set; } /// - /// Enables native file dialogs as well as global menu support on Linux desktop environments where it's supported (e. g. XFCE and MATE with plugin, KDE, etc). + /// Enables global menu support on Linux desktop environments where it's supported (e. g. XFCE and MATE with plugin, KDE, etc). /// The default value is true. /// public bool UseDBusMenu { get; set; } = true; /// - /// Enables GTK file picker instead of default FreeDesktop. - /// The default value is true. And FreeDesktop file picker is used instead if available. + /// Enables DBus file picker instead of GTK. + /// The default value is true. /// - public bool UseGtkFilePicker { get; set; } = false; + public bool UseDBusFilePicker { get; set; } = true; /// /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true. diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 2f92448f4b..009ccb6159 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -22,6 +22,7 @@ using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Avalonia.Threading; using Avalonia.X11.Glx; +using Avalonia.X11.NativeDialogs; using static Avalonia.X11.XLib; // ReSharper disable IdentifierTypo // ReSharper disable StringLiteralTypo @@ -215,9 +216,11 @@ namespace Avalonia.X11 _x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref _xSyncCounter, 1); } - var canUseFreeDekstopPicker = !platform.Options.UseGtkFilePicker && platform.Options.UseDBusMenu; - StorageProvider = canUseFreeDekstopPicker && DBusSystemDialog.TryCreate(Handle) is {} dBusStorage - ? dBusStorage : new NativeDialogs.GtkSystemDialog(this); + StorageProvider = new CompositeStorageProvider(new Func>[] + { + () => _platform.Options.UseDBusFilePicker ? DBusSystemDialog.TryCreate(Handle) : Task.FromResult(null), + () => GtkSystemDialog.TryCreate(this), + }); } class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo