Browse Source

upgrade `Quamotion.RemoteViewing` to 1.1.211 to work with `RealVNC Viewer`, Add `password` parameter to `StartWithHeadlessVncPlatform` (#15406)

* upgrade `Quamotion.RemoteViewing` to 1.1.211 to work with `RealVNC Viewer`

* change AfterSetup to AfterApplicationSetup

* remove netstandard2.0 as latest Quamotion.RemoteViewing doest not support it.

* downgrade RemoteViewer to 1.1.179 to work with netstandard2.0; remove ILogger parameter use Avalonia.Logging.Logger instead.

* adding password method overload to avoid binary break change.
Dameng 1 year ago
parent
commit
fa1fdd463f

+ 5 - 0
src/Avalonia.Base/Logging/LogArea.cs

@@ -79,5 +79,10 @@ namespace Avalonia.Logging
         /// The log event comes from Browser Platform
         /// </summary>
         public const string BrowserPlatform = nameof(BrowserPlatform);
+
+        /// <summary>
+        /// The log event comes from VNC Platform
+        /// </summary>
+        public const string VncPlatform = nameof(VncPlatform);
     }
 }

+ 1 - 1
src/Headless/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj

@@ -7,7 +7,7 @@
 
     <ItemGroup>
       <ProjectReference Include="..\Avalonia.Headless\Avalonia.Headless.csproj" />
-      <PackageReference Include="Quamotion.RemoteViewing" Version="1.1.21" />
+      <PackageReference Include="Quamotion.RemoteViewing" Version="1.1.179" />
     </ItemGroup>
 
     <Import Project="..\..\..\build\DevAnalyzers.props" />

+ 39 - 0
src/Headless/Avalonia.Headless.Vnc/AvaloniaVncLogger.cs

@@ -0,0 +1,39 @@
+using System;
+using Avalonia.Logging;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions.Internal;
+
+namespace Avalonia.Headless.Vnc;
+
+internal class AvaloniaVncLogger : ILogger
+{
+    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
+    {
+        Logger.TryGet(ToLogEventLevel(logLevel), LogArea.VncPlatform)
+            ?.Log(state, formatter(state,exception));
+    }
+
+    public bool IsEnabled(LogLevel logLevel)
+    {
+        return Logger.IsEnabled(ToLogEventLevel(logLevel), LogArea.VncPlatform);
+    }
+
+    public IDisposable BeginScope<TState>(TState state)
+    {
+        return NullScope.Instance;
+    }
+
+    private static LogEventLevel ToLogEventLevel(LogLevel logLevel)
+    {
+        return logLevel switch
+        {
+            LogLevel.Trace => LogEventLevel.Verbose,
+            LogLevel.Debug => LogEventLevel.Debug,
+            LogLevel.Information => LogEventLevel.Information,
+            LogLevel.Warning => LogEventLevel.Warning,
+            LogLevel.Error => LogEventLevel.Error,
+            LogLevel.Critical => LogEventLevel.Fatal,
+            _ => throw new ArgumentOutOfRangeException(nameof(logLevel))
+        };
+    }
+}

+ 20 - 8
src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs

@@ -23,7 +23,7 @@ namespace Avalonia.Headless.Vnc
             session.PointerChanged += (_, args) =>
             {
                 var pt = new Point(args.X, args.Y);
-                    
+
                 var buttons = (VncButton)args.PressedButtons;
 
                 MouseButton TranslateButton(VncButton vncButton) =>
@@ -36,14 +36,14 @@ namespace Avalonia.Headless.Vnc
                     };
 
                 var modifiers = (RawInputModifiers)(((int)buttons & 7) << 4);
-                
+
                 Dispatcher.UIThread.Post(() =>
                 {
                     Window?.MouseMove(pt);
                     foreach (var btn in CheckedButtons)
                         if (_previousButtons.HasFlag(btn) && !buttons.HasFlag(btn))
                             Window?.MouseUp(pt, TranslateButton(btn), modifiers);
-                    
+
                     foreach (var btn in CheckedButtons)
                         if (!_previousButtons.HasFlag(btn) && buttons.HasFlag(btn))
                             Window?.MouseDown(pt, TranslateButton(btn), modifiers);
@@ -96,11 +96,11 @@ namespace Avalonia.Headless.Vnc
                 KeySym.AltLeft or KeySym.AltRight => RawInputModifiers.Alt,
                 _ => null
             };
-            
-            if(!toggleModifier.HasValue)
+
+            if (!toggleModifier.HasValue)
                 return false;
 
-            if(args.Pressed)
+            if (args.Pressed)
                 _keyState |= toggleModifier.Value;
             else
                 _keyState &= ~toggleModifier.Value;
@@ -309,9 +309,9 @@ namespace Avalonia.Headless.Vnc
             ScrollUp = 8,
             ScrollDown = 16
         }
-        
 
-        private static VncButton[] CheckedButtons = new[] {VncButton.Left, VncButton.Middle, VncButton.Right}; 
+
+        private static VncButton[] CheckedButtons = new[] { VncButton.Left, VncButton.Middle, VncButton.Right };
 
         public unsafe VncFramebuffer Capture()
         {
@@ -338,5 +338,17 @@ namespace Avalonia.Headless.Vnc
 
             return _framebuffer;
         }
