Browse Source

[X11] For now use GtkFileChooser on a separate thread

pull/2011/head
Nikita Tsukanov 7 years ago
parent
commit
e9c5e42de9
  1. 1
      src/Avalonia.X11/Avalonia.X11.csproj
  2. 4
      src/Avalonia.X11/X11Platform.cs
  3. 115
      src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs
  4. 13
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  5. 36
      src/Gtk/Avalonia.Gtk3/SystemDialogs.cs

1
src/Avalonia.X11/Avalonia.X11.csproj

@ -7,6 +7,7 @@
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj" />
</ItemGroup>
</Project>

4
src/Avalonia.X11/X11Platform.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Gtk3;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
@ -41,7 +42,8 @@ namespace Avalonia.X11
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogsStub())
.Bind<IPlatformIconLoader>().ToConstant(new IconLoaderStub());
.Bind<IPlatformIconLoader>().ToConstant(new IconLoaderStub())
.Bind<ISystemDialogImpl>().ToConstant(new Gtk3ForeignX11SystemDialog());
X11Screens.Init(this);
if (Info.XInputVersion != null)
{

115
src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs

@ -0,0 +1,115 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Gtk3.Interop;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
namespace Avalonia.Gtk3
{
public class Gtk3ForeignX11SystemDialog : ISystemDialogImpl
{
private Task<bool> _initialized;
private SystemDialogBase _inner = new SystemDialogBase();
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
{
await EnsureInitialized();
var xid = parent.Handle.Handle;
return await await RunOnGtkThread(
() => _inner.ShowFileDialogAsync(dialog, GtkWindow.Null, chooser => UpdateParent(chooser, xid)));
}
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
{
await EnsureInitialized();
var xid = parent.Handle.Handle;
return await await RunOnGtkThread(
() => _inner.ShowFolderDialogAsync(dialog, GtkWindow.Null, chooser => UpdateParent(chooser, xid)));
}
void UpdateParent(GtkFileChooser chooser, IntPtr xid)
{
Native.GtkWidgetRealize(chooser);
var window = Native.GtkWidgetGetWindow(chooser);
var parent = Native.GdkWindowForeignNewForDisplay(GdkDisplay, xid);
if (window != IntPtr.Zero && parent != IntPtr.Zero)
Native.GdkWindowSetTransientFor(window, parent);
}
async Task EnsureInitialized()
{
if (_initialized == null)
{
var tcs = new TaskCompletionSource<bool>();
_initialized = tcs.Task;
new Thread(() => GtkThread(tcs))
{
IsBackground = true
}.Start();
}
if (!(await _initialized))
throw new Exception("Unable to initialize GTK on separate thread");
}
Task<T> RunOnGtkThread<T>(Func<T> action)
{
var tcs = new TaskCompletionSource<T>();
GlibTimeout.Add(0, 0, () =>
{
try
{
tcs.SetResult(action());
}
catch (Exception e)
{
tcs.TrySetException(e);
}
return false;
});
return tcs.Task;
}
void GtkThread(TaskCompletionSource<bool> tcs)
{
try
{
X11.XInitThreads();
}catch{}
Resolver.Resolve();
if (Native.GdkWindowForeignNewForDisplay == null)
throw new Exception("gdk_x11_window_foreign_new_for_display is not found in your libgdk-3.so");
using (var backends = new Utf8Buffer("x11"))
Native.GdkSetAllowedBackends?.Invoke(backends);
if (!Native.GtkInitCheck(0, IntPtr.Zero))
{
tcs.SetResult(false);
return;
}
using (var utf = new Utf8Buffer($"avalonia.app.a{Guid.NewGuid().ToString("N")}"))
App = Native.GtkApplicationNew(utf, 0);
if (App == IntPtr.Zero)
{
tcs.SetResult(false);
return;
}
GdkDisplay = Native.GdkGetDefaultDisplay();
tcs.SetResult(true);
while (true)
Native.GtkMainIteration();
}
private IntPtr GdkDisplay { get; set; }
private IntPtr App { get; set; }
}
}

13
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@ -50,6 +50,9 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_init(int argc, IntPtr argv);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate bool gtk_init_check(int argc, IntPtr argv);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk, optional: true)]
public delegate IntPtr gdk_set_allowed_backends (Utf8Buffer backends);
@ -288,6 +291,12 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_window_end_paint(IntPtr window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk, optional: true)]
public delegate IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_window_set_transient_for(IntPtr window, IntPtr parent);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_event_request_motions(IntPtr ev);
@ -414,6 +423,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_window_set_transient_for GtkWindowSetTransientFor;
public static D.gdk_set_allowed_backends GdkSetAllowedBackends;
public static D.gtk_init GtkInit;
public static D.gtk_init_check GtkInitCheck;
public static D.gtk_window_present GtkWindowPresent;
public static D.gtk_widget_hide GtkWidgetHide;
public static D.gtk_widget_show GtkWidgetShow;
@ -483,8 +493,9 @@ namespace Avalonia.Gtk3.Interop
public static D.gdk_window_process_updates GdkWindowProcessUpdates;
public static D.gdk_window_begin_paint_rect GdkWindowBeginPaintRect;
public static D.gdk_window_end_paint GdkWindowEndPaint;
public static D.gdk_x11_window_foreign_new_for_display GdkWindowForeignNewForDisplay;
public static D.gdk_window_set_transient_for GdkWindowSetTransientFor;
public static D.gdk_pixbuf_new_from_file GdkPixbufNewFromFile;
public static D.gtk_icon_theme_get_default GtkIconThemeGetDefault;
public static D.gtk_icon_theme_load_icon GtkIconThemeLoadIcon;

