Browse Source

Abstract linux DBus and GTK storage providers, use async initialization

pull/8548/head
Max Katz 4 years ago
parent
commit
235713823f
  1. 1
      Avalonia.sln
  2. 3
      src/Avalonia.FreeDesktop/DBusHelper.cs
  3. 33
      src/Avalonia.FreeDesktop/DBusSystemDialog.cs
  4. 2
      src/Avalonia.X11/NativeDialogs/Gtk.cs
  5. 30
      src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
  6. 72
      src/Avalonia.X11/NativeDialogs/LinuxStorageProvider.cs
  7. 8
      src/Avalonia.X11/X11Platform.cs
  8. 4
      src/Avalonia.X11/X11Window.cs

1
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}

3
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);
}
}

33
src/Avalonia.FreeDesktop/DBusSystemDialog.cs

@ -15,27 +15,26 @@ namespace Avalonia.FreeDesktop
{
internal class DBusSystemDialog : BclStorageProvider
{
private static readonly Lazy<IFileChooser?> s_fileChooser = new(() =>
private static readonly Lazy<IFileChooser?> s_fileChooser = new(() => DBusHelper.Connection?
.CreateProxy<IFileChooser>("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"));
internal static async Task<DBusSystemDialog?> TryCreate(IPlatformHandle handle)
{
var fileChooser = DBusHelper.Connection?.CreateProxy<IFileChooser>("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;

2
src/Avalonia.X11/NativeDialogs/Gtk.cs

@ -264,8 +264,6 @@ namespace Avalonia.X11.NativeDialogs
public static Task<bool> StartGtk()
{
return StartGtkCore();
lock (s_startGtkLock)
return s_startGtkTask ??= StartGtkCore();
}
private static void GtkThread(TaskCompletionSource<bool> tcs)

30
src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs

@ -17,10 +17,10 @@ namespace Avalonia.X11.NativeDialogs
{
internal class GtkSystemDialog : BclStorageProvider
{
private Task<bool>? _initialized;
private static Task<bool>? _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<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
internal static async Task<GtkSystemDialog?> TryCreate(X11Window window)
{
await EnsureInitialized();
_initialized ??= StartGtk();
return await _initialized ? new GtkSystemDialog(window) : null;
}
public override async Task<IReadOnlyList<IStorageFile>> 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<IReadOnlyList<IStorageFolder>> 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<IStorageFile?> 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;

72
src/Avalonia.X11/NativeDialogs/LinuxStorageProvider.cs

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.FreeDesktop;
using Avalonia.Platform.Storage;
namespace Avalonia.X11.NativeDialogs;
internal class LinuxStorageProvider : IStorageProvider
{
private readonly X11Window _window;
public LinuxStorageProvider(X11Window window)
{
_window = window;
}
public bool CanOpen => true;
public bool CanSave => true;
public bool CanPickFolder => true;
private async Task<IStorageProvider> EnsureStorageProvider()
{
var options = AvaloniaLocator.Current.GetService<X11PlatformOptions>() ?? new X11PlatformOptions();
if (options.UseDBusFilePicker)
{
var dBusDialog = await DBusSystemDialog.TryCreate(_window.Handle);
if (dBusDialog is not null)
{
return dBusDialog;
}
}
var gtkDialog = await GtkSystemDialog.TryCreate(_window);
if (gtkDialog is not null)
{
return gtkDialog;
}
throw new InvalidOperationException("Neither DBus nor GTK are available on the system");
}
public async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.OpenFilePickerAsync(options).ConfigureAwait(false);
}
public async Task<IStorageFile> SaveFilePickerAsync(FilePickerSaveOptions options)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.SaveFilePickerAsync(options).ConfigureAwait(false);
}
public async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.OpenFolderPickerAsync(options).ConfigureAwait(false);
}
public async Task<IStorageBookmarkFile> OpenFileBookmarkAsync(string bookmark)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.OpenFileBookmarkAsync(bookmark).ConfigureAwait(false);
}
public async Task<IStorageBookmarkFolder> OpenFolderBookmarkAsync(string bookmark)
{
var provider = await EnsureStorageProvider().ConfigureAwait(false);
return await provider.OpenFolderBookmarkAsync(bookmark).ConfigureAwait(false);
}
}

8
src/Avalonia.X11/X11Platform.cs

@ -216,16 +216,16 @@ namespace Avalonia
public bool OverlayPopups { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool UseDBusMenu { get; set; } = true;
/// <summary>
/// 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.
/// </summary>
public bool UseGtkFilePicker { get; set; } = false;
public bool UseDBusFilePicker { get; set; } = true;
/// <summary>
/// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true.

4
src/Avalonia.X11/X11Window.cs

@ -215,9 +215,7 @@ 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 NativeDialogs.LinuxStorageProvider(this);
}
class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo

Loading…
Cancel
Save