From a2551d505bcc5d4609e33ba8c3556fddced14fae Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 18 Aug 2017 18:50:29 +0300 Subject: [PATCH 01/14] Initial effort on Remote --- Avalonia.sln | 90 +++++++++++++++- samples/RemoteTest/Program.cs | 12 +++ samples/RemoteTest/RemoteTest.csproj | 25 +++++ .../Avalonia.Controls.csproj | 1 + .../Embedding/Offscreen/OffscreenTopLevel.cs | 63 +++++++++++ .../Offscreen/OffscreenTopLevelImpl.cs | 65 +++++++++++ src/Avalonia.Controls/Remote/RemoteServer.cs | 21 ++++ .../Remote/Server/RemoteServerTopLevelImpl.cs | 101 ++++++++++++++++++ src/Avalonia.Input/Key.cs | 5 +- .../Avalonia.Remote.Protocol.csproj | 9 ++ .../AvaloniaRemoteMessageGuidAttribute.cs | 19 ++++ .../DefaultMessageTypeResolver.cs | 35 ++++++ .../IMessageTypeResolver.cs | 10 ++ src/Avalonia.Remote.Protocol/ITransport.cs | 15 +++ src/Avalonia.Remote.Protocol/InputMessages.cs | 82 ++++++++++++++ .../ViewportMessages.cs | 54 ++++++++++ .../Platform/LockedFramebuffer.cs | 33 ++++++ 17 files changed, 638 insertions(+), 2 deletions(-) create mode 100644 samples/RemoteTest/Program.cs create mode 100644 samples/RemoteTest/RemoteTest.csproj create mode 100644 src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs create mode 100644 src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs create mode 100644 src/Avalonia.Controls/Remote/RemoteServer.cs create mode 100644 src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs create mode 100644 src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj create mode 100644 src/Avalonia.Remote.Protocol/AvaloniaRemoteMessageGuidAttribute.cs create mode 100644 src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs create mode 100644 src/Avalonia.Remote.Protocol/IMessageTypeResolver.cs create mode 100644 src/Avalonia.Remote.Protocol/ITransport.cs create mode 100644 src/Avalonia.Remote.Protocol/InputMessages.cs create mode 100644 src/Avalonia.Remote.Protocol/ViewportMessages.cs create mode 100644 src/Avalonia.Visuals/Platform/LockedFramebuffer.cs diff --git a/Avalonia.sln b/Avalonia.sln index 391d02382e..56c489fd0b 100644 --- a/Avalonia.sln +++ b/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 diff --git a/samples/RemoteTest/Program.cs b/samples/RemoteTest/Program.cs new file mode 100644 index 0000000000..eb5f388e9e --- /dev/null +++ b/samples/RemoteTest/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace RemoteTest +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/samples/RemoteTest/RemoteTest.csproj b/samples/RemoteTest/RemoteTest.csproj new file mode 100644 index 0000000000..2487c66e41 --- /dev/null +++ b/samples/RemoteTest/RemoteTest.csproj @@ -0,0 +1,25 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 037546b186..47e0f88341 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -36,6 +36,7 @@ + diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs new file mode 100644 index 0000000000..cd2da692ba --- /dev/null +++ b/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 Registered + { + add { _nameScope.Registered += value; } + remove { _nameScope.Registered -= value; } + } + + public event EventHandler 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(); + } + } +} diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs new file mode 100644 index 0000000000..088f20b270 --- /dev/null +++ b/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 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 Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action 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; } + } +} diff --git a/src/Avalonia.Controls/Remote/RemoteServer.cs b/src/Avalonia.Controls/Remote/RemoteServer.cs new file mode 100644 index 0000000000..8c0509c5ba --- /dev/null +++ b/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; } + } +} diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs new file mode 100644 index 0000000000..6c89264c64 --- /dev/null +++ b/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 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); + } + } +} diff --git a/src/Avalonia.Input/Key.cs b/src/Avalonia.Input/Key.cs index 3ea506d79e..c10cbc0a6c 100644 --- a/src/Avalonia.Input/Key.cs +++ b/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 { /// /// Defines the keys available on a keyboard. diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj new file mode 100644 index 0000000000..1ac7ab3a8b --- /dev/null +++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj @@ -0,0 +1,9 @@ + + + netstandard1.3 + AVALONIA_REMOTE_PROTOCOL;$(DefineConstants) + + + + + diff --git a/src/Avalonia.Remote.Protocol/AvaloniaRemoteMessageGuidAttribute.cs b/src/Avalonia.Remote.Protocol/AvaloniaRemoteMessageGuidAttribute.cs new file mode 100644 index 0000000000..12ee3178d3 --- /dev/null +++ b/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); + } + } +} diff --git a/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs b/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs new file mode 100644 index 0000000000..9d44f33627 --- /dev/null +++ b/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 _guidsToTypes = new Dictionary(); + private readonly Dictionary _typesToGuids = new Dictionary(); + 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(); + 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]; + } +} diff --git a/src/Avalonia.Remote.Protocol/IMessageTypeResolver.cs b/src/Avalonia.Remote.Protocol/IMessageTypeResolver.cs new file mode 100644 index 0000000000..2e2234b0d6 --- /dev/null +++ b/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); + } +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/ITransport.cs b/src/Avalonia.Remote.Protocol/ITransport.cs new file mode 100644 index 0000000000..e90a49ac71 --- /dev/null +++ b/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 OnMessage; + event Action OnException; + } +} diff --git a/src/Avalonia.Remote.Protocol/InputMessages.cs b/src/Avalonia.Remote.Protocol/InputMessages.cs new file mode 100644 index 0000000000..f1996372fc --- /dev/null +++ b/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 +{ + /// + /// Keep this in sync with InputModifiers in the main library + /// + [Flags] + public enum InputModifiers + { + Alt, + Control, + Shift, + Windows, + LeftMouseButton, + RightMouseButton, + MiddleMouseButton + } + + /// + /// Keep this in sync with InputModifiers in the main library + /// + 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; } + } + +} diff --git a/src/Avalonia.Remote.Protocol/ViewportMessages.cs b/src/Avalonia.Remote.Protocol/ViewportMessages.cs new file mode 100644 index 0000000000..8e1b0bd99e --- /dev/null +++ b/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; } + } + +} diff --git a/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs b/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs new file mode 100644 index 0000000000..c03b714956 --- /dev/null +++ b/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(); + } + } +} \ No newline at end of file From fe6e8a4d2b9f076d463b87d38e5f1451f83cb194 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 19 Aug 2017 10:53:29 +0300 Subject: [PATCH 02/14] [GTK3] Use lower priority for timers created by Signal() --- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 2 +- src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs | 6 ++++++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 4ea1a5af56..474f29733e 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -77,7 +77,7 @@ namespace Avalonia.Gtk3 if (!_signaled) { _signaled = true; - GlibTimeout.Add(0, () => + GlibTimeout.Add(100, 0, () => { lock (_lock) { diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs index 9971d8881d..1936d40086 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs @@ -35,6 +35,12 @@ namespace Avalonia.Gtk3.Interop var handle = GCHandle.Alloc(callback); Native.GTimeoutAdd(interval, PinnedHandler, GCHandle.ToIntPtr(handle)); } + + public static void Add(int priority, uint interval, Func callback) + { + var handle = GCHandle.Alloc(callback); + Native.GTimeoutAddFull(priority, interval, PinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero); + } class Timer : IDisposable { diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index fb1a9955e3..5ccdf3424c 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -241,6 +241,8 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public delegate ulong g_timeout_add(uint interval, timeout_callback callback, IntPtr data); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] + public delegate ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data, IntPtr destroy); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public delegate ulong g_free(IntPtr data); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public unsafe delegate void g_slist_free(GSList* data); @@ -302,6 +304,7 @@ namespace Avalonia.Gtk3.Interop public static D.g_signal_connect_object GSignalConnectObject; public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; public static D.g_timeout_add GTimeoutAdd; + public static D.g_timeout_add_full GTimeoutAddFull; public static D.g_free GFree; public static D.g_slist_free GSlistFree; public static D.g_memory_input_stream_new_from_data GMemoryInputStreamNewFromData; From d384a7f61a20a55f31c0766af173fc0a15c6fa12 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 19 Aug 2017 10:54:27 +0300 Subject: [PATCH 03/14] Added .idea/* to .gitignore --- .gitignore | 1 + src/Avalonia.Remote.Protocol/BsonStreamTransport.cs | 7 +++++++ src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs | 7 +++++++ 3 files changed, 15 insertions(+) create mode 100644 src/Avalonia.Remote.Protocol/BsonStreamTransport.cs create mode 100644 src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs diff --git a/.gitignore b/.gitignore index 640725fa26..dc3de9e54c 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,4 @@ artifacts/ nuget Avalonia.XBuild.sln project.lock.json +.idea/* diff --git a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs new file mode 100644 index 0000000000..b16966ffd7 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs @@ -0,0 +1,7 @@ +namespace Avalonia.Remote.Protocol +{ + public class BsonStreamTransport + { + + } +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs new file mode 100644 index 0000000000..a1f1df2a2d --- /dev/null +++ b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs @@ -0,0 +1,7 @@ +namespace Avalonia.Remote.Protocol +{ + public class TransportConnectionWrapper + { + + } +} \ No newline at end of file From 646db5b91472e40b405728396c2d68e4d118a9e2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 19 Aug 2017 10:55:51 +0300 Subject: [PATCH 04/14] Initial working version of remoting --- samples/ControlCatalog/MainWindow.xaml.cs | 3 +- samples/RemoteTest/Program.cs | 43 ++++- samples/RemoteTest/RemoteTest.csproj | 2 +- src/Avalonia.Base/AvaloniaObject.cs | 1 + .../Offscreen/OffscreenTopLevelImpl.cs | 13 +- src/Avalonia.Controls/Remote/RemoteServer.cs | 26 ++- src/Avalonia.Controls/Remote/RemoteWidget.cs | 79 +++++++++ .../Remote/Server/RemoteServerTopLevelImpl.cs | 107 +++++++++--- .../Avalonia.Remote.Protocol.csproj | 19 ++- .../BsonStreamTransport.cs | 157 +++++++++++++++++- .../BsonTcpTransport.cs | 27 +++ src/Avalonia.Remote.Protocol/EventStash.cs | 72 ++++++++ src/Avalonia.Remote.Protocol/ITransport.cs | 2 +- .../TcpTransportBase.cs | 84 ++++++++++ .../TransportConnectionWrapper.cs | 98 ++++++++++- .../ViewportMessages.cs | 5 +- 16 files changed, 691 insertions(+), 47 deletions(-) create mode 100644 src/Avalonia.Controls/Remote/RemoteWidget.cs create mode 100644 src/Avalonia.Remote.Protocol/BsonTcpTransport.cs create mode 100644 src/Avalonia.Remote.Protocol/EventStash.cs create mode 100644 src/Avalonia.Remote.Protocol/TcpTransportBase.cs diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 413794dfa2..c7ff0a60df 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -6,11 +6,12 @@ namespace ControlCatalog { public class MainWindow : Window { + public static bool DebugMode = false; public MainWindow() { this.InitializeComponent(); this.AttachDevTools(); - Renderer.DrawDirtyRects = Renderer.DrawFps = true; + Renderer.DrawDirtyRects = Renderer.DrawFps = DebugMode; } private void InitializeComponent() diff --git a/samples/RemoteTest/Program.cs b/samples/RemoteTest/Program.cs index eb5f388e9e..dce168c7ea 100644 --- a/samples/RemoteTest/Program.cs +++ b/samples/RemoteTest/Program.cs @@ -1,4 +1,13 @@ using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Remote; +using Avalonia.Remote.Protocol; +using Avalonia.Threading; +using ControlCatalog; namespace RemoteTest { @@ -6,7 +15,39 @@ namespace RemoteTest { static void Main(string[] args) { - Console.WriteLine("Hello World!"); + AppBuilder.Configure().UsePlatformDetect().SetupWithoutStarting(); + + var l = new TcpListener(IPAddress.Loopback, 0); + l.Start(); + var port = ((IPEndPoint) l.LocalEndpoint).Port; + l.Stop(); + + var transport = new BsonTcpTransport(); + transport.Listen(IPAddress.Loopback, port, sc => + { + Dispatcher.UIThread.InvokeAsync(() => + { + new RemoteServer(sc).Content = new MainView(); + }); + }); + + var cts = new CancellationTokenSource(); + transport.Connect(IPAddress.Loopback, port).ContinueWith(t => + { + Dispatcher.UIThread.InvokeAsync(() => + { + var window = new Window() + { + Content = new RemoteWidget(t.Result) + }; + window.Closed += delegate { cts.Cancel(); }; + window.Show(); + }); + }); + Dispatcher.UIThread.MainLoop(cts.Token); + + + } } } diff --git a/samples/RemoteTest/RemoteTest.csproj b/samples/RemoteTest/RemoteTest.csproj index 2487c66e41..8682916503 100644 --- a/samples/RemoteTest/RemoteTest.csproj +++ b/samples/RemoteTest/RemoteTest.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + netcoreapp1.1 diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1bdf1eb5e3..f94b6519d5 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -56,6 +56,7 @@ namespace Avalonia /// public AvaloniaObject() { + CheckAccess(); foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this)) { object value = property.IsDirect ? diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 088f20b270..c986c5d07c 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -6,12 +6,13 @@ using System.Threading.Tasks; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; +using Avalonia.Rendering; namespace Avalonia.Controls.Embedding.Offscreen { - abstract class OffscreenTopLevelImplBase : ITopLevelImpl + public abstract class OffscreenTopLevelImplBase : ITopLevelImpl { - private double _scaling; + private double _scaling = 1; private Size _clientSize; public IInputRoot InputRoot { get; private set; } @@ -20,6 +21,8 @@ namespace Avalonia.Controls.Embedding.Offscreen //No-op } + public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root); + public abstract void Invalidate(Rect rect); public abstract IEnumerable Surfaces { get; } @@ -51,15 +54,13 @@ namespace Avalonia.Controls.Embedding.Offscreen public virtual Point PointToClient(Point point) => point; - public Point PointToScreen(Point point) - { - throw new NotImplementedException(); - } + public virtual Point PointToScreen(Point point) => point; public virtual void SetCursor(IPlatformHandle cursor) { } public Action Closed { get; set; } + public abstract IMouseDevice MouseDevice { get; } } } diff --git a/src/Avalonia.Controls/Remote/RemoteServer.cs b/src/Avalonia.Controls/Remote/RemoteServer.cs index 8c0509c5ba..0bc43069e7 100644 --- a/src/Avalonia.Controls/Remote/RemoteServer.cs +++ b/src/Avalonia.Controls/Remote/RemoteServer.cs @@ -3,19 +3,37 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Avalonia.Controls.Embedding; +using Avalonia.Controls.Remote.Server; +using Avalonia.Platform; using Avalonia.Remote.Protocol; namespace Avalonia.Controls.Remote { public class RemoteServer { - private readonly IAvaloniaRemoteTransport _transport; + private EmbeddableControlRoot _topLevel; - public RemoteServer(IAvaloniaRemoteTransport transport) + class TopLevelImpl : RemoteServerTopLevelImpl, IEmbeddableWindowImpl { - _transport = transport; + public TopLevelImpl(IAvaloniaRemoteTransportConnection transport) : base(transport) + { + } + + public event Action LostFocus; + } + + public RemoteServer(IAvaloniaRemoteTransportConnection transport) + { + _topLevel = new EmbeddableControlRoot(new TopLevelImpl(transport)); + _topLevel.Prepare(); + //TODO: Somehow react on closed connection? } - public object Content { get; set; } + public object Content + { + get => _topLevel.Content; + set => _topLevel.Content = value; + } } } diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs new file mode 100644 index 0000000000..f4bcb37d48 --- /dev/null +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -0,0 +1,79 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; +using PixelFormat = Avalonia.Platform.PixelFormat; + +namespace Avalonia.Controls.Remote +{ + public class RemoteWidget : Control + { + private readonly IAvaloniaRemoteTransportConnection _connection; + private FrameMessage _lastFrame; + private WritableBitmap _bitmap; + public RemoteWidget(IAvaloniaRemoteTransportConnection connection) + { + _connection = connection; + _connection.OnMessage += msg => Dispatcher.UIThread.InvokeAsync(() => OnMessage(msg)); + _connection.Send(new ClientSupportedPixelFormatsMessage + { + Formats = new[] + { + Avalonia.Remote.Protocol.Viewport.PixelFormat.Bgra8888, + Avalonia.Remote.Protocol.Viewport.PixelFormat.Rgba8888, + } + }); + } + + private void OnMessage(object msg) + { + if (msg is FrameMessage frame) + { + _connection.Send(new FrameReceivedMessage + { + SequenceId = frame.SequenceId + }); + _lastFrame = frame; + InvalidateVisual(); + } + + } + + protected override void ArrangeCore(Rect finalRect) + { + _connection.Send(new ClientViewportAllocatedMessage + { + Width = finalRect.Width, + Height = finalRect.Height, + DpiX = 96, + DpiY = 96 //TODO: Somehow detect the actual DPI + }); + base.ArrangeCore(finalRect); + } + + public override void Render(DrawingContext context) + { + if (_lastFrame != null) + { + var fmt = (PixelFormat) _lastFrame.Format; + if (_bitmap == null || _bitmap.PixelWidth != _lastFrame.Width || + _bitmap.PixelHeight != _lastFrame.Height) + _bitmap = new WritableBitmap(_lastFrame.Width, _lastFrame.Height, fmt); + using (var l = _bitmap.Lock()) + { + var lineLen = (fmt == PixelFormat.Rgb565 ? 2 : 4) * _lastFrame.Width; + for (var y = 0; y < _lastFrame.Height; y++) + Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride, + new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen); + } + context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelWidth, _bitmap.PixelHeight), + new Rect(Bounds.Size)); + } + base.Render(context); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index 6c89264c64..be97f7caaa 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -6,6 +6,8 @@ using System.Text; using System.Threading.Tasks; using Avalonia.Controls.Embedding.Offscreen; using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Input; +using Avalonia.Layout; using Avalonia.Platform; using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Viewport; @@ -15,17 +17,20 @@ using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; namespace Avalonia.Controls.Remote.Server { - class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface + public class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface { - private readonly IAvaloniaRemoteTransport _transport; + private readonly IAvaloniaRemoteTransportConnection _transport; private LockedFramebuffer _framebuffer; private object _lock = new object(); - private long _lastSentFrame; + private long _lastSentFrame = -1; private long _lastReceivedFrame = -1; + private long _nextFrameNumber = 1; + private ClientViewportAllocatedMessage _pendingAllocation; private bool _invalidated; + private Vector _dpi = new Vector(96, 96); private ProtocolPixelFormat[] _supportedFormats; - public RemoteServerTopLevelImpl(IAvaloniaRemoteTransport transport) + public RemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport) { _transport = transport; _transport.OnMessage += OnMessage; @@ -35,24 +40,64 @@ namespace Avalonia.Controls.Remote.Server { lock (_lock) { - var lastFrame = obj as FrameReceivedMessage; - if (lastFrame != null) + if (obj is FrameReceivedMessage lastFrame) { lock (_lock) { _lastReceivedFrame = lastFrame.SequenceId; } - Dispatcher.UIThread.InvokeAsync(CheckNeedsRender); + Dispatcher.UIThread.InvokeAsync(RenderIfNeeded); + } + if (obj is ClientSupportedPixelFormatsMessage supportedFormats) + { + lock (_lock) + _supportedFormats = supportedFormats.Formats; + Dispatcher.UIThread.InvokeAsync(RenderIfNeeded); + } + if (obj is MeasureViewportMessage measure) + Dispatcher.UIThread.InvokeAsync(() => + { + var m = Measure(new Size(measure.Width, measure.Height)); + _transport.Send(new MeasureViewportMessage + { + Width = m.Width, + Height = m.Height + }); + }); + if (obj is ClientViewportAllocatedMessage allocated) + { + lock (_lock) + { + if (_pendingAllocation == null) + Dispatcher.UIThread.InvokeAsync(() => + { + ClientViewportAllocatedMessage allocation; + lock (_lock) + { + allocation = _pendingAllocation; + _pendingAllocation = null; + } + _dpi = new Vector(allocation.DpiX, allocation.DpiY); + ClientSize = new Size(allocation.Width, allocation.Height); + RenderIfNeeded(); + }); + + _pendingAllocation = allocated; + } } - var supportedFormats = obj as ClientSupportedPixelFormatsMessage; - if (supportedFormats != null) - _supportedFormats = supportedFormats.Formats; } } + protected virtual Size Measure(Size constaint) + { + var l = (ILayoutable) InputRoot; + l.Measure(constaint); + return l.DesiredSize; + } + public override IEnumerable Surfaces => new[] { this }; - FrameMessage RenderFrame(int width, int height, Size dpi, ProtocolPixelFormat? format) + FrameMessage RenderFrame(int width, int height, ProtocolPixelFormat? format) { var fmt = format ?? ProtocolPixelFormat.Rgba8888; var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4; @@ -60,7 +105,7 @@ namespace Avalonia.Controls.Remote.Server var handle = GCHandle.Alloc(data, GCHandleType.Pinned); try { - _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), width, height, width * bpp, dpi, (PixelFormat)fmt, + _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), width, height, width * bpp, _dpi, (PixelFormat)fmt, null); Paint?.Invoke(new Rect(0, 0, width, height)); } @@ -69,7 +114,14 @@ namespace Avalonia.Controls.Remote.Server _framebuffer = null; handle.Free(); } - return new FrameMessage(); + return new FrameMessage + { + Data = data, + Format = (ProtocolPixelFormat) format, + Width = width, + Height = height, + Stride = width * bpp, + }; } public ILockedFramebuffer Lock() @@ -79,23 +131,40 @@ namespace Avalonia.Controls.Remote.Server return _framebuffer; } - void CheckNeedsRender() + void RenderIfNeeded() { - ProtocolPixelFormat[] formats; lock (_lock) { - if (_lastReceivedFrame != _lastSentFrame && !_invalidated) + if (_lastReceivedFrame != _lastSentFrame || !_invalidated || _supportedFormats == null) return; - formats = _supportedFormats; + } + if (ClientSize.Width < 1 || ClientSize.Height < 1) + return; + var format = ProtocolPixelFormat.Rgba8888; + foreach(var fmt in _supportedFormats) + if (fmt <= ProtocolPixelFormat.MaxValue) + { + format = fmt; + break; + } - //var frame = RenderFrame() + var frame = RenderFrame((int) ClientSize.Width, (int) ClientSize.Height, format); + lock (_lock) + { + _lastSentFrame = _nextFrameNumber++; + frame.SequenceId = _lastSentFrame; + _invalidated = false; + } + _transport.Send(frame); } public override void Invalidate(Rect rect) { _invalidated = true; - Dispatcher.UIThread.InvokeAsync(CheckNeedsRender); + Dispatcher.UIThread.InvokeAsync(RenderIfNeeded); } + + public override IMouseDevice MouseDevice { get; } = new MouseDevice(); } } diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj index 1ac7ab3a8b..a9c6b9e9a0 100644 --- a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj +++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj @@ -1,9 +1,10 @@ - - - netstandard1.3 - AVALONIA_REMOTE_PROTOCOL;$(DefineConstants) - - - - - + + + netstandard1.3 + AVALONIA_REMOTE_PROTOCOL;$(DefineConstants) + + + + + + \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs index b16966ffd7..8c983c385d 100644 --- a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs +++ b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs @@ -1,7 +1,160 @@ -namespace Avalonia.Remote.Protocol +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using Newtonsoft.Json; +using System.Threading.Tasks; +using Newtonsoft.Json.Bson; + +namespace Avalonia.Remote.Protocol { - public class BsonStreamTransport + public class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection { + private readonly IMessageTypeResolver _resolver; + private readonly Stream _inputStream; + private readonly Stream _outputStream; + private readonly Action _disposeCallback; + private readonly CancellationToken _cancel; + private readonly CancellationTokenSource _cancelSource; + private readonly MemoryStream _outputBlock = new MemoryStream(); + private readonly object _lock = new object(); + private bool _writeOperationPending; + private bool _readingAlreadyStarted; + private bool _writerIsBroken; + static readonly JsonSerializer Serializer = new JsonSerializer(); + private static readonly byte[] ZeroLength = new byte[4]; + + public BsonStreamTransportConnection(IMessageTypeResolver resolver, Stream inputStream, Stream outputStream, Action disposeCallback) + { + _resolver = resolver; + _inputStream = inputStream; + _outputStream = outputStream; + _disposeCallback = disposeCallback; + _cancelSource = new CancellationTokenSource(); + _cancel = _cancelSource.Token; + } + + public void Dispose() + { + _cancelSource.Cancel(); + _disposeCallback?.Invoke(); + } + public void StartReading() + { + lock (_lock) + { + if(_readingAlreadyStarted) + throw new InvalidOperationException("Reading has already started"); + _readingAlreadyStarted = true; + Task.Run(Reader, _cancel); + } + } + + async Task ReadExact(byte[] buffer) + { + int read = 0; + while (read != buffer.Length) + { + var readNow = await _inputStream.ReadAsync(buffer, read, buffer.Length - read, _cancel) + .ConfigureAwait(false); + if (readNow == 0) + throw new EndOfStreamException(); + read += readNow; + } + } + + async Task Reader() + { + Task.Yield(); + try + { + while (true) + { + var infoBlock = new byte[20]; + await ReadExact(infoBlock).ConfigureAwait(false); + var length = BitConverter.ToInt32(infoBlock, 0); + var guidBytes = new byte[16]; + Buffer.BlockCopy(infoBlock, 4, guidBytes, 0, 16); + var guid = new Guid(guidBytes); + var buffer = new byte[length]; + await ReadExact(buffer).ConfigureAwait(false); + if (Environment.GetEnvironmentVariable("WTF") == "WTF") + { + + using (var f = System.IO.File.Create("/tmp/wtf2.bin")) + { + f.Write(infoBlock, 0, infoBlock.Length); + f.Write(buffer, 0, buffer.Length); + } + } + var message = Serializer.Deserialize(new BsonReader(new MemoryStream(buffer)), _resolver.GetByGuid(guid)); + OnMessage?.Invoke(message); + } + } + catch (Exception e) + { + FireException(e); + } + } + + + public async Task Send(object data) + { + lock (_lock) + { + if(_writerIsBroken) //Ignore further calls, since there is no point of writing to "broken" stream + return; + if (_writeOperationPending) + throw new InvalidOperationException("Previous send operation was not finished"); + _writeOperationPending = true; + } + try + { + var guid = _resolver.GetGuid(data.GetType()).ToByteArray(); + _outputBlock.Seek(0, SeekOrigin.Begin); + _outputBlock.SetLength(0); + _outputBlock.Write(ZeroLength, 0, 4); + _outputBlock.Write(guid, 0, guid.Length); + var writer = new BsonWriter(_outputBlock); + Serializer.Serialize(writer, data); + _outputBlock.Seek(0, SeekOrigin.Begin); + var length = BitConverter.GetBytes((int)_outputBlock.Length - 20); + _outputBlock.Write(length, 0, length.Length); + _outputBlock.Seek(0, SeekOrigin.Begin); + + try + { + await _outputBlock.CopyToAsync(_outputStream, 0x1000, _cancel).ConfigureAwait(false); + } + catch (Exception e) //We are only catching "network"-related exceptions here + { + lock (_lock) + { + _writerIsBroken = true; + } + FireException(e); + } + } + finally + { + lock (_lock) + { + _writeOperationPending = false; + } + } + } + + void FireException(Exception e) + { + var cancel = e as OperationCanceledException; + if (cancel?.CancellationToken == _cancel) + return; + OnException?.Invoke(e); + } + + + public event Action OnMessage; + public event Action OnException; } } \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs b/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs new file mode 100644 index 0000000000..e647f75997 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs @@ -0,0 +1,27 @@ +using System; +using System.IO; +using System.Reflection; + +namespace Avalonia.Remote.Protocol +{ + public class BsonTcpTransport : TcpTransportBase + { + public BsonTcpTransport(IMessageTypeResolver resolver) : base(resolver) + { + } + + public BsonTcpTransport() : this(new DefaultMessageTypeResolver(typeof(BsonTcpTransport).GetTypeInfo().Assembly)) + { + + } + + protected override IAvaloniaRemoteTransportConnection CreateTransport(IMessageTypeResolver resolver, + Stream stream, Action dispose) + { + var t = new BsonStreamTransportConnection(resolver, stream, stream, dispose); + var wrap = new TransportConnectionWrapper(t); + t.StartReading(); + return wrap; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/EventStash.cs b/src/Avalonia.Remote.Protocol/EventStash.cs new file mode 100644 index 0000000000..06840c8253 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/EventStash.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; + +namespace Avalonia.Remote.Protocol +{ + public class EventStash + { + private readonly Action _exceptionHandler; + private List _stash; + private Action _delegate; + + public EventStash(Action exceptionHandler = null) + { + _exceptionHandler = exceptionHandler; + } + + public void Add(Action handler) + { + List stash; + lock (this) + { + var needsReplay = _delegate == null; + _delegate += handler; + if(!needsReplay) + return; + + lock (this) + { + stash = _stash; + if(_stash == null) + return; + _stash = null; + } + } + foreach (var m in stash) + { + if (_exceptionHandler != null) + try + { + _delegate?.Invoke(m); + } + catch (Exception e) + { + _exceptionHandler(e); + } + else + _delegate?.Invoke(m); + } + } + + + public void Remove(Action handler) + { + lock (this) + _delegate -= handler; + } + + public void Fire(T ev) + { + if (_delegate == null) + { + lock (this) + { + _stash = _stash ?? new List(); + _stash.Add(ev); + } + } + else + _delegate?.Invoke(ev); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/ITransport.cs b/src/Avalonia.Remote.Protocol/ITransport.cs index e90a49ac71..d4e5427cb4 100644 --- a/src/Avalonia.Remote.Protocol/ITransport.cs +++ b/src/Avalonia.Remote.Protocol/ITransport.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Avalonia.Remote.Protocol { - public interface IAvaloniaRemoteTransport + public interface IAvaloniaRemoteTransportConnection : IDisposable { Task Send(object data); event Action OnMessage; diff --git a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs new file mode 100644 index 0000000000..4278badaae --- /dev/null +++ b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs @@ -0,0 +1,84 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace Avalonia.Remote.Protocol +{ + public abstract class TcpTransportBase + { + private readonly IMessageTypeResolver _resolver; + + public TcpTransportBase(IMessageTypeResolver resolver) + { + _resolver = resolver; + } + + protected abstract IAvaloniaRemoteTransportConnection CreateTransport(IMessageTypeResolver resolver, + Stream stream, Action disposeCallback); + + class DisposableServer : IDisposable + { + private readonly TcpListener _l; + + public DisposableServer(TcpListener l) + { + _l = l; + } + public void Dispose() + { + try + { + _l.Stop(); + } + catch + { + //Ignore + } + } + } + + public IDisposable Listen(IPAddress address, int port, Action cb) + { + var server = new TcpListener(address, port); + async void AcceptNew() + { + try + { + var cl = await server.AcceptTcpClientAsync(); + AcceptNew(); + Task.Run(async () => + { + try + { + var tcs = new TaskCompletionSource(); + var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0)); + cb(t); + await tcs.Task; + } + finally + { + cl.Dispose(); + } + + }); + } + catch + { + //Ignore and stop + } + } + server.Start(); + AcceptNew(); + return new DisposableServer(server); + } + + public async Task Connect(IPAddress address, int port) + { + var c = new TcpClient(); + await c.ConnectAsync(address, port); + return CreateTransport(_resolver, c.GetStream(), c.Dispose); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs index a1f1df2a2d..b01b6d7303 100644 --- a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs +++ b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs @@ -1,7 +1,101 @@ -namespace Avalonia.Remote.Protocol +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Avalonia.Remote.Protocol { - public class TransportConnectionWrapper + public class TransportConnectionWrapper : IAvaloniaRemoteTransportConnection { + private readonly IAvaloniaRemoteTransportConnection _conn; + private EventStash _onMessage; + private EventStash _onException = new EventStash(); + private Queue _sendQueue = new Queue(); + private object _lock =new object(); + private TaskCompletionSource _signal; + private bool _workerIsAlive; + public TransportConnectionWrapper(IAvaloniaRemoteTransportConnection conn) + { + _conn = conn; + _onMessage = new EventStash(_onException.Fire); + _conn.OnException +=_onException.Fire; + conn.OnMessage += _onMessage.Fire; + + } + + class SendOperation + { + public object Message { get; set; } + public TaskCompletionSource Tcs { get; set; } + } + + public void Dispose() => _conn.Dispose(); + + async void Worker() + { + while (true) + { + SendOperation wi = null; + lock (_lock) + { + if (_sendQueue.Count != 0) + wi = _sendQueue.Dequeue(); + } + if (wi == null) + { + var signal = new TaskCompletionSource(); + lock (_lock) + _signal = signal; + await signal.Task.ConfigureAwait(false); + continue; + } + try + { + await _conn.Send(wi.Message).ConfigureAwait(false); + wi.Tcs.TrySetResult(0); + } + catch (Exception e) + { + wi.Tcs.TrySetException(e); + } + } + + } + + public Task Send(object data) + { + var tcs = new TaskCompletionSource(); + lock (_lock) + { + if (!_workerIsAlive) + { + _workerIsAlive = true; + Worker(); + } + _sendQueue.Enqueue(new SendOperation + { + Message = data, + Tcs = tcs + }); + if (_signal != null) + { + _signal.SetResult(0); + _signal = null; + } + } + return tcs.Task; + } + + public event Action OnMessage + { + add => _onMessage.Add(value); + remove => _onMessage.Remove(value); + } + + public event Action OnException + { + add => _onException.Add(value); + remove => _onException.Remove(value); + } } } \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/ViewportMessages.cs b/src/Avalonia.Remote.Protocol/ViewportMessages.cs index 8e1b0bd99e..82a7688f37 100644 --- a/src/Avalonia.Remote.Protocol/ViewportMessages.cs +++ b/src/Avalonia.Remote.Protocol/ViewportMessages.cs @@ -10,7 +10,8 @@ namespace Avalonia.Remote.Protocol.Viewport { Rgb565, Rgba8888, - Bgra8888 + Bgra8888, + MaxValue = Bgra8888 } [AvaloniaRemoteMessageGuid("6E3C5310-E2B1-4C3D-8688-01183AA48C5B")] @@ -25,6 +26,8 @@ namespace Avalonia.Remote.Protocol.Viewport { public double Width { get; set; } public double Height { get; set; } + public double DpiX { get; set; } + public double DpiY { get; set; } } [AvaloniaRemoteMessageGuid("63481025-7016-43FE-BADC-F2FD0F88609E")] From 9a1348b37b87a30048ddb197c5c2f3d6545bfe6e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 24 Sep 2017 00:37:11 +0300 Subject: [PATCH 05/14] Initial remote XAML previewer implementation --- Avalonia.sln | 92 +++++++++- Avalonia.sln.DotSettings | 1 + samples/ControlCatalog.NetCore/Program.cs | 22 ++- samples/Previewer/App.xaml | 6 + samples/Previewer/App.xaml.cs | 14 ++ samples/Previewer/Center.cs | 19 ++ samples/Previewer/MainWindow.xaml | 12 ++ samples/Previewer/MainWindow.xaml.cs | 86 +++++++++ samples/Previewer/Previewer.csproj | 27 +++ samples/Previewer/Program.cs | 13 ++ samples/RemoteTest/RemoteTest.csproj | 2 +- src/Avalonia.Controls/Design.cs | 2 +- .../InternalPlatformThreadingInterface.cs} | 14 +- src/Avalonia.Controls/Remote/RemoteWidget.cs | 2 +- .../Remote/Server/RemoteServerTopLevelImpl.cs | 10 +- .../DesignWindowLoader.cs | 76 ++++++++ .../DesignerAssist.cs | 60 +----- .../Avalonia.DotNetFrameworkRuntime.csproj | 4 +- .../BsonStreamTransport.cs | 19 +- .../DesignMessages.cs | 17 ++ src/Avalonia.Remote.Protocol/EventStash.cs | 20 +- src/Avalonia.Remote.Protocol/ITransport.cs | 4 +- .../TcpTransportBase.cs | 16 +- .../TransportConnectionWrapper.cs | 12 +- .../ViewportMessages.cs | 7 + .../Avalonia.LinuxFramebuffer.csproj | 24 +-- .../LinuxFramebufferPlatform.cs | 7 +- src/Linux/Avalonia.LinuxFramebuffer/Mice.cs | 2 +- .../Avalonia.Designer.HostApp.csproj | 25 +++ .../DetachableTransportConnection.cs | 35 ++++ .../PreviewerWindowImpl.cs | 91 +++++++++ .../PreviewerWindowingPlatform.cs | 66 +++++++ .../Avalonia.Designer.HostApp/Program.cs | 173 ++++++++++++++++++ src/tools/Avalonia.Designer.HostApp/Stubs.cs | 136 ++++++++++++++ 34 files changed, 977 insertions(+), 139 deletions(-) create mode 100644 samples/Previewer/App.xaml create mode 100644 samples/Previewer/App.xaml.cs create mode 100644 samples/Previewer/Center.cs create mode 100644 samples/Previewer/MainWindow.xaml create mode 100644 samples/Previewer/MainWindow.xaml.cs create mode 100644 samples/Previewer/Previewer.csproj create mode 100644 samples/Previewer/Program.cs rename src/{Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs => Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs} (89%) create mode 100644 src/Avalonia.DesignerSupport/DesignWindowLoader.cs create mode 100644 src/Avalonia.Remote.Protocol/DesignMessages.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj create mode 100644 src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/Program.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/Stubs.cs diff --git a/Avalonia.sln b/Avalonia.sln index 56c489fd0b..2f2b175fa3 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 @@ -189,6 +189,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Remote.Protocol", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteTest", "samples\RemoteTest\RemoteTest.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{4ED8B739-6F4E-4CD4-B993-545E6B5CE637}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer.HostApp", "src\tools\Avalonia.Designer.HostApp\Avalonia.Designer.HostApp.csproj", "{050CC912-FF49-4A8B-B534-9544017446DD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Previewer", "samples\Previewer\Previewer.csproj", "{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -2598,6 +2604,86 @@ Global {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 + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.ActiveCfg = Ad-Hoc|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.Build.0 = Ad-Hoc|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.Build.0 = Ad-Hoc|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.Build.0 = AppStore|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.ActiveCfg = AppStore|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.Build.0 = AppStore|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.ActiveCfg = AppStore|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.Build.0 = AppStore|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.ActiveCfg = AppStore|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.Build.0 = AppStore|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.ActiveCfg = Debug|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.Build.0 = Debug|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.ActiveCfg = Debug|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.Build.0 = Debug|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.ActiveCfg = Debug|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.Build.0 = Debug|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.ActiveCfg = Release|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.Build.0 = Release|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.ActiveCfg = Release|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.Build.0 = Release|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.ActiveCfg = Release|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.Build.0 = Release|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.ActiveCfg = Ad-Hoc|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.Build.0 = Ad-Hoc|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.Build.0 = Ad-Hoc|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.Build.0 = AppStore|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.ActiveCfg = AppStore|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.Build.0 = AppStore|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.ActiveCfg = AppStore|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.Build.0 = AppStore|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.ActiveCfg = AppStore|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.Build.0 = AppStore|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.ActiveCfg = Debug|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.Build.0 = Debug|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.ActiveCfg = Debug|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.Build.0 = Debug|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.ActiveCfg = Debug|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.Build.0 = Debug|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Any CPU.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.ActiveCfg = Release|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.Build.0 = Release|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.ActiveCfg = Release|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.Build.0 = Release|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.ActiveCfg = Release|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2656,8 +2742,12 @@ Global {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} + {050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1E8CA5AA-707A-4C57-A682-D265A49E10C3} + {050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection EndGlobal diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings index 1fd6f8d092..d61208c358 100644 --- a/Avalonia.sln.DotSettings +++ b/Avalonia.sln.DotSettings @@ -1,4 +1,5 @@  + True ExplicitlyExcluded ExplicitlyExcluded ExplicitlyExcluded diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 7c67a239e6..7e7fb7d2bf 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Avalonia; +using Avalonia.Controls; namespace ControlCatalog.NetCore { @@ -8,17 +9,22 @@ namespace ControlCatalog.NetCore { static void Main(string[] args) { - if (args.Contains("--fbdev")) AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => - { - tl.Content = new MainView(); - System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); - }); + if (args.Contains("--fbdev")) + AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => + { + tl.Content = new MainView(); + System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); + }); else - AppBuilder.Configure() - .UsePlatformDetect() - .Start(); + BuildAvaloniaApp().Start(); } + /// + /// This method is needed for IDE previewer infrastructure + /// + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure().UsePlatformDetect(); + static void ConsoleSilencer() { Console.CursorVisible = false; diff --git a/samples/Previewer/App.xaml b/samples/Previewer/App.xaml new file mode 100644 index 0000000000..6bae1955af --- /dev/null +++ b/samples/Previewer/App.xaml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs new file mode 100644 index 0000000000..fffa987a27 --- /dev/null +++ b/samples/Previewer/App.xaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Markup.Xaml; + +namespace Previewer +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + } + +} \ No newline at end of file diff --git a/samples/Previewer/Center.cs b/samples/Previewer/Center.cs new file mode 100644 index 0000000000..7a28827d61 --- /dev/null +++ b/samples/Previewer/Center.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; + +namespace Previewer +{ + public class Center : Decorator + { + protected override Size ArrangeOverride(Size finalSize) + { + if (Child != null) + { + var desired = Child.DesiredSize; + Child.Arrange(new Rect((finalSize.Width - desired.Width) / 2, (finalSize.Height - desired.Height) / 2, + desired.Width, desired.Height)); + } + return finalSize; + } + } +} \ No newline at end of file diff --git a/samples/Previewer/MainWindow.xaml b/samples/Previewer/MainWindow.xaml new file mode 100644 index 0000000000..eb612303f2 --- /dev/null +++ b/samples/Previewer/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Previewer/MainWindow.xaml.cs b/samples/Previewer/MainWindow.xaml.cs new file mode 100644 index 0000000000..c72b1f7e55 --- /dev/null +++ b/samples/Previewer/MainWindow.xaml.cs @@ -0,0 +1,86 @@ +using System; +using System.Net; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Remote; +using Avalonia.Markup.Xaml; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Designer; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; + +namespace Previewer +{ + public class MainWindow : Window + { + private const string InitialXaml = @" + Hello world! + + "; + private IAvaloniaRemoteTransportConnection _connection; + private Control _errorsContainer; + private TextBlock _errors; + private RemoteWidget _remote; + + + public MainWindow() + { + this.InitializeComponent(); + var tb = this.FindControl("Xaml"); + tb.Text = InitialXaml; + var scroll = this.FindControl("Remote"); + var rem = new Center(); + scroll.Content = rem; + _errorsContainer = this.FindControl("ErrorsContainer"); + _errors = this.FindControl("Errors"); + tb.GetObservable(TextBox.TextProperty).Subscribe(text => _connection?.Send(new UpdateXamlMessage + { + Xaml = text + })); + new BsonTcpTransport().Listen(IPAddress.Loopback, 25000, t => + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (_connection != null) + { + _connection.Dispose(); + _connection.OnMessage -= OnMessage; + } + _connection = t; + rem.Child = _remote = new RemoteWidget(t); + t.Send(new UpdateXamlMessage + { + Xaml = tb.Text + }); + + t.OnMessage += OnMessage; + }); + }); + Title = "Listening on 127.0.0.1:25000"; + } + + private void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (transport != _connection) + return; + if (obj is UpdateXamlResultMessage result) + { + _errorsContainer.IsVisible = result.Error != null; + _errors.Text = result.Error ?? ""; + } + if (obj is RequestViewportResizeMessage resize) + { + _remote.Width = Math.Min(4096, Math.Max(resize.Width, 1)); + _remote.Height = Math.Min(4096, Math.Max(resize.Height, 1)); + } + }); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj new file mode 100644 index 0000000000..13e2418d67 --- /dev/null +++ b/samples/Previewer/Previewer.csproj @@ -0,0 +1,27 @@ + + + Exe + netcoreapp2.0 + + + + %(Filename) + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Previewer/Program.cs b/samples/Previewer/Program.cs new file mode 100644 index 0000000000..48363e27f2 --- /dev/null +++ b/samples/Previewer/Program.cs @@ -0,0 +1,13 @@ +using System; +using Avalonia; + +namespace Previewer +{ + class Program + { + static void Main(string[] args) + { + AppBuilder.Configure().UsePlatformDetect().Start(); + } + } +} \ No newline at end of file diff --git a/samples/RemoteTest/RemoteTest.csproj b/samples/RemoteTest/RemoteTest.csproj index 8682916503..2487c66e41 100644 --- a/samples/RemoteTest/RemoteTest.csproj +++ b/samples/RemoteTest/RemoteTest.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp1.1 + netcoreapp2.0 diff --git a/src/Avalonia.Controls/Design.cs b/src/Avalonia.Controls/Design.cs index a0b0d5d0ca..ce52891749 100644 --- a/src/Avalonia.Controls/Design.cs +++ b/src/Avalonia.Controls/Design.cs @@ -64,7 +64,7 @@ namespace Avalonia.Controls return rv; } - internal static void ApplyDesignerProperties(Control target, Control source) + public static void ApplyDesignModeProperties(Control target, Control source) { if (source.IsSet(WidthProperty)) target.Width = source.GetValue(WidthProperty); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs similarity index 89% rename from src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs rename to src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs index 9231649754..eebde51555 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs +++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs @@ -1,19 +1,16 @@ 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 +namespace Avalonia.Controls.Platform { - class PlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop + public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop { - public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); - - public PlatformThreadingInterface() + public InternalPlatformThreadingInterface() { TlsCurrentThreadIsLoopThread = true; StartTimer(new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs())); @@ -36,7 +33,7 @@ namespace Avalonia.LinuxFramebuffer while (true) { Action item; - lock(_actions) + lock (_actions) if (_actions.Count == 0) break; else @@ -66,6 +63,7 @@ namespace Avalonia.LinuxFramebuffer _timer = timer; _handle = GCHandle.Alloc(_timer); } + public void Dispose() { _handle.Free(); @@ -109,4 +107,4 @@ namespace Avalonia.LinuxFramebuffer public event EventHandler Tick; } -} +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs index f4bcb37d48..c05aeaf970 100644 --- a/src/Avalonia.Controls/Remote/RemoteWidget.cs +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls.Remote public RemoteWidget(IAvaloniaRemoteTransportConnection connection) { _connection = connection; - _connection.OnMessage += msg => Dispatcher.UIThread.InvokeAsync(() => OnMessage(msg)); + _connection.OnMessage += (t, msg) => Dispatcher.UIThread.InvokeAsync(() => OnMessage(msg)); _connection.Send(new ClientSupportedPixelFormatsMessage { Formats = new[] diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index be97f7caaa..c2e6a200f9 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -36,7 +36,7 @@ namespace Avalonia.Controls.Remote.Server _transport.OnMessage += OnMessage; } - private void OnMessage(object obj) + protected virtual void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) { lock (_lock) { @@ -88,6 +88,12 @@ namespace Avalonia.Controls.Remote.Server } } + protected void SetDpi(Vector dpi) + { + _dpi = dpi; + RenderIfNeeded(); + } + protected virtual Size Measure(Size constaint) { var l = (ILayoutable) InputRoot; @@ -131,7 +137,7 @@ namespace Avalonia.Controls.Remote.Server return _framebuffer; } - void RenderIfNeeded() + protected void RenderIfNeeded() { lock (_lock) { diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs new file mode 100644 index 0000000000..9ad2a216b4 --- /dev/null +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; + +namespace Avalonia.DesignerSupport +{ + public class DesignWindowLoader + { + public static Window LoadDesignerWindow(string xaml, string assemblyPath) + { + Window window; + Control control; + using (PlatformManager.DesignerMode()) + { + var loader = new AvaloniaXamlLoader(); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)); + + + + Uri baseUri = null; + if (assemblyPath != null) + { + //Fabricate fake Uri + baseUri = + new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(assemblyPath)); + } + + var loaded = loader.Load(stream, null, baseUri); + var styles = loaded as Styles; + if (styles != null) + { + var substitute = Design.GetPreviewWith(styles) ?? + styles.Select(Design.GetPreviewWith).FirstOrDefault(s => s != null); + if (substitute != null) + { + substitute.Styles.AddRange(styles); + control = substitute; + } + else + control = new StackPanel + { + Children = + { + new TextBlock {Text = "Styles can't be previewed without Design.PreviewWith. Add"}, + new TextBlock {Text = ""}, + new TextBlock {Text = " "}, + new TextBlock {Text = ""}, + new TextBlock {Text = "before setters in your first Style"} + } + }; + } + if (loaded is Application) + control = new TextBlock {Text = "Application can't be previewed in design view"}; + else + control = (Control) loaded; + + window = control as Window; + if (window == null) + { + window = new Window() {Content = (Control)control}; + } + + if (!window.IsSet(Window.SizeToContentProperty)) + window.SizeToContent = SizeToContent.WidthAndHeight; + } + window.Show(); + Design.ApplyDesignModeProperties(window, control); + return window; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.DesignerSupport/DesignerAssist.cs b/src/Avalonia.DesignerSupport/DesignerAssist.cs index 2f5fc79147..65c4c14d83 100644 --- a/src/Avalonia.DesignerSupport/DesignerAssist.cs +++ b/src/Avalonia.DesignerSupport/DesignerAssist.cs @@ -86,67 +86,11 @@ namespace Avalonia.DesignerSupport private static void UpdateXaml2(Dictionary dic) { var xamlInfo = new DesignerApiXamlFileInfo(dic); - Window window; - Control control; - - using (PlatformManager.DesignerMode()) - { - var loader = new AvaloniaXamlLoader(); - var stream = new MemoryStream(Encoding.UTF8.GetBytes(xamlInfo.Xaml)); - - - - Uri baseUri = null; - if (xamlInfo.AssemblyPath != null) - { - //Fabricate fake Uri - baseUri = - new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(xamlInfo.AssemblyPath)); - } - - var loaded = loader.Load(stream, null, baseUri); - var styles = loaded as Styles; - if (styles != null) - { - var substitute = Design.GetPreviewWith(styles) ?? - styles.Select(Design.GetPreviewWith).FirstOrDefault(s => s != null); - if (substitute != null) - { - substitute.Styles.AddRange(styles); - control = substitute; - } - else - control = new StackPanel - { - Children = - { - new TextBlock {Text = "Styles can't be previewed without Design.PreviewWith. Add"}, - new TextBlock {Text = ""}, - new TextBlock {Text = " "}, - new TextBlock {Text = ""}, - new TextBlock {Text = "before setters in your first Style"} - } - }; - } - if (loaded is Application) - control = new TextBlock {Text = "Application can't be previewed in design view"}; - else - control = (Control) loaded; - - window = control as Window; - if (window == null) - { - window = new Window() {Content = (Control)control}; - } - - if (!window.IsSet(Window.SizeToContentProperty)) - window.SizeToContent = SizeToContent.WidthAndHeight; - } + Window window = DesignWindowLoader.LoadDesignerWindow(xamlInfo.Xaml, xamlInfo.AssemblyPath); s_currentWindow?.Close(); s_currentWindow = window; - window.Show(); - Design.ApplyDesignerProperties(window, control); + // ReSharper disable once PossibleNullReferenceException // Always not null at this point Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle); diff --git a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj index 181f5e3a1e..32171e8495 100644 --- a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj +++ b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj @@ -10,7 +10,7 @@ Properties\SharedAssemblyInfo.cs - + @@ -18,4 +18,4 @@ - \ No newline at end of file + diff --git a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs index 8c983c385d..82765f98f6 100644 --- a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs +++ b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs @@ -8,7 +8,7 @@ using Newtonsoft.Json.Bson; namespace Avalonia.Remote.Protocol { - public class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection + class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection { private readonly IMessageTypeResolver _resolver; private readonly Stream _inputStream; @@ -79,17 +79,8 @@ namespace Avalonia.Remote.Protocol var guid = new Guid(guidBytes); var buffer = new byte[length]; await ReadExact(buffer).ConfigureAwait(false); - if (Environment.GetEnvironmentVariable("WTF") == "WTF") - { - - using (var f = System.IO.File.Create("/tmp/wtf2.bin")) - { - f.Write(infoBlock, 0, infoBlock.Length); - f.Write(buffer, 0, buffer.Length); - } - } var message = Serializer.Deserialize(new BsonReader(new MemoryStream(buffer)), _resolver.GetByGuid(guid)); - OnMessage?.Invoke(message); + OnMessage?.Invoke(this, message); } } catch (Exception e) @@ -150,11 +141,11 @@ namespace Avalonia.Remote.Protocol var cancel = e as OperationCanceledException; if (cancel?.CancellationToken == _cancel) return; - OnException?.Invoke(e); + OnException?.Invoke(this, e); } - public event Action OnMessage; - public event Action OnException; + public event Action OnMessage; + public event Action OnException; } } \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/DesignMessages.cs b/src/Avalonia.Remote.Protocol/DesignMessages.cs new file mode 100644 index 0000000000..d6e898f320 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/DesignMessages.cs @@ -0,0 +1,17 @@ +namespace Avalonia.Remote.Protocol.Designer +{ + [AvaloniaRemoteMessageGuid("9AEC9A2E-6315-4066-B4BA-E9A9EFD0F8CC")] + public class UpdateXamlMessage + { + public string Xaml { get; set; } + public string AssemblyPath { get; set; } + } + + [AvaloniaRemoteMessageGuid("B7A70093-0C5D-47FD-9261-22086D43A2E2")] + public class UpdateXamlResultMessage + { + public string Error { get; set; } + } + + +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/EventStash.cs b/src/Avalonia.Remote.Protocol/EventStash.cs index 06840c8253..1fe561d473 100644 --- a/src/Avalonia.Remote.Protocol/EventStash.cs +++ b/src/Avalonia.Remote.Protocol/EventStash.cs @@ -3,18 +3,20 @@ using System.Collections.Generic; namespace Avalonia.Remote.Protocol { - public class EventStash + class EventStash { + private readonly IAvaloniaRemoteTransportConnection _transport; private readonly Action _exceptionHandler; private List _stash; - private Action _delegate; + private Action _delegate; - public EventStash(Action exceptionHandler = null) + public EventStash(IAvaloniaRemoteTransportConnection transport, Action exceptionHandler = null) { + _transport = transport; _exceptionHandler = exceptionHandler; } - public void Add(Action handler) + public void Add(Action handler) { List stash; lock (this) @@ -37,25 +39,25 @@ namespace Avalonia.Remote.Protocol if (_exceptionHandler != null) try { - _delegate?.Invoke(m); + _delegate?.Invoke(_transport, m); } catch (Exception e) { _exceptionHandler(e); } else - _delegate?.Invoke(m); + _delegate?.Invoke(_transport, m); } } - public void Remove(Action handler) + public void Remove(Action handler) { lock (this) _delegate -= handler; } - public void Fire(T ev) + public void Fire(IAvaloniaRemoteTransportConnection transport, T ev) { if (_delegate == null) { @@ -66,7 +68,7 @@ namespace Avalonia.Remote.Protocol } } else - _delegate?.Invoke(ev); + _delegate?.Invoke(_transport, ev); } } } \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/ITransport.cs b/src/Avalonia.Remote.Protocol/ITransport.cs index d4e5427cb4..afc7d86845 100644 --- a/src/Avalonia.Remote.Protocol/ITransport.cs +++ b/src/Avalonia.Remote.Protocol/ITransport.cs @@ -9,7 +9,7 @@ namespace Avalonia.Remote.Protocol public interface IAvaloniaRemoteTransportConnection : IDisposable { Task Send(object data); - event Action OnMessage; - event Action OnException; + event Action OnMessage; + event Action OnException; } } diff --git a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs index 4278badaae..af62f2da59 100644 --- a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs +++ b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs @@ -50,17 +50,11 @@ namespace Avalonia.Remote.Protocol AcceptNew(); Task.Run(async () => { - try - { - var tcs = new TaskCompletionSource(); - var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0)); - cb(t); - await tcs.Task; - } - finally - { - cl.Dispose(); - } + var tcs = new TaskCompletionSource(); + var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0)); + cb(t); + await tcs.Task; + }); } diff --git a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs index b01b6d7303..1e821b7c24 100644 --- a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs +++ b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs @@ -8,7 +8,7 @@ namespace Avalonia.Remote.Protocol { private readonly IAvaloniaRemoteTransportConnection _conn; private EventStash _onMessage; - private EventStash _onException = new EventStash(); + private EventStash _onException; private Queue _sendQueue = new Queue(); private object _lock =new object(); @@ -17,7 +17,8 @@ namespace Avalonia.Remote.Protocol public TransportConnectionWrapper(IAvaloniaRemoteTransportConnection conn) { _conn = conn; - _onMessage = new EventStash(_onException.Fire); + _onException = new EventStash(this); + _onMessage = new EventStash(this, e => _onException.Fire(this, e)); _conn.OnException +=_onException.Fire; conn.OnMessage += _onMessage.Fire; @@ -58,8 +59,7 @@ namespace Avalonia.Remote.Protocol { wi.Tcs.TrySetException(e); } - } - + } } public Task Send(object data) @@ -86,13 +86,13 @@ namespace Avalonia.Remote.Protocol return tcs.Task; } - public event Action OnMessage + public event Action OnMessage { add => _onMessage.Add(value); remove => _onMessage.Remove(value); } - public event Action OnException + public event Action OnException { add => _onException.Add(value); remove => _onException.Remove(value); diff --git a/src/Avalonia.Remote.Protocol/ViewportMessages.cs b/src/Avalonia.Remote.Protocol/ViewportMessages.cs index 82a7688f37..f71e893d26 100644 --- a/src/Avalonia.Remote.Protocol/ViewportMessages.cs +++ b/src/Avalonia.Remote.Protocol/ViewportMessages.cs @@ -30,6 +30,13 @@ namespace Avalonia.Remote.Protocol.Viewport public double DpiY { get; set; } } + [AvaloniaRemoteMessageGuid("9B47B3D8-61DF-4C38-ACD4-8C1BB72554AC")] + public class RequestViewportResizeMessage + { + public double Width { get; set; } + public double Height { get; set; } + } + [AvaloniaRemoteMessageGuid("63481025-7016-43FE-BADC-F2FD0F88609E")] public class ClientSupportedPixelFormatsMessage { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj index c38cb0f114..466b47f7a0 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj +++ b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj @@ -1,14 +1,14 @@  - - netstandard2.0 - true - - - - - - - - - + + netstandard2.0 + true + + + + + + + + + \ No newline at end of file diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index bc1684cc15..e733beae27 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -12,6 +12,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; using Avalonia; +using Avalonia.Controls.Platform; namespace Avalonia.LinuxFramebuffer { @@ -22,6 +23,7 @@ namespace Avalonia.LinuxFramebuffer public static MouseDevice MouseDevice = new MouseDevice(); private static readonly Stopwatch St = Stopwatch.StartNew(); internal static uint Timestamp => (uint)St.ElapsedTicks; + public static InternalPlatformThreadingInterface Threading; public static FramebufferToplevelImpl TopLevel; LinuxFramebufferPlatform(string fbdev = null) { @@ -31,12 +33,13 @@ namespace Avalonia.LinuxFramebuffer void Initialize() { + Threading = new InternalPlatformThreadingInterface(); AvaloniaLocator.CurrentMutable .Bind().ToTransient() .Bind().ToConstant(KeyboardDevice) .Bind().ToSingleton() - .Bind().ToConstant(PlatformThreadingInterface.Instance) - .Bind().ToConstant(PlatformThreadingInterface.Instance); + .Bind().ToConstant(Threading) + .Bind().ToConstant(Threading); } internal static TopLevel Initialize(T builder, string fbdev = null) where T : AppBuilderBase, new() diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs index d1cf9eefb6..7e85796c0c 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs @@ -55,7 +55,7 @@ namespace Avalonia.LinuxFramebuffer if (!ev.HasValue) break; - PlatformThreadingInterface.Instance.Send(() => ProcessEvent(dev, ev.Value)); + LinuxFramebufferPlatform.Threading.Send(() => ProcessEvent(dev, ev.Value)); } } } diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj new file mode 100644 index 0000000000..f0f3b12cc5 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -0,0 +1,25 @@ + + + Exe + netcoreapp2.0 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs b/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs new file mode 100644 index 0000000000..9cd87a2c59 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading.Tasks; +using Avalonia.Remote.Protocol; + +namespace Avalonia.Designer.HostApp +{ + class DetachableTransportConnection : IAvaloniaRemoteTransportConnection + { + private IAvaloniaRemoteTransportConnection _inner; + + public DetachableTransportConnection(IAvaloniaRemoteTransportConnection inner) + { + _inner = inner; + _inner.OnMessage += FireOnMessage; + } + + public void Dispose() + { + if (_inner != null) + _inner.OnMessage -= FireOnMessage; + _inner = null; + } + + public void FireOnMessage(IAvaloniaRemoteTransportConnection transport, object obj) => OnMessage?.Invoke(transport, obj); + + public Task Send(object data) + { + return _inner?.Send(data); + } + + public event Action OnMessage; + + public event Action OnException; + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs b/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs new file mode 100644 index 0000000000..5ffaa6459a --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs @@ -0,0 +1,91 @@ +using System; +using System.Reactive.Disposables; +using Avalonia.Controls; +using Avalonia.Controls.Remote.Server; +using Avalonia.Platform; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; + +namespace Avalonia.Designer.HostApp +{ + public class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl, IEmbeddableWindowImpl + { + private readonly IAvaloniaRemoteTransportConnection _transport; + + public PreviewerWindowImpl(IAvaloniaRemoteTransportConnection transport) : base(transport) + { + _transport = transport; + ClientSize = new Size(1, 1); + } + + public void Show() + { + } + + public void Hide() + { + } + + public void BeginMoveDrag() + { + } + + public void BeginResizeDrag(WindowEdge edge) + { + } + + public Point Position { get; set; } + public Action PositionChanged { get; set; } + public Action Deactivated { get; set; } + public Action Activated { get; set; } + public IPlatformHandle Handle { get; } + public WindowState WindowState { get; set; } + public Size MaxClientSize { get; } = new Size(4096, 4096); + public event Action LostFocus; + + protected override void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) + { + // In previewer mode we completely ignore client-side viewport size + if (obj is ClientViewportAllocatedMessage alloc) + { + Dispatcher.UIThread.InvokeAsync(() => SetDpi(new Vector(alloc.DpiX, alloc.DpiY))); + return; + } + base.OnMessage(transport, obj); + } + + public void Resize(Size clientSize) + { + _transport.Send(new RequestViewportResizeMessage + { + Width = clientSize.Width, + Height = clientSize.Height + }); + ClientSize = clientSize; + RenderIfNeeded(); + } + + public void Activate() + { + } + + public void SetTitle(string title) + { + } + + public IDisposable ShowDialog() + { + return Disposable.Empty; + } + + public void SetSystemDecorations(bool enabled) + { + } + + public void SetIcon(IWindowIconImpl icon) + { + } + + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs b/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs new file mode 100644 index 0000000000..b33f871139 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Platform; +using Avalonia.Remote.Protocol; +using Avalonia.Rendering; + +namespace Avalonia.Designer.HostApp +{ + class PreviewerWindowingPlatform : IWindowingPlatform, IPlatformSettings + { + static readonly IKeyboardDevice Keyboard = new KeyboardDevice(); + private static IAvaloniaRemoteTransportConnection s_transport; + private static DetachableTransportConnection s_lastWindowTransport; + private static PreviewerWindowImpl s_lastWindow; + public static List PreFlightMessages = new List(); + + public IWindowImpl CreateWindow() => new WindowStub(); + + public IEmbeddableWindowImpl CreateEmbeddableWindow() + { + if (s_lastWindow != null) + { + s_lastWindowTransport.Dispose(); + try + { + s_lastWindow.Dispose(); + } + catch + { + //Ignore + } + } + s_lastWindow = + new PreviewerWindowImpl(s_lastWindowTransport = new DetachableTransportConnection(s_transport)); + foreach (var pf in PreFlightMessages) + s_lastWindowTransport.FireOnMessage(s_lastWindowTransport, pf); + return s_lastWindow; + } + + public IPopupImpl CreatePopup() => new WindowStub(); + + public static void Initialize(IAvaloniaRemoteTransportConnection transport) + { + s_transport = transport; + var instance = new PreviewerWindowingPlatform(); + var threading = new InternalPlatformThreadingInterface(); + AvaloniaLocator.CurrentMutable + .Bind().ToSingleton() + .Bind().ToSingleton() + .Bind().ToConstant(Keyboard) + .Bind().ToConstant(instance) + .Bind().ToConstant(threading) + .Bind().ToConstant(threading) + .Bind().ToSingleton() + .Bind().ToConstant(instance) + .Bind().ToSingleton(); + + } + + public Size DoubleClickSize { get; } = new Size(2, 2); + public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/Program.cs b/src/tools/Avalonia.Designer.HostApp/Program.cs new file mode 100644 index 0000000000..88dadf479e --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/Program.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.DesignerSupport; +using Avalonia.Input; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Designer; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; + +namespace Avalonia.Designer.HostApp +{ + class Program + { + + private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats; + private static ClientViewportAllocatedMessage s_viewportAllocatedMessage; + private static IAvaloniaRemoteTransportConnection s_transport; + class CommandLineArgs + { + public string AppPath { get; set; } + public Uri Transport { get; set; } + } + + static Exception Die(string error) + { + if (error != null) + { + Console.Error.WriteLine(error); + Console.Error.Flush(); + } + Environment.Exit(1); + return new Exception("APPEXIT"); + } + + static Exception PrintUsage() + { + Console.Error.WriteLine("Usage: --transport transport_spec app"); + Console.Error.WriteLine(); + Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ MyApp.exe"); + Console.Error.Flush(); + return Die(null); + } + + static CommandLineArgs ParseCommandLineArgs(string[] args) + { + var rv = new CommandLineArgs(); + Action next = null; + try + { + foreach (var arg in args) + { + if (next != null) + { + next(arg); + next = null; + } + else if (arg == "--transport") + next = a => rv.Transport = new Uri(a, UriKind.Absolute); + else if (rv.AppPath == null) + rv.AppPath = arg; + else + PrintUsage(); + + } + if (rv.AppPath == null || rv.Transport == null) + PrintUsage(); + } + catch + { + PrintUsage(); + } + return rv; + } + + static IAvaloniaRemoteTransportConnection CreateTransport(Uri transport) + { + if (transport.Scheme == "tcp-bson") + { + return new BsonTcpTransport().Connect(IPAddress.Parse(transport.Host), transport.Port).Result; + } + PrintUsage(); + return null; + } + + interface IAppInitializer + { + Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj); + } + + class AppInitializer : IAppInitializer where T : AppBuilderBase, new() + { + public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj) + { + var builder = (AppBuilderBase) obj; + builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport)); + builder.SetupWithoutStarting(); + return builder.Instance; + } + } + + private const string BuilderMethodName = "BuildAvaloniaApp"; + + class NeverClose : ICloseable + { + public event EventHandler Closed; + } + + static void Main(string[] cmdline) + { + var args = ParseCommandLineArgs(cmdline); + var transport = CreateTransport(args.Transport); + var asm = Assembly.LoadFile(System.IO.Path.GetFullPath(args.AppPath)); + var entryPoint = asm.EntryPoint; + if (entryPoint == null) + throw Die($"Assembly {args.AppPath} doesn't have an entry point"); + var builderMethod = entryPoint.DeclaringType.GetMethod(BuilderMethodName, + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (builderMethod == null) + throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}"); + + var appBuilder = builderMethod.Invoke(null, null); + var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType())); + var app = initializer.GetConfiguredApp(transport, appBuilder); + s_transport = transport; + transport.OnMessage += OnTransportMessage; + transport.OnException += (t, e) => Die(e.ToString()); + app.Run(new NeverClose()); + } + + + private static void RebuildPreFlight() + { + PreviewerWindowingPlatform.PreFlightMessages = new List + { + s_supportedPixelFormats, + s_viewportAllocatedMessage + }; + } + + private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.InvokeAsync(() => + { + if (obj is ClientSupportedPixelFormatsMessage formats) + { + s_supportedPixelFormats = formats; + RebuildPreFlight(); + } + if (obj is ClientViewportAllocatedMessage viewport) + { + s_viewportAllocatedMessage = viewport; + RebuildPreFlight(); + } + if (obj is UpdateXamlMessage xaml) + { + try + { + DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath); + s_transport.Send(new UpdateXamlResultMessage()); + } + catch (Exception e) + { + s_transport.Send(new UpdateXamlResultMessage + { + Error = e.ToString() + }); + } + } + }); + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/Stubs.cs b/src/tools/Avalonia.Designer.HostApp/Stubs.cs new file mode 100644 index 0000000000..a4160e7da4 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/Stubs.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reactive.Disposables; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Input.Raw; +using Avalonia.Platform; +using Avalonia.Rendering; + +namespace Avalonia.Designer.HostApp +{ + /// + /// Popups are no-op + /// + + class WindowStub : IPopupImpl, IWindowImpl + { + public Action Deactivated { get; set; } + public Action Activated { get; set; } + public IPlatformHandle Handle { get; } + public Size MaxClientSize { get; } + public Size ClientSize { get; } + public double Scaling { get; } + public IEnumerable Surfaces { get; } + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Action Closed { get; set; } + public IMouseDevice MouseDevice { get; } = new MouseDevice(); + public Point Position { get; set; } + public Action PositionChanged { get; set; } + public WindowState WindowState { get; set; } + public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root); + public void Dispose() + { + } + public void Invalidate(Rect rect) + { + } + + public void SetInputRoot(IInputRoot inputRoot) + { + } + + public Point PointToClient(Point point) => point; + + public Point PointToScreen(Point point) => point; + + public void SetCursor(IPlatformHandle cursor) + { + } + + public void Show() + { + } + + public void Hide() + { + } + + public void BeginMoveDrag() + { + } + + public void BeginResizeDrag(WindowEdge edge) + { + } + + public void Activate() + { + } + + public void Resize(Size clientSize) + { + } + + public void SetTitle(string title) + { + } + + public IDisposable ShowDialog() => Disposable.Empty; + + public void SetSystemDecorations(bool enabled) + { + } + + public void SetIcon(IWindowIconImpl icon) + { + } + } + + class ClipboardStub : IClipboard + { + public Task GetTextAsync() => Task.FromResult(""); + + public Task SetTextAsync(string text) => Task.CompletedTask; + + public Task ClearAsync() => Task.CompletedTask; + } + + class CursorFactoryStub : IStandardCursorFactory + { + public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB"); + } + + class IconLoaderStub : IPlatformIconLoader + { + class IconStub : IWindowIconImpl + { + public void Save(Stream outputStream) + { + + } + } + + public IWindowIconImpl LoadIcon(string fileName) => new IconStub(); + + public IWindowIconImpl LoadIcon(Stream stream) => new IconStub(); + + public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) => new IconStub(); + } + + class SystemDialogsStub : ISystemDialogImpl + { + public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) => + Task.FromResult((string[]) null); + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) => + Task.FromResult((string) null); + } +} \ No newline at end of file From 7c1245829d9361002cf5d9ba1e277f2964e04c81 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 26 Sep 2017 21:08:42 +0300 Subject: [PATCH 06/14] [GTK3] Timers should have lower priority than Signal() --- src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs index 1936d40086..07ca4a9539 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs @@ -55,7 +55,7 @@ namespace Avalonia.Gtk3.Interop public static IDisposable StarTimer(uint interval, Action tick) { var timer = new Timer (); - GlibTimeout.Add(interval, + GlibTimeout.Add(101, interval, () => { if (timer.Stopped) From d7e74229990205b1865586f44c65fb7571ba0d0e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 27 Sep 2017 13:37:58 +0300 Subject: [PATCH 07/14] Moved remote previewer entry point to DesignSupport.dll --- .../Remote}/DetachableTransportConnection.cs | 2 +- .../Remote}/PreviewerWindowImpl.cs | 4 +- .../Remote}/PreviewerWindowingPlatform.cs | 2 +- .../Remote/RemoteDesignerEntryPoint.cs | 172 ++++++++++++++++++ .../Remote}/Stubs.cs | 6 +- .../Avalonia.Designer.HostApp.csproj | 1 - .../Avalonia.Designer.HostApp/Program.cs | 169 +---------------- 7 files changed, 180 insertions(+), 176 deletions(-) rename src/{tools/Avalonia.Designer.HostApp => Avalonia.DesignerSupport/Remote}/DetachableTransportConnection.cs (95%) rename src/{tools/Avalonia.Designer.HostApp => Avalonia.DesignerSupport/Remote}/PreviewerWindowImpl.cs (94%) rename src/{tools/Avalonia.Designer.HostApp => Avalonia.DesignerSupport/Remote}/PreviewerWindowingPlatform.cs (98%) create mode 100644 src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs rename src/{tools/Avalonia.Designer.HostApp => Avalonia.DesignerSupport/Remote}/Stubs.cs (97%) diff --git a/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs b/src/Avalonia.DesignerSupport/Remote/DetachableTransportConnection.cs similarity index 95% rename from src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs rename to src/Avalonia.DesignerSupport/Remote/DetachableTransportConnection.cs index 9cd87a2c59..1fb10a3a7c 100644 --- a/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs +++ b/src/Avalonia.DesignerSupport/Remote/DetachableTransportConnection.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using Avalonia.Remote.Protocol; -namespace Avalonia.Designer.HostApp +namespace Avalonia.DesignerSupport.Remote { class DetachableTransportConnection : IAvaloniaRemoteTransportConnection { diff --git a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs similarity index 94% rename from src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs rename to src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 5ffaa6459a..a787c8e8e3 100644 --- a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -7,9 +7,9 @@ using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Viewport; using Avalonia.Threading; -namespace Avalonia.Designer.HostApp +namespace Avalonia.DesignerSupport.Remote { - public class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl, IEmbeddableWindowImpl + class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl, IEmbeddableWindowImpl { private readonly IAvaloniaRemoteTransportConnection _transport; diff --git a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs similarity index 98% rename from src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs rename to src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs index b33f871139..ca3bb2b97f 100644 --- a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs @@ -7,7 +7,7 @@ using Avalonia.Platform; using Avalonia.Remote.Protocol; using Avalonia.Rendering; -namespace Avalonia.Designer.HostApp +namespace Avalonia.DesignerSupport.Remote { class PreviewerWindowingPlatform : IWindowingPlatform, IPlatformSettings { diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs new file mode 100644 index 0000000000..ac3438d71c --- /dev/null +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.DesignerSupport; +using Avalonia.Input; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Designer; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; + +namespace Avalonia.DesignerSupport.Remote +{ + public class RemoteDesignerEntryPoint + { + private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats; + private static ClientViewportAllocatedMessage s_viewportAllocatedMessage; + private static IAvaloniaRemoteTransportConnection s_transport; + class CommandLineArgs + { + public string AppPath { get; set; } + public Uri Transport { get; set; } + } + + static Exception Die(string error) + { + if (error != null) + { + Console.Error.WriteLine(error); + Console.Error.Flush(); + } + Environment.Exit(1); + return new Exception("APPEXIT"); + } + + static Exception PrintUsage() + { + Console.Error.WriteLine("Usage: --transport transport_spec app"); + Console.Error.WriteLine(); + Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ MyApp.exe"); + Console.Error.Flush(); + return Die(null); + } + + static CommandLineArgs ParseCommandLineArgs(string[] args) + { + var rv = new CommandLineArgs(); + Action next = null; + try + { + foreach (var arg in args) + { + if (next != null) + { + next(arg); + next = null; + } + else if (arg == "--transport") + next = a => rv.Transport = new Uri(a, UriKind.Absolute); + else if (rv.AppPath == null) + rv.AppPath = arg; + else + PrintUsage(); + + } + if (rv.AppPath == null || rv.Transport == null) + PrintUsage(); + } + catch + { + PrintUsage(); + } + return rv; + } + + static IAvaloniaRemoteTransportConnection CreateTransport(Uri transport) + { + if (transport.Scheme == "tcp-bson") + { + return new BsonTcpTransport().Connect(IPAddress.Parse(transport.Host), transport.Port).Result; + } + PrintUsage(); + return null; + } + + interface IAppInitializer + { + Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj); + } + + class AppInitializer : IAppInitializer where T : AppBuilderBase, new() + { + public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj) + { + var builder = (AppBuilderBase) obj; + builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport)); + builder.SetupWithoutStarting(); + return builder.Instance; + } + } + + private const string BuilderMethodName = "BuildAvaloniaApp"; + + class NeverClose : ICloseable + { + public event EventHandler Closed; + } + + public static void Main(string[] cmdline) + { + var args = ParseCommandLineArgs(cmdline); + var transport = CreateTransport(args.Transport); + var asm = Assembly.LoadFile(System.IO.Path.GetFullPath(args.AppPath)); + var entryPoint = asm.EntryPoint; + if (entryPoint == null) + throw Die($"Assembly {args.AppPath} doesn't have an entry point"); + var builderMethod = entryPoint.DeclaringType.GetMethod(BuilderMethodName, + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (builderMethod == null) + throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}"); + + var appBuilder = builderMethod.Invoke(null, null); + var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType())); + var app = initializer.GetConfiguredApp(transport, appBuilder); + s_transport = transport; + transport.OnMessage += OnTransportMessage; + transport.OnException += (t, e) => Die(e.ToString()); + app.Run(new NeverClose()); + } + + + private static void RebuildPreFlight() + { + PreviewerWindowingPlatform.PreFlightMessages = new List + { + s_supportedPixelFormats, + s_viewportAllocatedMessage + }; + } + + private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.InvokeAsync(() => + { + if (obj is ClientSupportedPixelFormatsMessage formats) + { + s_supportedPixelFormats = formats; + RebuildPreFlight(); + } + if (obj is ClientViewportAllocatedMessage viewport) + { + s_viewportAllocatedMessage = viewport; + RebuildPreFlight(); + } + if (obj is UpdateXamlMessage xaml) + { + try + { + DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath); + s_transport.Send(new UpdateXamlResultMessage()); + } + catch (Exception e) + { + s_transport.Send(new UpdateXamlResultMessage + { + Error = e.ToString() + }); + } + } + }); + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs similarity index 97% rename from src/tools/Avalonia.Designer.HostApp/Stubs.cs rename to src/Avalonia.DesignerSupport/Remote/Stubs.cs index a4160e7da4..5aad114cf7 100644 --- a/src/tools/Avalonia.Designer.HostApp/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -11,12 +11,8 @@ using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Rendering; -namespace Avalonia.Designer.HostApp +namespace Avalonia.DesignerSupport.Remote { - /// - /// Popups are no-op - /// - class WindowStub : IPopupImpl, IWindowImpl { public Action Deactivated { get; set; } diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index f0f3b12cc5..2a68862f18 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -4,7 +4,6 @@ netcoreapp2.0 - diff --git a/src/tools/Avalonia.Designer.HostApp/Program.cs b/src/tools/Avalonia.Designer.HostApp/Program.cs index 88dadf479e..29640ffc6f 100644 --- a/src/tools/Avalonia.Designer.HostApp/Program.cs +++ b/src/tools/Avalonia.Designer.HostApp/Program.cs @@ -1,173 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Reflection; -using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.DesignerSupport; -using Avalonia.Input; -using Avalonia.Remote.Protocol; -using Avalonia.Remote.Protocol.Designer; -using Avalonia.Remote.Protocol.Viewport; -using Avalonia.Threading; + namespace Avalonia.Designer.HostApp { class Program { - - private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats; - private static ClientViewportAllocatedMessage s_viewportAllocatedMessage; - private static IAvaloniaRemoteTransportConnection s_transport; - class CommandLineArgs - { - public string AppPath { get; set; } - public Uri Transport { get; set; } - } - - static Exception Die(string error) - { - if (error != null) - { - Console.Error.WriteLine(error); - Console.Error.Flush(); - } - Environment.Exit(1); - return new Exception("APPEXIT"); - } - - static Exception PrintUsage() - { - Console.Error.WriteLine("Usage: --transport transport_spec app"); - Console.Error.WriteLine(); - Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ MyApp.exe"); - Console.Error.Flush(); - return Die(null); - } - - static CommandLineArgs ParseCommandLineArgs(string[] args) - { - var rv = new CommandLineArgs(); - Action next = null; - try - { - foreach (var arg in args) - { - if (next != null) - { - next(arg); - next = null; - } - else if (arg == "--transport") - next = a => rv.Transport = new Uri(a, UriKind.Absolute); - else if (rv.AppPath == null) - rv.AppPath = arg; - else - PrintUsage(); - - } - if (rv.AppPath == null || rv.Transport == null) - PrintUsage(); - } - catch - { - PrintUsage(); - } - return rv; - } - - static IAvaloniaRemoteTransportConnection CreateTransport(Uri transport) - { - if (transport.Scheme == "tcp-bson") - { - return new BsonTcpTransport().Connect(IPAddress.Parse(transport.Host), transport.Port).Result; - } - PrintUsage(); - return null; - } - - interface IAppInitializer - { - Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj); - } - - class AppInitializer : IAppInitializer where T : AppBuilderBase, new() - { - public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj) - { - var builder = (AppBuilderBase) obj; - builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport)); - builder.SetupWithoutStarting(); - return builder.Instance; - } - } - - private const string BuilderMethodName = "BuildAvaloniaApp"; - - class NeverClose : ICloseable - { - public event EventHandler Closed; - } - - static void Main(string[] cmdline) - { - var args = ParseCommandLineArgs(cmdline); - var transport = CreateTransport(args.Transport); - var asm = Assembly.LoadFile(System.IO.Path.GetFullPath(args.AppPath)); - var entryPoint = asm.EntryPoint; - if (entryPoint == null) - throw Die($"Assembly {args.AppPath} doesn't have an entry point"); - var builderMethod = entryPoint.DeclaringType.GetMethod(BuilderMethodName, - BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - if (builderMethod == null) - throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}"); - - var appBuilder = builderMethod.Invoke(null, null); - var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType())); - var app = initializer.GetConfiguredApp(transport, appBuilder); - s_transport = transport; - transport.OnMessage += OnTransportMessage; - transport.OnException += (t, e) => Die(e.ToString()); - app.Run(new NeverClose()); - } - - - private static void RebuildPreFlight() - { - PreviewerWindowingPlatform.PreFlightMessages = new List - { - s_supportedPixelFormats, - s_viewportAllocatedMessage - }; - } - - private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.InvokeAsync(() => - { - if (obj is ClientSupportedPixelFormatsMessage formats) - { - s_supportedPixelFormats = formats; - RebuildPreFlight(); - } - if (obj is ClientViewportAllocatedMessage viewport) - { - s_viewportAllocatedMessage = viewport; - RebuildPreFlight(); - } - if (obj is UpdateXamlMessage xaml) - { - try - { - DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath); - s_transport.Send(new UpdateXamlResultMessage()); - } - catch (Exception e) - { - s_transport.Send(new UpdateXamlResultMessage - { - Error = e.ToString() - }); - } - } - }); + public static void Main(string[] args) + => Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(args); } } \ No newline at end of file From 2b8953c7abbd4f9b7fed59f34ac3648703c42d81 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 6 Oct 2017 19:23:14 +0300 Subject: [PATCH 08/14] Fixed merge issues and made new previewer to work with desktop .net --- Avalonia.sln | 206 +++++++++++------- packages.cake | 12 + samples/ControlCatalog.Desktop/Program.cs | 10 +- samples/ControlCatalog.NetCore/Program.cs | 17 +- .../Avalonia.Controls.csproj | 2 +- src/Avalonia.Controls/Remote/RemoteServer.cs | 3 +- .../Avalonia.DesignerSupport.csproj | 2 +- .../Remote/PreviewerWindowImpl.cs | 7 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 14 ++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 2 - .../Avalonia.Designer.HostApp.NetFX.csproj | 28 +++ .../Program.cs | 14 ++ .../Properties/launchSettings.json | 8 + .../Avalonia.Designer.HostApp.csproj | 1 + 14 files changed, 222 insertions(+), 104 deletions(-) create mode 100644 src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj create mode 100644 src/tools/Avalonia.Designer.HostApp.NetFX/Program.cs create mode 100644 src/tools/Avalonia.Designer.HostApp.NetFX/Properties/launchSettings.json diff --git a/Avalonia.sln b/Avalonia.sln index 6f46bf953e..7f516667ee 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 @@ -178,19 +179,22 @@ 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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteTest", "samples\RemoteTest\RemoteTest.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{4ED8B739-6F4E-4CD4-B993-545E6B5CE637}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer.HostApp", "src\tools\Avalonia.Designer.HostApp\Avalonia.Designer.HostApp.csproj", "{050CC912-FF49-4A8B-B534-9544017446DD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Designer.HostApp", "src\tools\Avalonia.Designer.HostApp\Avalonia.Designer.HostApp.csproj", "{050CC912-FF49-4A8B-B534-9544017446DD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Previewer", "samples\Previewer\Previewer.csproj", "{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Previewer", "samples\Previewer\Previewer.csproj", "{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OSX", "OSX", "{A59C4C0A-64DF-4621-B450-2BA00D6F61E2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MonoMac", "src\OSX\Avalonia.MonoMac\Avalonia.MonoMac.csproj", "{CBFD5788-567D-401B-9DFA-74E4224025A0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer.HostApp.NetFX", "src\tools\Avalonia.Designer.HostApp.NetFX\Avalonia.Designer.HostApp.NetFX.csproj", "{4ADA61C8-D191-428D-9066-EF4F0D86520F}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -200,7 +204,6 @@ Global tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{48840edd-24bf-495d-911e-2eb12ae75d3b}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{7863ea94-f0fb-4386-bf8c-e5bfa761560a}*SharedItemsImports = 4 - src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 4 src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4 src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{9defc6b7-845b-4d8f-afc0-d32bf0032b8c}*SharedItemsImports = 13 @@ -2444,86 +2447,86 @@ Global {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 - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.ActiveCfg = Ad-Hoc|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.Build.0 = Ad-Hoc|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|x86 - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.Build.0 = Ad-Hoc|x86 - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.Build.0 = AppStore|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.ActiveCfg = AppStore|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.Build.0 = AppStore|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.ActiveCfg = AppStore|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.Build.0 = AppStore|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.ActiveCfg = AppStore|x86 - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.Build.0 = AppStore|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.Build.0 = Release|Any CPU {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.ActiveCfg = Debug|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.Build.0 = Debug|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.ActiveCfg = Debug|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.Build.0 = Debug|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.ActiveCfg = Debug|x86 - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.Build.0 = Debug|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.Build.0 = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.ActiveCfg = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.Build.0 = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.ActiveCfg = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.Build.0 = Debug|Any CPU {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.Build.0 = Release|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.ActiveCfg = Release|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.Build.0 = Release|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.ActiveCfg = Release|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.Build.0 = Release|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.ActiveCfg = Release|x86 - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.Build.0 = Release|x86 - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.ActiveCfg = Ad-Hoc|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.Build.0 = Ad-Hoc|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|x86 - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.Build.0 = Ad-Hoc|x86 - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.Build.0 = AppStore|Any CPU - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.ActiveCfg = AppStore|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.Build.0 = AppStore|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.ActiveCfg = AppStore|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.Build.0 = AppStore|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.ActiveCfg = AppStore|x86 - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.Build.0 = AppStore|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.Build.0 = Release|Any CPU {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.ActiveCfg = Debug|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.Build.0 = Debug|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.ActiveCfg = Debug|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.Build.0 = Debug|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.ActiveCfg = Debug|x86 - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.Build.0 = Debug|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.Build.0 = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.ActiveCfg = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.Build.0 = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.ActiveCfg = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.Build.0 = Debug|Any CPU {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Any CPU.Build.0 = Release|Any CPU - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.ActiveCfg = Release|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.Build.0 = Release|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.ActiveCfg = Release|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.Build.0 = Release|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.ActiveCfg = Release|x86 - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.Build.0 = Release|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.Build.0 = Release|Any CPU {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU @@ -2564,6 +2567,46 @@ Global {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Mono.Build.0 = Release|Any CPU {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|x86.ActiveCfg = Release|Any CPU {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|x86.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|Mono.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|Any CPU.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|iPhone.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|Mono.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|Mono.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|x86.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|x86.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|iPhone.Build.0 = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|Mono.ActiveCfg = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|Mono.Build.0 = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|x86.ActiveCfg = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|x86.Build.0 = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|Any CPU.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|iPhone.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|iPhone.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|Mono.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|Mono.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|x86.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2620,12 +2663,7 @@ Global {E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098} {050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1E8CA5AA-707A-4C57-A682-D265A49E10C3} - {050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098} - {CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2} + {4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/packages.cake b/packages.cake index a22ff65e72..98aa08bb50 100644 --- a/packages.cake +++ b/packages.cake @@ -223,6 +223,17 @@ public class Packages }; }); + var toolsContent = new[] { + new NuSpecContent{ + Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp/bin/" + parameters.DirSuffix + "/netcoreapp2.0/Avalonia.Designer.HostApp.dll")).FullPath, + Target = "tools/netcoreapp2.0/previewer" + }, + new NuSpecContent{ + Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/net461/Avalonia.Designer.HostApp.NetFx.exe")).FullPath, + Target = "tools/net461/previewer" + } + }; + var nuspecNuGetSettingsCore = new [] { /////////////////////////////////////////////////////////////////////////////// @@ -253,6 +264,7 @@ public class Packages Files = coreLibrariesNuSpecContent .Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform) .Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform) + .Concat(toolsContent) .ToList(), BasePath = context.Directory("./"), OutputDirectory = parameters.NugetRoot diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index b67c5ea51d..20bf4ed342 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -16,11 +16,15 @@ namespace ControlCatalog // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. - AppBuilder.Configure() - .UsePlatformDetect() - .Start(); + BuildAvaloniaApp().Start(); } + /// + /// This method is needed for IDE previewer infrastructure + /// + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure().UsePlatformDetect(); + // This will be made into a runtime configuration extension soon! private static void InitializeLogging() { diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 640dce0a80..346535d39d 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -28,19 +28,14 @@ namespace ControlCatalog.NetCore System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); }); else - AppBuilder.Configure() - .CustomPlatformDetect() - .UseReactiveUI() - .Start(); + BuildAvaloniaApp().Start(); } - static AppBuilder CustomPlatformDetect(this AppBuilder builder) - { - //This is needed because we still aren't ready to have MonoMac backend as default one - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return builder.UseSkia().UseMonoMac(); - return builder.UsePlatformDetect(); - } + /// + /// This method is needed for IDE previewer infrastructure + /// + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure().UsePlatformDetect().UseReactiveUI(); static void ConsoleSilencer() { diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 0c9981f209..c8f6b4e65a 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -12,7 +12,7 @@ prompt 4 bin\Debug\Avalonia.Controls.XML - CS1591 + CS1591;CS0067 pdbonly diff --git a/src/Avalonia.Controls/Remote/RemoteServer.cs b/src/Avalonia.Controls/Remote/RemoteServer.cs index 0bc43069e7..86f8318e15 100644 --- a/src/Avalonia.Controls/Remote/RemoteServer.cs +++ b/src/Avalonia.Controls/Remote/RemoteServer.cs @@ -19,8 +19,9 @@ namespace Avalonia.Controls.Remote public TopLevelImpl(IAvaloniaRemoteTransportConnection transport) : base(transport) { } - +#pragma warning disable 67 public event Action LostFocus; + } public RemoteServer(IAvaloniaRemoteTransportConnection transport) diff --git a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj index 63cb8132a9..9f54137e47 100644 --- a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj +++ b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj @@ -12,7 +12,7 @@ prompt 4 bin\Debug\Avalonia.DesignerSupport.xml - CS1591 + CS1591;CS0067 pdbonly diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index a787c8e8e3..535dfd700b 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -65,7 +65,9 @@ namespace Avalonia.DesignerSupport.Remote ClientSize = clientSize; RenderIfNeeded(); } - + + public IScreenImpl Screen { get; } = new ScreenStub(); + public void Activate() { } @@ -87,5 +89,8 @@ namespace Avalonia.DesignerSupport.Remote { } + public void ShowTaskbarIcon(bool value) + { + } } } \ No newline at end of file diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 5aad114cf7..2ed434a2dc 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -75,6 +75,8 @@ namespace Avalonia.DesignerSupport.Remote { } + public IScreenImpl Screen { get; } = new ScreenStub(); + public void SetTitle(string title) { } @@ -88,6 +90,10 @@ namespace Avalonia.DesignerSupport.Remote public void SetIcon(IWindowIconImpl icon) { } + + public void ShowTaskbarIcon(bool value) + { + } } class ClipboardStub : IClipboard @@ -129,4 +135,12 @@ namespace Avalonia.DesignerSupport.Remote public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) => Task.FromResult((string) null); } + + class ScreenStub : IScreenImpl + { + public int ScreenCount => 1; + + public Screen[] AllScreens { get; } = + {new Screen(new Rect(0, 0, 4000, 4000), new Rect(0, 0, 4000, 4000), true)}; + } } \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index ab1bf4eb34..15b3a11fbb 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -333,8 +333,6 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public delegate ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data, IntPtr destroy); - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] - public delegate ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data, IntPtr destroy); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public delegate ulong g_free(IntPtr data); diff --git a/src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj b/src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj new file mode 100644 index 0000000000..cb4ce3f301 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj @@ -0,0 +1,28 @@ + + + Exe + net461 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp.NetFX/Program.cs b/src/tools/Avalonia.Designer.HostApp.NetFX/Program.cs new file mode 100644 index 0000000000..d3f80dd621 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp.NetFX/Program.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Designer.HostApp.NetFX +{ + class Program + { + public static void Main(string[] args) + => Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(args); + } +} diff --git a/src/tools/Avalonia.Designer.HostApp.NetFX/Properties/launchSettings.json b/src/tools/Avalonia.Designer.HostApp.NetFX/Properties/launchSettings.json new file mode 100644 index 0000000000..493caa2693 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp.NetFX/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Avalonia.Designer.HostApp.NetFX": { + "commandName": "Project", + "commandLineArgs": "--transport tcp-bson://127.0.0.1:25000/ bin/Debug/net461/ControlCatalog.Desktop.exe" + } + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 2a68862f18..5bb4b9dd4a 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -2,6 +2,7 @@ Exe netcoreapp2.0 + From e2b9e5dbaa46af922b062906b829eb23a7f50b84 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 7 Oct 2017 21:15:45 +0300 Subject: [PATCH 09/14] Call VerifyAccess in AvaloniaObject.ctor --- src/Avalonia.Base/AvaloniaObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 7defcfbe6a..efcbb57244 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -56,7 +56,7 @@ namespace Avalonia /// public AvaloniaObject() { - CheckAccess(); + VerifyAccess(); foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this)) { object value = property.IsDirect ? @@ -818,4 +818,4 @@ namespace Avalonia throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}"); } } -} \ No newline at end of file +} From 884423d879140fe30b36795c1cd3e91034184684 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 10 Oct 2017 21:43:01 +0300 Subject: [PATCH 10/14] Adapt threading tests to VerifyAccess on AvaloniaObject.ctor --- .../AvaloniaObjectTests_Threading.cs | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs index 229a34643d..215e872e8c 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs @@ -14,13 +14,24 @@ namespace Avalonia.Base.UnitTests { public class AvaloniaObjectTests_Threading { + private ThreadingInterface _threading = new ThreadingInterface(true); + [Fact] - public void StyledProperty_GetValue_Should_Throw() + public void AvaloniaObject_Constructor_Should_Throw() { using (UnitTestApplication.Start(new TestServices(threadingInterface: new ThreadingInterface()))) { - var target = new Class1(); + Assert.Throws(() => new Class1()); + } + } + [Fact] + public void StyledProperty_GetValue_Should_Throw() + { + using (UnitTestApplication.Start(new TestServices(threadingInterface: _threading))) + { + var target = new Class1(); + _threading.CurrentThreadIsLoopThread = false; Assert.Throws(() => target.GetValue(Class1.StyledProperty)); } } @@ -28,10 +39,10 @@ namespace Avalonia.Base.UnitTests [Fact] public void StyledProperty_SetValue_Should_Throw() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: new ThreadingInterface()))) + using (UnitTestApplication.Start(new TestServices(threadingInterface: _threading))) { var target = new Class1(); - + _threading.CurrentThreadIsLoopThread = false; Assert.Throws(() => target.SetValue(Class1.StyledProperty, "foo")); } } @@ -39,10 +50,10 @@ namespace Avalonia.Base.UnitTests [Fact] public void Setting_StyledProperty_Binding_Should_Throw() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: new ThreadingInterface()))) + using (UnitTestApplication.Start(new TestServices(threadingInterface: _threading))) { var target = new Class1(); - + _threading.CurrentThreadIsLoopThread = false; Assert.Throws(() => target.Bind( Class1.StyledProperty, @@ -53,10 +64,10 @@ namespace Avalonia.Base.UnitTests [Fact] public void StyledProperty_ClearValue_Should_Throw() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: new ThreadingInterface()))) + using (UnitTestApplication.Start(new TestServices(threadingInterface: _threading))) { var target = new Class1(); - + _threading.CurrentThreadIsLoopThread = false; Assert.Throws(() => target.ClearValue(Class1.StyledProperty)); } } @@ -64,10 +75,10 @@ namespace Avalonia.Base.UnitTests [Fact] public void StyledProperty_IsSet_Should_Throw() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: new ThreadingInterface()))) + using (UnitTestApplication.Start(new TestServices(threadingInterface: _threading))) { var target = new Class1(); - + _threading.CurrentThreadIsLoopThread = false; Assert.Throws(() => target.IsSet(Class1.StyledProperty)); } } @@ -75,10 +86,10 @@ namespace Avalonia.Base.UnitTests [Fact] public void DirectProperty_GetValue_Should_Throw() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: new ThreadingInterface()))) + using (UnitTestApplication.Start(new TestServices(threadingInterface: _threading))) { var target = new Class1(); - + _threading.CurrentThreadIsLoopThread = false; Assert.Throws(() => target.GetValue(Class1.DirectProperty)); } } @@ -86,10 +97,10 @@ namespace Avalonia.Base.UnitTests [Fact] public void DirectProperty_SetValue_Should_Throw() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: new ThreadingInterface()))) + using (UnitTestApplication.Start(new TestServices(threadingInterface: _threading))) { var target = new Class1(); - + _threading.CurrentThreadIsLoopThread = false; Assert.Throws(() => target.SetValue(Class1.DirectProperty, "foo")); } } @@ -97,10 +108,10 @@ namespace Avalonia.Base.UnitTests [Fact] public void Setting_DirectProperty_Binding_Should_Throw() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: new ThreadingInterface()))) + using (UnitTestApplication.Start(new TestServices(threadingInterface: _threading))) { var target = new Class1(); - + _threading.CurrentThreadIsLoopThread = false; Assert.Throws(() => target.Bind( Class1.DirectProperty, @@ -111,10 +122,10 @@ namespace Avalonia.Base.UnitTests [Fact] public void DirectProperty_ClearValue_Should_Throw() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: new ThreadingInterface()))) + using (UnitTestApplication.Start(new TestServices(threadingInterface: _threading))) { var target = new Class1(); - + _threading.CurrentThreadIsLoopThread = false; Assert.Throws(() => target.ClearValue(Class1.DirectProperty)); } } @@ -122,10 +133,10 @@ namespace Avalonia.Base.UnitTests [Fact] public void DirectProperty_IsSet_Should_Throw() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: new ThreadingInterface()))) + using (UnitTestApplication.Start(new TestServices(threadingInterface: _threading))) { var target = new Class1(); - + _threading.CurrentThreadIsLoopThread = false; Assert.Throws(() => target.IsSet(Class1.DirectProperty)); } } @@ -141,6 +152,7 @@ namespace Avalonia.Base.UnitTests private class ThreadingInterface : IPlatformThreadingInterface { + public ThreadingInterface(bool isLoopThread = false) { CurrentThreadIsLoopThread = isLoopThread; From a5f0d23ccb7e22a1f440092cbc3765b79afe9f8c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 11 Oct 2017 15:08:22 +0300 Subject: [PATCH 11/14] Embed Metsys.Bson as a serializer to avoid referencing JSON.NET --- .../Avalonia.DotNetFrameworkRuntime.csproj | 1 - .../Avalonia.Remote.Protocol.csproj | 3 +- .../BsonStreamTransport.cs | 13 +- src/Avalonia.Remote.Protocol/MetsysBson.cs | 1632 +++++++++++++++++ 4 files changed, 1639 insertions(+), 10 deletions(-) create mode 100644 src/Avalonia.Remote.Protocol/MetsysBson.cs diff --git a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj index db28f8c469..dc4a06d2a7 100644 --- a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj +++ b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj @@ -10,7 +10,6 @@ Properties\SharedAssemblyInfo.cs - diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj index a9c6b9e9a0..871c9cd995 100644 --- a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj +++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj @@ -1,10 +1,9 @@  - netstandard1.3 + netstandard2.0 AVALONIA_REMOTE_PROTOCOL;$(DefineConstants) - \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs index 82765f98f6..604703cac8 100644 --- a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs +++ b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.IO; using System.Threading; -using Newtonsoft.Json; using System.Threading.Tasks; -using Newtonsoft.Json.Bson; +using Metsys.Bson; namespace Avalonia.Remote.Protocol { @@ -20,8 +19,7 @@ namespace Avalonia.Remote.Protocol private readonly object _lock = new object(); private bool _writeOperationPending; private bool _readingAlreadyStarted; - private bool _writerIsBroken; - static readonly JsonSerializer Serializer = new JsonSerializer(); + private bool _writerIsBroken; private static readonly byte[] ZeroLength = new byte[4]; public BsonStreamTransportConnection(IMessageTypeResolver resolver, Stream inputStream, Stream outputStream, Action disposeCallback) @@ -79,7 +77,8 @@ namespace Avalonia.Remote.Protocol var guid = new Guid(guidBytes); var buffer = new byte[length]; await ReadExact(buffer).ConfigureAwait(false); - var message = Serializer.Deserialize(new BsonReader(new MemoryStream(buffer)), _resolver.GetByGuid(guid)); + var message = Deserializer.Deserialize(new BinaryReader(new MemoryStream(buffer)), + _resolver.GetByGuid(guid)); OnMessage?.Invoke(this, message); } } @@ -107,8 +106,8 @@ namespace Avalonia.Remote.Protocol _outputBlock.SetLength(0); _outputBlock.Write(ZeroLength, 0, 4); _outputBlock.Write(guid, 0, guid.Length); - var writer = new BsonWriter(_outputBlock); - Serializer.Serialize(writer, data); + var serialized = Serializer.Serialize(data); + _outputBlock.Write(serialized, 0, serialized.Length); _outputBlock.Seek(0, SeekOrigin.Begin); var length = BitConverter.GetBytes((int)_outputBlock.Length - 20); _outputBlock.Write(length, 0, length.Length); diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs new file mode 100644 index 0000000000..eb6445c3a5 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -0,0 +1,1632 @@ +/* +Copyright (c) 2010, Karl Seguin - http://www.openmymind.net/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Code imported from https://github.com/elaberge/Metsys.Bson without any changes + +*/ + +using System.Text; +using System.Text.RegularExpressions; +using System.IO; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Security.Cryptography; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System; +using System.Runtime.Serialization; + +using Metsys.Bson.Configuration; +// ReSharper disable All + +namespace Metsys.Bson +{ + internal enum Types + { + Double = 1, + String = 2, + Object = 3, + Array = 4, + Binary = 5, + Undefined = 6, + ObjectId = 7, + Boolean = 8, + DateTime = 9, + Null = 10, + Regex = 11, + Reference = 12, + Code = 13, + Symbol = 14, + ScopedCode = 15, + Int32 = 16, + Timestamp = 17, + Int64 = 18, + } +} + +namespace Metsys.Bson +{ + + internal class Serializer + { + private static readonly IDictionary _typeMap = new Dictionary + { + {typeof (int), Types.Int32}, + {typeof (long), Types.Int64}, + {typeof (bool), Types.Boolean}, + {typeof (string), Types.String}, + {typeof (double), Types.Double}, + {typeof (Guid), Types.Binary}, + {typeof (Regex), Types.Regex}, + {typeof (DateTime), Types.DateTime}, + {typeof (float), Types.Double}, + {typeof (byte[]), Types.Binary}, + {typeof (ObjectId), Types.ObjectId}, + {typeof (ScopedCode), Types.ScopedCode} + }; + + private readonly BinaryWriter _writer; + private Document _current; + + public static byte[] Serialize(T document) + { + var type = document.GetType(); + if (type.IsValueType || + (typeof(IEnumerable).IsAssignableFrom(type) && typeof(IDictionary).IsAssignableFrom(type) == false) + ) + { + throw new BsonException("Root type must be an object"); + } + using (var ms = new MemoryStream(250)) + using (var writer = new BinaryWriter(ms)) + { + new Serializer(writer).WriteDocument(document); + return ms.ToArray(); + } + } + public static byte[] Serialize(object document) + { + var type = document.GetType(); + if (type.IsValueType || + (typeof(IEnumerable).IsAssignableFrom(type) && typeof(IDictionary).IsAssignableFrom(type) == false) + ) + { + throw new BsonException("Root type must be an object"); + } + using (var ms = new MemoryStream(250)) + using (var writer = new BinaryWriter(ms)) + { + new Serializer(writer).WriteDocument(document); + return ms.ToArray(); + } + } + + private Serializer(BinaryWriter writer) + { + _writer = writer; + } + + private void NewDocument() + { + var old = _current; + _current = new Document { Parent = old, Length = (int)_writer.BaseStream.Position, Digested = 4 }; + _writer.Write(0); // length placeholder + } + private void EndDocument(bool includeEeo) + { + var old = _current; + if (includeEeo) + { + Written(1); + _writer.Write((byte)0); + } + + _writer.Seek(_current.Length, SeekOrigin.Begin); + _writer.Write(_current.Digested); // override the document length placeholder + _writer.Seek(0, SeekOrigin.End); // back to the end + _current = _current.Parent; + if (_current != null) + { + Written(old.Digested); + } + } + + private void Written(int length) + { + _current.Digested += length; + } + + private void WriteDocument(object document) + { + NewDocument(); + WriteObject(document); + EndDocument(true); + } + + private void WriteObject(object document) + { + var asDictionary = document as IDictionary; + if (asDictionary != null) + { + Write(asDictionary); + return; + } + + var typeHelper = TypeHelper.GetHelperForType(document.GetType()); + foreach (var property in typeHelper.GetProperties()) + { + if (property.Ignored) { continue; } + var name = property.Name; + var value = property.Getter(document); + if (value == null && property.IgnoredIfNull) + { + continue; + } + SerializeMember(name, value); + } + } + + private void SerializeMember(string name, object value) + { + if (value == null) + { + Write(Types.Null); + WriteName(name); + return; + } + + var type = value.GetType(); + if (type.IsEnum) + { + type = Enum.GetUnderlyingType(type); + } + + Types storageType; + if (!_typeMap.TryGetValue(type, out storageType)) + { + // this isn't a simple type; + Write(name, value); + return; + } + + Write(storageType); + WriteName(name); + switch (storageType) + { + case Types.Int32: + Written(4); + _writer.Write((int)value); + return; + case Types.Int64: + Written(8); + _writer.Write((long)value); + return; + case Types.String: + Write((string)value); + return; + case Types.Double: + Written(8); + if (value is float) + { + _writer.Write(Convert.ToDouble((float)value)); + } + else + { + _writer.Write((double)value); + } + + return; + case Types.Boolean: + Written(1); + _writer.Write((bool)value ? (byte)1 : (byte)0); + return; + case Types.DateTime: + Written(8); + _writer.Write((long)((DateTime)value).ToUniversalTime().Subtract(Helper.Epoch).TotalMilliseconds); + return; + case Types.Binary: + WriteBinnary(value); + return; + case Types.ScopedCode: + Write((ScopedCode)value); + return; + case Types.ObjectId: + Written(((ObjectId)value).Value.Length); + _writer.Write(((ObjectId)value).Value); + return; + case Types.Regex: + Write((Regex)value); + break; + } + } + + private void Write(string name, object value) + { + if (value is IDictionary) + { + Write(Types.Object); + WriteName(name); + NewDocument(); + Write((IDictionary)value); + EndDocument(true); + } + else if (value is IEnumerable) + { + Write(Types.Array); + WriteName(name); + NewDocument(); + Write((IEnumerable)value); + EndDocument(true); + } + else + { + Write(Types.Object); + WriteName(name); + WriteDocument(value); // Write manages new/end document + } + } + + private void Write(IEnumerable enumerable) + { + var index = 0; + foreach (var value in enumerable) + { + SerializeMember((index++).ToString(), value); + } + } + + private void Write(IDictionary dictionary) + { + foreach (var key in dictionary.Keys) + { + SerializeMember((string)key, dictionary[key]); + } + } + + private void WriteBinnary(object value) + { + if (value is byte[]) + { + var bytes = (byte[])value; + var length = bytes.Length; + _writer.Write(length + 4); + _writer.Write((byte)2); + _writer.Write(length); + _writer.Write(bytes); + Written(9 + length); + } + else if (value is Guid) + { + var guid = (Guid)value; + var bytes = guid.ToByteArray(); + _writer.Write(bytes.Length); + _writer.Write((byte)3); + _writer.Write(bytes); + Written(5 + bytes.Length); + } + } + + private void Write(Types type) + { + _writer.Write((byte)type); + Written(1); + } + + private void WriteName(string name) + { + var bytes = Encoding.UTF8.GetBytes(name); + _writer.Write(bytes); + _writer.Write((byte)0); + Written(bytes.Length + 1); + } + + private void Write(string name) + { + var bytes = Encoding.UTF8.GetBytes(name); + _writer.Write(bytes.Length + 1); + _writer.Write(bytes); + _writer.Write((byte)0); + Written(bytes.Length + 5); // stringLength + length + null byte + } + + private void Write(Regex regex) + { + WriteName(regex.ToString()); + + var options = string.Empty; + if ((regex.Options & RegexOptions.ECMAScript) == RegexOptions.ECMAScript) + { + options = string.Concat(options, 'e'); + } + + if ((regex.Options & RegexOptions.IgnoreCase) == RegexOptions.IgnoreCase) + { + options = string.Concat(options, 'i'); + } + + if ((regex.Options & RegexOptions.CultureInvariant) == RegexOptions.CultureInvariant) + { + options = string.Concat(options, 'l'); + } + + if ((regex.Options & RegexOptions.Multiline) == RegexOptions.Multiline) + { + options = string.Concat(options, 'm'); + } + + if ((regex.Options & RegexOptions.Singleline) == RegexOptions.Singleline) + { + options = string.Concat(options, 's'); + } + + options = string.Concat(options, 'u'); // all .net regex are unicode regex, therefore: + if ((regex.Options & RegexOptions.IgnorePatternWhitespace) == RegexOptions.IgnorePatternWhitespace) + { + options = string.Concat(options, 'w'); + } + + if ((regex.Options & RegexOptions.ExplicitCapture) == RegexOptions.ExplicitCapture) + { + options = string.Concat(options, 'x'); + } + + WriteName(options); + } + + private void Write(ScopedCode value) + { + NewDocument(); + Write(value.CodeString); + WriteDocument(value.Scope); + EndDocument(false); + } + } +} +namespace Metsys.Bson +{ + internal class ScopedCode + { + public string CodeString { get; set; } + public object Scope { get; set; } + } + + internal class ScopedCode : ScopedCode + { + public new T Scope { get; set; } + } +} + +namespace Metsys.Bson +{ + internal static class ObjectIdGenerator + { + private static readonly object _inclock = new object(); + + private static int _counter; + private static readonly byte[] _machineHash = GenerateHostHash(); + private static readonly byte[] _processId = BitConverter.GetBytes(GenerateProcId()); + + public static byte[] Generate() + { + var oid = new byte[12]; + var copyidx = 0; + + Array.Copy(BitConverter.GetBytes(GenerateTime()), 0, oid, copyidx, 4); + copyidx += 4; + + Array.Copy(_machineHash, 0, oid, copyidx, 3); + copyidx += 3; + + Array.Copy(_processId, 0, oid, copyidx, 2); + copyidx += 2; + + Array.Copy(BitConverter.GetBytes(GenerateInc()), 0, oid, copyidx, 3); + return oid; + } + + private static int GenerateTime() + { + var now = DateTime.Now.ToUniversalTime(); + + var nowtime = new DateTime(Helper.Epoch.Year, Helper.Epoch.Month, Helper.Epoch.Day, now.Hour, now.Minute, now.Second, now.Millisecond); + var diff = nowtime - Helper.Epoch; + return Convert.ToInt32(Math.Floor(diff.TotalMilliseconds)); + } + + private static int GenerateInc() + { + lock (_inclock) + { + return _counter++; + } + } + + private static byte[] GenerateHostHash() + { + using (var md5 = MD5.Create()) + { + var host = Dns.GetHostName(); + return md5.ComputeHash(Encoding.Default.GetBytes(host)); + } + } + private static int GenerateProcId() + { + var proc = Process.GetCurrentProcess(); + return proc.Id; + } + } +} + +namespace Metsys.Bson +{ + internal class ObjectId + { + private string _string; + + public ObjectId() + { + } + + public ObjectId(string value) : this(DecodeHex(value)) + { + } + + internal ObjectId(byte[] value) + { + Value = value; + } + + public static ObjectId Empty + { + get { return new ObjectId("000000000000000000000000"); } + } + + public byte[] Value { get; private set; } + + public static ObjectId NewObjectId() + { + // TODO: generate random-ish bits. + return new ObjectId { Value = ObjectIdGenerator.Generate() }; + } + + public static bool TryParse(string value, out ObjectId id) + { + id = Empty; + if (value == null || value.Length != 24) + { + return false; + } + + try + { + id = new ObjectId(value); + return true; + } + catch (FormatException) + { + return false; + } + } + + public static bool operator ==(ObjectId a, ObjectId b) + { + if (ReferenceEquals(a, b)) + { + return true; + } + + if (((object)a == null) || ((object)b == null)) + { + return false; + } + + return a.Equals(b); + } + + public static bool operator !=(ObjectId a, ObjectId b) + { + return !(a == b); + } + + public override int GetHashCode() + { + return Value != null ? ToString().GetHashCode() : 0; + } + + public override string ToString() + { + if (_string == null && Value != null) + { + _string = BitConverter.ToString(Value).Replace("-", string.Empty).ToLower(); + } + + return _string; + } + + public override bool Equals(object o) + { + var other = o as ObjectId; + return Equals(other); + } + + public bool Equals(ObjectId other) + { + return other != null && ToString() == other.ToString(); + } + + protected static byte[] DecodeHex(string val) + { + var chars = val.ToCharArray(); + var numberChars = chars.Length; + var bytes = new byte[numberChars / 2]; + + for (var i = 0; i < numberChars; i += 2) + { + bytes[i / 2] = Convert.ToByte(new string(chars, i, 2), 16); + } + + return bytes; + } + + public static implicit operator string(ObjectId oid) + { + return oid == null ? null : oid.ToString(); + } + public static implicit operator ObjectId(string oidString) + { + return new ObjectId(oidString); + } + } +} + +namespace Metsys.Bson +{ + public interface IExpando + { + IDictionary Expando { get; } + } +} + +namespace Metsys.Bson +{ + internal class MagicProperty + { + private readonly PropertyInfo _property; + private readonly string _name; + private readonly bool _ignored; + public readonly bool _ignoredIfNull; + + public Type Type + { + get { return _property.PropertyType; } + } + public string Name + { + get { return _name; } + } + public bool Ignored + { + get { return _ignored; } + } + public bool IgnoredIfNull + { + get { return _ignoredIfNull; } + } + + public Action Setter { get; private set; } + + public Func Getter { get; private set; } + + public MagicProperty(PropertyInfo property, string name, bool ignored, bool ignoredIfNull) + { + _property = property; + _name = name; + _ignored = ignored; + _ignoredIfNull = ignoredIfNull; + Getter = CreateGetterMethod(property); + Setter = CreateSetterMethod(property); + } + + private static Action CreateSetterMethod(PropertyInfo property) + { + var genericHelper = typeof(MagicProperty).GetMethod("SetterMethod", BindingFlags.Static | BindingFlags.NonPublic); + var constructedHelper = genericHelper.MakeGenericMethod(property.DeclaringType, property.PropertyType); + return (Action)constructedHelper.Invoke(null, new object[] { property }); + } + + private static Func CreateGetterMethod(PropertyInfo property) + { + var genericHelper = typeof(MagicProperty).GetMethod("GetterMethod", BindingFlags.Static | BindingFlags.NonPublic); + var constructedHelper = genericHelper.MakeGenericMethod(property.DeclaringType, property.PropertyType); + return (Func)constructedHelper.Invoke(null, new object[] { property }); + } + + //called via reflection + private static Action SetterMethod(PropertyInfo method) where TTarget : class + { + var m = method.GetSetMethod(true); + if (m == null) { return null; } //no setter + var func = (Action)Delegate.CreateDelegate(typeof(Action), m); + return (target, param) => func((TTarget)target, (TParam)param); + } + + //called via reflection + private static Func GetterMethod(PropertyInfo method) where TTarget : class + { + var m = method.GetGetMethod(true); + var func = (Func)Delegate.CreateDelegate(typeof(Func), m); + return target => func((TTarget)target); + } + } +} + +namespace Metsys.Bson +{ + internal class TypeHelper + { + private static readonly IDictionary _cachedTypeLookup = new Dictionary(); + private static readonly BsonConfiguration _configuration = BsonConfiguration.Instance; + + private readonly IDictionary _properties; + + private TypeHelper(Type type) + { + var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); + _properties = LoadMagicProperties(type, properties); + if (typeof(IExpando).IsAssignableFrom(type)) + { + Expando = _properties["Expando"]; + } + } + + public MagicProperty Expando { get; private set; } + + public ICollection GetProperties() + { + return _properties.Values; + } + + public MagicProperty FindProperty(string name) + { + return _properties.ContainsKey(name) ? _properties[name] : null; + } + + public static TypeHelper GetHelperForType(Type type) + { + TypeHelper helper; + if (!_cachedTypeLookup.TryGetValue(type, out helper)) + { + helper = new TypeHelper(type); + _cachedTypeLookup[type] = helper; + } + return helper; + } + + public static string FindProperty(LambdaExpression lambdaExpression) + { + Expression expressionToCheck = lambdaExpression; + + var done = false; + while (!done) + { + switch (expressionToCheck.NodeType) + { + case ExpressionType.Convert: + expressionToCheck = ((UnaryExpression)expressionToCheck).Operand; + break; + + case ExpressionType.Lambda: + expressionToCheck = ((LambdaExpression)expressionToCheck).Body; + break; + + case ExpressionType.MemberAccess: + var memberExpression = (MemberExpression)expressionToCheck; + + if (memberExpression.Expression.NodeType != ExpressionType.Parameter && memberExpression.Expression.NodeType != ExpressionType.Convert) + { + throw new ArgumentException(string.Format("Expression '{0}' must resolve to top-level member.", lambdaExpression), "lambdaExpression"); + } + return memberExpression.Member.Name; + default: + done = true; + break; + } + } + + return null; + } + + public static PropertyInfo FindProperty(Type type, string name) + { + return type.GetProperties().Where(p => p.Name == name).First(); + } + + private static IDictionary LoadMagicProperties(Type type, IEnumerable properties) + { + var magic = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + foreach (var property in properties) + { + if (property.GetIndexParameters().Length > 0) + { + continue; + } + var name = _configuration.AliasFor(type, property.Name); + var ignored = _configuration.IsIgnored(type, property.Name); + var ignoredIfNull = _configuration.IsIgnoredIfNull(type, property.Name); + magic.Add(name, new MagicProperty(property, name, ignored, ignoredIfNull)); + } + return magic; + } + } +} + +namespace Metsys.Bson +{ + internal class ListWrapper : BaseWrapper + { + private IList _list; + + public override object Collection + { + get { return _list; } + } + public override void Add(object value) + { + _list.Add(value); + } + + protected override object CreateContainer(Type type, Type itemType) + { + if (type.IsInterface) + { + return Activator.CreateInstance(typeof(List<>).MakeGenericType(itemType)); + } + if (type.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null) != null) + { + return Activator.CreateInstance(type); + } + return null; + } + protected override void SetContainer(object container) + { + _list = container == null ? new ArrayList() : (IList)container; + } + } +} + +namespace Metsys.Bson +{ + internal static class ListHelper + { + public static Type GetListItemType(Type enumerableType) + { + if (enumerableType.IsArray) + { + return enumerableType.GetElementType(); + } + return enumerableType.IsGenericType ? enumerableType.GetGenericArguments()[0] : typeof(object); + } + + public static Type GetDictionarKeyType(Type enumerableType) + { + return enumerableType.IsGenericType + ? enumerableType.GetGenericArguments()[0] + : typeof(object); + } + + public static Type GetDictionarValueType(Type enumerableType) + { + return enumerableType.IsGenericType + ? enumerableType.GetGenericArguments()[1] + : typeof(object); + } + + public static IDictionary CreateDictionary(Type dictionaryType, Type keyType, Type valueType) + { + if (dictionaryType.IsInterface) + { + return (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(keyType, valueType)); + } + + if (dictionaryType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null) != null) + { + return (IDictionary)Activator.CreateInstance(dictionaryType); + } + + return new Dictionary(); + } + } +} + +namespace Metsys.Bson +{ + internal class CollectionWrapper : BaseWrapper + { + private ICollection _list; + + public override object Collection + { + get { return _list; } + } + + public override void Add(object value) + { + _list.Add((T)value); + } + + protected override object CreateContainer(Type type, Type itemType) + { + return Activator.CreateInstance(type); + } + protected override void SetContainer(object container) + { + _list = (ICollection)container; + } + } +} + +namespace Metsys.Bson +{ + internal abstract class BaseWrapper + { + public static BaseWrapper Create(Type type, Type itemType, object existingContainer) + { + var instance = CreateWrapperFromType(existingContainer == null ? type : existingContainer.GetType(), itemType); + instance.SetContainer(existingContainer ?? instance.CreateContainer(type, itemType)); + return instance; + } + + private static BaseWrapper CreateWrapperFromType(Type type, Type itemType) + { + if (type.IsArray) + { + return (BaseWrapper)Activator.CreateInstance(typeof(ArrayWrapper<>).MakeGenericType(itemType)); + } + + var isCollection = false; + var types = new List(type.GetInterfaces().Select(h => h.IsGenericType ? h.GetGenericTypeDefinition() : h)); + types.Insert(0, type.IsGenericType ? type.GetGenericTypeDefinition() : type); + foreach (var @interface in types) + { + if (typeof(IList<>).IsAssignableFrom(@interface) || typeof(IList).IsAssignableFrom(@interface)) + { + return new ListWrapper(); + } + if (typeof(ICollection<>).IsAssignableFrom(@interface)) + { + isCollection = true; + } + } + if (isCollection) + { + return (BaseWrapper)Activator.CreateInstance(typeof(CollectionWrapper<>).MakeGenericType(itemType)); + } + + //a last-ditch pass + foreach (var @interface in types) + { + if (typeof(IEnumerable<>).IsAssignableFrom(@interface) || typeof(IEnumerable).IsAssignableFrom(@interface)) + { + return new ListWrapper(); + } + } + throw new BsonException(string.Format("Collection of type {0} cannot be deserialized", type.FullName)); + } + + public abstract void Add(object value); + public abstract object Collection { get; } + + protected abstract object CreateContainer(Type type, Type itemType); + protected abstract void SetContainer(object container); + } +} + +namespace Metsys.Bson +{ + + internal class ArrayWrapper : BaseWrapper + { + private readonly List _list = new List(); + + public override void Add(object value) + { + _list.Add((T)value); + } + + protected override object CreateContainer(Type type, Type itemType) + { + return null; + } + + protected override void SetContainer(object container) + { + if (container != null) + { + throw new BsonException("An container cannot exist when trying to deserialize an array"); + } + } + + public override object Collection + { + get + { + return _list.ToArray(); + } + } + } +} + +namespace Metsys.Bson +{ + public static class Helper + { + public static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + } +} +namespace Metsys.Bson +{ + internal class Document + { + public int Length; + public Document Parent; + public int Digested; + } +} + +namespace Metsys.Bson +{ + internal class Deserializer + { + internal class Options + { + public bool LongIntegers { get; set; } + public bool StringDates { get; set; } + } + + private readonly static IDictionary _typeMap = new Dictionary + { + {Types.Int32, typeof(int)}, + {Types.Int64, typeof (long)}, + {Types.Boolean, typeof (bool)}, + {Types.String, typeof (string)}, + {Types.Double, typeof(double)}, + {Types.Binary, typeof (byte[])}, + {Types.Regex, typeof (Regex)}, + {Types.DateTime, typeof (DateTime)}, + {Types.ObjectId, typeof(ObjectId)}, + {Types.Array, typeof(List)}, + {Types.Object, typeof(Dictionary)}, + {Types.Null, null}, + }; + private readonly BinaryReader _reader; + private Document _current; + + private Deserializer(BinaryReader reader) + { + _reader = reader; + } + + public static T Deserialize(byte[] objectData, Options options = null) where T : class + { + using (var ms = new MemoryStream()) + { + ms.Write(objectData, 0, objectData.Length); + ms.Position = 0; + return Deserialize(new BinaryReader(ms), options ?? new Options()); + } + } + + private static T Deserialize(BinaryReader stream, Options options) + { + return new Deserializer(stream).Read(options); + } + + private T Read(Options options) + { + NewDocument(_reader.ReadInt32()); + var @object = (T)DeserializeValue(typeof(T), Types.Object, options); + return @object; + } + + public static object Deserialize(BinaryReader stream, Type t, Options options = null) + { + return new Deserializer(stream).Read(t, options ?? new Options()); + } + + object Read(Type t, Options options) + { + NewDocument(_reader.ReadInt32()); + return DeserializeValue(t, Types.Object, options); + } + + private void Read(int read) + { + _current.Digested += read; + } + + private bool IsDone() + { + var isDone = _current.Digested + 1 == _current.Length; + if (isDone) + { + _reader.ReadByte(); // EOO + var old = _current; + _current = old.Parent; + if (_current != null) { Read(old.Length); } + } + return isDone; + } + + private void NewDocument(int length) + { + var old = _current; + _current = new Document { Length = length, Parent = old, Digested = 4 }; + } + + private object DeserializeValue(Type type, Types storedType, Options options) + { + return DeserializeValue(type, storedType, null, options); + } + + private object DeserializeValue(Type type, Types storedType, object container, Options options) + { + if (storedType == Types.Null) + { + return null; + } + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + type = Nullable.GetUnderlyingType(type); + } + if (type == typeof(string)) + { + return ReadString(); + } + if (type == typeof(int)) + { + var val = ReadInt(storedType); + return options.LongIntegers ? (object)(long)val : (object)val; + } + if (type.IsEnum) + { + return ReadEnum(type, storedType); + } + if (type == typeof(float)) + { + Read(8); + return (float)_reader.ReadDouble(); + } + if (storedType == Types.Binary) + { + return ReadBinary(); + } + if (typeof(IEnumerable).IsAssignableFrom(type)) + { + return ReadList(type, container, options); + } + if (type == typeof(bool)) + { + Read(1); + return _reader.ReadBoolean(); + } + if (type == typeof(DateTime)) + { + var value = Helper.Epoch.AddMilliseconds(ReadLong(Types.Int64)); + return options.StringDates ? value.ToString("s", System.Globalization.CultureInfo.InvariantCulture) : (object)value; + } + if (type == typeof(ObjectId)) + { + Read(12); + return new ObjectId(_reader.ReadBytes(12)); + } + if (type == typeof(long)) + { + return ReadLong(storedType); + } + if (type == typeof(double)) + { + Read(8); + return _reader.ReadDouble(); + } + if (type == typeof(Regex)) + { + return ReadRegularExpression(); + } + if (type == typeof(ScopedCode)) + { + return ReadScopedCode(options); + } + return ReadObject(type, options); + } + + private object ReadObject(Type type, Options options) + { + var instance = Activator.CreateInstance(type, true); + var typeHelper = TypeHelper.GetHelperForType(type); + while (true) + { + var storageType = ReadType(); + var name = ReadName(); + var isNull = false; + if (storageType == Types.Object) + { + var length = _reader.ReadInt32(); + if (length == 5) + { + _reader.ReadByte(); //eoo + Read(5); + isNull = true; + } + else + { + NewDocument(length); + } + } + object container = null; + var property = typeHelper.FindProperty(name); + var propertyType = property != null ? property.Type : _typeMap.ContainsKey(storageType) ? _typeMap[storageType] : typeof(object); + if (property == null && typeHelper.Expando == null) + { + throw new BsonException(string.Format("Deserialization failed: type {0} does not have a property named {1}", type.FullName, name)); + } + if (property != null && property.Setter == null) + { + container = property.Getter(instance); + } + var value = isNull ? null : DeserializeValue(propertyType, storageType, container, options); + if (property == null) + { + ((IDictionary)typeHelper.Expando.Getter(instance))[name] = value; + } + else if (container == null && value != null && !property.Ignored) + { + property.Setter(instance, value); + } + if (IsDone()) + { + break; + } + } + return instance; + } + + private object ReadList(Type listType, object existingContainer, Options options) + { + if (IsDictionary(listType)) + { + return ReadDictionary(listType, existingContainer, options); + } + + NewDocument(_reader.ReadInt32()); + var itemType = ListHelper.GetListItemType(listType); + var isObject = typeof(object) == itemType; + var wrapper = BaseWrapper.Create(listType, itemType, existingContainer); + + while (!IsDone()) + { + var storageType = ReadType(); + ReadName(); + if (storageType == Types.Object) + { + NewDocument(_reader.ReadInt32()); + } + var specificItemType = isObject ? _typeMap[storageType] : itemType; + var value = DeserializeValue(specificItemType, storageType, options); + wrapper.Add(value); + } + return wrapper.Collection; + } + + private static bool IsDictionary(Type type) + { + var types = new List(type.GetInterfaces()); + types.Insert(0, type); + foreach (var interfaceType in types) + { + if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + { + return true; + } + } + return false; + } + + private object ReadDictionary(Type listType, object existingContainer, Options options) + { + var valueType = ListHelper.GetDictionarValueType(listType); + var isObject = typeof(object) == valueType; + var container = existingContainer == null ? ListHelper.CreateDictionary(listType, ListHelper.GetDictionarKeyType(listType), valueType) : (IDictionary)existingContainer; + + while (!IsDone()) + { + var storageType = ReadType(); + + var key = ReadName(); + if (storageType == Types.Object) + { + NewDocument(_reader.ReadInt32()); + } + var specificItemType = isObject ? _typeMap[storageType] : valueType; + var value = DeserializeValue(specificItemType, storageType, options); + container.Add(key, value); + } + return container; + } + + private object ReadBinary() + { + var length = _reader.ReadInt32(); + var subType = _reader.ReadByte(); + Read(5 + length); + if (subType == 2) + { + return _reader.ReadBytes(_reader.ReadInt32()); + } + if (subType == 3) + { + return new Guid(_reader.ReadBytes(length)); + } + throw new BsonException("No support for binary type: " + subType); + } + + private string ReadName() + { + var buffer = new List(128); //todo: use a pool to prevent fragmentation + byte b; + while ((b = _reader.ReadByte()) > 0) + { + buffer.Add(b); + } + Read(buffer.Count + 1); + return Encoding.UTF8.GetString(buffer.ToArray()); + } + + private string ReadString() + { + var length = _reader.ReadInt32(); + var buffer = _reader.ReadBytes(length - 1); //todo: again, look at fragementation prevention + _reader.ReadByte(); //null; + Read(4 + length); + + return Encoding.UTF8.GetString(buffer); + } + + private int ReadInt(Types storedType) + { + switch (storedType) + { + case Types.Int32: + Read(4); + return _reader.ReadInt32(); + case Types.Int64: + Read(8); + return (int)_reader.ReadInt64(); + case Types.Double: + Read(8); + return (int)_reader.ReadDouble(); + default: + throw new BsonException("Could not create an int from " + storedType); + } + } + + private long ReadLong(Types storedType) + { + switch (storedType) + { + case Types.Int32: + Read(4); + return _reader.ReadInt32(); + case Types.Int64: + Read(8); + return _reader.ReadInt64(); + case Types.Double: + Read(8); + return (long)_reader.ReadDouble(); + default: + throw new BsonException("Could not create an int64 from " + storedType); + } + } + + private object ReadEnum(Type type, Types storedType) + { + if (storedType == Types.Int64) + { + return Enum.Parse(type, ReadLong(storedType).ToString(), false); + } + return Enum.Parse(type, ReadInt(storedType).ToString(), false); + } + + private object ReadRegularExpression() + { + var pattern = ReadName(); + var optionsString = ReadName(); + + var options = RegexOptions.None; + if (optionsString.Contains("e")) options = options | RegexOptions.ECMAScript; + if (optionsString.Contains("i")) options = options | RegexOptions.IgnoreCase; + if (optionsString.Contains("l")) options = options | RegexOptions.CultureInvariant; + if (optionsString.Contains("m")) options = options | RegexOptions.Multiline; + if (optionsString.Contains("s")) options = options | RegexOptions.Singleline; + if (optionsString.Contains("w")) options = options | RegexOptions.IgnorePatternWhitespace; + if (optionsString.Contains("x")) options = options | RegexOptions.ExplicitCapture; + + return new Regex(pattern, options); + } + + private Types ReadType() + { + Read(1); + return (Types)_reader.ReadByte(); + } + + private ScopedCode ReadScopedCode(Options options) + { + _reader.ReadInt32(); //length + Read(4); + var name = ReadString(); + NewDocument(_reader.ReadInt32()); + return new ScopedCode { CodeString = name, Scope = DeserializeValue(typeof(object), Types.Object, options) }; + } + } +} + +namespace Metsys.Bson.Configuration +{ + public interface ITypeConfiguration + { + ITypeConfiguration UseAlias(Expression> expression, string alias); + ITypeConfiguration Ignore(Expression> expression); + ITypeConfiguration Ignore(string name); + ITypeConfiguration IgnoreIfNull(Expression> expression); + } + + internal class TypeConfiguration : ITypeConfiguration + { + private readonly BsonConfiguration _configuration; + + internal TypeConfiguration(BsonConfiguration configuration) + { + _configuration = configuration; + } + + public ITypeConfiguration UseAlias(Expression> expression, string alias) + { + var member = expression.GetMemberExpression(); + _configuration.AddMap(member.GetName(), alias); + return this; + } + + public ITypeConfiguration Ignore(Expression> expression) + { + var member = expression.GetMemberExpression(); + return Ignore(member.GetName()); + } + + public ITypeConfiguration Ignore(string name) + { + _configuration.AddIgnore(name); + return this; + } + + public ITypeConfiguration IgnoreIfNull(Expression> expression) + { + var member = expression.GetMemberExpression(); + _configuration.AddIgnoreIfNull(member.GetName()); + return this; + } + } +} + +namespace Metsys.Bson.Configuration +{ + public static class ExpressionHelper + { + public static string GetName(this MemberExpression expression) + { + return new ExpressionNameVisitor().Visit(expression); + } + + public static MemberExpression GetMemberExpression(this Expression> expression) + { + if (expression == null) + { + return null; + } + if (expression.Body is MemberExpression) + { + return (MemberExpression)expression.Body; + } + if (expression.Body is UnaryExpression) + { + var operand = ((UnaryExpression)expression.Body).Operand; + if (operand is MemberExpression) + { + return (MemberExpression)operand; + } + if (operand is MethodCallExpression) + { + return ((MethodCallExpression)operand).Object as MemberExpression; + } + } + return null; + } + + private class ExpressionNameVisitor + { + public string Visit(Expression expression) + { + if (expression is UnaryExpression) + { + expression = ((UnaryExpression)expression).Operand; + } + if (expression is MethodCallExpression) + { + return Visit((MethodCallExpression)expression); + } + if (expression is MemberExpression) + { + return Visit((MemberExpression)expression); + } + if (expression is BinaryExpression && expression.NodeType == ExpressionType.ArrayIndex) + { + return Visit((BinaryExpression)expression); + } + return null; + } + + private string Visit(BinaryExpression expression) + { + string result = null; + if (expression.Left is MemberExpression) + { + result = Visit((MemberExpression)expression.Left); + } + var index = Expression.Lambda(expression.Right).Compile().DynamicInvoke(); + return result + string.Format("[{0}]", index); + } + + private string Visit(MemberExpression expression) + { + var name = expression.Member.Name; + var ancestorName = Visit(expression.Expression); + if (ancestorName != null) + { + name = ancestorName + "." + name; + } + return name; + } + + private string Visit(MethodCallExpression expression) + { + string name = null; + if (expression.Object is MemberExpression) + { + name = Visit((MemberExpression)expression.Object); + } + + //TODO: Is there a more certain way to determine if this is an indexed property? + if (expression.Method.Name == "get_Item" && expression.Arguments.Count == 1) + { + var index = Expression.Lambda(expression.Arguments[0]).Compile().DynamicInvoke(); + name += string.Format("[{0}]", index); + } + return name; + } + } + } +} + +namespace Metsys.Bson.Configuration +{ + + internal class BsonConfiguration + { + private readonly IDictionary> _aliasMap = new Dictionary>(); + private readonly IDictionary> _ignored = new Dictionary>(); + private readonly IDictionary> _ignoredIfNull = new Dictionary>(); + + //not thread safe + private static BsonConfiguration _instance; + internal static BsonConfiguration Instance + { + get + { + if (_instance == null) { _instance = new BsonConfiguration(); } + return _instance; + } + } + + private BsonConfiguration() { } + + public static void ForType(Action> action) + { + action(new TypeConfiguration(Instance)); + } + + internal void AddMap(string property, string alias) + { + var type = typeof(T); + if (!_aliasMap.ContainsKey(type)) + { + _aliasMap[type] = new Dictionary(); + } + _aliasMap[type][property] = alias; + } + internal string AliasFor(Type type, string property) + { + IDictionary map; + if (!_aliasMap.TryGetValue(type, out map)) + { + return property; + } + return map.ContainsKey(property) ? map[property] : property; + } + + public void AddIgnore(string name) + { + var type = typeof(T); + if (!_ignored.ContainsKey(type)) + { + _ignored[type] = new HashSet(); + } + _ignored[type].Add(name); + } + public bool IsIgnored(Type type, string name) + { + HashSet list; + return _ignored.TryGetValue(type, out list) && list.Contains(name); + } + + public void AddIgnoreIfNull(string name) + { + var type = typeof(T); + if (!_ignoredIfNull.ContainsKey(type)) + { + _ignoredIfNull[type] = new HashSet(); + } + _ignoredIfNull[type].Add(name); + } + public bool IsIgnoredIfNull(Type type, string name) + { + HashSet list; + return _ignoredIfNull.TryGetValue(type, out list) && list.Contains(name); + } + } +} +namespace Metsys.Bson +{ + + internal class BsonException : Exception + { + public BsonException() { } + public BsonException(string message) : base(message) { } + public BsonException(string message, Exception innerException) : base(message, innerException) { } + protected BsonException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} From 1ba432c0f9f1eaa46b159ee91606360fab1f1587 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 11 Oct 2017 16:43:22 +0300 Subject: [PATCH 12/14] Use old SDK for Avalonia.Designer.HostApp.NetFX.csproj --- packages.cake | 2 +- .../Avalonia.Designer.HostApp.NetFX.csproj | 125 +++++++++++++++--- 2 files changed, 105 insertions(+), 22 deletions(-) diff --git a/packages.cake b/packages.cake index 98aa08bb50..deb8eaf3d5 100644 --- a/packages.cake +++ b/packages.cake @@ -229,7 +229,7 @@ public class Packages Target = "tools/netcoreapp2.0/previewer" }, new NuSpecContent{ - Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/net461/Avalonia.Designer.HostApp.NetFx.exe")).FullPath, + Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/Avalonia.Designer.HostApp.exe")).FullPath, Target = "tools/net461/previewer" } }; diff --git a/src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj b/src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj index cb4ce3f301..c18cee8d08 100644 --- a/src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj +++ b/src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj @@ -1,28 +1,111 @@ - + + + - Exe - net461 - + Debug + AnyCPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F} + WinExe + Properties + Avalonia.Designer.HostApp + Avalonia.Designer.HostApp + v4.6.1 + 512 + true + + PackageReference + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + - + + {799a7bb5-3c2c-48b6-85a7-406a12c420da} + Avalonia.DesignerSupport + + + {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0} + Avalonia.DotNetFrameworkRuntime + + + {3E53A01A-B331-47F3-B828-4A5717E77A24} + Avalonia.Markup.Xaml + + + {6417E941-21BC-467B-A771-0DE389353CE6} + Avalonia.Markup + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Avalonia.Animation + + + {B09B78D8-9B26-48B0-9149-D64A2F120F3F} + Avalonia.Base + + + {D2221C82-4A25-4583-9B43-D791E3F6820C} + Avalonia.Controls + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Avalonia.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Avalonia.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Avalonia.Layout + + + {B61B66A3-B82D-4875-8001-89D3394FE0C9} + Avalonia.Logging.Serilog + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Avalonia.Visuals + + + {F1BAA01A-F176-4C6A-B39D-5B40BB1B148F} + Avalonia.Styling + + + {3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F} + Avalonia.Themes.Default + + \ No newline at end of file From a3937812ab4aa742cddec764e33b7e99e2508de6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 11 Oct 2017 16:44:00 +0300 Subject: [PATCH 13/14] [REMOTE] Renamed private class to avoid confusion --- src/Avalonia.Controls/Remote/RemoteServer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Remote/RemoteServer.cs b/src/Avalonia.Controls/Remote/RemoteServer.cs index 86f8318e15..9c0f3464a5 100644 --- a/src/Avalonia.Controls/Remote/RemoteServer.cs +++ b/src/Avalonia.Controls/Remote/RemoteServer.cs @@ -14,9 +14,9 @@ namespace Avalonia.Controls.Remote { private EmbeddableControlRoot _topLevel; - class TopLevelImpl : RemoteServerTopLevelImpl, IEmbeddableWindowImpl + class EmbeddableRemoteServerTopLevelImpl : RemoteServerTopLevelImpl, IEmbeddableWindowImpl { - public TopLevelImpl(IAvaloniaRemoteTransportConnection transport) : base(transport) + public EmbeddableRemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport) : base(transport) { } #pragma warning disable 67 @@ -26,7 +26,7 @@ namespace Avalonia.Controls.Remote public RemoteServer(IAvaloniaRemoteTransportConnection transport) { - _topLevel = new EmbeddableControlRoot(new TopLevelImpl(transport)); + _topLevel = new EmbeddableControlRoot(new EmbeddableRemoteServerTopLevelImpl(transport)); _topLevel.Prepare(); //TODO: Somehow react on closed connection? } From cde1e232056cd7c82fdb53640c2cd3f1c617f1ef Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 11 Oct 2017 17:29:25 +0300 Subject: [PATCH 14/14] Added Avalonia.Remote.Protocol package --- packages.cake | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages.cake b/packages.cake index deb8eaf3d5..98075ef36b 100644 --- a/packages.cake +++ b/packages.cake @@ -248,6 +248,7 @@ public class Packages new NuSpecDependency() { Id = "Splat", Version = SplatVersion }, new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion }, new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion }, + new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version }, //.NET Core new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp2.0", Version = "4.3.0" }, new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp2.0", Version = "1.1.0" }, @@ -256,6 +257,7 @@ public class Packages new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp2.0", Version = SerilogVersion }, new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp2.0", Version = SpracheVersion }, new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp2.0", Version = SystemReactiveVersion }, + new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", TargetFramework = "netcoreapp2.0", Version = parameters.Version }, } .Deps(new string[]{null, "netcoreapp2.0"}, "System.ValueTuple", "System.ComponentModel.TypeConverter", "System.ComponentModel.Primitives", @@ -303,6 +305,19 @@ public class Packages BasePath = context.Directory("./src/Avalonia.ReactiveUI/bin/" + parameters.DirSuffix + "/netstandard2.0"), OutputDirectory = parameters.NugetRoot }, + /////////////////////////////////////////////////////////////////////////////// + // Avalonia.Remote.Protocol + /////////////////////////////////////////////////////////////////////////////// + new NuGetPackSettings() + { + Id = "Avalonia.Remote.Protocol", + Files = new [] + { + new NuSpecContent { Source = "Avalonia.Remote.Protocol.dll", Target = "lib/netstandard2.0" } + }, + BasePath = context.Directory("./src/Avalonia.Remote.Protocol/bin/" + parameters.DirSuffix + "/netstandard2.0"), + OutputDirectory = parameters.NugetRoot + }, }; var nuspecNuGetSettingsMobile = new []