36
src/Gtk/Avalonia.Gtk3/SystemDialogs.cs

@ -11,16 +11,17 @@ using Avalonia.Platform.Interop;
namespace Avalonia.Gtk3
{
class SystemDialog : ISystemDialogImpl
class SystemDialogBase
{
unsafe static Task<string[]> ShowDialog(string title, GtkWindow parent, GtkFileChooserAction action,
bool multiselect, string initialFileName)
public unsafe static Task<string[]> ShowDialog(string title, GtkWindow parent, GtkFileChooserAction action,
bool multiselect, string initialFileName, Action<GtkFileChooser> modify)
{
GtkFileChooser dlg;
parent = parent ?? GtkWindow.Null;
using (var name = new Utf8Buffer(title))
dlg = Native.GtkFileChooserDialogNew(name, parent, action, IntPtr.Zero);
modify?.Invoke(dlg);
if (multiselect)
Native.GtkFileChooserSetSelectMultiple(dlg, true);
@ -42,7 +43,7 @@ namespace Avalonia.Gtk3
dispose();
return false;
}),
Signal.Connect<Native.D.signal_dialog_response>(dlg, "response", (_, resp, __)=>
Signal.Connect<Native.D.signal_dialog_response>(dlg, "response", (_, resp, __) =>
{
string[] result = null;
if (resp == GtkResponseType.Accept)
@ -56,9 +57,11 @@ namespace Avalonia.Gtk3
rlst.Add(Utf8Buffer.StringFromPtr(cgs->Data));
cgs = cgs->Next;
}
Native.GSlistFree(gs);
result = rlst.ToArray();
}
Native.GtkWidgetHide(dlg);
dispose();
tcs.TrySetResult(result);
@ -70,27 +73,38 @@ namespace Avalonia.Gtk3
Native.GtkDialogAddButton(dlg, open, GtkResponseType.Accept);
using (var open = new Utf8Buffer("Cancel"))
Native.GtkDialogAddButton(dlg, open, GtkResponseType.Cancel);
if(initialFileName!=null)
if (initialFileName != null)
using (var fn = new Utf8Buffer(initialFileName))
Native.GtkFileChooserSetFilename(dlg, fn);
Native.GtkWindowPresent(dlg);
return tcs.Task;
}
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, GtkWindow parent,
Action<GtkFileChooser> modify = null)
{
return ShowDialog(dialog.Title, ((WindowBaseImpl)parent)?.GtkWidget,
return ShowDialog(dialog.Title, parent,
dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save,
(dialog as OpenFileDialog)?.AllowMultiple ?? false,
Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory,
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName));
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName), modify);
}
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, GtkWindow parent,
Action<GtkFileChooser> modify = null)
{
var res = await ShowDialog(dialog.Title, ((WindowBaseImpl) parent)?.GtkWidget,
GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory);
var res = await ShowDialog(dialog.Title, parent,
GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory, modify);
return res?.FirstOrDefault();
}
}
class SystemDialog : SystemDialogBase, ISystemDialogImpl
{
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
=> ShowFolderDialogAsync(dialog, ((WindowBaseImpl)parent)?.GtkWidget);
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
=> ShowFileDialogAsync(dialog, ((WindowBaseImpl)parent)?.GtkWidget);
}
}

Loading…
Cancel
Save