+        
+        public ExtendedDesktopSizeStatus SetDesktopSize(int width, int height)
+        {
+            Dispatcher.UIThread.Post(() =>
+            {
+                Window.Width = width;
+                Window.Height = height;
+            });
+            return ExtendedDesktopSizeStatus.Success;
+        }
+
+        public bool SupportsResizing => true;
     }
 }

+ 69 - 14
src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs

@@ -1,10 +1,12 @@
 using System;
 using System.Net;
 using System.Net.Sockets;
+using System.Threading.Tasks;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Headless;
 using Avalonia.Headless.Vnc;
+using Avalonia.Logging;
 using Avalonia.Platform;
 using RemoteViewing.Vnc;
 using RemoteViewing.Vnc.Server;
@@ -13,13 +15,45 @@ namespace Avalonia
 {
     public static class HeadlessVncPlatformExtensions
     {
+        /// <summary>
+        /// Start Avalonia application with Headless VNC platform without password.
+        /// </summary>
+        /// <param name="builder">Application Builder</param>
+        /// <param name="host">VNC Server IP will be bind, if null or empty IPAddress.LoopBack will be used.</param>
+        /// <param name="port">VNC Server port will be bind</param>
+        /// <param name="args">Avalonia application start args</param>
+        /// <param name="shutdownMode">shut down mode <see cref="ShutdownMode"/></param>
+        /// <returns></returns>
         public static int StartWithHeadlessVncPlatform(
             this AppBuilder builder,
             string host, int port,
-            string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose)
+            string[] args,
+            ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose)
         {
-            var tcpServer = new TcpListener(host == null ? IPAddress.Loopback : IPAddress.Parse(host), port);
-            tcpServer.Start();    
+            return StartWithHeadlessVncPlatform(builder, host, port, null, args, shutdownMode);
+        }
+
+        /// <summary>
+        /// Start Avalonia application with Headless VNC platform with password.
+        /// </summary>
+        /// <param name="builder">Application Builder</param>
+        /// <param name="host">VNC Server IP will be bind, if null or empty IPAddress.LoopBack will be used.</param>
+        /// <param name="port">VNC Server port will be bind</param>
+        /// <param name="password">VNC connection auth password</param>
+        /// <param name="args">Avalonia application start args</param>
+        /// <param name="shutdownMode">shut down mode <see cref="ShutdownMode"/></param>
+        /// <returns></returns>
+        /// <exception cref="InvalidOperationException"></exception>
+        public static int StartWithHeadlessVncPlatform(
+            this AppBuilder builder,
+            string host, int port,
+            string? password,
+            string[] args,
+            ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose)
+        {
+            var vncLogger = new AvaloniaVncLogger();
+            var tcpServer = new TcpListener(string.IsNullOrEmpty(host) ? IPAddress.Loopback : IPAddress.Parse(host), port);
+            tcpServer.Start();
             return builder
                 .UseHeadless(new AvaloniaHeadlessPlatformOptions
                 {
@@ -28,23 +62,44 @@ namespace Avalonia
                 })
                 .AfterApplicationSetup(_ =>
                 {
-                    var lt = ((IClassicDesktopStyleApplicationLifetime)builder.Instance!.ApplicationLifetime!);
+                    var lt = ((IClassicDesktopStyleApplicationLifetime) builder.Instance!.ApplicationLifetime!);
                     lt.Startup += async delegate
                     {
                         while (true)
                         {
-                            var client = await tcpServer.AcceptTcpClientAsync();
-                            var options = new VncServerSessionOptions
+                            try
+                            {
+                                var client = await tcpServer.AcceptTcpClientAsync();
+                                var options = new VncServerSessionOptions
+                                {
+                                    AuthenticationMethod = string.IsNullOrWhiteSpace(password)
+                                        ? AuthenticationMethod.None
+                                        : AuthenticationMethod.Password
+                                };
+                                var session = new VncServerSession(new VncPasswordChallenge(), logger:vncLogger);
+                                if (string.IsNullOrWhiteSpace(password) == false)
+                                {
+                                    session.PasswordProvided += (s, e) =>
+                                    {
+                                        e.Accept(password.ToCharArray());
+                                    };
+                                }
+
+                                session.SetFramebufferSource(new HeadlessVncFramebufferSource(
+                                    session,
+                                    lt.MainWindow ??
+                                    throw new InvalidOperationException("MainWindow wasn't initialized")));
+                                session.Connect(client.GetStream(), options);
+                            }
+                            catch (Exception e)
+                            {
+                                Logger.TryGet(LogEventLevel.Error, LogArea.VncPlatform)?.Log(tcpServer,"Error accepting client:{Exception}", e);
+                            }
+                            finally
                             {
-                                AuthenticationMethod = AuthenticationMethod.None
-                            };
-                            var session = new VncServerSession();
-                            
-                            session.SetFramebufferSource(new HeadlessVncFramebufferSource(
-                                session, lt.MainWindow ?? throw new InvalidOperationException("MainWindow wasn't initialized")));
-                            session.Connect(client.GetStream(), options);
+                                await Task.Delay(100);
+                            }
                         }
-                        
                     };
                 })
                 .StartWithClassicDesktopLifetime(args, shutdownMode);