Browse Source

Merge branch 'master' into scenegraph

 Conflicts:
	src/Avalonia.Visuals/Media/FormattedTextStyleSpan.cs
	src/Avalonia.Visuals/Media/Typeface.cs
	src/Avalonia.Visuals/Rendering/RendererMixin.cs
	src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs
	src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
	tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs
	tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
	tests/Avalonia.UnitTests/TestServices.cs
	tests/Avalonia.Visuals.UnitTests/Media/TypefaceTests.cs
Steven Kirk 8 years ago
parent
commit
ae7cf4a6ef
33 changed files with 1120 additions and 39 deletions
  1. 5 0
      .ncrunch/Avalonia.LinuxFramebuffer.v3.ncrunchproject
  2. 46 1
      Avalonia.sln
  3. 2 1
      appveyor.yml
  4. 1 1
      build.cake
  5. 19 3
      packages.cake
  6. 1 1
      readme.md
  7. 1 0
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  8. 17 3
      samples/ControlCatalog.NetCore/Program.cs
  9. 1 1
      src/Avalonia.Controls/Button.cs
  10. 3 3
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  11. 1 1
      src/Avalonia.Controls/Primitives/Thumb.cs
  12. 1 1
      src/Avalonia.Controls/TextBlock.cs
  13. 1 1
      src/Avalonia.Controls/TextBox.cs
  14. 3 1
      src/Avalonia.Controls/Window.cs
  15. 1 1
      src/Avalonia.HtmlRenderer/HtmlControl.cs
  16. 1 1
      src/Avalonia.Input/InputElement.cs
  17. 20 0
      src/Avalonia.Visuals/Media/FormattedTextStyleSpan.cs
  18. 25 1
      src/Avalonia.Visuals/Media/Typeface.cs
  19. 1 1
      src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs
  20. 14 0
      src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj
  21. 89 0
      src/Linux/Avalonia.LinuxFramebuffer/EvDevDevice.cs
  22. 68 0
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  23. 138 0
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs
  24. 75 0
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  25. 47 0
      src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs
  26. 120 0
      src/Linux/Avalonia.LinuxFramebuffer/Mice.cs
  27. 254 0
      src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs
  28. 112 0
      src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs
  29. 21 0
      src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
  30. 13 13
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  31. 2 2
      tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
  32. 15 0
      tests/Avalonia.UnitTests/TestServices.cs
  33. 2 2
      tests/Avalonia.Visuals.UnitTests/Media/TypefaceTests.cs

+ 5 - 0
.ncrunch/Avalonia.LinuxFramebuffer.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
+  </Settings>
+</ProjectConfiguration>

+ 46 - 1
Avalonia.sln

@@ -1,6 +1,6 @@
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
-VisualStudioVersion = 15.0.26228.9
+VisualStudioVersion = 15.0.26228.4
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
 EndProject
@@ -185,6 +185,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6F
 		build\UnitTests.NetCore.targets = build\UnitTests.NetCore.targets
 	EndProjectSection
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.LinuxFramebuffer", "src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj", "{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}"
+EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
@@ -2503,6 +2507,46 @@ Global
 		{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}.Release|Mono.Build.0 = Release|Any CPU
 		{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}.Release|x86.ActiveCfg = Release|Any CPU
 		{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}.Release|x86.Build.0 = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|Mono.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|Mono.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|Mono.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|Mono.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|x86.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|x86.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|Mono.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|Mono.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|x86.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Any CPU.Build.0 = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|iPhone.Build.0 = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Mono.ActiveCfg = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Mono.Build.0 = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|x86.ActiveCfg = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -2561,5 +2605,6 @@ Global
 		{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A} = {74487168-7D91-487E-BF93-055F2251461E}
 		{4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E}
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
 	EndGlobalSection
 EndGlobal

+ 2 - 1
appveyor.yml

@@ -1,6 +1,7 @@
-os: Visual Studio 2017
+os: Previous Visual Studio 2017
 platform:
 - Any CPU
+skip_branch_with_pr: true
 configuration:
 - Release
 environment:

+ 1 - 1
build.cake

@@ -4,7 +4,7 @@
 
 #addin "nuget:?package=Polly&version=4.2.0"
 #addin "nuget:?package=NuGet.Core&version=2.12.0"
-#tool "nuget:https://dotnet.myget.org/F/nuget-build/?package=NuGet.CommandLine&version=4.3.0-beta1-2361&prerelease"
+#tool "nuget:https://dotnet.myget.org/F/nuget-build/?package=NuGet.CommandLine&version=4.3.0-preview1-3980&prerelease"
 #tool "nuget:?package=JetBrains.dotMemoryUnit&version=2.1.20150828.125449"
 ///////////////////////////////////////////////////////////////////////////////
 // TOOLS

+ 19 - 3
packages.cake

@@ -198,7 +198,6 @@ public class Packages
                     new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp1.0", Version = "4.3.0" },
                     new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp1.0", Version = "1.1.0" },
                     new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netcoreapp1.0", Version = "1.6.0" },
-                    new NuSpecDependency() { Id = "Microsoft.NETCore.Portable.Compatibility", TargetFramework = "netcoreapp1.0", Version = "1.0.1" },
                     new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp1.0", Version = SplatVersion },
                     new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp1.0", Version = SerilogVersion },
                     new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp1.0", Version = SpracheVersion },
@@ -429,8 +428,7 @@ public class Packages
                     //netstandard1.3
                     new NuSpecDependency() { Id = "Avalonia", TargetFramework = "netstandard1.3", Version = parameters.Version },
                     new NuSpecDependency() { Id = "SkiaSharp", TargetFramework = "netstandard1.3", Version = SkiaSharpVersion },
-                    new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netstandard1.3", Version = "1.6.0" },
-                    new NuSpecDependency() { Id = "Microsoft.NETCore.Portable.Compatibility", TargetFramework = "netstandard1.3", Version = "1.0.1" }
+                    new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netstandard1.3", Version = "1.6.0" }
                 },
                 Files = new []
                 {
@@ -460,6 +458,24 @@ public class Packages
                 },
                 BasePath = context.Directory("./"),
                 OutputDirectory = parameters.NugetRoot
