From fa1fdd463f0cfaff29d78a5d5250ab1c6a5621d1 Mon Sep 17 00:00:00 2001 From: Dameng <313880747@qq.com> Date: Thu, 18 Apr 2024 11:50:08 +0800 Subject: [PATCH] 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. --- src/Avalonia.Base/Logging/LogArea.cs | 5 ++ .../Avalonia.Headless.Vnc.csproj | 2 +- .../AvaloniaVncLogger.cs | 39 +++++++++ .../HeadlessVncFramebufferSource.cs | 28 +++++-- .../HeadlessVncPlatformExtensions.cs | 83 +++++++++++++++---- 5 files changed, 134 insertions(+), 23 deletions(-) create mode 100644 src/Headless/Avalonia.Headless.Vnc/AvaloniaVncLogger.cs diff --git a/src/Avalonia.Base/Logging/LogArea.cs b/src/Avalonia.Base/Logging/LogArea.cs index 6070865cf8..139129e623 100644 --- a/src/Avalonia.Base/Logging/LogArea.cs +++ b/src/Avalonia.Base/Logging/LogArea.cs @@ -79,5 +79,10 @@ namespace Avalonia.Logging /// The log event comes from Browser Platform /// public const string BrowserPlatform = nameof(BrowserPlatform); + + /// + /// The log event comes from VNC Platform + /// + public const string VncPlatform = nameof(VncPlatform); } } diff --git a/src/Headless/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj b/src/Headless/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj index 7e6a76df49..daafe1db54 100644 --- a/src/Headless/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj +++ b/src/Headless/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Headless/Avalonia.Headless.Vnc/AvaloniaVncLogger.cs b/src/Headless/Avalonia.Headless.Vnc/AvaloniaVncLogger.cs new file mode 100644 index 0000000000..a155985038 --- /dev/null +++ b/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(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func 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 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)) + }; + } +} diff --git a/src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs b/src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs index 8cc33ea359..15288675fb 100644 --- a/src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs +++ b/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; } } diff --git a/src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs b/src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs index 9c01d356dd..383075a746 100644 --- a/src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs +++ b/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 { + /// + /// Start Avalonia application with Headless VNC platform without password. + /// + /// Application Builder + /// VNC Server IP will be bind, if null or empty IPAddress.LoopBack will be used. + /// VNC Server port will be bind + /// Avalonia application start args + /// shut down mode + /// 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); + } + + /// + /// Start Avalonia application with Headless VNC platform with password. + /// + /// Application Builder + /// VNC Server IP will be bind, if null or empty IPAddress.LoopBack will be used. + /// VNC Server port will be bind + /// VNC connection auth password + /// Avalonia application start args + /// shut down mode + /// + /// + 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);