diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj
index 3e75180468..40db6d8c62 100644
--- a/src/Avalonia.X11/Avalonia.X11.csproj
+++ b/src/Avalonia.X11/Avalonia.X11.csproj
@@ -7,6 +7,7 @@
+
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index 79aef75e4c..675ed4bd53 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/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().ToConstant(new X11Clipboard(this))
.Bind().ToConstant(new PlatformSettingsStub())
.Bind().ToConstant(new SystemDialogsStub())
- .Bind().ToConstant(new IconLoaderStub());
+ .Bind().ToConstant(new IconLoaderStub())
+ .Bind().ToConstant(new Gtk3ForeignX11SystemDialog());
X11Screens.Init(this);
if (Info.XInputVersion != null)
{
diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs b/src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs
new file mode 100644
index 0000000000..b80914572b
--- /dev/null
+++ b/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 _initialized;
+ private SystemDialogBase _inner = new SystemDialogBase();
+
+
+ public async Task 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 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();
+ _initialized = tcs.Task;
+ new Thread(() => GtkThread(tcs))
+ {
+ IsBackground = true
+ }.Start();
+ }
+
+ if (!(await _initialized))
+ throw new Exception("Unable to initialize GTK on separate thread");
+
+ }
+
+ Task RunOnGtkThread(Func action)
+ {
+ var tcs = new TaskCompletionSource();
+ GlibTimeout.Add(0, 0, () =>
+ {
+
+ try
+ {
+ tcs.SetResult(action());
+ }
+ catch (Exception e)
+ {
+ tcs.TrySetException(e);
+ }
+
+ return false;
+ });
+ return tcs.Task;
+ }
+
+
+ void GtkThread(TaskCompletionSource 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; }
+ }
+}
diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs
index bf973119a9..765c19a796 100644
--- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs
+++ b/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;
diff --git a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs b/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs
index 5e6615f92d..1e85eaa156 100644
--- a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs
+++ b/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 ShowDialog(string title, GtkWindow parent, GtkFileChooserAction action,
- bool multiselect, string initialFileName)
+ public unsafe static Task ShowDialog(string title, GtkWindow parent, GtkFileChooserAction action,
+ bool multiselect, string initialFileName, Action 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(dlg, "response", (_, resp, __)=>
+ Signal.Connect(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 ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
+ public Task ShowFileDialogAsync(FileDialog dialog, GtkWindow parent,
+ Action 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 ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
+ public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, GtkWindow parent,
+ Action 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 ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
+ => ShowFolderDialogAsync(dialog, ((WindowBaseImpl)parent)?.GtkWidget);
+
+ public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
+ => ShowFileDialogAsync(dialog, ((WindowBaseImpl)parent)?.GtkWidget);
+ }
}