+            },
+            ///////////////////////////////////////////////////////////////////////////////
+            // Avalonia.LinuxFramebuffer
+            ///////////////////////////////////////////////////////////////////////////////
+            new NuGetPackSettings()
+            {
+                Id = "Avalonia.LinuxFramebuffer",
+                Dependencies = new []
+                {
+                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", Version = parameters.Version }
+                },
+                Files = new []
+                {
+                    new NuSpecContent { Source = "Avalonia.LinuxFramebuffer/bin/" + parameters.DirSuffix + "/netstandard1.3/Avalonia.LinuxFramebuffer.dll", Target = "lib/netstandard1.3" }
+                },
+                BasePath = context.Directory("./src/Linux/"),
+                OutputDirectory = parameters.NugetRoot
             }
         };
 

+ 1 - 1
readme.md

@@ -3,7 +3,7 @@
 
 | Gitter Chat | Windows Build Status | Linux/Mac Build Status | Code Coverage |
 |---|---|---|---|
-| [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) | [![codecov](https://codecov.io/gh/AvaloniaUI/Avalonia/branch/master/graph/badge.svg)](https://codecov.io/gh/AvaloniaUI/Avalonia) |
+| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) | [![codecov](https://codecov.io/gh/AvaloniaUI/Avalonia/branch/master/graph/badge.svg)](https://codecov.io/gh/AvaloniaUI/Avalonia) |
 
 A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android.
 

+ 1 - 0
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@@ -7,6 +7,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" />
+    <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
   </ItemGroup>
 

+ 17 - 3
samples/ControlCatalog.NetCore/Program.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Linq;
 using Avalonia;
 
 namespace ControlCatalog.NetCore
@@ -7,9 +8,22 @@ namespace ControlCatalog.NetCore
     {
         static void Main(string[] args)
         {
-            AppBuilder.Configure<App>()
-                .UsePlatformDetect()
-                .Start<MainWindow>();
+            if (args.Contains("--fbdev")) AppBuilder.Configure<App>().InitializeWithLinuxFramebuffer(tl =>
+            {
+                tl.Content = new MainView();
+                System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer());
+            });
+            else
+                AppBuilder.Configure<App>()
+                    .UsePlatformDetect()
+                    .Start<MainWindow>();
+        }
+
+        static void ConsoleSilencer()
+        {
+            Console.CursorVisible = false;
+            while (true)
+                Console.ReadKey(true);
         }
     }
 }

+ 1 - 1
src/Avalonia.Controls/Button.cs

@@ -226,7 +226,7 @@ namespace Avalonia.Controls
         }
 
         /// <inheritdoc/>
-        protected override void OnPointerReleased(PointerEventArgs e)
+        protected override void OnPointerReleased(PointerReleasedEventArgs e)
         {
             base.OnPointerReleased(e);
 

+ 3 - 3
src/Avalonia.Controls/Presenters/TextPresenter.cs

@@ -127,11 +127,11 @@ namespace Avalonia.Controls.Presenters
             base.Render(context);
 
             if (selectionStart == selectionEnd)
-            {                
+            {
                 var backgroundColor = (((Control)TemplatedParent).GetValue(BackgroundProperty) as SolidColorBrush)?.Color;
                 var caretBrush = Brushes.Black;
 
-                if(backgroundColor.HasValue)
+                if (backgroundColor.HasValue)
                 {
                     byte red = (byte)~(backgroundColor.Value.R);
                     byte green = (byte)~(backgroundColor.Value.G);
@@ -139,7 +139,7 @@ namespace Avalonia.Controls.Presenters
 
                     caretBrush = new SolidColorBrush(Color.FromRgb(red, green, blue));
                 }
-                
+
                 if (_caretBlink)
                 {
                     var charPos = FormattedText.HitTestTextPosition(CaretIndex);

+ 1 - 1
src/Avalonia.Controls/Primitives/Thumb.cs

@@ -86,7 +86,7 @@ namespace Avalonia.Controls.Primitives
             RaiseEvent(ev);
         }
 
-        protected override void OnPointerReleased(PointerEventArgs e)
+        protected override void OnPointerReleased(PointerReleasedEventArgs e)
         {
             if (_lastPoint.HasValue)
             {

+ 1 - 1
src/Avalonia.Controls/TextBlock.cs

@@ -116,7 +116,7 @@ namespace Avalonia.Controls
                 this.GetObservable(TextAlignmentProperty).Select(_ => Unit.Default),
                 this.GetObservable(FontSizeProperty).Select(_ => Unit.Default),
                 this.GetObservable(FontStyleProperty).Select(_ => Unit.Default),
-                this.GetObservable(FontWeightProperty).Select(_=>Unit.Default))
+                this.GetObservable(FontWeightProperty).Select(_ => Unit.Default))
                 .Subscribe(_ =>
                 {
                     InvalidateFormattedText();

+ 1 - 1
src/Avalonia.Controls/TextBox.cs

@@ -522,7 +522,7 @@ namespace Avalonia.Controls
             }
         }
 
-        protected override void OnPointerReleased(PointerEventArgs e)
+        protected override void OnPointerReleased(PointerReleasedEventArgs e)
         {
             if (_presenter != null && e.Device.Captured == _presenter)
             {

+ 3 - 1
src/Avalonia.Controls/Window.cs

@@ -281,7 +281,9 @@ namespace Avalonia.Controls
                 var modal = PlatformImpl.ShowDialog();
                 var result = new TaskCompletionSource<TResult>();
 
-                Observable.FromEventPattern(this, nameof(Closed))
+                Observable.FromEventPattern<EventHandler, EventArgs>(
+                    x => this.Closed += x,
+                    x => this.Closed -= x)
                     .Take(1)
                     .Subscribe(_ =>
                     {

+ 1 - 1
src/Avalonia.HtmlRenderer/HtmlControl.cs

@@ -413,7 +413,7 @@ namespace Avalonia.Controls.Html
         /// <summary>
         /// Handle mouse up to handle selection and link click. 
         /// </summary>
-        protected override void OnPointerReleased(PointerEventArgs e)
+        protected override void OnPointerReleased(PointerReleasedEventArgs e)
         {
             base.OnPointerReleased(e);
             LeftMouseButton = false;

+ 1 - 1
src/Avalonia.Input/InputElement.cs

@@ -473,7 +473,7 @@ namespace Avalonia.Input
         /// Called before the <see cref="PointerReleased"/> event occurs.
         /// </summary>
         /// <param name="e">The event args.</param>
-        protected virtual void OnPointerReleased(PointerEventArgs e)
+        protected virtual void OnPointerReleased(PointerReleasedEventArgs e)
         {
         }
 

+ 20 - 0
src/Avalonia.Visuals/Media/FormattedTextStyleSpan.cs

@@ -2,8 +2,17 @@
 
 namespace Avalonia.Media
 {
+    /// <summary>
+    /// Describes the formatting for a span of text in a <see cref="FormattedText"/> object.
+    /// </summary>
     public class FormattedTextStyleSpan
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="FormattedTextStyleSpan"/> class.
+        /// </summary>
+        /// <param name="startIndex">The index of the first character in the span.</param>
+        /// <param name="length">The length of the span.</param>
+        /// <param name="foregroundBrush">The span's foreground brush.</param>
         public FormattedTextStyleSpan(
             int startIndex,
             int length,
@@ -14,8 +23,19 @@ namespace Avalonia.Media
             ForegroundBrush = foregroundBrush;
         }
 
+        /// <summary>
+        /// Gets the index of the first character in the span.
+        /// </summary>
         public int StartIndex { get; }
+
+        /// <summary>
+        /// Gets the length of the span.
+        /// </summary>
         public int Length { get; }
+
+        /// <summary>
+        /// Gets the span's foreground brush.
+        /// </summary>
         public IBrush ForegroundBrush { get; }
     }
 }

+ 25 - 1
src/Avalonia.Visuals/Media/Typeface.cs

@@ -1,10 +1,19 @@
 using System;
-using Avalonia.Media;
 
 namespace Avalonia.Media
 {
+    /// <summary>
+    /// Represents a typeface.
+    /// </summary>
     public class Typeface
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Typeface"/> class.
+        /// </summary>
+        /// <param name="fontFamilyName">The name of the font family.</param>
+        /// <param name="fontSize">The font size, in DIPs.</param>
+        /// <param name="style">The font style.</param>
+        /// <param name="weight">The font weight.</param>
         public Typeface(
             string fontFamilyName,
             double fontSize,
@@ -27,9 +36,24 @@ namespace Avalonia.Media
             Weight = weight;
         }
 
+        /// <summary>
+        /// Gets the name of the font family.
+        /// </summary>
         public string FontFamilyName { get; }
+
+        /// <summary>
+        /// Gets the size of the font in DIPs.
+        /// </summary>
         public double FontSize { get; }
+
+        /// <summary>
+        /// Gets the font style.
+        /// </summary>
         public FontStyle Style { get; }
+
+        /// <summary>
+        /// Gets the font weight.
+        /// </summary>
         public FontWeight Weight { get; }
     }
 }

+ 1 - 1
src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs

@@ -52,7 +52,7 @@ namespace Avalonia.Cairo.Media
         public Rect GetRenderBounds(double strokeThickness)
         {
             // TODO: Calculate properly.
-			return Bounds.TransformToAABB(Transform).Inflate(strokeThickness);
+            return Bounds.TransformToAABB(Transform).Inflate(strokeThickness);
         }
 
         public IStreamGeometryContextImpl Open()

+ 14 - 0
src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>netstandard1.3</TargetFramework>
+        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    </PropertyGroup>
+    <ItemGroup>
+      <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
+      <ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" />
+      <ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" />
+      <ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
+      <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
+      <ProjectReference Include="..\..\Skia\Avalonia.Skia.Desktop.NetStandard\Avalonia.Skia.Desktop.NetStandard.csproj" />
+    </ItemGroup>
+</Project>

+ 89 - 0
src/Linux/Avalonia.LinuxFramebuffer/EvDevDevice.cs

@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    unsafe class EvDevDevice
+    {
+        private static readonly Lazy<List<EvDevDevice>> AllMouseDevices = new Lazy<List<EvDevDevice>>(()
+            => OpenMouseDevices());
+
+        private static List<EvDevDevice> OpenMouseDevices()
+        {
+            var rv = new List<EvDevDevice>();
+            foreach (var dev in Directory.GetFiles("/dev/input", "event*").Select(Open))
+            {
+                if (!dev.IsMouse)
+                    NativeUnsafeMethods.close(dev.Fd);
+                else
+                    rv.Add(dev);
+            }
+            return rv;
+        }
+
+        public static IReadOnlyList<EvDevDevice> MouseDevices => AllMouseDevices.Value;
+
+        
+        public int Fd { get; }
+        private IntPtr _dev;
+        public string Name { get; }
+        public List<EvType> EventTypes { get; private set; } = new List<EvType>();
+        public input_absinfo? AbsX { get; }
+        public input_absinfo? AbsY { get; }
+
+        public EvDevDevice(int fd, IntPtr dev)
+        {
+            Fd = fd;
+            _dev = dev;
+            Name = Marshal.PtrToStringAnsi(NativeUnsafeMethods.libevdev_get_name(_dev));
+            foreach (EvType type in Enum.GetValues(typeof(EvType)))
+            {
+                if (NativeUnsafeMethods.libevdev_has_event_type(dev, type) != 0)
+                    EventTypes.Add(type);
+            }
+            var ptr = NativeUnsafeMethods.libevdev_get_abs_info(dev, (int) AbsAxis.ABS_X);
+            if (ptr != null)
+                AbsX = *ptr;
+            ptr = NativeUnsafeMethods.libevdev_get_abs_info(dev, (int)AbsAxis.ABS_Y);
+            if (ptr != null)
+                AbsY = *ptr;
+        }
+
+        public input_event? NextEvent()
+        {
+            input_event ev;
+            if (NativeUnsafeMethods.libevdev_next_event(_dev, 2, out ev) == 0)
+                return ev;
+            return null;
+        }
+
+        public bool IsMouse => EventTypes.Contains(EvType.EV_REL);
+
+        public static EvDevDevice Open(string device)
+        {
+            var fd = NativeUnsafeMethods.open(device, 2048, 0);
+            if (fd <= 0)
+                throw new Exception($"Unable to open {device} code {Marshal.GetLastWin32Error()}");
+            IntPtr dev;
+            var rc = NativeUnsafeMethods.libevdev_new_from_fd(fd, out dev);
+            if (rc < 0)
+            {
+                NativeUnsafeMethods.close(fd);
+                throw new Exception($"Unable to initialize evdev for {device} code {Marshal.GetLastWin32Error()}");
+            }
+            return new EvDevDevice(fd, dev);
+        }
+
+
+    }
+
+    public class EvDevAxisInfo
+    {
+        public int Minimum { get; set; }
+        public int Maximum { get; set; }
+    }
+}

+ 68 - 0
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@@ -0,0 +1,68 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Platform;
+using Avalonia.Threading;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    class FramebufferToplevelImpl : IEmbeddableWindowImpl
+    {
+        private readonly LinuxFramebuffer _fb;
+        private bool _renderQueued;
+        public IInputRoot InputRoot { get; private set; }
+
+        public FramebufferToplevelImpl(LinuxFramebuffer fb)
+        {
+            _fb = fb;
+            Invalidate(default(Rect));
+            var mice = new Mice(ClientSize.Width, ClientSize.Height);
+            mice.Start();
+            mice.Event += e => Input?.Invoke(e);
+        }
+
+        public void Dispose()
+        {
+            throw new NotSupportedException();
+        }
+
+        
+        public void Invalidate(Rect rect)
+        {
+            if(_renderQueued)
+                return;
+            _renderQueued = true;
+            Dispatcher.UIThread.InvokeAsync(() =>
+            {
+                Paint?.Invoke(new Rect(default(Point), ClientSize));
+                _renderQueued = false;
+            });
+        }
+
+        public void SetInputRoot(IInputRoot inputRoot)
+        {
+            InputRoot = inputRoot;
+        }
+
+        public Point PointToClient(Point point) => point;
+
+        public Point PointToScreen(Point point) => point;
+
+        public void SetCursor(IPlatformHandle cursor)
+        {
+        }
+
+        public Size ClientSize => _fb.PixelSize;
+        public double Scaling => 1;
+        public IEnumerable<object> Surfaces => new object[] {_fb};
+        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 Closed { get; set; }
+        public event Action LostFocus;
+    }
+}

+ 138 - 0
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs

@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    public sealed unsafe class LinuxFramebuffer : IFramebufferPlatformSurface, IDisposable
+    {
+        private readonly Size _dpi;
+        private int _fd;
+        private fb_fix_screeninfo _fixedInfo;
+        private fb_var_screeninfo _varInfo;
+        private IntPtr _mappedLength;
+        private IntPtr _mappedAddress;
+
+        public LinuxFramebuffer(string fileName = null, Size? dpi = null)
+        {
+            _dpi = dpi ?? new Size(96, 96);
+            fileName = fileName ?? Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0";
+            _fd = NativeUnsafeMethods.open(fileName, 2, 0);
+            if (_fd <= 0)
+                throw new Exception("Error: " + Marshal.GetLastWin32Error());
+
+            try
+            {
+                Init();
+            }
+            catch
+            {
+                Dispose();
+                throw;
+            }
+        }
+
+        void Init()
+        {
+            fixed (void* pnfo = &_varInfo)
+            {
+                if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
+                    throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
+
+                SetBpp();
+
+                _varInfo.yoffset = 100;
+                if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo))
+                    _varInfo.transp = new fb_bitfield();
+
+                if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo))
+                    throw new Exception("FBIOPUT_VSCREENINFO error: " + Marshal.GetLastWin32Error());
+
+                if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
+                    throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
+
+                if (_varInfo.bits_per_pixel != 32)
+                    throw new Exception("Unable to set 32-bit display mode");
+            }
+            fixed(void*pnfo = &_fixedInfo)
+                if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_FSCREENINFO, pnfo))
+                    throw new Exception("FBIOGET_FSCREENINFO error: " + Marshal.GetLastWin32Error());
+
+            _mappedLength = new IntPtr(_fixedInfo.line_length * _varInfo.yres);
+            _mappedAddress = NativeUnsafeMethods.mmap(IntPtr.Zero, _mappedLength, 3, 1, _fd, IntPtr.Zero);
+            if (_mappedAddress == new IntPtr(-1))
+                throw new Exception($"Unable to mmap {_mappedLength} bytes, error {Marshal.GetLastWin32Error()}");
+            fixed (fb_fix_screeninfo* pnfo = &_fixedInfo)
+            {
+                int idlen;
+                for (idlen = 0; idlen < 16 && pnfo->id[idlen] != 0; idlen++) ;
+                Id = Encoding.ASCII.GetString(pnfo->id, idlen);
+            }
+        }
+
+        void SetBpp()
+        {
+            _varInfo.bits_per_pixel = 32;
+            _varInfo.grayscale = 0;
+            _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
+            {
+                length = 8
+            };
+            _varInfo.green.offset = 8;
+            _varInfo.blue.offset = 16;
+            _varInfo.transp.offset = 24;
+        }
+
+        public string Id { get; private set; }
+
+        public Size PixelSize
+        {
+            get
+            {
+                fb_var_screeninfo nfo;
+                if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, &nfo))
+                    throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
+                return new Size(nfo.xres, nfo.yres);
+            }
+        }
+
+        public ILockedFramebuffer Lock()
+        {
+            if (_fd <= 0)
+                throw new ObjectDisposedException("LinuxFramebuffer");
+            return new LockedFramebuffer(_fd, _fixedInfo, _varInfo, _mappedAddress, _dpi);
+        }
+
+
+        private void ReleaseUnmanagedResources()
+        {
+            if (_mappedAddress != IntPtr.Zero)
+            {
+                NativeUnsafeMethods.munmap(_mappedAddress, _mappedLength);
+                _mappedAddress = IntPtr.Zero;
+            }
+            if(_fd == 0)
+                return;
+            NativeUnsafeMethods.close(_fd);
+            _fd = 0;
+        }
+
+        public void Dispose()
+        {
+            ReleaseUnmanagedResources();
+            GC.SuppressFinalize(this);
+        }
+
+        ~LinuxFramebuffer()
+        {
+            ReleaseUnmanagedResources();
+        }
+    }
+}

