Browse Source

Initial commit of GTK3 backend that shows something on screen

pull/875/head
Nikita Tsukanov 10 years ago
parent
commit
e35677081a
  1. 43
      Avalonia.sln
  2. 4
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  3. 4
      samples/ControlCatalog.Desktop/Program.cs
  4. 1
      src/Gtk/Avalonia.Gtk3/.gitignore
  5. 80
      src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj
  6. 91
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  7. 142
      src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs
  8. 87
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  9. 24
      src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs
  10. 104
      src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs
  11. 45
      src/Gtk/Avalonia.Gtk3/Interop/Signal.cs
  12. 32
      src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs
  13. 30
      src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs
  14. 9
      src/Gtk/Avalonia.Gtk3/README.md
  15. 47
      src/Gtk/Avalonia.Gtk3/Stubs.cs
  16. 95
      src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs
  17. 48
      src/Gtk/Avalonia.Gtk3/WindowImpl.cs
  18. 10
      src/Gtk/Avalonia.Gtk3/project.json

43
Avalonia.sln

@ -155,6 +155,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\Rende
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Gtk3", "src\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj", "{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
@ -2329,6 +2331,46 @@ Global
{29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.ActiveCfg = Release|Any CPU
{29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.Build.0 = Release|Any CPU
{29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.Deploy.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Mono.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|x86.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Any CPU.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhone.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Mono.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Mono.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|x86.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|x86.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhone.Build.0 = Debug|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Mono.ActiveCfg = Debug|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Mono.Build.0 = Debug|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|x86.ActiveCfg = Debug|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|x86.Build.0 = Debug|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Any CPU.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhone.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhone.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Mono.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Mono.Build.0 = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|x86.ActiveCfg = Release|Any CPU
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2382,5 +2424,6 @@ Global
{BD7F352C-6DC1-4740-BAF2-2D34A038728C} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{F1FDC5B0-4654-416F-AE69-E3E9BBD87801} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{29132311-1848-4FD6-AE0C-4FF841151BD3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658} = {B9894058-278A-46B5-B6ED-AD613FCC03B3}
EndGlobalSection
EndGlobal

4
samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj

@ -72,6 +72,10 @@
<Project>{FB05AC90-89BA-4F2F-A924-F37875FB547C}</Project>
<Name>Avalonia.Cairo</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj">
<Project>{bb1f7bb5-6ad4-4776-94d9-c09d0a972658}</Project>
<Name>Avalonia.Gtk3</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Gtk\Avalonia.Gtk\Avalonia.Gtk.csproj">
<Project>{54F237D5-A70A-4752-9656-0C70B1A7B047}</Project>
<Name>Avalonia.Gtk</Name>

4
samples/ControlCatalog.Desktop/Program.cs

@ -2,6 +2,7 @@ using System;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Gtk3;
using Avalonia.Logging.Serilog;
using Avalonia.Platform;
using Serilog;
@ -17,7 +18,8 @@ namespace ControlCatalog
// TODO: Make this work with GTK/Skia/Cairo depending on command-line args
// again.
AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseDirect2D1()
.UseGtk3()
.Start<MainWindow>();
}

1
src/Gtk/Avalonia.Gtk3/.gitignore

@ -0,0 +1 @@
project.lock.json

80
src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Avalonia.Gtk3</RootNamespace>
<AssemblyName>Avalonia.Gtk3</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<None Include="project.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="Gtk3Platform.cs" />
<Compile Include="Interop\DynLoader.cs" />
<Compile Include="Interop\Native.cs" />
<Compile Include="Interop\NativeException.cs" />
<Compile Include="Interop\Resolver.cs" />
<Compile Include="Interop\Signal.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Stubs.cs" />
<Compile Include="TopLevelImpl.cs" />
<Compile Include="Interop\Utf8Buffer.cs" />
<Compile Include="WindowImpl.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj">
<Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
<Name>Avalonia.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj">
<Project>{D2221C82-4A25-4583-9B43-D791E3F6820C}</Project>
<Name>Avalonia.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj">
<Project>{62024B2D-53EB-4638-B26B-85EEAA54866E}</Project>
<Name>Avalonia.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.SceneGraph\Avalonia.SceneGraph.csproj">
<Project>{EB582467-6ABB-43A1-B052-E981BA910E3A}</Project>
<Name>Avalonia.SceneGraph</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

91
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Gtk3.Interop;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
namespace Avalonia.Gtk3
{
public class Gtk3Platform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface
{
internal static readonly Gtk3Platform Instance = new Gtk3Platform();
internal static readonly MouseDevice Mouse = new MouseDevice();
internal static readonly KeyboardDevice Keyboard = new KeyboardDevice();
internal static IntPtr App { get; set; }
public static void Initialize()
{
Resolver.Resolve();
Native.GtkInit(0, IntPtr.Zero);
App = Native.GtkApplicationNew("avalonia.app." + Guid.NewGuid(), 0);
//Mark current thread as UI thread
s_tlsMarker = true;
AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatform>().ToConstant(Instance)
.Bind<IClipboard>().ToSingleton<ClipboardStub>()
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactoryStub())
.Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IMouseDevice>().ToConstant(Mouse)
.Bind<IPlatformSettings>().ToConstant(Instance)
.Bind<IPlatformThreadingInterface>().ToConstant(Instance)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogStub>()
.Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoaderStub());
}
public IWindowImpl CreateWindow() => new WindowImpl();
public IEmbeddableWindowImpl CreateEmbeddableWindow()
{
throw new NotImplementedException();
}
public IPopupImpl CreatePopup()
{
throw new NotImplementedException();
}
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(100); //STUB
public double RenderScalingFactor { get; } = 1;
public double LayoutScalingFactor { get; } = 1;
public void RunLoop(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
Native.GtkMainIteration();
}
public IDisposable StartTimer(TimeSpan interval, Action tick)
{
return null;
}
public void Signal()
{
}
public event Action Signaled;
[ThreadStatic]
private static bool s_tlsMarker;
public bool CurrentThreadIsLoopThread => s_tlsMarker;
}
public static class Gtk3AppBuilderExtensions
{
public static T UseGtk3<T>(this AppBuilderBase<T> builder) where T : AppBuilderBase<T>, new()
=> builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3");
}
}

142
src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs

@ -0,0 +1,142 @@
using System;
using System.Runtime.InteropServices;
/*
* Source code imported from https://github.com/kekekeks/evhttp-sharp
* Source is provided under MIT license for Avalonia project and derived works
*/
namespace Avalonia.Gtk3.Interop
{
internal interface IDynLoader
{
IntPtr LoadLibrary(string basePath, string dll);
IntPtr GetProcAddress(IntPtr dll, string proc);
}
class UnixLoader : IDynLoader
{
// ReSharper disable InconsistentNaming
static class LinuxImports
{
[DllImport("libdl.so.2")]
private static extern IntPtr dlopen(string path, int flags);
[DllImport("libdl.so.2")]
private static extern IntPtr dlsym(IntPtr handle, string symbol);
[DllImport("libdl.so.2")]
private static extern IntPtr dlerror();
public static void Init()
{
DlOpen = dlopen;
DlSym = dlsym;
DlError = dlerror;
Postfix = ".so.0";
}
}
static class OsXImports
{
[DllImport("/usr/lib/libSystem.dylib")]
private static extern IntPtr dlopen(string path, int flags);
[DllImport("/usr/lib/libSystem.dylib")]
private static extern IntPtr dlsym(IntPtr handle, string symbol);
[DllImport("/usr/lib/libSystem.dylib")]
private static extern IntPtr dlerror();
public static void Init()
{
DlOpen = dlopen;
DlSym = dlsym;
DlError = dlerror;
Postfix = ".dylib"; //TODO
}
}
[DllImport("libc")]
static extern int uname(IntPtr buf);
static UnixLoader()
{
var buffer = Marshal.AllocHGlobal(0x1000);
uname(buffer);
var unixName = Marshal.PtrToStringAnsi(buffer);
Marshal.FreeHGlobal(buffer);
if(unixName == "Darwin")
OsXImports.Init();
else
LinuxImports.Init();
}
private static Func<string, int, IntPtr> DlOpen;
private static Func<IntPtr, string, IntPtr> DlSym;
private static Func<IntPtr> DlError;
private static string Postfix;
// ReSharper restore InconsistentNaming
static string DlErrorString()
{
return Marshal.PtrToStringAnsi(DlError());
}
public IntPtr LoadLibrary(string basePath, string dll)
{
dll += Postfix;
if (basePath != null)
dll = System.IO.Path.Combine(basePath, dll);
var handle = DlOpen(dll, 1);
if (handle == IntPtr.Zero)
throw new NativeException(DlErrorString());
return handle;
}
public IntPtr GetProcAddress(IntPtr dll, string proc)
{
var ptr = DlSym(dll, proc);
if (ptr == IntPtr.Zero)
throw new NativeException(DlErrorString());
return ptr;
}
}
internal class Win32Loader : IDynLoader
{
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32", EntryPoint = "LoadLibraryW", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibrary(string lpszLib);
IntPtr IDynLoader.LoadLibrary(string basePath, string dll)
{
dll += "-0.dll";
if (basePath != null)
dll = System.IO.Path.Combine(basePath, dll);
var handle = LoadLibrary(dll);
if (handle == IntPtr.Zero)
throw new NativeException("Error " + Marshal.GetLastWin32Error());
return handle;
}
IntPtr IDynLoader.GetProcAddress(IntPtr dll, string proc)
{
var ptr = GetProcAddress(dll, proc);
if (ptr == IntPtr.Zero)
throw new NativeException("Error " + Marshal.GetLastWin32Error());
return ptr;
}
}
}

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

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Gtk3.Interop
{
static class Native
{
public static class D
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_application_new([MarshalAs(UnmanagedType.AnsiBStr)] string appId, int flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_main_iteration();
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_window_new(GtkWindowType windowType);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_init(int argc, IntPtr argv);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_present(IntPtr gtkWindow);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_widget_hide(IntPtr gtkWidget);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] //No manual import
public delegate IntPtr gdk_get_native_handle(IntPtr gdkWindow);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_widget_get_window(IntPtr gtkWidget);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_widget_set_double_buffered(IntPtr gtkWidget, bool value);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_widget_realize(IntPtr gtkWidget);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_title(IntPtr gtkWindow, Utf8Buffer title);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_decorated(IntPtr gtkWindow, bool decorated);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_get_size(IntPtr gtkWindow, out int width, out int height);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
public delegate ulong g_signal_connect_object(IntPtr instance, [MarshalAs(UnmanagedType.AnsiBStr)]string signal, IntPtr handler, IntPtr userData, int flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
public delegate ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool signal_widget_draw(IntPtr gtkWidget, IntPtr cairoContext, IntPtr userData);
}
public static D.gtk_window_set_decorated GtkWindowSetDecorated;
public static D.gtk_window_set_title GtkWindowSetTitle;
public static D.gtk_application_new GtkApplicationNew;
public static D.gtk_main_iteration GtkMainIteration;
public static D.gtk_window_new GtkWindowNew;
public static D.gtk_init GtkInit;
public static D.gtk_window_present GtkWindowPresent;
public static D.gtk_widget_hide GtkWidgetHide;
public static D.gdk_get_native_handle GetNativeGdkWindowHandle;
public static D.gtk_widget_get_window GtkWidgetGetWindow;
public static D.gtk_widget_realize GtkWidgetRealize;
public static D.gtk_window_get_size GtkWindowGetSize;
public static D.g_signal_connect_object GSignalConnectObject;
public static D.g_signal_handler_disconnect GSignalHandlerDisconnect;
public static D.gtk_widget_set_double_buffered GtkWidgetSetDoubleBuffered;
}
public enum GtkWindowType
{
TopLevel,
Popup
}
}

24
src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Gtk3.Interop
{
public class NativeException : Exception
{
public NativeException()
{
}
public NativeException(string message) : base(message)
{
}
public NativeException(string message, Exception inner) : base(message, inner)
{
}
}
}

104
src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Gtk3.Interop
{
internal class GtkImportAttribute : Attribute
{
public GtkDll Dll { get; set; }
public string Name { get; set; }
public bool Optional { get; set; }
public GtkImportAttribute(GtkDll dll, string name = null, bool optional = false)
{
Dll = dll;
Name = name;
Optional = optional;
}
}
internal enum GtkDll
{
Gdk,
Gtk,
Glib,
Gio,
Gobject
}
static class Resolver
{
[DllImport("kernel32.dll")]
static extern int GetVersion();
static bool IsWin32()
{
try
{
GetVersion();
return true;
}
catch
{
return false;
}
}
public static void Resolve(string basePath = null)
{
var loader = IsWin32() ? (IDynLoader)new Win32Loader() : new UnixLoader();
var gdk = loader.LoadLibrary(basePath, "libgdk-3");
var gtk = loader.LoadLibrary(basePath, "libgtk-3");
var gio = loader.LoadLibrary(basePath, "libgio-2.0");
var glib = loader.LoadLibrary(basePath, "libglib-2.0");
var gobject = loader.LoadLibrary(basePath, "libgobject-2.0");
foreach (var fieldInfo in typeof(Native).GetTypeInfo().DeclaredFields)
{
var import = fieldInfo.FieldType.GetTypeInfo().GetCustomAttributes(typeof(GtkImportAttribute), true).Cast<GtkImportAttribute>().FirstOrDefault();
if(import == null)
continue;
IntPtr lib;
if (import.Dll == GtkDll.Gtk)
lib = gtk;
else if (import.Dll == GtkDll.Gdk)
lib = gdk;
else if (import.Dll == GtkDll.Gio)
lib = gio;
else if (import.Dll == GtkDll.Glib)
lib = glib;
else if (import.Dll == GtkDll.Gobject)
lib = gobject;
else
throw new ArgumentException("Invalid GtkImportAttribute for " + fieldInfo.FieldType);
var funcPtr = loader.GetProcAddress(lib, import.Name ?? fieldInfo.FieldType.Name);
fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(funcPtr, fieldInfo.FieldType));
}
var nativeHandleNames = new[] {"gdk_x11_window_get_xid", "gdk_win32_window_get_handle"};
foreach (var name in nativeHandleNames)
{
try
{
Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle)Marshal
.GetDelegateForFunctionPointer(loader.GetProcAddress(gdk, name), typeof(Native.D.gdk_get_native_handle));
}
catch { }
}
if (Native.GetNativeGdkWindowHandle == null)
throw new Exception($"Unable to locate any of [{string.Join(", ", nativeHandleNames)}] in libgdk");
}
}
}

