Browse Source

Initial effort on Remote

Nikita Tsukanov 8 years ago
parent
commit
a2551d505b

+ 89 - 1
Avalonia.sln

@@ -1,6 +1,6 @@
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
-VisualStudioVersion = 15.0.26228.4
+VisualStudioVersion = 15.0.26730.3
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
 EndProject
@@ -185,6 +185,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "s
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests", "tests\Avalonia.RenderTests\Avalonia.Skia.RenderTests.csproj", "{E1582370-37B3-403C-917F-8209551B1634}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Remote.Protocol", "src\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj", "{D78A720C-C0C6-478B-8564-F167F9BDD01B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteTest", "samples\RemoteTest\RemoteTest.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}"
+EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@@ -2514,6 +2518,86 @@ Global
 		{E1582370-37B3-403C-917F-8209551B1634}.Release|Mono.Build.0 = Release|Any CPU
 		{E1582370-37B3-403C-917F-8209551B1634}.Release|x86.ActiveCfg = Release|Any CPU
 		{E1582370-37B3-403C-917F-8209551B1634}.Release|x86.Build.0 = Release|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|Mono.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|Mono.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|Mono.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|Mono.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|x86.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|x86.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|Mono.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|Mono.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|x86.Build.0 = Debug|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|iPhone.Build.0 = Release|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|Mono.ActiveCfg = Release|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|Mono.Build.0 = Release|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|x86.ActiveCfg = Release|Any CPU
+		{D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|x86.Build.0 = Release|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|Mono.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|Mono.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|Mono.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|Mono.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|x86.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|x86.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|Mono.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|Mono.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|x86.Build.0 = Debug|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|iPhone.Build.0 = Release|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Mono.ActiveCfg = Release|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Mono.Build.0 = Release|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|x86.ActiveCfg = Release|Any CPU
+		{E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -2571,5 +2655,9 @@ Global
 		{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
 		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
 		{E1582370-37B3-403C-917F-8209551B1634} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+		{E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {1E8CA5AA-707A-4C57-A682-D265A49E10C3}
 	EndGlobalSection
 EndGlobal

+ 12 - 0
samples/RemoteTest/Program.cs

@@ -0,0 +1,12 @@
+using System;
+
+namespace RemoteTest
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            Console.WriteLine("Hello World!");
+        }
+    }
+}

+ 25 - 0
samples/RemoteTest/RemoteTest.csproj

@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
+    <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
+  </ItemGroup>
+
+</Project>

+ 1 - 0
src/Avalonia.Controls/Avalonia.Controls.csproj

@@ -36,6 +36,7 @@
     <ProjectReference Include="..\Avalonia.Input\Avalonia.Input.csproj" />
     <ProjectReference Include="..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
     <ProjectReference Include="..\Avalonia.Layout\Avalonia.Layout.csproj" />
+    <ProjectReference Include="..\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj" />
     <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
     <ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
   </ItemGroup>

+ 63 - 0
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs

@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Layout;
+using Avalonia.Styling;
+
+namespace Avalonia.Controls.Embedding.Offscreen
+{
+    class OffscreenTopLevel : TopLevel, IStyleable
+    {
+        public OffscreenTopLevelImplBase Impl { get; }
+
+        public OffscreenTopLevel(OffscreenTopLevelImplBase impl) : base(impl)
+        {
+            Impl = impl;
+            Prepare();
+        }
+
+        public void Prepare()
+        {
+            EnsureInitialized();
+            ApplyTemplate();
+            LayoutManager.Instance.ExecuteInitialLayoutPass(this);
+        }
+
+        private void EnsureInitialized()
+        {
+            if (!this.IsInitialized)
+            {
+                var init = (ISupportInitialize)this;
+                init.BeginInit();
+                init.EndInit();
+            }
+        }
+        
+        private readonly NameScope _nameScope = new NameScope();
+        public event EventHandler<NameScopeEventArgs> Registered
+        {
+            add { _nameScope.Registered += value; }
+            remove { _nameScope.Registered -= value; }
+        }
+
+        public event EventHandler<NameScopeEventArgs> Unregistered
+        {
+            add { _nameScope.Unregistered += value; }
+            remove { _nameScope.Unregistered -= value; }
+        }
+
+        public void Register(string name, object element) => _nameScope.Register(name, element);
+
+        public object Find(string name) => _nameScope.Find(name);
+
+        public void Unregister(string name) => _nameScope.Unregister(name);
+
+        Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
+        public void Dispose()
+        {
+            PlatformImpl.Dispose();
+        }
+    }
+}

+ 65 - 0
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Platform;
+
+namespace Avalonia.Controls.Embedding.Offscreen
+{
+    abstract class OffscreenTopLevelImplBase : ITopLevelImpl
+    {
+        private double _scaling;
+        private Size _clientSize;
+        public IInputRoot InputRoot { get; private set; }
+
+        public virtual void Dispose()
+        {
+            //No-op
+        }
+
+        public abstract void Invalidate(Rect rect);
+        public abstract IEnumerable<object> Surfaces { get; }
+
+        public Size ClientSize
+        {
+            get { return _clientSize; }
+            set
+            {
+                _clientSize = value;
+                Resized?.Invoke(value);
+            }
+        }
+
+        public double Scaling
+        {
+            get { return _scaling; }
+            set
+            {
+                _scaling = value;
+                ScalingChanged?.Invoke(value);
+            }
+        }
+        
+        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 void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot;
+
+        public virtual Point PointToClient(Point point) => point;
+
+        public Point PointToScreen(Point point)
+        {
+            throw new NotImplementedException();
+        }
+
+        public virtual void SetCursor(IPlatformHandle cursor)
+        {
+        }
+
+        public Action Closed { get; set; }
+    }
+}

+ 21 - 0
src/Avalonia.Controls/Remote/RemoteServer.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Remote.Protocol;
+
+namespace Avalonia.Controls.Remote
+{
+    public class RemoteServer
+    {
+        private readonly IAvaloniaRemoteTransport _transport;
+
+        public RemoteServer(IAvaloniaRemoteTransport transport)
+        {
+            _transport = transport;
+        }
+
+        public object Content { get; set; }
+    }
+}

+ 101 - 0
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Controls.Embedding.Offscreen;
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
+using Avalonia.Remote.Protocol;
+using Avalonia.Remote.Protocol.Viewport;
+using Avalonia.Threading;
+using PixelFormat = Avalonia.Platform.PixelFormat;
+using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
+
+namespace Avalonia.Controls.Remote.Server
+{
+    class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface
+    {
+        private readonly IAvaloniaRemoteTransport _transport;
+        private LockedFramebuffer _framebuffer;
+        private object _lock = new object();
+        private long _lastSentFrame;
+        private long _lastReceivedFrame = -1;
+        private bool _invalidated;
+        private ProtocolPixelFormat[] _supportedFormats;
+
+        public RemoteServerTopLevelImpl(IAvaloniaRemoteTransport transport)
+        {
+            _transport = transport;
+            _transport.OnMessage += OnMessage;
+        }
+
+        private void OnMessage(object obj)
+        {
+            lock (_lock)
+            {
+                var lastFrame = obj as FrameReceivedMessage;
+                if (lastFrame != null)
+                {
+                    lock (_lock)
+                    {
+                        _lastReceivedFrame = lastFrame.SequenceId;
+                    }
+                    Dispatcher.UIThread.InvokeAsync(CheckNeedsRender);
+                }
+                var supportedFormats = obj as ClientSupportedPixelFormatsMessage;
+                if (supportedFormats != null)
+                    _supportedFormats = supportedFormats.Formats;
+            }
+        }
+
+        public override IEnumerable<object> Surfaces => new[] { this };
+        
+        FrameMessage RenderFrame(int width, int height, Size dpi, ProtocolPixelFormat? format)
+        {
+            var fmt = format ?? ProtocolPixelFormat.Rgba8888;
+            var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4;
+            var data = new byte[width * height * bpp];
+            var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
+            try
+            {
+                _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), width, height, width * bpp, dpi, (PixelFormat)fmt,
+                    null);
+                Paint?.Invoke(new Rect(0, 0, width, height));
+            }
+            finally
+            {
+                _framebuffer = null;
+                handle.Free();
+            }
+            return new FrameMessage();
+        }
+
+        public ILockedFramebuffer Lock()
+        {
+            if (_framebuffer == null)
+                throw new InvalidOperationException("Paint was not requested, wait for Paint event");
+            return _framebuffer;
+        }
+
+        void CheckNeedsRender()
+        {
+            ProtocolPixelFormat[] formats;
+            lock (_lock)
+            {
+                if (_lastReceivedFrame != _lastSentFrame && !_invalidated)
+                    return;
+                formats = _supportedFormats;
+            }
+            
+            //var frame = RenderFrame()
+        }
+
+        public override void Invalidate(Rect rect)
+        {
+            _invalidated = true;
+            Dispatcher.UIThread.InvokeAsync(CheckNeedsRender);
+        }
+    }
+}