+ 75 - 0
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Threading;
+using Avalonia.Controls;
+using Avalonia.Controls.Embedding;
+using Avalonia.Input;
+using Avalonia.Input.Platform;
+using Avalonia.LinuxFramebuffer;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Threading;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    class LinuxFramebufferPlatform
+    {
+        LinuxFramebuffer _fb;
+        public static KeyboardDevice KeyboardDevice = new KeyboardDevice();
+        public static MouseDevice MouseDevice = new MouseDevice();
+        private static readonly Stopwatch St = Stopwatch.StartNew();
+        internal static uint Timestamp => (uint)St.ElapsedTicks;
+        public static FramebufferToplevelImpl TopLevel;
+        LinuxFramebufferPlatform(string fbdev = null)
+        {
+            _fb = new LinuxFramebuffer(fbdev);
+        }
+
+
+        void Initialize()
+        {
+            AvaloniaLocator.CurrentMutable
+                .Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
+                .Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
+                .Bind<IMouseDevice>().ToConstant(MouseDevice)
+                .Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
+                .Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)
+                .Bind<IRenderLoop>().ToConstant(PlatformThreadingInterface.Instance);
+        }
+
+        internal static TopLevel Initialize<T>(T builder, string fbdev = null) where T : AppBuilderBase<T>, new()
+        {
+            var platform = new LinuxFramebufferPlatform(fbdev);
+            builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev")
+                .SetupWithoutStarting();
+            var tl = new EmbeddableControlRoot(TopLevel = new FramebufferToplevelImpl(platform._fb));
+            tl.Prepare();
+            return tl;
+        }
+    }
+}
+
+public static class LinuxFramebufferPlatformExtensions
+{
+    class TokenClosable : ICloseable
+    {
+        public event EventHandler Closed;
+
+        public TokenClosable(CancellationToken token)
+        {
+            token.Register(() => Dispatcher.UIThread.InvokeAsync(() => Closed?.Invoke(this, new EventArgs())));
+        }
+    }
+
+    public static void InitializeWithLinuxFramebuffer<T>(this T builder, Action<TopLevel> setup,
+        CancellationToken stop = default(CancellationToken), string fbdev = null)
+        where T : AppBuilderBase<T>, new()
+    {
+        setup(LinuxFramebufferPlatform.Initialize(builder, fbdev));
+        builder.BeforeStartCallback(builder);
+        builder.Instance.Run(new TokenClosable(stop));
+    }
+}
+