45
src/Gtk/Avalonia.Gtk3/Interop/Signal.cs

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Gtk3.Interop
{
public class Signal
{
class ConnectedSignal : IDisposable
{
private readonly IntPtr _instance;
private GCHandle _handle;
private readonly ulong _id;
public ConnectedSignal(IntPtr instance, GCHandle handle, ulong id)
{
_instance = instance;
_handle = handle;
_id = id;
}
public void Dispose()
{
if (_handle.IsAllocated)
{
Native.GSignalHandlerDisconnect(_instance, _id);
_handle.Free();
}
}
}
public static IDisposable Connect<T>(IntPtr obj, string name, T handler)
{
var handle = GCHandle.Alloc(handler);
var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler);
var id = Native.GSignalConnectObject(obj, name, ptr, IntPtr.Zero, 0);
if (id == 0)
throw new ArgumentException("Unable to connect to signal " + name);
return new ConnectedSignal(obj, handle, id);
}
}
}

32
src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs

@ -0,0 +1,32 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Avalonia.Gtk3.Interop
{
class Utf8Buffer : SafeHandle
{
private GCHandle _gchandle;
private byte[] _data;
public Utf8Buffer(string s) : base(IntPtr.Zero, true)
{
_data = Encoding.UTF8.GetBytes(s);
_gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
handle = _gchandle.AddrOfPinnedObject();
}
protected override bool ReleaseHandle()
{
if (handle != IntPtr.Zero)
{
handle = IntPtr.Zero;
_data = null;
_gchandle.Free();
}
return true;
}
public override bool IsInvalid => handle == IntPtr.Zero;
}
}

