@ -1,6 +1,5 @@
using System ;
using System.Collections.Generic ;
using System.Runtime.InteropServices ;
using Avalonia.Controls.Embedding.Offscreen ;
using Avalonia.Controls.Platform.Surfaces ;
using Avalonia.Input ;
@ -11,7 +10,6 @@ using Avalonia.Platform;
using Avalonia.Remote.Protocol ;
using Avalonia.Remote.Protocol.Input ;
using Avalonia.Remote.Protocol.Viewport ;
using Avalonia.Rendering ;
using Avalonia.Threading ;
using Key = Avalonia . Input . Key ;
using ProtocolPixelFormat = Avalonia . Remote . Protocol . Viewport . PixelFormat ;
@ -20,28 +18,28 @@ using ProtocolMouseButton = Avalonia.Remote.Protocol.Input.MouseButton;
namespace Avalonia.Controls.Remote.Server
{
[Unstable]
internal class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase , IFramebufferPlatformSurface , ITopLevelImpl
internal partial class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase , IFramebufferPlatformSurface , ITopLevelImpl
{
private readonly IAvaloniaRemoteTransportConnection _ transport ;
private LockedFramebuffer ? _f ramebuffer ;
private readonly object _l ock = new ( ) ;
private readonly Action _ sendLastFrameIfNeeded ;
private readonly Action _ renderAndSendFrameIfNeeded ;
private Framebuffer _f ramebuffer = Framebuffer . Empty ;
private long _l astSentFrame = - 1 ;
private long _l astReceivedFrame = - 1 ;
private long _ nextFrameNumber = 1 ;
private ClientViewportAllocatedMessage ? _ pendingAllocation ;
private bool _ queuedNextRender ;
private bool _ inRender ;
private Vector _d pi = new Vector ( 9 6 , 9 6 ) ;
private ProtocolPixelFormat [ ] ? _ supportedFormats ;
private ProtocolPixelFormat ? _f ormat ;
public RemoteServerTopLevelImpl ( IAvaloniaRemoteTransportConnection transport )
{
_ sendLastFrameIfNeeded = SendLastFrameIfNeeded ;
_ renderAndSendFrameIfNeeded = RenderAndSendFrameIfNeeded ;
_ transport = transport ;
_ transport . OnMessage + = OnMessage ;
KeyboardDevice = AvaloniaLocator . Current . GetRequiredService < IKeyboardDevice > ( ) ;
QueueNextRender ( ) ;
Compositor . AfterCommit + = QueueNextRender ;
}
private static RawPointerEventType GetAvaloniaEventType ( ProtocolMouseButton button , bool pressed )
@ -112,45 +110,41 @@ namespace Avalonia.Controls.Remote.Server
{
lock ( _l ock )
{
if ( obj is FrameReceivedMessage lastFrame )
switch ( obj )
{
lock ( _l ock )
{
case FrameReceivedMessage lastFrame :
_l astReceivedFrame = Math . Max ( lastFrame . SequenceId , _l astReceivedFrame ) ;
}
Dispatcher . UIThread . Post ( RenderIfNeeded ) ;
}
if ( obj is ClientRenderInfoMessage renderInfo )
{
lock ( _l ock )
{
_d pi = new Vector ( renderInfo . DpiX , renderInfo . DpiY ) ;
_ queuedNextRender = true ;
}
Dispatcher . UIThread . Post ( RenderIfNeeded ) ;
}
if ( obj is ClientSupportedPixelFormatsMessage supportedFormats )
{
lock ( _l ock )
_ supportedFormats = supportedFormats . Formats ;
Dispatcher . UIThread . Post ( RenderIfNeeded ) ;
}
if ( obj is MeasureViewportMessage measure )
Dispatcher . UIThread . Post ( ( ) = >
{
var m = Measure ( new Size ( measure . Width , measure . Height ) ) ;
_ transport . Send ( new MeasureViewportMessage
Dispatcher . UIThread . Post ( _ sendLastFrameIfNeeded ) ;
break ;
case ClientRenderInfoMessage renderInfo :
Dispatcher . UIThread . Post ( ( ) = >
{
Width = m . Width ,
Height = m . Height
RenderScaling = renderInfo . DpiX / 9 6.0 ;
RenderAndSendFrameIfNeeded ( ) ;
} ) ;
} ) ;
if ( obj is ClientViewportAllocatedMessage allocated )
{
lock ( _l ock )
{
break ;
case ClientSupportedPixelFormatsMessage supportedFormats :
_f ormat = TryGetValidPixelFormat ( supportedFormats . Formats ) ;
Dispatcher . UIThread . Post ( _ renderAndSendFrameIfNeeded ) ;
break ;
case MeasureViewportMessage measure :
Dispatcher . UIThread . Post ( ( ) = >
{
var m = Measure ( new Size ( measure . Width , measure . Height ) ) ;
_ transport . Send ( new MeasureViewportMessage
{
Width = m . Width ,
Height = m . Height
} ) ;
} ) ;
break ;
case ClientViewportAllocatedMessage allocated :
if ( _ pendingAllocation = = null )
{
Dispatcher . UIThread . Post ( ( ) = >
{
ClientViewportAllocatedMessage allocation ;
@ -159,101 +153,111 @@ namespace Avalonia.Controls.Remote.Server
allocation = _ pendingAllocation ! ;
_ pendingAllocation = null ;
}
_d pi = new Vector ( allocation . DpiX , allocation . DpiY ) ;
RenderScaling = allocation . DpiX / 9 6.0 ;
ClientSize = new Size ( allocation . Width , allocation . Height ) ;
RenderIfNeeded ( ) ;
RenderAndSendFrame IfNeeded ( ) ;
} ) ;
}
_ pendingAllocation = allocated ;
}
}
if ( obj is PointerMovedEventMessage pointer )
{
Dispatcher . UIThread . Post ( ( ) = >
{
Input ? . Invoke ( new RawPointerEventArgs (
MouseDevice ,
0 ,
InputRoot ! ,
RawPointerEventType . Move ,
new Point ( pointer . X , pointer . Y ) ,
GetAvaloniaRawInputModifiers ( pointer . Modifiers ) ) ) ;
} , DispatcherPriority . Input ) ;
}
if ( obj is PointerPressedEventMessage pressed )
{
Dispatcher . UIThread . Post ( ( ) = >
{
Input ? . Invoke ( new RawPointerEventArgs (
MouseDevice ,
0 ,
InputRoot ! ,
GetAvaloniaEventType ( pressed . Button , true ) ,
new Point ( pressed . X , pressed . Y ) ,
GetAvaloniaRawInputModifiers ( pressed . Modifiers ) ) ) ;
} , DispatcherPriority . Input ) ;
}
if ( obj is PointerReleasedEventMessage released )
{
Dispatcher . UIThread . Post ( ( ) = >
{
Input ? . Invoke ( new RawPointerEventArgs (
MouseDevice ,
0 ,
InputRoot ! ,
GetAvaloniaEventType ( released . Button , false ) ,
new Point ( released . X , released . Y ) ,
GetAvaloniaRawInputModifiers ( released . Modifiers ) ) ) ;
} , DispatcherPriority . Input ) ;
}
if ( obj is ScrollEventMessage scroll )
{
Dispatcher . UIThread . Post ( ( ) = >
{
Input ? . Invoke ( new RawMouseWheelEventArgs (
MouseDevice ,
0 ,
InputRoot ! ,
new Point ( scroll . X , scroll . Y ) ,
new Vector ( scroll . DeltaX , scroll . DeltaY ) ,
GetAvaloniaRawInputModifiers ( scroll . Modifiers ) ) ) ;
} , DispatcherPriority . Input ) ;
}
if ( obj is KeyEventMessage key )
{
Dispatcher . UIThread . Post ( ( ) = >
{
Dispatcher . UIThread . RunJobs ( DispatcherPriority . Input + 1 ) ;
Input ? . Invoke ( new RawKeyEventArgs (
KeyboardDevice ,
0 ,
InputRoot ! ,
key . IsDown ? RawKeyEventType . KeyDown : RawKeyEventType . KeyUp ,
( Key ) key . Key ,
GetAvaloniaRawInputModifiers ( key . Modifiers ) ) ) ;
} , DispatcherPriority . Input ) ;
}
if ( obj is TextInputEventMessage text )
{
Dispatcher . UIThread . Post ( ( ) = >
{
Dispatcher . UIThread . RunJobs ( DispatcherPriority . Input + 1 ) ;
Input ? . Invoke ( new RawTextInputEventArgs (
KeyboardDevice ,
0 ,
InputRoot ! ,
text . Text ) ) ;
} , DispatcherPriority . Input ) ;
break ;
case PointerMovedEventMessage pointer :
Dispatcher . UIThread . Post ( ( ) = >
{
Input ? . Invoke ( new RawPointerEventArgs (
MouseDevice ,
0 ,
InputRoot ! ,
RawPointerEventType . Move ,
new Point ( pointer . X , pointer . Y ) ,
GetAvaloniaRawInputModifiers ( pointer . Modifiers ) ) ) ;
} , DispatcherPriority . Input ) ;
break ;
case PointerPressedEventMessage pressed :
Dispatcher . UIThread . Post ( ( ) = >
{
Input ? . Invoke ( new RawPointerEventArgs (
MouseDevice ,
0 ,
InputRoot ! ,
GetAvaloniaEventType ( pressed . Button , true ) ,
new Point ( pressed . X , pressed . Y ) ,
GetAvaloniaRawInputModifiers ( pressed . Modifiers ) ) ) ;
} , DispatcherPriority . Input ) ;
break ;
case PointerReleasedEventMessage released :
Dispatcher . UIThread . Post ( ( ) = >
{
Input ? . Invoke ( new RawPointerEventArgs (
MouseDevice ,
0 ,
InputRoot ! ,
GetAvaloniaEventType ( released . Button , false ) ,
new Point ( released . X , released . Y ) ,
GetAvaloniaRawInputModifiers ( released . Modifiers ) ) ) ;
} , DispatcherPriority . Input ) ;
break ;
case ScrollEventMessage scroll :
Dispatcher . UIThread . Post ( ( ) = >
{
Input ? . Invoke ( new RawMouseWheelEventArgs (
MouseDevice ,
0 ,
InputRoot ! ,
new Point ( scroll . X , scroll . Y ) ,
new Vector ( scroll . Delta X, scroll . Delta Y) ,
GetAvaloniaRawInputModifiers ( scroll . Modifiers ) ) ) ;
} , DispatcherPriority . Input ) ;
break ;
case KeyEventMessage key :
Dispatcher . UIThread . Post ( ( ) = >
{
Dispatcher . UIThread . RunJobs ( DispatcherPriority . Input + 1 ) ;
Input ? . Invoke ( new RawKeyEventArgs (
KeyboardDevice ,
0 ,
InputRoot ! ,
key . IsDown ? RawKeyEventType . KeyDown : RawKeyEventType . KeyUp ,
( Key ) key . Key ,
GetAvaloniaRawInputModifiers ( key . Modifiers ) ) ) ;
} , DispatcherPriority . Input ) ;
break ;
case TextInputEventMessage text :
Dispatcher . UIThread . Post ( ( ) = >
{
Dispatcher . UIThread . RunJobs ( DispatcherPriority . Input + 1 ) ;
Input ? . Invoke ( new RawTextInputEventArgs (
KeyboardDevice ,
0 ,
InputRoot ! ,
text . Text ) ) ;
} , DispatcherPriority . Input ) ;
break ;
}
}
}
protected void SetDpi ( Vector dpi )
private static ProtocolPixelFormat ? TryGetValidPixelFormat ( ProtocolPixelFormat [ ] ? formats )
{
_d pi = dpi ;
RenderIfNeeded ( ) ;
if ( formats is not null )
{
foreach ( var format in formats )
{
if ( format is > = 0 and < = ProtocolPixelFormat . MaxValue )
return format ;
}
}
return null ;
}
protected virtual Size Measure ( Size constraint )
@ -265,88 +269,63 @@ namespace Avalonia.Controls.Remote.Server
public override IEnumerable < object > Surfaces = > new [ ] { this } ;
private FrameMessage RenderFrame ( int width , int height , ProtocolPixelFormat ? format )
private Framebuffer GetOrCreateFramebuffer ( )
{
var scalingX = _d pi . X / 9 6.0 ;
var scalingY = _d pi . Y / 9 6.0 ;
width = ( int ) ( width * scalingX ) ;
height = ( int ) ( height * scalingY ) ;
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
{
if ( width > 0 & & height > 0 )
{
_f ramebuffer = new LockedFramebuffer ( handle . AddrOfPinnedObject ( ) , new PixelSize ( width , height ) , width * bpp , _d pi , new ( ( PixelFormatEnum ) fmt ) ,
null ) ;
Paint ? . Invoke ( new Rect ( 0 , 0 , width , height ) ) ;
}
}
finally
lock ( _l ock )
{
_f ramebuffer = null ;
handle . Free ( ) ;
if ( _f ormat is not { } format )
_f ramebuffer = Framebuffer . Empty ;
else if ( _f ramebuffer . Format ! = format | | _f ramebuffer . ClientSize ! = ClientSize | | _f ramebuffer . RenderScaling ! = RenderScaling )
_f ramebuffer = new Framebuffer ( format , ClientSize , RenderScaling ) ;
return _f ramebuffer ;
}
return new FrameMessage
{
Data = data ,
Format = fmt ,
Width = width ,
Height = height ,
Stride = width * bpp ,
DpiX = _d pi . X ,
DpiY = _d pi . Y
} ;
}
public ILockedFramebuffer Lock ( )
{
if ( _f ramebuffer = = null )
throw new InvalidOperationException ( "Paint was not requested, wait for Paint event" ) ;
return _f ramebuffer ;
}
= > GetOrCreateFramebuffer ( ) . Lock ( _ sendLastFrameIfNeeded ) ;
protected void Render IfNeeded ( )
private void SendLastFrameIfNeeded ( )
{
lock ( _l ock )
{
if ( _l astReceivedFrame ! = _l astSentFrame | | ! _ queuedNextRender | | _ supportedFormats = = null )
return ;
if ( IsDisposed )
return ;
}
Framebuffer framebuffer ;
long sequenceId ;
var format = ProtocolPixelFormat . Rgba8888 ;
foreach ( var fmt in _ supportedFormats )
if ( fmt < = ProtocolPixelFormat . MaxValue )
{
format = fmt ;
break ;
}
_ inRender = true ;
var frame = RenderFrame ( ( int ) ClientSize . Width , ( int ) ClientSize . Height , format ) ;
lock ( _l ock )
{
// Ideally we should only send a frame if its status is Rendered: since the renderer might not be
// initialized at the start, we're sending black frames in this case. However, this was the historical
// behavior and some external programs are depending on receiving a frame asap.
if ( _l astReceivedFrame ! = _l astSentFrame | | _f ramebuffer . GetStatus ( ) = = FrameStatus . CopiedToMessage )
return ;
framebuffer = _f ramebuffer ;
_l astSentFrame = _ nextFrameNumber + + ;
frame . SequenceId = _l astSentFrame ;
_ queuedNextRender = false ;
sequenceId = _l astSentFrame ;
}
_ inRender = false ;
_ transport . Send ( frame ) ;
_ transport . Send ( framebuffer . ToMessage ( sequenceId ) ) ;
}
private void QueueNextRender ( )
protected void RenderAndSendFrameIfNeeded ( )
{
if ( ! _ inRender & & ! IsDisposed )
if ( IsDisposed )
return ;
lock ( _l ock )
{
_ queuedNextRender = true ;
DispatcherTimer . RunOnce ( RenderIfNeeded , TimeSpan . FromMilliseconds ( 2 ) , DispatcherPriority . Background ) ;
if ( _l astReceivedFrame ! = _l astSentFrame | | _f ormat is null )
return ;
}
var framebuffer = GetOrCreateFramebuffer ( ) ;
if ( framebuffer . Stride > 0 )
Paint ? . Invoke ( new Rect ( framebuffer . ClientSize ) ) ;
SendLastFrameIfNeeded ( ) ;
}
public override IMouseDevice MouseDevice { get ; } = new MouseDevice ( ) ;