+ 47 - 0
src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Runtime.InteropServices;
+using Avalonia.Platform;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    unsafe class LockedFramebuffer : ILockedFramebuffer
+    {
+        private readonly int _fb;
+        private readonly fb_fix_screeninfo _fixedInfo;
+        private fb_var_screeninfo _varInfo;
+        private readonly IntPtr _address;
+
+        public LockedFramebuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr address, Size dpi)
+        {
+            _fb = fb;
+            _fixedInfo = fixedInfo;
+            _varInfo = varInfo;
+            _address = address;
+            Dpi = dpi;
+            //Use double buffering to avoid flicker
+            Address = Marshal.AllocHGlobal(RowBytes * Height);
+        }
+
+
+        void VSync()
+        {
+            NativeUnsafeMethods.ioctl(_fb, FbIoCtl.FBIO_WAITFORVSYNC, null);
+        }
+
+        public void Dispose()
+        {
+            VSync();
+            NativeUnsafeMethods.memcpy(_address, Address, new IntPtr(RowBytes * Height));
+
+            Marshal.FreeHGlobal(Address);
+            Address = IntPtr.Zero;
+        }
+
+        public IntPtr Address { get; private set; }
+        public int Width => (int)_varInfo.xres;
+        public int Height => (int) _varInfo.yres;
+        public int RowBytes => (int) _fixedInfo.line_length;
+        public Size Dpi { get; }
+        public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888;
+    }
+}

+ 120 - 0
src/Linux/Avalonia.LinuxFramebuffer/Mice.cs

@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Platform;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    public unsafe class Mice
+    {
+        private readonly double _width;
+        private readonly double _height;
+        private double _x;
+        private double _y;
+
+        public event Action<RawInputEventArgs> Event;
+
+        public Mice(double width, double height)
+        {
+            _width = width;
+            _height = height;
+        }
+
+        public void Start() => AvaloniaLocator.Current.GetService<IRuntimePlatform>().PostThreadPoolItem(Worker);
+
+        private void Worker()
+        {
+
+            var mouseDevices = EvDevDevice.MouseDevices.Where(d => d.IsMouse).ToList();
+            if (mouseDevices.Count == 0)
+                return;
+            var are = new AutoResetEvent(false);
+            while (true)
+            {
+                try
+                {
+                    var rfds = new fd_set {count = mouseDevices.Count};
+                    for (int c = 0; c < mouseDevices.Count; c++)
+                        rfds.fds[c] = mouseDevices[c].Fd;
+                    IntPtr* timeval = stackalloc IntPtr[2];
+                    timeval[0] = new IntPtr(0);
+                    timeval[1] = new IntPtr(100);
+                    are.WaitOne(30);
+                    foreach (var dev in mouseDevices)
+                    {
+                        while(true)
+                        {
+                            var ev = dev.NextEvent();
+                            if (!ev.HasValue)
+                                break;
+
+                            PlatformThreadingInterface.Instance.Send(() => ProcessEvent(dev, ev.Value));
+                        } 
+                    }
+                }
+                catch (Exception e)
+                {
+                    Console.Error.WriteLine(e.ToString());
+                }
+            }
+        }
+
+        static double TranslateAxis(input_absinfo axis, int value, double max)
+        {
+            return (value - axis.minimum) / (double) (axis.maximum - axis.minimum) * max;
+        }
+
+        private void ProcessEvent(EvDevDevice device, input_event ev)
+        {
+            if (ev.type == (short)EvType.EV_REL)
+            {
+                if (ev.code == (short) AxisEventCode.REL_X)
+                    _x = Math.Min(_width, Math.Max(0, _x + ev.value));
+                else if (ev.code == (short) AxisEventCode.REL_Y)
+                    _y = Math.Min(_height, Math.Max(0, _y + ev.value));
+                else
+                    return;
+                Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice,
+                    LinuxFramebufferPlatform.Timestamp,
+                    LinuxFramebufferPlatform.TopLevel.InputRoot, RawMouseEventType.Move, new Point(_x, _y),
+                    InputModifiers.None));
+            }
+            if (ev.type ==(int) EvType.EV_ABS)
+            {
+                if (ev.code == (short) AbsAxis.ABS_X && device.AbsX.HasValue)
+                    _x = TranslateAxis(device.AbsX.Value, ev.value, _width);
+                else if (ev.code == (short) AbsAxis.ABS_Y && device.AbsY.HasValue)
+                    _y = TranslateAxis(device.AbsY.Value, ev.value, _height);
+                else
+                    return;
+                Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice,
+                    LinuxFramebufferPlatform.Timestamp,
+                    LinuxFramebufferPlatform.TopLevel.InputRoot, RawMouseEventType.Move, new Point(_x, _y),
+                    InputModifiers.None));
+            }
+            if (ev.type == (short) EvType.EV_KEY)
+            {
+                RawMouseEventType? type = null;
+                if (ev.code == (ushort) EvKey.BTN_LEFT)
+                    type = ev.value == 1 ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp;
+                if (ev.code == (ushort)EvKey.BTN_RIGHT)
+                    type = ev.value == 1 ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp;
+                if (ev.code == (ushort) EvKey.BTN_MIDDLE)
+                    type = ev.value == 1 ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp;
+                if (!type.HasValue)
+                    return;
+
+                Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice,
+                    LinuxFramebufferPlatform.Timestamp,
+                    LinuxFramebufferPlatform.TopLevel.InputRoot, type.Value, new Point(_x, _y), default(InputModifiers)));
+            }
+        }
+    }
+}

