Skip to content

Commit 1160d8c

Browse files
committed
Add Windows screen QR scan support for avalonia
1 parent 75f2cba commit 1160d8c

4 files changed

Lines changed: 203 additions & 13 deletions

File tree

v2rayN/Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
1616
<PackageVersion Include="MaterialDesignThemes" Version="5.3.1" />
1717
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />
18-
<PackageVersion Include="QRCoder" Version="1.7.0" />
18+
<PackageVersion Include="QRCoder" Version="1.8.0" />
1919
<PackageVersion Include="ReactiveUI" Version="23.2.1" />
2020
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
2121
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.1" />
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
using System.Runtime.InteropServices;
2+
using SkiaSharp;
3+
4+
namespace v2rayN.Desktop.Common;
5+
6+
public partial class QRCodeAvaloniaUtils
7+
{
8+
public static byte[]? CaptureScreen()
9+
{
10+
if (!Utils.IsWindows())
11+
{
12+
return null;
13+
}
14+
15+
try
16+
{
17+
return CaptureScreenWindows();
18+
}
19+
catch (Exception ex)
20+
{
21+
Logging.SaveLog("CaptureScreen", ex);
22+
return null;
23+
}
24+
}
25+
26+
private static byte[]? CaptureScreenWindows()
27+
{
28+
var hdcScreen = IntPtr.Zero;
29+
var hdcMemory = IntPtr.Zero;
30+
var hBitmap = IntPtr.Zero;
31+
32+
try
33+
{
34+
var workArea = new RECT();
35+
SystemParametersInfo(SPI_GETWORKAREA, 0, ref workArea, 0);
36+
37+
var left = workArea.Left;
38+
var top = workArea.Top;
39+
var width = workArea.Right - workArea.Left;
40+
var height = workArea.Bottom - workArea.Top;
41+
42+
if (width <= 0 || height <= 0)
43+
{
44+
left = 0;
45+
top = 0;
46+
width = GetSystemMetrics(0);
47+
height = GetSystemMetrics(1);
48+
}
49+
50+
hdcScreen = GetDC(IntPtr.Zero);
51+
if (hdcScreen == IntPtr.Zero)
52+
{
53+
return null;
54+
}
55+
56+
hdcMemory = CreateCompatibleDC(hdcScreen);
57+
hBitmap = CreateCompatibleBitmap(hdcScreen, width, height);
58+
59+
if (hBitmap == IntPtr.Zero)
60+
{
61+
return null;
62+
}
63+
64+
SelectObject(hdcMemory, hBitmap);
65+
66+
const int SRCCOPY = 0x00CC0020;
67+
BitBlt(hdcMemory, 0, 0, width, height, hdcScreen, left, top, SRCCOPY);
68+
69+
var bmi = new BITMAPINFO
70+
{
71+
biSize = Marshal.SizeOf(typeof(BITMAPINFO)),
72+
biWidth = width,
73+
biHeight = -height,
74+
biPlanes = 1,
75+
biBitCount = 32,
76+
biCompression = 0
77+
};
78+
79+
var imageSize = width * height * 4;
80+
var imageData = new byte[imageSize];
81+
82+
var scanLines = GetDIBits(hdcScreen, hBitmap, 0, (uint)height, imageData, ref bmi, 0);
83+
84+
if (scanLines == 0)
85+
{
86+
return null;
87+
}
88+
89+
using var bitmap = new SKBitmap(width, height, SKColorType.Bgra8888, SKAlphaType.Premul);
90+
Marshal.Copy(imageData, 0, bitmap.GetPixels(), imageSize);
91+
92+
using var image = SKImage.FromBitmap(bitmap);
93+
using var encoded = image.Encode(SKEncodedImageFormat.Png, 100);
94+
return encoded.ToArray();
95+
}
96+
catch (Exception ex)
97+
{
98+
Logging.SaveLog("CaptureScreenWindows", ex);
99+
return null;
100+
}
101+
finally
102+
{
103+
if (hBitmap != IntPtr.Zero)
104+
{
105+
DeleteObject(hBitmap);
106+
}
107+
108+
if (hdcMemory != IntPtr.Zero)
109+
{
110+
DeleteDC(hdcMemory);
111+
}
112+
113+
if (hdcScreen != IntPtr.Zero)
114+
{
115+
ReleaseDC(IntPtr.Zero, hdcScreen);
116+
}
117+
}
118+
}
119+
120+
#region Win32 API
121+
122+
[LibraryImport("user32.dll")]
123+
private static partial IntPtr GetDC(IntPtr hwnd);
124+
125+
[LibraryImport("user32.dll")]
126+
private static partial int ReleaseDC(IntPtr hwnd, IntPtr hdc);
127+
128+
[LibraryImport("gdi32.dll")]
129+
private static partial IntPtr CreateCompatibleDC(IntPtr hdc);
130+
131+
[LibraryImport("gdi32.dll")]
132+
private static partial IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
133+
134+
[LibraryImport("gdi32.dll")]
135+
private static partial IntPtr SelectObject(IntPtr hdc, IntPtr hObject);
136+
137+
[LibraryImport("gdi32.dll")]
138+
[return: MarshalAs(UnmanagedType.Bool)]
139+
private static partial bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight,
140+
IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);
141+
142+
[LibraryImport("gdi32.dll")]
143+
[return: MarshalAs(UnmanagedType.Bool)]
144+
private static partial bool DeleteObject(IntPtr hObject);
145+
146+
[LibraryImport("gdi32.dll")]
147+
[return: MarshalAs(UnmanagedType.Bool)]
148+
private static partial bool DeleteDC(IntPtr hdc);
149+
150+
[LibraryImport("gdi32.dll")]
151+
private static partial int GetDIBits(IntPtr hdc, IntPtr hbmp, uint uStartScan, uint cScanLines,
152+
byte[] lpvBits, ref BITMAPINFO lpbmi, uint uUsage);
153+
154+
[LibraryImport("user32.dll")]
155+
private static partial int GetSystemMetrics(int nIndex);
156+
157+
[LibraryImport("user32.dll", EntryPoint = "SystemParametersInfoW", SetLastError = true)]
158+
[return: MarshalAs(UnmanagedType.Bool)]
159+
private static partial bool SystemParametersInfo(int uiAction, int uiParam, ref RECT pvParam, int fWinIni);
160+
161+
private const int SPI_GETWORKAREA = 0x0030;
162+
163+
[StructLayout(LayoutKind.Sequential)]
164+
private struct RECT
165+
{
166+
public int Left;
167+
public int Top;
168+
public int Right;
169+
public int Bottom;
170+
}
171+
172+
[StructLayout(LayoutKind.Sequential)]
173+
private struct BITMAPINFO
174+
{
175+
public int biSize;
176+
public int biWidth;
177+
public int biHeight;
178+
public short biPlanes;
179+
public short biBitCount;
180+
public int biCompression;
181+
public int biSizeImage;
182+
public int biXPelsPerMeter;
183+
public int biYPelsPerMeter;
184+
public int biClrUsed;
185+
public int biClrImportant;
186+
}
187+
188+
#endregion Win32 API
189+
}