+ 4 - 1
src/Avalonia.Input/Key.cs

@@ -1,7 +1,10 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
-
+#if AVALONIA_REMOTE_PROTOCOL
+namespace Avalonia.Remote.Protocol.Input
+#else
 namespace Avalonia.Input
+#endif
 {
     /// <summary>
     /// Defines the keys available on a keyboard.

+ 9 - 0
src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj

@@ -0,0 +1,9 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>netstandard1.3</TargetFramework>
+        <DefineConstants>AVALONIA_REMOTE_PROTOCOL;$(DefineConstants)</DefineConstants>
+    </PropertyGroup>
+    <ItemGroup>
+        <Compile Include="..\Avalonia.Input\Key.cs" />
+    </ItemGroup>
+</Project>

+ 19 - 0
src/Avalonia.Remote.Protocol/AvaloniaRemoteMessageGuidAttribute.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Remote.Protocol
+{
+    [AttributeUsage(AttributeTargets.Class)]
+    public class AvaloniaRemoteMessageGuidAttribute : Attribute
+    {
+        public Guid Guid { get; }
+
+        public AvaloniaRemoteMessageGuidAttribute(string guid)
+        {
+            Guid = Guid.Parse(guid);
+        }
+    }
+}

+ 35 - 0
src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Remote.Protocol
+{
+    public class DefaultMessageTypeResolver : IMessageTypeResolver
+    {
+        private readonly Dictionary<Guid, Type> _guidsToTypes = new Dictionary<Guid, Type>();
+        private readonly Dictionary<Type, Guid> _typesToGuids = new Dictionary<Type, Guid>();
+        public DefaultMessageTypeResolver(params Assembly[] assemblies)
+        {
+            foreach (var asm in
+                (assemblies ?? new Assembly[0]).Concat(new[]
+                    {typeof(AvaloniaRemoteMessageGuidAttribute).GetTypeInfo().Assembly}))
+            {
+                foreach (var t in asm.ExportedTypes)
+                {
+                    var attr = t.GetTypeInfo().GetCustomAttribute<AvaloniaRemoteMessageGuidAttribute>();
+                    if (attr != null)
+                    {
+                        _guidsToTypes[attr.Guid] = t;
+                        _typesToGuids[t] = attr.Guid;
+                    }
+                }
+            }
+        }
+
+        public Type GetByGuid(Guid id) => _guidsToTypes[id];
+        public Guid GetGuid(Type type) => _typesToGuids[type];
+    }
+}

+ 10 - 0
src/Avalonia.Remote.Protocol/IMessageTypeResolver.cs

@@ -0,0 +1,10 @@
+using System;
+
+namespace Avalonia.Remote.Protocol
+{
+    public interface IMessageTypeResolver
+    {
+        Type GetByGuid(Guid id);
+        Guid GetGuid(Type type);
+    }
+}

+ 15 - 0
src/Avalonia.Remote.Protocol/ITransport.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Remote.Protocol
+{
+    public interface IAvaloniaRemoteTransport
+    {
+        Task Send(object data);
+        event Action<object> OnMessage;
+        event Action<Exception> OnException;
+    }
+}

+ 82 - 0
src/Avalonia.Remote.Protocol/InputMessages.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+/*
+ We are keeping copies of core events here, so they can be used 
+ without referencing Avalonia itself, e. g. from projects that
+ are using WPF, GTK#, etc
+ */
+namespace Avalonia.Remote.Protocol.Input
+{
+    /// <summary>
+    /// Keep this in sync with InputModifiers in the main library
+    /// </summary>
+    [Flags]
+    public enum InputModifiers
+    {
+        Alt,
+        Control,
+        Shift,
+        Windows,
+        LeftMouseButton,
+        RightMouseButton,
+        MiddleMouseButton
+    }
+
+    /// <summary>
+    /// Keep this in sync with InputModifiers in the main library
+    /// </summary>
+    public enum MouseButton
+    {
+        None,
+        Left,
+        Right,
+        Middle
+    }
+
+    public abstract class InputEventMessageBase
+    {
+        public InputModifiers[] Modifiers { get; set; }
+    }
+
+    public abstract class PointerEventMessageBase : InputEventMessageBase
+    {
+        public double X { get; set; }
+        public double Y { get; set; }
+    }
+
+    [AvaloniaRemoteMessageGuid("6228F0B9-99F2-4F62-A621-414DA2881648")]
+    public class PointerMovedEventMessage : PointerEventMessageBase
+    {
+        
+    }
+
+    [AvaloniaRemoteMessageGuid("7E9E2818-F93F-411A-800E-6B1AEB11DA46")]
+    public class PointerPressedEventMessage : PointerEventMessageBase
+    {
+        public MouseButton Button { get; set; }
+    }
+    
+    [AvaloniaRemoteMessageGuid("4ADC84EE-E7C8-4BCF-986C-DE3A2F78EDE4")]
+    public class PointerReleasedEventMessage : PointerEventMessageBase
+    {
+        public MouseButton Button { get; set; }
+    }
+
+    [AvaloniaRemoteMessageGuid("79301A05-F02D-4B90-BB39-472563B504AE")]
+    public class ScrollEventMessage : PointerEventMessageBase
+    {
+        public double DeltaX { get; set; }
+        public double DeltaY { get; set; }
+    }
+
+    [AvaloniaRemoteMessageGuid("1C3B691E-3D54-4237-BFB0-9FEA83BC1DB8")]
+    public class KeyEventMessage : InputEventMessageBase
+    {
+        public bool IsDown { get; set; }
+        public Key Key { get; set; }
+    }
+
+}

+ 54 - 0
src/Avalonia.Remote.Protocol/ViewportMessages.cs

@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Remote.Protocol.Viewport
+{
+    public enum PixelFormat
+    {
+        Rgb565,
+        Rgba8888,
+        Bgra8888
+    }
+
+    [AvaloniaRemoteMessageGuid("6E3C5310-E2B1-4C3D-8688-01183AA48C5B")]
+    public class MeasureViewportMessage
+    {
+        public double Width { get; set; }
+        public double Height { get; set; }
+    }
+
+    [AvaloniaRemoteMessageGuid("BD7A8DE6-3DB8-4A13-8583-D6D4AB189A31")]
+    public class ClientViewportAllocatedMessage
+    {
+        public double Width { get; set; }
+        public double Height { get; set; }
+    }
+
+    [AvaloniaRemoteMessageGuid("63481025-7016-43FE-BADC-F2FD0F88609E")]
+    public class ClientSupportedPixelFormatsMessage
+    {
+        public PixelFormat[] Formats { get; set; }
+    }
+
+    [AvaloniaRemoteMessageGuid("68014F8A-289D-4851-8D34-5367EDA7F827")]
+    public class FrameReceivedMessage
+    {
+        public long SequenceId { get; set; }
+    }
+
+
+    [AvaloniaRemoteMessageGuid("F58313EE-FE69-4536-819D-F52EDF201A0E")]
+    public class FrameMessage
+    {
+        public long SequenceId { get; set; }
+        public PixelFormat Format { get; set; }
+        public byte[] Data { get; set; }
+        public int Width { get; set; }
+        public int Height { get; set; }
+        public int Stride { get; set; }
+    }
+
+}

+ 33 - 0
src/Avalonia.Visuals/Platform/LockedFramebuffer.cs

@@ -0,0 +1,33 @@
+using System;
+
+namespace Avalonia.Platform
+{
+    public class LockedFramebuffer : ILockedFramebuffer
+    {
+        private readonly Action _onDispose;
+
+        public LockedFramebuffer(IntPtr address, int width, int height, int rowBytes, Vector dpi, PixelFormat format,
+            Action onDispose)
+        {
+            _onDispose = onDispose;
+            Address = address;
+            Width = width;
+            Height = height;
+            RowBytes = rowBytes;
+            Dpi = dpi;
+            Format = format;
+        }
+
+        public IntPtr Address { get; }
+        public int Width { get; }
+        public int Height { get; }
+        public int RowBytes { get; }
+        public Vector Dpi { get; }
+        public PixelFormat Format { get; }
+
+        public void Dispose()
+        {
+            _onDispose?.Invoke();
+        }
+    }
+}