+ 254 - 0
src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs

@@ -0,0 +1,254 @@
+using __u32 = System.UInt32;
+using __s32 = System.Int32;
+using __u16 = System.UInt16;
+using System;
+using System.Runtime.InteropServices;
+// ReSharper disable FieldCanBeMadeReadOnly.Local
+// ReSharper disable ArrangeTypeMemberModifiers
+// ReSharper disable BuiltInTypeReferenceStyle
+// ReSharper disable InconsistentNaming
+
+namespace Avalonia.LinuxFramebuffer
+{
+    unsafe class NativeUnsafeMethods
+    {
+        [DllImport("libc", EntryPoint = "open", SetLastError = true)]
+        public static extern int open(string pathname, int flags, int mode);
+
+        [DllImport("libc", EntryPoint = "close", SetLastError = true)]
+        public static extern int close(int fd);
+
+        [DllImport("libc", EntryPoint = "ioctl", SetLastError = true)]
+        public static extern int ioctl(int fd, FbIoCtl code, void* arg);
+
+        [DllImport("libc", EntryPoint = "mmap", SetLastError = true)]
+        public static extern IntPtr mmap(IntPtr addr, IntPtr length, int prot, int flags,
+                  int fd, IntPtr offset);
+        [DllImport("libc", EntryPoint = "munmap", SetLastError = true)]
+        public static extern int munmap(IntPtr addr, IntPtr length);
+
+        [DllImport("libc", EntryPoint = "memcpy", SetLastError = true)]
+        public static extern int memcpy(IntPtr dest, IntPtr src, IntPtr length);
+
+        [DllImport("libc", EntryPoint = "select", SetLastError = true)]
+        public static extern int select(int nfds, void* rfds, void* wfds, void* exfds, IntPtr* timevals);
+
+        [DllImport("libevdev.so.2", EntryPoint = "libevdev_new_from_fd", SetLastError = true)]
+        public static extern int libevdev_new_from_fd(int fd, out IntPtr dev);
+
+        [DllImport("libevdev.so.2", EntryPoint = "libevdev_has_event_type", SetLastError = true)]
+        public static extern int libevdev_has_event_type(IntPtr dev, EvType type);
+
+        [DllImport("libevdev.so.2", EntryPoint = "libevdev_next_event", SetLastError = true)]
+        public static extern int libevdev_next_event(IntPtr dev, int flags, out input_event ev);
+
+        [DllImport("libevdev.so.2", EntryPoint = "libevdev_get_name", SetLastError = true)]
+        public static extern IntPtr libevdev_get_name(IntPtr dev);
+        [DllImport("libevdev.so.2", EntryPoint = "libevdev_get_abs_info", SetLastError = true)]
+        public static extern input_absinfo* libevdev_get_abs_info(IntPtr dev, int code);
+    }
+
+    enum FbIoCtl : uint
+    {
+        FBIOGET_VSCREENINFO = 0x4600,
+        FBIOPUT_VSCREENINFO = 0x4601,
+        FBIOGET_FSCREENINFO = 0x4602,
+        FBIOGET_VBLANK = 0x80204612u,
+        FBIO_WAITFORVSYNC = 0x40044620,
+        FBIOPAN_DISPLAY = 0x4606
+    }
+
+    [Flags]
+    enum VBlankFlags
+    {
+        FB_VBLANK_VBLANKING = 0x001 /* currently in a vertical blank */,
+        FB_VBLANK_HBLANKING = 0x002 /* currently in a horizontal blank */,
+        FB_VBLANK_HAVE_VBLANK = 0x004 /* vertical blanks can be detected */,
+        FB_VBLANK_HAVE_HBLANK = 0x008 /* horizontal blanks can be detected */,
+        FB_VBLANK_HAVE_COUNT = 0x010 /* global retrace counter is available */,
+        FB_VBLANK_HAVE_VCOUNT = 0x020 /* the vcount field is valid */,
+        FB_VBLANK_HAVE_HCOUNT = 0x040 /* the hcount field is valid */,
+        FB_VBLANK_VSYNCING = 0x080 /* currently in a vsync */,
+        FB_VBLANK_HAVE_VSYNC = 0x100 /* verical syncs can be detected */
+    }
+
+    unsafe struct fb_vblank {
+        public VBlankFlags flags;			/* FB_VBLANK flags */
+        __u32 count;			/* counter of retraces since boot */
+        __u32 vcount;			/* current scanline position */
+        __u32 hcount;			/* current scandot position */
+        fixed __u32 reserved[4];		/* reserved for future compatibility */
+    };
+
+    [StructLayout(LayoutKind.Sequential)]
+    unsafe struct fb_fix_screeninfo
+    {
+        public fixed byte id[16]; /* identification string eg "TT Builtin" */
+
+        public IntPtr smem_start; /* Start of frame buffer mem */
+
+        /* (physical address) */
+        public __u32 smem_len; /* Length of frame buffer mem */
+
+        public __u32 type; /* see FB_TYPE_*		*/
+        public __u32 type_aux; /* Interleave for interleaved Planes */
+        public __u32 visual; /* see FB_VISUAL_*		*/
+        public __u16 xpanstep; /* zero if no hardware panning  */
+        public __u16 ypanstep; /* zero if no hardware panning  */
+        public __u16 ywrapstep; /* zero if no hardware ywrap    */
+        public __u32 line_length; /* length of a line in bytes    */
+
+        public IntPtr mmio_start; /* Start of Memory Mapped I/O   */
+
+        /* (physical address) */
+        public __u32 mmio_len; /* Length of Memory Mapped I/O  */
+
+        public __u32 accel; /* Type of acceleration available */
+        public fixed __u16 reserved[3]; /* Reserved for future compatibility */
+    };
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct fb_bitfield
+    {
+        public __u32 offset; /* beginning of bitfield	*/
+        public __u32 length; /* length of bitfield		*/
+
+        public __u32 msb_right; /* != 0 : Most significant bit is */
+        /* right */
+    };
+
+    [StructLayout(LayoutKind.Sequential)]
+    unsafe struct fb_var_screeninfo
+    {
+        public __u32 xres; /* visible resolution		*/
+        public __u32 yres;
+        public __u32 xres_virtual; /* virtual resolution		*/
+        public __u32 yres_virtual;
+        public __u32 xoffset; /* offset from virtual to visible */
+        public __u32 yoffset; /* resolution			*/
+
+        public __u32 bits_per_pixel; /* guess what			*/
+        public __u32 grayscale; /* != 0 Graylevels instead of colors */
+
+        public fb_bitfield red; /* bitfield in fb mem if true color, */
+        public fb_bitfield green; /* else only length is significant */
+        public fb_bitfield blue;
+        public fb_bitfield transp; /* transparency			*/
+
+        public __u32 nonstd; /* != 0 Non standard pixel format */
+
+        public __u32 activate; /* see FB_ACTIVATE_*		*/
+
+        public __u32 height; /* height of picture in mm    */
+        public __u32 width; /* width of picture in mm     */
+
+        public __u32 accel_flags; /* acceleration flags (hints)	*/
+
+        /* Timing: All values in pixclocks, except pixclock (of course) */
+        public __u32 pixclock; /* pixel clock in ps (pico seconds) */
+
+        public __u32 left_margin; /* time from sync to picture	*/
+        public __u32 right_margin; /* time from picture to sync	*/
+        public __u32 upper_margin; /* time from sync to picture	*/
+        public __u32 lower_margin;
+        public __u32 hsync_len; /* length of horizontal sync	*/
+        public __u32 vsync_len; /* length of vertical sync	*/
+        public __u32 sync; /* see FB_SYNC_*		*/
+        public __u32 vmode; /* see FB_VMODE_*		*/
+        public fixed __u32 reserved[6]; /* Reserved for future compatibility */
+    };
+
+
+    enum EvType
+    {
+        EV_SYN = 0x00,
+        EV_KEY = 0x01,
+        EV_REL = 0x02,
+        EV_ABS = 0x03,
+        EV_MSC = 0x04,
+        EV_SW = 0x05,
+        EV_LED = 0x11,
+        EV_SND = 0x12,
+        EV_REP = 0x14,
+        EV_FF = 0x15,
+        EV_PWR = 0x16,
+        EV_FF_STATUS = 0x17,
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct input_event
+    {
+        private IntPtr crap1, crap2;
+        public ushort type, code;
+        public int value;
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    unsafe struct fd_set
+    {
+        public int count;
+        public fixed int fds [256];
+    }
+
+    enum AxisEventCode
+    {
+        REL_X = 0x00,
+        REL_Y = 0x01,
+        REL_Z = 0x02,
+        REL_RX = 0x03,
+        REL_RY = 0x04,
+        REL_RZ = 0x05,
+        REL_HWHEEL = 0x06,
+        REL_DIAL = 0x07,
+        REL_WHEEL = 0x08,
+        REL_MISC = 0x09,
+        REL_MAX = 0x0f
+    }
+
+    enum AbsAxis
+    {
+        ABS_X = 0x00,
+        ABS_Y = 0x01,
+        ABS_Z = 0x02,
+        ABS_RX = 0x03,
+        ABS_RY = 0x04,
+        ABS_RZ = 0x05,
+        ABS_THROTTLE = 0x06,
+        ABS_RUDDER = 0x07,
+        ABS_WHEEL = 0x08,
+        ABS_GAS = 0x09,
+        ABS_BRAKE = 0x0a,
+        ABS_HAT0X = 0x10,
+        ABS_HAT0Y = 0x11,
+        ABS_HAT1X = 0x12,
+        ABS_HAT1Y = 0x13,
+        ABS_HAT2X = 0x14,
+        ABS_HAT2Y = 0x15,
+        ABS_HAT3X = 0x16,
+        ABS_HAT3Y = 0x17,
+        ABS_PRESSURE = 0x18,
+        ABS_DISTANCE = 0x19,
+        ABS_TILT_X = 0x1a,
+        ABS_TILT_Y = 0x1b,
+        ABS_TOOL_WIDTH = 0x1c
+    }
+
+    enum EvKey
+    {
+        BTN_LEFT = 0x110,
+        BTN_RIGHT = 0x111,
+        BTN_MIDDLE = 0x112
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct input_absinfo
+    {
+        public __s32 value;
+        public __s32 minimum;
+        public __s32 maximum;
+        public __s32 fuzz;
+        public __s32 flat;
+        public __s32 resolution;
+
+    }
+}

+ 112 - 0
src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs

@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    class PlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop
+    {
+        public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface();
+
+        public PlatformThreadingInterface()
+        {
+            TlsCurrentThreadIsLoopThread = true;
+            StartTimer(new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs()));
+        }
+
+        private readonly AutoResetEvent _signaled = new AutoResetEvent(false);
+        private readonly AutoResetEvent _queued = new AutoResetEvent(false);
+
+        private readonly Queue<Action> _actions = new Queue<Action>();
+
+        public void RunLoop(CancellationToken cancellationToken)
+        {
+            var handles = new[] {_signaled, _queued};
+            while (true)
+            {
+                if (0 == WaitHandle.WaitAny(handles))
+                    Signaled?.Invoke();
+                else
+                {
+                    while (true)
+                    {
+                        Action item;
+                        lock(_actions)
+                            if (_actions.Count == 0)
+                                break;
+                            else
+                                item = _actions.Dequeue();
+                        item();
+                    }
+                }
+            }
+        }
+
+        public void Send(Action cb)
+        {
+            lock (_actions)
+            {
+                _actions.Enqueue(cb);
+                _queued.Set();
+            }
+        }
+
+        class WatTimer : IDisposable
+        {
+            private readonly IDisposable _timer;
+            private GCHandle _handle;
+
+            public WatTimer(IDisposable timer)
+            {
+                _timer = timer;
+                _handle = GCHandle.Alloc(_timer);
+            }
+            public void Dispose()
+            {
+                _handle.Free();
+                _timer.Dispose();
+            }
+        }
+
+        public IDisposable StartTimer(TimeSpan interval, Action tick)
+        {
+            return new WatTimer(new System.Threading.Timer(delegate
+            {
+                var tcs = new TaskCompletionSource<int>();
+                Send(() =>
+                {
+                    try
+                    {
+                        tick();
+                    }
+                    finally
+                    {
+                        tcs.SetResult(0);
+                    }
+                });
+
+
+                tcs.Task.Wait();
+            }, null, TimeSpan.Zero, interval));
+
+
+        }
+
+        public void Signal()
+        {
+            _signaled.Set();
+        }
+
+        [ThreadStatic] private static bool TlsCurrentThreadIsLoopThread;
+
+        public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread;
+        public event Action Signaled;
+        public event EventHandler<EventArgs> Tick;
+
+    }
+}

+ 21 - 0
src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Input;
+using Avalonia.Platform;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    internal class CursorFactoryStub : IStandardCursorFactory
+    {
+        public IPlatformHandle GetCursor(StandardCursorType cursorType)
+        {
+            return new PlatformHandle(IntPtr.Zero, null);
+        }
+    }
+    internal class PlatformSettings : IPlatformSettings
+    {
+        public Size DoubleClickSize { get; } = new Size(4, 4);
+        public TimeSpan DoubleClickTime { get; } = new TimeSpan(0, 0, 0, 0, 500);
+    }
+}

+ 13 - 13
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@@ -39,19 +39,6 @@ namespace Avalonia.Direct2D1
 
         private static readonly SharpDX.WIC.ImagingFactory s_imagingFactory = new SharpDX.WIC.ImagingFactory();
 
-        public static void Initialize()
-        {
-            AvaloniaLocator.CurrentMutable
-                .Bind<IPlatformRenderInterface>().ToConstant(s_instance)
-                .Bind<SharpDX.Direct2D1.Factory>().ToConstant(s_d2D1Factory)
-                .Bind<SharpDX.Direct2D1.Factory1>().ToConstant(s_d2D1Factory)
-                .BindToSelf(s_dwfactory)
-                .BindToSelf(s_imagingFactory)
-                .BindToSelf(s_dxgiDevice)
-                .BindToSelf(s_d2D1Device);
-            SharpDX.Configuration.EnableReleaseOnFinalizer = true;
-        }
-
         private static readonly SharpDX.DXGI.Device s_dxgiDevice;
 
         private static readonly SharpDX.Direct2D1.Device s_d2D1Device;
@@ -83,6 +70,19 @@ namespace Avalonia.Direct2D1
             }
         }
 
+        public static void Initialize()
+        {
+            AvaloniaLocator.CurrentMutable
+                .Bind<IPlatformRenderInterface>().ToConstant(s_instance)
+                .Bind<SharpDX.Direct2D1.Factory>().ToConstant(s_d2D1Factory)
+                .Bind<SharpDX.Direct2D1.Factory1>().ToConstant(s_d2D1Factory)
+                .BindToSelf(s_dwfactory)
+                .BindToSelf(s_imagingFactory)
+                .BindToSelf(s_dxgiDevice)
+                .BindToSelf(s_d2D1Device);
+            SharpDX.Configuration.EnableReleaseOnFinalizer = true;
+        }
+
         public IBitmapImpl CreateBitmap(int width, int height)
         {
             return new WicBitmapImpl(s_imagingFactory, width, height);

+ 2 - 2
tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs

@@ -163,7 +163,7 @@ namespace Avalonia.Layout.UnitTests
         {
             var globalStyles = new Mock<IGlobalStyles>();
             var renderInterface = new Mock<IPlatformRenderInterface>();
-            renderInterface.Setup(x => 
+            renderInterface.Setup(x =>
                 x.CreateFormattedText(
                     It.IsAny<string>(),
                     It.IsAny<Typeface>(),
@@ -172,7 +172,7 @@ namespace Avalonia.Layout.UnitTests
                     It.IsAny<Size>(),
                     It.IsAny<IReadOnlyList<FormattedTextStyleSpan>>()))
                 .Returns(new FormattedTextMock("TEST"));
-                
+
             var windowImpl = new Mock<IWindowImpl>();
 
             Size clientSize = default(Size);

+ 15 - 0
tests/Avalonia.UnitTests/TestServices.cs

@@ -13,6 +13,7 @@ using Avalonia.Styling;
 using Avalonia.Themes.Default;
 using Avalonia.Rendering;
 using System.Reactive.Concurrency;
+using System.Collections.Generic;
 
 namespace Avalonia.UnitTests
 {
@@ -165,5 +166,19 @@ namespace Avalonia.UnitTests
 
             return result;
         }
+
+        private static IPlatformRenderInterface CreateRenderInterfaceMock()
+        {
+            return Mock.Of<IPlatformRenderInterface>(x => 
+                x.CreateFormattedText(
+                    It.IsAny<string>(),
+                    It.IsAny<Typeface>(),
+                    It.IsAny<TextAlignment>(),
+                    It.IsAny<TextWrapping>(),
+                    It.IsAny<Size>(),
+                    It.IsAny<IReadOnlyList<FormattedTextStyleSpan>>()) == Mock.Of<IFormattedTextImpl>() &&
+                x.CreateStreamGeometry() == Mock.Of<IStreamGeometryImpl>(
+                    y => y.Open() == Mock.Of<IStreamGeometryContextImpl>()));
+        }
     }
 }

+ 2 - 2
tests/Avalonia.Visuals.UnitTests/Media/TypefaceTests.cs

@@ -7,13 +7,13 @@ namespace Avalonia.Visuals.UnitTests.Media
     public class TypefaceTests
     {
         [Fact]
-        public void Exception_Should_Be_Thrown_If_FontSize_0()
+        public void Exception_Should_Be_Thrown_If_FontSize_LessThanEqualTo_0()
         {
             Assert.Throws<ArgumentException>(() => new Typeface("foo", 0));
         }
 
         [Fact]
-        public void Exception_Should_Be_Thrown_If_FontWeight_0()
+        public void Exception_Should_Be_Thrown_If_FontWeight_LessThanEqualTo_0()
         {
             Assert.Throws<ArgumentException>(() => new Typeface("foo", 12, weight: 0));
         }