v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ public MainWindow()
162162
else
163163
{
164164
Title = $"{Utils.GetVersion()}";
165+
menuAddServerViaScan.IsVisible = false;
165166
}
166-
menuAddServerViaScan.IsVisible = false;
167167

168168
if (_config.UiItem.AutoHideStartup && Utils.IsWindows())
169169
{
@@ -336,17 +336,17 @@ public async Task AddServerViaClipboardAsync()
336336

337337
public async Task ScanScreenTaskAsync()
338338
{
339-
//ShowHideWindow(false);
339+
ShowHideWindow(false);
340340

341-
NoticeManager.Instance.SendMessageAndEnqueue("Not yet implemented.(还未实现)");
342-
await Task.CompletedTask;
343-
//if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
344-
//{
345-
// //var bytes = QRCodeHelper.CaptureScreen(desktop);
346-
// //await ViewModel?.ScanScreenResult(bytes);
347-
//}
341+
await Task.Delay(200);
342+
343+
var bytes = QRCodeAvaloniaUtils.CaptureScreen();
344+
if (bytes != null && ViewModel != null)
345+
{
346+
await ViewModel.ScanScreenResult(bytes);
347+
}
348348

349-
//ShowHideWindow(true);
349+
ShowHideWindow(true);
350350
}
351351

352352
private async Task ScanImageTaskAsync()

v2rayN/v2rayN.Desktop/v2rayN.Desktop.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<OutputType>WinExe</OutputType>
55
<ApplicationIcon>Assets\v2rayN.ico</ApplicationIcon>
66
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
77
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
88
<AssemblyName>v2rayN</AssemblyName>
9+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
910
</PropertyGroup>
1011

1112
<ItemGroup>
@@ -52,4 +53,4 @@
5253
</None>
5354
</ItemGroup>
5455

55-
</Project>
56+
</Project>

0 commit comments

Comments
 (0)