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