30
src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs

@ -0,0 +1,30 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Avalonia.Gtk3")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Avalonia.Gtk3")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

9
src/Gtk/Avalonia.Gtk3/README.md

@ -0,0 +1,9 @@
P/Invoke based GTK3 backend
===========================
Code is EXPERIMENTAL at this point. It also needs Direct2D/Skia for rendering.
Windows GTK3 binaries aren't included in the repo, you need to download them from http://www.tarnyko.net/repo/gtk3_build_system/gtk+-bundle_3.4.2-20130513_win32.zip
Then you need to extract them somewhere and add `bin` directory to PATH. Support for specifying exact path to binaries will be implemented later.
On Linux it should work out of the box with system-provided GTK3. On OSX you should be able to wire GTK3 using DYLD_LIBRARY_PATH environment variable.

47
src/Gtk/Avalonia.Gtk3/Stubs.cs

@ -0,0 +1,47 @@
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.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
//TODO: This file should be empty once everything is implemented
namespace Avalonia.Gtk3
{
class ClipboardStub : IClipboard
{
public Task<string> GetTextAsync() => Task.FromResult("");
public Task SetTextAsync(string text) => Task.FromResult(0);
public Task ClearAsync() => Task.FromResult(0);
}
class CursorFactoryStub : IStandardCursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB");
}
class SystemDialogStub : ISystemDialogImpl
{
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) => Task.FromResult(new string[0]);
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
=> Task.FromResult((string) null);
}
class PlatformIconLoaderStub : IPlatformIconLoader
{
public IWindowIconImpl LoadIcon(string fileName) => null;
public IWindowIconImpl LoadIcon(Stream stream) => null;
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) => null;
}
}

