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);