Add Windows screen QR scan support for avalonia

This commit is contained in:
2dust 2026-04-06 15:34:25 +08:00
parent 75f2cbaef9
commit 1160d8c154
4 changed files with 203 additions and 13 deletions

View file

@ -15,7 +15,7 @@
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.1" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />
<PackageVersion Include="QRCoder" Version="1.7.0" />
<PackageVersion Include="QRCoder" Version="1.8.0" />
<PackageVersion Include="ReactiveUI" Version="23.2.1" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.1" />

View file

@ -0,0 +1,189 @@
using System.Runtime.InteropServices;
using SkiaSharp;
namespace v2rayN.Desktop.Common;
public partial class QRCodeAvaloniaUtils
{
public static byte[]? CaptureScreen()
{
if (!Utils.IsWindows())
{
return null;
}
try
{
return CaptureScreenWindows();
}
catch (Exception ex)
{
Logging.SaveLog("CaptureScreen", ex);
return null;
}
}
private static byte[]? CaptureScreenWindows()
{
var hdcScreen = IntPtr.Zero;
var hdcMemory = IntPtr.Zero;
var hBitmap = IntPtr.Zero;
try
{
var workArea = new RECT();
SystemParametersInfo(SPI_GETWORKAREA, 0, ref workArea, 0);
var left = workArea.Left;
var top = workArea.Top;
var width = workArea.Right - workArea.Left;
var height = workArea.Bottom - workArea.Top;
if (width <= 0 || height <= 0)
{
left = 0;
top = 0;
width = GetSystemMetrics(0);
height = GetSystemMetrics(1);
}
hdcScreen = GetDC(IntPtr.Zero);
if (hdcScreen == IntPtr.Zero)
{
return null;
}
hdcMemory = CreateCompatibleDC(hdcScreen);
hBitmap = CreateCompatibleBitmap(hdcScreen, width, height);
if (hBitmap == IntPtr.Zero)
{
return null;
}
SelectObject(hdcMemory, hBitmap);
const int SRCCOPY = 0x00CC0020;
BitBlt(hdcMemory, 0, 0, width, height, hdcScreen, left, top, SRCCOPY);
var bmi = new BITMAPINFO
{
biSize = Marshal.SizeOf(typeof(BITMAPINFO)),
biWidth = width,
biHeight = -height,
biPlanes = 1,
biBitCount = 32,
biCompression = 0
};
var imageSize = width * height * 4;
var imageData = new byte[imageSize];
var scanLines = GetDIBits(hdcScreen, hBitmap, 0, (uint)height, imageData, ref bmi, 0);
if (scanLines == 0)
{
return null;
}
using var bitmap = new SKBitmap(width, height, SKColorType.Bgra8888, SKAlphaType.Premul);
Marshal.Copy(imageData, 0, bitmap.GetPixels(), imageSize);
using var image = SKImage.FromBitmap(bitmap);
using var encoded = image.Encode(SKEncodedImageFormat.Png, 100);
return encoded.ToArray();
}
catch (Exception ex)
{
Logging.SaveLog("CaptureScreenWindows", ex);
return null;
}
finally
{
if (hBitmap != IntPtr.Zero)
{
DeleteObject(hBitmap);
}
if (hdcMemory != IntPtr.Zero)
{
DeleteDC(hdcMemory);
}
if (hdcScreen != IntPtr.Zero)
{
ReleaseDC(IntPtr.Zero, hdcScreen);
}
}
}
#region Win32 API
[LibraryImport("user32.dll")]
private static partial IntPtr GetDC(IntPtr hwnd);
[LibraryImport("user32.dll")]
private static partial int ReleaseDC(IntPtr hwnd, IntPtr hdc);
[LibraryImport("gdi32.dll")]
private static partial IntPtr CreateCompatibleDC(IntPtr hdc);
[LibraryImport("gdi32.dll")]
private static partial IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[LibraryImport("gdi32.dll")]
private static partial IntPtr SelectObject(IntPtr hdc, IntPtr hObject);
[LibraryImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight,
IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);
[LibraryImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool DeleteObject(IntPtr hObject);
[LibraryImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool DeleteDC(IntPtr hdc);
[LibraryImport("gdi32.dll")]
private static partial int GetDIBits(IntPtr hdc, IntPtr hbmp, uint uStartScan, uint cScanLines,
byte[] lpvBits, ref BITMAPINFO lpbmi, uint uUsage);
[LibraryImport("user32.dll")]
private static partial int GetSystemMetrics(int nIndex);
[LibraryImport("user32.dll", EntryPoint = "SystemParametersInfoW", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool SystemParametersInfo(int uiAction, int uiParam, ref RECT pvParam, int fWinIni);
private const int SPI_GETWORKAREA = 0x0030;
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
private struct BITMAPINFO
{
public int biSize;
public int biWidth;
public int biHeight;
public short biPlanes;
public short biBitCount;
public int biCompression;
public int biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public int biClrUsed;
public int biClrImportant;
}
#endregion Win32 API
}

View file

@ -162,8 +162,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
else
{
Title = $"{Utils.GetVersion()}";
menuAddServerViaScan.IsVisible = false;
}
menuAddServerViaScan.IsVisible = false;
if (_config.UiItem.AutoHideStartup && Utils.IsWindows())
{
@ -336,17 +336,17 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
public async Task ScanScreenTaskAsync()
{
//ShowHideWindow(false);
ShowHideWindow(false);
NoticeManager.Instance.SendMessageAndEnqueue("Not yet implemented.(还未实现)");
await Task.CompletedTask;
//if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
//{
// //var bytes = QRCodeHelper.CaptureScreen(desktop);
// //await ViewModel?.ScanScreenResult(bytes);
//}
await Task.Delay(200);
//ShowHideWindow(true);
var bytes = QRCodeAvaloniaUtils.CaptureScreen();
if (bytes != null && ViewModel != null)
{
await ViewModel.ScanScreenResult(bytes);
}
ShowHideWindow(true);
}
private async Task ScanImageTaskAsync()

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
@ -6,6 +6,7 @@
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<AssemblyName>v2rayN</AssemblyName>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>