95
src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs

@ -0,0 +1,95 @@
using System;
using Avalonia.Controls;
using Avalonia.Gtk3.Interop;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
namespace Avalonia.Gtk3
{
abstract class TopLevelImpl : ITopLevelImpl, IPlatformHandle
{
protected readonly IntPtr _gtkWidget;
private IInputRoot _inputRoot;
public TopLevelImpl(IntPtr gtkWidget)
{
_gtkWidget = gtkWidget;
Native.GtkWidgetRealize(gtkWidget);
Native.GtkWidgetSetDoubleBuffered(gtkWidget, false);
Signal.Connect<Native.D.signal_widget_draw>(_gtkWidget, "draw", OnDraw);
}
private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata)
{
Paint?.Invoke(new Rect(ClientSize));
return true;
}
public void Dispose()
{
//STUB
}
public abstract Size ClientSize { get; set; }
public Size MaxClientSize => new Size(1024, 768); //STUB
public double Scaling => 1; //TODO: Implement scaling
public IPlatformHandle Handle => this;
string IPlatformHandle.HandleDescriptor => "HWND";
public Action Activated { get; set; }
public Action Closed { get; set; }
public Action Deactivated { get; set; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Action<Point> PositionChanged { get; set; }
public void Activate()
{
throw new NotImplementedException();
}
public void Invalidate(Rect rect)
{
throw new NotImplementedException();
}
public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
public Point PointToClient(Point point)
{
throw new NotImplementedException();
}
public Point PointToScreen(Point point)
{
throw new NotImplementedException();
}
public void SetCursor(IPlatformHandle cursor)
{
//STUB
}
public void Show() => Native.GtkWindowPresent(_gtkWidget);
public void Hide() => Native.GtkWidgetHide(_gtkWidget);
public void BeginMoveDrag()
{
//STUB
}
public void BeginResizeDrag(WindowEdge edge)
{
//STUB
}
public Point Position { get; set; }
IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(_gtkWidget));
}
}

