17 changed files with 638 additions and 2 deletions
@ -0,0 +1,12 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace RemoteTest |
||||
|
{ |
||||
|
class Program |
||||
|
{ |
||||
|
static void Main(string[] args) |
||||
|
{ |
||||
|
Console.WriteLine("Hello World!"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFramework>netcoreapp2.0</TargetFramework> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" /> |
||||
|
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,63 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Layout; |
||||
|
using Avalonia.Styling; |
||||
|
|
||||
|
namespace Avalonia.Controls.Embedding.Offscreen |
||||
|
{ |
||||
|
class OffscreenTopLevel : TopLevel, IStyleable |
||||
|
{ |
||||
|
public OffscreenTopLevelImplBase Impl { get; } |
||||
|
|
||||
|
public OffscreenTopLevel(OffscreenTopLevelImplBase impl) : base(impl) |
||||
|
{ |
||||
|
Impl = impl; |
||||
|
Prepare(); |
||||
|
} |
||||
|
|
||||
|
public void Prepare() |
||||
|
{ |
||||
|
EnsureInitialized(); |
||||
|
ApplyTemplate(); |
||||
|
LayoutManager.Instance.ExecuteInitialLayoutPass(this); |
||||
|
} |
||||
|
|
||||
|
private void EnsureInitialized() |
||||
|
{ |
||||
|
if (!this.IsInitialized) |
||||
|
{ |
||||
|
var init = (ISupportInitialize)this; |
||||
|
init.BeginInit(); |
||||
|
init.EndInit(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private readonly NameScope _nameScope = new NameScope(); |
||||
|
public event EventHandler<NameScopeEventArgs> Registered |
||||
|
{ |
||||
|
add { _nameScope.Registered += value; } |
||||
|
remove { _nameScope.Registered -= value; } |
||||
|
} |
||||
|
|
||||
|
public event EventHandler<NameScopeEventArgs> Unregistered |
||||
|
{ |
||||
|
add { _nameScope.Unregistered += value; } |
||||
|
remove { _nameScope.Unregistered -= value; } |
||||
|
} |
||||
|
|
||||
|
public void Register(string name, object element) => _nameScope.Register(name, element); |
||||
|
|
||||
|
public object Find(string name) => _nameScope.Find(name); |
||||
|
|
||||
|
public void Unregister(string name) => _nameScope.Unregister(name); |
||||
|
|
||||
|
Type IStyleable.StyleKey => typeof(EmbeddableControlRoot); |
||||
|
public void Dispose() |
||||
|
{ |
||||
|
PlatformImpl.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Controls.Embedding.Offscreen |
||||
|
{ |
||||
|
abstract class OffscreenTopLevelImplBase : ITopLevelImpl |
||||
|
{ |
||||
|
private double _scaling; |
||||
|
private Size _clientSize; |
||||
|
public IInputRoot InputRoot { get; private set; } |
||||
|
|
||||
|
public virtual void Dispose() |
||||
|
{ |
||||
|
//No-op
|
||||
|
} |
||||
|
|
||||
|
public abstract void Invalidate(Rect rect); |
||||
|
public abstract IEnumerable<object> Surfaces { get; } |
||||
|
|
||||
|
public Size ClientSize |
||||
|
{ |
||||
|
get { return _clientSize; } |
||||
|
set |
||||
|
{ |
||||
|
_clientSize = value; |
||||
|
Resized?.Invoke(value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public double Scaling |
||||
|
{ |
||||
|
get { return _scaling; } |
||||
|
set |
||||
|
{ |
||||
|
_scaling = value; |
||||
|
ScalingChanged?.Invoke(value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Action<RawInputEventArgs> Input { get; set; } |
||||
|
public Action<Rect> Paint { get; set; } |
||||
|
public Action<Size> Resized { get; set; } |
||||
|
public Action<double> ScalingChanged { get; set; } |
||||
|
public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot; |
||||
|
|
||||
|
public virtual Point PointToClient(Point point) => point; |
||||
|
|
||||
|
public Point PointToScreen(Point point) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public virtual void SetCursor(IPlatformHandle cursor) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public Action Closed { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,101 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls.Embedding.Offscreen; |
||||
|
using Avalonia.Controls.Platform.Surfaces; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Remote.Protocol; |
||||
|
using Avalonia.Remote.Protocol.Viewport; |
||||
|
using Avalonia.Threading; |
||||
|
using PixelFormat = Avalonia.Platform.PixelFormat; |
||||
|
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; |
||||
|
|
||||
|
namespace Avalonia.Controls.Remote.Server |
||||
|
{ |
||||
|
class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface |
||||
|
{ |
||||
|
private readonly IAvaloniaRemoteTransport _transport; |
||||
|
private LockedFramebuffer _framebuffer; |
||||
|
private object _lock = new object(); |
||||
|
private long _lastSentFrame; |
||||
|
private long _lastReceivedFrame = -1; |
||||
|
private bool _invalidated; |
||||
|
private ProtocolPixelFormat[] _supportedFormats; |
||||
|
|
||||
|
public RemoteServerTopLevelImpl(IAvaloniaRemoteTransport transport) |
||||
|
{ |
||||
|
_transport = transport; |
||||
|
_transport.OnMessage += OnMessage; |
||||
|
} |
||||
|
|
||||
|
private void OnMessage(object obj) |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
var lastFrame = obj as FrameReceivedMessage; |
||||
|
if (lastFrame != null) |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
_lastReceivedFrame = lastFrame.SequenceId; |
||||
|
} |
||||
|
Dispatcher.UIThread.InvokeAsync(CheckNeedsRender); |
||||
|
} |
||||
|
var supportedFormats = obj as ClientSupportedPixelFormatsMessage; |
||||
|
if (supportedFormats != null) |
||||
|
_supportedFormats = supportedFormats.Formats; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override IEnumerable<object> Surfaces => new[] { this }; |
||||
|
|
||||
|
FrameMessage RenderFrame(int width, int height, Size dpi, ProtocolPixelFormat? format) |
||||
|
{ |
||||
|
var fmt = format ?? ProtocolPixelFormat.Rgba8888; |
||||
|
var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4; |
||||
|
var data = new byte[width * height * bpp]; |
||||
|
var handle = GCHandle.Alloc(data, GCHandleType.Pinned); |
||||
|
try |
||||
|
{ |
||||
|
_framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), width, height, width * bpp, dpi, (PixelFormat)fmt, |
||||
|
null); |
||||
|
Paint?.Invoke(new Rect(0, 0, width, height)); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
_framebuffer = null; |
||||
|
handle.Free(); |
||||
|
} |
||||
|
return new FrameMessage(); |
||||
|
} |
||||
|
|
||||
|
public ILockedFramebuffer Lock() |
||||
|
{ |
||||
|
if (_framebuffer == null) |
||||
|
throw new InvalidOperationException("Paint was not requested, wait for Paint event"); |
||||
|
return _framebuffer; |
||||
|
} |
||||
|
|
||||
|
void CheckNeedsRender() |
||||
|
{ |
||||
|
ProtocolPixelFormat[] formats; |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
if (_lastReceivedFrame != _lastSentFrame && !_invalidated) |
||||
|
return; |
||||
|
formats = _supportedFormats; |
||||
|
} |
||||
|
|
||||
|
//var frame = RenderFrame()
|
||||
|
} |
||||
|
|
||||
|
public override void Invalidate(Rect rect) |
||||
|
{ |
||||
|
_invalidated = true; |
||||
|
Dispatcher.UIThread.InvokeAsync(CheckNeedsRender); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard1.3</TargetFramework> |
||||
|
<DefineConstants>AVALONIA_REMOTE_PROTOCOL;$(DefineConstants)</DefineConstants> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<Compile Include="..\Avalonia.Input\Key.cs" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol |
||||
|
{ |
||||
|
public class DefaultMessageTypeResolver : IMessageTypeResolver |
||||
|
{ |
||||
|
private readonly Dictionary<Guid, Type> _guidsToTypes = new Dictionary<Guid, Type>(); |
||||
|
private readonly Dictionary<Type, Guid> _typesToGuids = new Dictionary<Type, Guid>(); |
||||
|
public DefaultMessageTypeResolver(params Assembly[] assemblies) |
||||
|
{ |
||||
|
foreach (var asm in |
||||
|
(assemblies ?? new Assembly[0]).Concat(new[] |
||||
|
{typeof(AvaloniaRemoteMessageGuidAttribute).GetTypeInfo().Assembly})) |
||||
|
{ |
||||
|
foreach (var t in asm.ExportedTypes) |
||||
|
{ |
||||
|
var attr = t.GetTypeInfo().GetCustomAttribute<AvaloniaRemoteMessageGuidAttribute>(); |
||||
|
if (attr != null) |
||||
|
{ |
||||
|
_guidsToTypes[attr.Guid] = t; |
||||
|
_typesToGuids[t] = attr.Guid; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Type GetByGuid(Guid id) => _guidsToTypes[id]; |
||||
|
public Guid GetGuid(Type type) => _typesToGuids[type]; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol |
||||
|
{ |
||||
|
public interface IMessageTypeResolver |
||||
|
{ |
||||
|
Type GetByGuid(Guid id); |
||||
|
Guid GetGuid(Type type); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Remote.Protocol |
||||
|
{ |
||||
|
public interface IAvaloniaRemoteTransport |
||||
|
{ |
||||
|
Task Send(object data); |
||||
|
event Action<object> OnMessage; |
||||
|
event Action<Exception> OnException; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
/* |
||||
|
We are keeping copies of core events here, so they can be used |
||||
|
without referencing Avalonia itself, e. g. from projects that |
||||
|
are using WPF, GTK#, etc |
||||
|
*/ |
||||
|
namespace Avalonia.Remote.Protocol.Input |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Keep this in sync with InputModifiers in the main library
|
||||
|
/// </summary>
|
||||
|
[Flags] |
||||
|
public enum InputModifiers |
||||
|
{ |
||||
|
Alt, |
||||
|
Control, |
||||
|
Shift, |
||||
|
Windows, |
||||
|
LeftMouseButton, |
||||
|
RightMouseButton, |
||||
|
MiddleMouseButton |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Keep this in sync with InputModifiers in the main library
|
||||
|
/// </summary>
|
||||
|
public enum MouseButton |
||||
|
{ |
||||
|
None, |
||||
|
Left, |
||||
|
Right, |
||||
|
Middle |
||||
|
} |
||||
|
|
||||
|
public abstract class InputEventMessageBase |
||||
|
{ |
||||
|
public InputModifiers[] Modifiers { get; set; } |
||||
|
} |
||||
|
|
||||
|
public abstract class PointerEventMessageBase : InputEventMessageBase |
||||
|
{ |
||||
|
public double X { get; set; } |
||||
|
public double Y { get; set; } |
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("6228F0B9-99F2-4F62-A621-414DA2881648")] |
||||
|
public class PointerMovedEventMessage : PointerEventMessageBase |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("7E9E2818-F93F-411A-800E-6B1AEB11DA46")] |
||||
|
public class PointerPressedEventMessage : PointerEventMessageBase |
||||
|
{ |
||||
|
public MouseButton Button { get; set; } |
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("4ADC84EE-E7C8-4BCF-986C-DE3A2F78EDE4")] |
||||
|
public class PointerReleasedEventMessage : PointerEventMessageBase |
||||
|
{ |
||||
|
public MouseButton Button { get; set; } |
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("79301A05-F02D-4B90-BB39-472563B504AE")] |
||||
|
public class ScrollEventMessage : PointerEventMessageBase |
||||
|
{ |
||||
|
public double DeltaX { get; set; } |
||||
|
public double DeltaY { get; set; } |
||||
|
} |
||||
|
|
||||
|
[AvaloniaRemoteMessageGuid("1C3B691E-3D54-4237-BFB0-9FEA83BC1DB8")] |
||||
|
public class KeyEventMessage : InputEventMessageBase |
||||
|
{ |
||||
|
public bool IsDown { get; set; } |
||||
|
public Key Key { get; set; } |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue