mirror of https://github.com/2dust/v2rayN
Add scanning QR code from image
parent
b74ddc0b43
commit
5c0fba8744
|
@ -1,4 +1,6 @@
|
||||||
using QRCoder;
|
using QRCoder;
|
||||||
|
using SkiaSharp;
|
||||||
|
using ZXing.SkiaSharp;
|
||||||
|
|
||||||
namespace ServiceLib.Common
|
namespace ServiceLib.Common
|
||||||
{
|
{
|
||||||
|
@ -11,5 +13,78 @@ namespace ServiceLib.Common
|
||||||
using PngByteQRCode qrCode = new(qrCodeData);
|
using PngByteQRCode qrCode = new(qrCodeData);
|
||||||
return qrCode.GetGraphic(20);
|
return qrCode.GetGraphic(20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string? ParseBarcode(string? fileName)
|
||||||
|
{
|
||||||
|
if (fileName == null || !File.Exists(fileName))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var image = SKImage.FromEncodedData(fileName);
|
||||||
|
var bitmap = SKBitmap.FromImage(image);
|
||||||
|
|
||||||
|
return ReaderBarcode(bitmap);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? ParseBarcode(byte[]? bytes)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bitmap = SKBitmap.Decode(bytes);
|
||||||
|
//using var stream = new FileStream("test2.png", FileMode.Create, FileAccess.Write);
|
||||||
|
//using var image = SKImage.FromBitmap(bitmap);
|
||||||
|
//using var encodedImage = image.Encode();
|
||||||
|
//encodedImage.SaveTo(stream);
|
||||||
|
return ReaderBarcode(bitmap);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ReaderBarcode(SKBitmap? bitmap)
|
||||||
|
{
|
||||||
|
var reader = new BarcodeReader();
|
||||||
|
var result = reader.Decode(bitmap);
|
||||||
|
|
||||||
|
if (result != null && Utils.IsNotEmpty(result.Text))
|
||||||
|
{
|
||||||
|
return result.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
//FlipBitmap
|
||||||
|
var result2 = reader.Decode(FlipBitmap(bitmap));
|
||||||
|
return result2?.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SKBitmap FlipBitmap(SKBitmap bmp)
|
||||||
|
{
|
||||||
|
// Create a bitmap (to return)
|
||||||
|
var flipped = new SKBitmap(bmp.Width, bmp.Height, bmp.Info.ColorType, bmp.Info.AlphaType);
|
||||||
|
|
||||||
|
// Create a canvas to draw into the bitmap
|
||||||
|
using var canvas = new SKCanvas(flipped);
|
||||||
|
|
||||||
|
// Set a transform matrix which moves the bitmap to the right,
|
||||||
|
// and then "scales" it by -1, which just flips the pixels
|
||||||
|
// horizontally
|
||||||
|
canvas.Translate(bmp.Width, 0);
|
||||||
|
canvas.Scale(-1, 1);
|
||||||
|
canvas.DrawBitmap(bmp, 0, 0);
|
||||||
|
return flipped;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,6 +15,7 @@
|
||||||
ShareServer,
|
ShareServer,
|
||||||
ShowHideWindow,
|
ShowHideWindow,
|
||||||
ScanScreenTask,
|
ScanScreenTask,
|
||||||
|
ScanImageTask,
|
||||||
Shutdown,
|
Shutdown,
|
||||||
BrowseServer,
|
BrowseServer,
|
||||||
ImportRulesFromFile,
|
ImportRulesFromFile,
|
||||||
|
|
|
@ -681,6 +681,15 @@ namespace ServiceLib.Resx {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Scan QR code in the image 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string menuAddServerViaImage {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("menuAddServerViaImage", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Scan QR code on the screen (Ctrl+S) 的本地化字符串。
|
/// 查找类似 Scan QR code on the screen (Ctrl+S) 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1351,4 +1351,7 @@
|
||||||
<data name="TbSettingsChinaUserTip" xml:space="preserve">
|
<data name="TbSettingsChinaUserTip" xml:space="preserve">
|
||||||
<value>Users in China region can ignore this item</value>
|
<value>Users in China region can ignore this item</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="menuAddServerViaImage" xml:space="preserve">
|
||||||
|
<value>Scan QR code in the image</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -1348,4 +1348,7 @@
|
||||||
<data name="menuRegionalPresetsRussia" xml:space="preserve">
|
<data name="menuRegionalPresetsRussia" xml:space="preserve">
|
||||||
<value>俄罗斯</value>
|
<value>俄罗斯</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="menuAddServerViaImage" xml:space="preserve">
|
||||||
|
<value>扫描图片中的二维码</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -1228,4 +1228,7 @@
|
||||||
<data name="menuRegionalPresetsRussia" xml:space="preserve">
|
<data name="menuRegionalPresetsRussia" xml:space="preserve">
|
||||||
<value>俄羅斯</value>
|
<value>俄羅斯</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="menuAddServerViaImage" xml:space="preserve">
|
||||||
|
<value>掃描圖片中的二維碼</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -16,7 +16,9 @@
|
||||||
<PackageReference Include="WebDav.Client" Version="2.8.0" />
|
<PackageReference Include="WebDav.Client" Version="2.8.0" />
|
||||||
<PackageReference Include="YamlDotNet" Version="16.1.3" />
|
<PackageReference Include="YamlDotNet" Version="16.1.3" />
|
||||||
<PackageReference Include="QRCoder" Version="1.6.0" />
|
<PackageReference Include="QRCoder" Version="1.6.0" />
|
||||||
<PackageReference Include="CliWrap" Version="3.6.6" />
|
<PackageReference Include="CliWrap" Version="3.6.6" />
|
||||||
|
<PackageReference Include="SkiaSharp.QrCode" Version="0.7.0" />
|
||||||
|
<PackageReference Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -25,6 +25,7 @@ namespace ServiceLib.ViewModels
|
||||||
public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; }
|
public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
|
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
|
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
|
||||||
|
public ReactiveCommand<Unit, Unit> AddServerViaImageCmd { get; }
|
||||||
|
|
||||||
//Subscription
|
//Subscription
|
||||||
public ReactiveCommand<Unit, Unit> SubSettingCmd { get; }
|
public ReactiveCommand<Unit, Unit> SubSettingCmd { get; }
|
||||||
|
@ -46,6 +47,7 @@ namespace ServiceLib.ViewModels
|
||||||
|
|
||||||
//Presets
|
//Presets
|
||||||
public ReactiveCommand<Unit, Unit> RegionalPresetDefaultCmd { get; }
|
public ReactiveCommand<Unit, Unit> RegionalPresetDefaultCmd { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> RegionalPresetRussiaCmd { get; }
|
public ReactiveCommand<Unit, Unit> RegionalPresetRussiaCmd { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> ReloadCmd { get; }
|
public ReactiveCommand<Unit, Unit> ReloadCmd { get; }
|
||||||
|
@ -121,7 +123,11 @@ namespace ServiceLib.ViewModels
|
||||||
});
|
});
|
||||||
AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () =>
|
AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
await AddServerViaScanTaskAsync();
|
await AddServerViaScanAsync();
|
||||||
|
});
|
||||||
|
AddServerViaImageCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
|
{
|
||||||
|
await AddServerViaImageAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
//Subscription
|
//Subscription
|
||||||
|
@ -386,12 +392,34 @@ namespace ServiceLib.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddServerViaScanTaskAsync()
|
public async Task AddServerViaScanAsync()
|
||||||
{
|
{
|
||||||
_updateView?.Invoke(EViewAction.ScanScreenTask, null);
|
_updateView?.Invoke(EViewAction.ScanScreenTask, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ScanScreenResult(string result)
|
public async Task ScanScreenResult(byte[]? bytes)
|
||||||
|
{
|
||||||
|
var result = QRCodeHelper.ParseBarcode(bytes);
|
||||||
|
await AddScanResultAsync(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddServerViaImageAsync()
|
||||||
|
{
|
||||||
|
_updateView?.Invoke(EViewAction.ScanImageTask, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ScanImageResult(string fileName)
|
||||||
|
{
|
||||||
|
if (Utils.IsNullOrEmpty(fileName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = QRCodeHelper.ParseBarcode(fileName);
|
||||||
|
await AddScanResultAsync(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddScanResultAsync(string? result)
|
||||||
{
|
{
|
||||||
if (Utils.IsNullOrEmpty(result))
|
if (Utils.IsNullOrEmpty(result))
|
||||||
{
|
{
|
||||||
|
@ -571,6 +599,6 @@ namespace ServiceLib.ViewModels
|
||||||
Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion Presets
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -211,7 +211,7 @@ namespace ServiceLib.ViewModels
|
||||||
private async Task AddServerViaScan()
|
private async Task AddServerViaScan()
|
||||||
{
|
{
|
||||||
var service = Locator.Current.GetService<MainWindowViewModel>();
|
var service = Locator.Current.GetService<MainWindowViewModel>();
|
||||||
if (service != null) await service.AddServerViaScanTaskAsync();
|
if (service != null) await service.AddServerViaScanAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateSubscriptionProcess(bool blProxy)
|
private async Task UpdateSubscriptionProcess(bool blProxy)
|
||||||
|
|
|
@ -32,10 +32,8 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</MenuItem.Header>
|
</MenuItem.Header>
|
||||||
<MenuItem x:Name="menuAddServerViaClipboard" Header="{x:Static resx:ResUI.menuAddServerViaClipboard}" />
|
<MenuItem x:Name="menuAddServerViaClipboard" Header="{x:Static resx:ResUI.menuAddServerViaClipboard}" />
|
||||||
<MenuItem
|
<MenuItem x:Name="menuAddServerViaScan" Header="{x:Static resx:ResUI.menuAddServerViaScan}" />
|
||||||
x:Name="menuAddServerViaScan"
|
<MenuItem x:Name="menuAddServerViaImage" Header="{x:Static resx:ResUI.menuAddServerViaImage}" />
|
||||||
Header="{x:Static resx:ResUI.menuAddServerViaScan}"
|
|
||||||
IsVisible="False" />
|
|
||||||
<MenuItem x:Name="menuAddCustomServer" Header="{x:Static resx:ResUI.menuAddCustomServer}" />
|
<MenuItem x:Name="menuAddCustomServer" Header="{x:Static resx:ResUI.menuAddCustomServer}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem x:Name="menuAddVmessServer" Header="{x:Static resx:ResUI.menuAddVmessServer}" />
|
<MenuItem x:Name="menuAddVmessServer" Header="{x:Static resx:ResUI.menuAddVmessServer}" />
|
||||||
|
|
|
@ -4,6 +4,7 @@ using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Controls.Notifications;
|
using Avalonia.Controls.Notifications;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using DialogHostAvalonia;
|
using DialogHostAvalonia;
|
||||||
|
@ -60,6 +61,7 @@ namespace v2rayN.Desktop.Views
|
||||||
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);
|
||||||
|
this.BindCommand(ViewModel, vm => vm.AddServerViaImageCmd, v => v.menuAddServerViaImage).DisposeWith(disposables);
|
||||||
|
|
||||||
//sub
|
//sub
|
||||||
this.BindCommand(ViewModel, vm => vm.SubSettingCmd, v => v.menuSubSetting).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.SubSettingCmd, v => v.menuSubSetting).DisposeWith(disposables);
|
||||||
|
@ -224,6 +226,10 @@ namespace v2rayN.Desktop.Views
|
||||||
await ScanScreenTaskAsync();
|
await ScanScreenTaskAsync();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case EViewAction.ScanImageTask:
|
||||||
|
await ScanImageTaskAsync();
|
||||||
|
break;
|
||||||
|
|
||||||
case EViewAction.AddServerViaClipboard:
|
case EViewAction.AddServerViaClipboard:
|
||||||
var clipboardData = await AvaUtils.GetClipboardData(this);
|
var clipboardData = await AvaUtils.GetClipboardData(this);
|
||||||
ViewModel?.AddServerViaClipboardAsync(clipboardData);
|
ViewModel?.AddServerViaClipboardAsync(clipboardData);
|
||||||
|
@ -324,7 +330,16 @@ namespace v2rayN.Desktop.Views
|
||||||
|
|
||||||
ShowHideWindow(true);
|
ShowHideWindow(true);
|
||||||
|
|
||||||
//ViewModel?.ScanScreenTaskAsync(result);
|
//ViewModel?.ScanScreenResult(result);
|
||||||
|
}
|
||||||
|
private async Task ScanImageTaskAsync()
|
||||||
|
{
|
||||||
|
var fileName = await UI.OpenFileDialog(this,null );
|
||||||
|
if (fileName.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await ViewModel?.ScanImageResult(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MenuCheckUpdate_Click(object? sender, RoutedEventArgs e)
|
private void MenuCheckUpdate_Click(object? sender, RoutedEventArgs e)
|
||||||
|
|
|
@ -17,8 +17,7 @@
|
||||||
<Image
|
<Image
|
||||||
Name="imgQrcode"
|
Name="imgQrcode"
|
||||||
Width="300"
|
Width="300"
|
||||||
Height="300"
|
Height="300" />
|
||||||
Source="/Assets/close.png" />
|
|
||||||
|
|
||||||
<TextBox
|
<TextBox
|
||||||
x:Name="txtContent"
|
x:Name="txtContent"
|
||||||
|
@ -29,6 +28,6 @@
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
MaxLines="1" />
|
MaxLines="1" />
|
||||||
|
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
|
@ -4,10 +4,6 @@ using System.Windows;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using ZXing;
|
|
||||||
using ZXing.Common;
|
|
||||||
using ZXing.QrCode;
|
|
||||||
using ZXing.Windows.Compatibility;
|
|
||||||
|
|
||||||
namespace v2rayN
|
namespace v2rayN
|
||||||
{
|
{
|
||||||
|
@ -25,11 +21,7 @@ namespace v2rayN
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var qrCodeImage = ServiceLib.Common.QRCodeHelper.GenQRCode(strContent);
|
var qrCodeImage = ServiceLib.Common.QRCodeHelper.GenQRCode(strContent);
|
||||||
if (qrCodeImage is null)
|
return qrCodeImage is null ? null : ByteToImage(qrCodeImage);
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ByteToImage(qrCodeImage);
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -37,82 +29,54 @@ namespace v2rayN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImageSource ByteToImage(byte[] imageData)
|
public static byte[]? CaptureScreen(Window window)
|
||||||
{
|
|
||||||
BitmapImage biImg = new();
|
|
||||||
MemoryStream ms = new(imageData);
|
|
||||||
biImg.BeginInit();
|
|
||||||
biImg.StreamSource = ms;
|
|
||||||
biImg.EndInit();
|
|
||||||
|
|
||||||
ImageSource imgSrc = biImg as ImageSource;
|
|
||||||
|
|
||||||
return imgSrc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ScanScreen(float dpiX, float dpiY)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
GetDpi(window, out var dpiX, out var dpiY);
|
||||||
|
|
||||||
var left = (int)(SystemParameters.WorkArea.Left);
|
var left = (int)(SystemParameters.WorkArea.Left);
|
||||||
var top = (int)(SystemParameters.WorkArea.Top);
|
var top = (int)(SystemParameters.WorkArea.Top);
|
||||||
var width = (int)(SystemParameters.WorkArea.Width / dpiX);
|
var width = (int)(SystemParameters.WorkArea.Width / dpiX);
|
||||||
var height = (int)(SystemParameters.WorkArea.Height / dpiY);
|
var height = (int)(SystemParameters.WorkArea.Height / dpiY);
|
||||||
|
|
||||||
using Bitmap fullImage = new Bitmap(width, height);
|
using var fullImage = new Bitmap(width, height);
|
||||||
using (Graphics g = Graphics.FromImage(fullImage))
|
using var g = Graphics.FromImage(fullImage);
|
||||||
{
|
|
||||||
g.CopyFromScreen(left, top, 0, 0, fullImage.Size, CopyPixelOperation.SourceCopy);
|
|
||||||
}
|
|
||||||
int maxTry = 10;
|
|
||||||
for (int i = 0; i < maxTry; i++)
|
|
||||||
{
|
|
||||||
int marginLeft = (int)((double)fullImage.Width * i / 2.5 / maxTry);
|
|
||||||
int marginTop = (int)((double)fullImage.Height * i / 2.5 / maxTry);
|
|
||||||
Rectangle cropRect = new(marginLeft, marginTop, fullImage.Width - marginLeft * 2, fullImage.Height - marginTop * 2);
|
|
||||||
Bitmap target = new(width, height);
|
|
||||||
|
|
||||||
double imageScale = (double)width / (double)cropRect.Width;
|
g.CopyFromScreen(left, top, 0, 0, fullImage.Size, CopyPixelOperation.SourceCopy);
|
||||||
using (Graphics g = Graphics.FromImage(target))
|
//fullImage.Save("test1.png", ImageFormat.Png);
|
||||||
{
|
return ImageToByte(fullImage);
|
||||||
g.DrawImage(fullImage, new Rectangle(0, 0, target.Width, target.Height),
|
|
||||||
cropRect,
|
|
||||||
GraphicsUnit.Pixel);
|
|
||||||
}
|
|
||||||
|
|
||||||
BitmapLuminanceSource source = new(target);
|
|
||||||
QRCodeReader reader = new();
|
|
||||||
|
|
||||||
BinaryBitmap bitmap = new(new HybridBinarizer(source));
|
|
||||||
var result = reader.decode(bitmap);
|
|
||||||
if (result != null)
|
|
||||||
{
|
|
||||||
return result.Text;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BinaryBitmap bitmap2 = new(new HybridBinarizer(source.invert()));
|
|
||||||
var result2 = reader.decode(bitmap2);
|
|
||||||
if (result2 != null)
|
|
||||||
{
|
|
||||||
return result2.Text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
Logging.SaveLog(ex.Message, ex);
|
return null;
|
||||||
}
|
}
|
||||||
return string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Tuple<float, float> GetDpiXY(Window window)
|
private static void GetDpi(Window window, out float x, out float y)
|
||||||
{
|
{
|
||||||
IntPtr hWnd = new WindowInteropHelper(window).EnsureHandle();
|
var hWnd = new WindowInteropHelper(window).EnsureHandle();
|
||||||
Graphics g = Graphics.FromHwnd(hWnd);
|
var g = Graphics.FromHwnd(hWnd);
|
||||||
|
|
||||||
return new(96 / g.DpiX, 96 / g.DpiY);
|
x = 96 / g.DpiX;
|
||||||
|
y = 96 / g.DpiY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImageSource ByteToImage(byte[] imageData)
|
||||||
|
{
|
||||||
|
BitmapImage biImg = new();
|
||||||
|
using MemoryStream ms = new(imageData);
|
||||||
|
biImg.BeginInit();
|
||||||
|
biImg.StreamSource = ms;
|
||||||
|
biImg.EndInit();
|
||||||
|
|
||||||
|
return biImg as ImageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[]? ImageToByte(Image img)
|
||||||
|
{
|
||||||
|
var converter = new ImageConverter();
|
||||||
|
return converter.ConvertTo(img, typeof(byte[])) as byte[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -64,6 +64,10 @@
|
||||||
x:Name="menuAddServerViaScan"
|
x:Name="menuAddServerViaScan"
|
||||||
Height="{StaticResource MenuItemHeight}"
|
Height="{StaticResource MenuItemHeight}"
|
||||||
Header="{x:Static resx:ResUI.menuAddServerViaScan}" />
|
Header="{x:Static resx:ResUI.menuAddServerViaScan}" />
|
||||||
|
<MenuItem
|
||||||
|
x:Name="menuAddServerViaImage"
|
||||||
|
Height="{StaticResource MenuItemHeight}"
|
||||||
|
Header="{x:Static resx:ResUI.menuAddServerViaImage}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
x:Name="menuAddCustomServer"
|
x:Name="menuAddCustomServer"
|
||||||
Height="{StaticResource MenuItemHeight}"
|
Height="{StaticResource MenuItemHeight}"
|
||||||
|
|
|
@ -82,6 +82,7 @@ namespace v2rayN.Views
|
||||||
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);
|
||||||
|
this.BindCommand(ViewModel, vm => vm.AddServerViaImageCmd, v => v.menuAddServerViaImage).DisposeWith(disposables);
|
||||||
|
|
||||||
//sub
|
//sub
|
||||||
this.BindCommand(ViewModel, vm => vm.SubSettingCmd, v => v.menuSubSetting).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.SubSettingCmd, v => v.menuSubSetting).DisposeWith(disposables);
|
||||||
|
@ -221,6 +222,10 @@ namespace v2rayN.Views
|
||||||
await ScanScreenTaskAsync();
|
await ScanScreenTaskAsync();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case EViewAction.ScanImageTask:
|
||||||
|
await ScanImageTaskAsync();
|
||||||
|
break;
|
||||||
|
|
||||||
case EViewAction.AddServerViaClipboard:
|
case EViewAction.AddServerViaClipboard:
|
||||||
var clipboardData = WindowsUtils.GetClipboardData();
|
var clipboardData = WindowsUtils.GetClipboardData();
|
||||||
ViewModel?.AddServerViaClipboardAsync(clipboardData);
|
ViewModel?.AddServerViaClipboardAsync(clipboardData);
|
||||||
|
@ -317,15 +322,26 @@ namespace v2rayN.Views
|
||||||
{
|
{
|
||||||
ShowHideWindow(false);
|
ShowHideWindow(false);
|
||||||
|
|
||||||
var dpiXY = QRCodeHelper.GetDpiXY(Application.Current.MainWindow);
|
if (Application.Current?.MainWindow is Window window)
|
||||||
string result = await Task.Run(() =>
|
|
||||||
{
|
{
|
||||||
return QRCodeHelper.ScanScreen(dpiXY.Item1, dpiXY.Item2);
|
var bytes = QRCodeHelper.CaptureScreen(window);
|
||||||
});
|
await ViewModel?.ScanScreenResult(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
ShowHideWindow(true);
|
ShowHideWindow(true);
|
||||||
|
}
|
||||||
|
|
||||||
ViewModel?.ScanScreenResult(result);
|
private async Task ScanImageTaskAsync()
|
||||||
|
{
|
||||||
|
if (UI.OpenFileDialog(out var fileName, "PNG|*.png|All|*.*") != true)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fileName.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await ViewModel?.ScanImageResult(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MenuCheckUpdate_Click(object sender, RoutedEventArgs e)
|
private void MenuCheckUpdate_Click(object sender, RoutedEventArgs e)
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
<PackageReference Include="MaterialDesignThemes" Version="5.1.0" />
|
<PackageReference Include="MaterialDesignThemes" Version="5.1.0" />
|
||||||
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.1.3" />
|
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.1.3" />
|
||||||
<PackageReference Include="TaskScheduler" Version="2.11.0" />
|
<PackageReference Include="TaskScheduler" Version="2.11.0" />
|
||||||
<PackageReference Include="ZXing.Net.Bindings.Windows.Compatibility" Version="0.16.12" />
|
|
||||||
<PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
|
<PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||||
<PackageReference Include="ReactiveUI.WPF" Version="20.1.63" />
|
<PackageReference Include="ReactiveUI.WPF" Version="20.1.63" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
Loading…
Reference in New Issue