48
src/Gtk/Avalonia.Gtk3/WindowImpl.cs

@ -0,0 +1,48 @@
using System;
using Avalonia.Controls;
using Avalonia.Gtk3.Interop;
using Avalonia.Platform;
namespace Avalonia.Gtk3
{
class WindowImpl : TopLevelImpl, IWindowImpl
{
public WindowState WindowState { get; set; } //STUB
public void SetTitle(string title)
{
using (var t = new Utf8Buffer(title))
Native.GtkWindowSetTitle(_gtkWidget, t);
}
public IDisposable ShowDialog()
{
return null;
//STUB
}
public void SetSystemDecorations(bool enabled) => Native.GtkWindowSetDecorated(_gtkWidget, enabled);
public void SetIcon(IWindowIconImpl icon)
{
//STUB
}
public WindowImpl() : base(Native.GtkWindowNew(GtkWindowType.TopLevel))
{
}
public override Size ClientSize
{
get
{
int w, h;
Native.GtkWindowGetSize(_gtkWidget, out w, out h);
return new Size(w, h);
}
set
{
//STUB
}
}
}
}

10
src/Gtk/Avalonia.Gtk3/project.json

@ -0,0 +1,10 @@
{
"supports": {},
"dependencies": {
"Microsoft.NETCore.Portable.Compatibility": "1.0.1",
"NETStandard.Library": "1.6.0"
},
"frameworks": {
"netstandard1.1": {}
}
}
Loading…
Cancel
Save