Compare commits

...

7 commits

Author SHA1 Message Date
DHR60
5c4f1ea38f
Merge 18ccabd193 into 18ac76e683 2025-09-21 21:15:55 +08:00
2dust
18ac76e683 up 7.14.12
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-09-21 14:50:01 +08:00
2dust
3e1e23a524 Update Directory.Packages.props 2025-09-21 14:48:54 +08:00
2dust
534c7ab444 Optimize and improve QR code display 2025-09-21 14:35:49 +08:00
DHR60
18ccabd193 Code clean 2025-09-17 21:25:08 +08:00
DHR60
942335db61 Refactor 2025-09-17 21:11:05 +08:00
DHR60
12b46e7c43 Add CertSha256 support 2025-09-17 21:04:58 +08:00
25 changed files with 216 additions and 24 deletions

View file

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.14.11</Version> <Version>7.14.12</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -18,8 +18,8 @@
<PackageVersion Include="ReactiveUI" Version="20.4.1" /> <PackageVersion Include="ReactiveUI" Version="20.4.1" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" /> <PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" /> <PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" /> <PackageVersion Include="Semi.Avalonia" Version="11.2.1.10" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" /> <PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.10" />
<PackageVersion Include="Splat.NLog" Version="16.2.1" /> <PackageVersion Include="Splat.NLog" Version="16.2.1" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" /> <PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" /> <PackageVersion Include="TaskScheduler" Version="2.12.2" />

View file

@ -1,4 +1,5 @@
using QRCoder; using QRCoder;
using QRCoder.Exceptions;
using SkiaSharp; using SkiaSharp;
using ZXing.SkiaSharp; using ZXing.SkiaSharp;
@ -8,10 +9,45 @@ public class QRCodeUtils
{ {
public static byte[]? GenQRCode(string? url) public static byte[]? GenQRCode(string? url)
{ {
if (url.IsNullOrEmpty())
{
return null;
}
using QRCodeGenerator qrGenerator = new(); using QRCodeGenerator qrGenerator = new();
using var qrCodeData = qrGenerator.CreateQrCode(url ?? string.Empty, QRCodeGenerator.ECCLevel.Q); DataTooLongException? lastDtle = null;
using PngByteQRCode qrCode = new(qrCodeData);
return qrCode.GetGraphic(20); var levels = new[]
{
QRCodeGenerator.ECCLevel.H,
QRCodeGenerator.ECCLevel.Q,
QRCodeGenerator.ECCLevel.M,
QRCodeGenerator.ECCLevel.L
};
foreach (var level in levels)
{
try
{
using var qrCodeData = qrGenerator.CreateQrCode(url, level);
using PngByteQRCode qrCode = new(qrCodeData);
return qrCode.GetGraphic(20);
}
catch (DataTooLongException ex)
{
lastDtle = ex;
continue;
}
catch
{
throw;
}
}
if (lastDtle != null)
{
throw lastDtle;
}
return null;
} }
public static string? ParseBarcode(string? fileName) public static string? ParseBarcode(string? fileName)

View file

@ -8,6 +8,7 @@ using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Security.Principal; using System.Security.Principal;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using CliWrap; using CliWrap;
using CliWrap.Buffered; using CliWrap.Buffered;
@ -357,6 +358,14 @@ public class Utils
return userHostsMap; return userHostsMap;
} }
public static List<string> ParseCertSha256ToList(string certSha256Content)
{
return String2List(certSha256Content)
.Select(s => s.Replace(":", "").Replace(" ", ""))
.Where(s => s.Length == 64 && Regex.IsMatch(s, @"\A\b[0-9a-fA-F]+\b\Z"))
.ToList();
}
#endregion #endregion
#region #region

View file

@ -253,6 +253,7 @@ public static class ConfigHandler
item.ShortId = profileItem.ShortId; item.ShortId = profileItem.ShortId;
item.SpiderX = profileItem.SpiderX; item.SpiderX = profileItem.SpiderX;
item.Mldsa65Verify = profileItem.Mldsa65Verify; item.Mldsa65Verify = profileItem.Mldsa65Verify;
item.CertSha256 = profileItem.CertSha256;
item.Extra = profileItem.Extra; item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled; item.MuxEnabled = profileItem.MuxEnabled;
} }

View file

@ -94,6 +94,7 @@ public class ProfileItem : ReactiveObject
public string ShortId { get; set; } public string ShortId { get; set; }
public string SpiderX { get; set; } public string SpiderX { get; set; }
public string Mldsa65Verify { get; set; } public string Mldsa65Verify { get; set; }
public string CertSha256 { get; set; }
public string Extra { get; set; } public string Extra { get; set; }
public bool? MuxEnabled { get; set; } public bool? MuxEnabled { get; set; }
} }

View file

@ -182,6 +182,7 @@ public class Tls4Sbox
public bool? fragment { get; set; } public bool? fragment { get; set; }
public string? fragment_fallback_delay { get; set; } public string? fragment_fallback_delay { get; set; }
public bool? record_fragment { get; set; } public bool? record_fragment { get; set; }
public List<string>? certificate_sha256 { get; set; }
} }
public class Multiplex4Sbox public class Multiplex4Sbox

View file

@ -355,6 +355,7 @@ public class TlsSettings4Ray
public string? shortId { get; set; } public string? shortId { get; set; }
public string? spiderX { get; set; } public string? spiderX { get; set; }
public string? mldsa65Verify { get; set; } public string? mldsa65Verify { get; set; }
public List<string>? pinnedPeerCertificateSha256 { get; set; }
} }
public class TcpSettings4Ray public class TcpSettings4Ray

View file

@ -2364,6 +2364,24 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Server Certificate Fingerprint (SHA-256) 的本地化字符串。
/// </summary>
public static string TbCertSha256 {
get {
return ResourceManager.GetString("TbCertSha256", resourceCulture);
}
}
/// <summary>
/// 查找类似 Hex SHA-256, comma-separated for certificate chain 的本地化字符串。
/// </summary>
public static string TbCertSha256Tips {
get {
return ResourceManager.GetString("TbCertSha256Tips", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Clear system proxy 的本地化字符串。 /// 查找类似 Clear system proxy 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1515,4 +1515,10 @@
<data name="TbFakeIPTips" xml:space="preserve"> <data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value> <value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data> </data>
<data name="TbCertSha256" xml:space="preserve">
<value>Server Certificate Fingerprint (SHA-256)</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Hex SHA-256, comma-separated for certificate chain</value>
</data>
</root> </root>

View file

@ -1515,4 +1515,10 @@
<data name="TbFakeIPTips" xml:space="preserve"> <data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value> <value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data> </data>
<data name="TbCertSha256" xml:space="preserve">
<value>Server Certificate Fingerprint (SHA-256)</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Hex SHA-256, comma-separated for certificate chain</value>
</data>
</root> </root>

View file

@ -1515,4 +1515,10 @@
<data name="TbFakeIPTips" xml:space="preserve"> <data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value> <value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data> </data>
<data name="TbCertSha256" xml:space="preserve">
<value>Server Certificate Fingerprint (SHA-256)</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Hex SHA-256, comma-separated for certificate chain</value>
</data>
</root> </root>

View file

@ -1515,4 +1515,10 @@
<data name="TbFakeIPTips" xml:space="preserve"> <data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value> <value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data> </data>
<data name="TbCertSha256" xml:space="preserve">
<value>Server Certificate Fingerprint (SHA-256)</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Hex SHA-256, comma-separated for certificate chain</value>
</data>
</root> </root>

View file

@ -1512,4 +1512,10 @@
<data name="TbFakeIPTips" xml:space="preserve"> <data name="TbFakeIPTips" xml:space="preserve">
<value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value> <value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value>
</data> </data>
<data name="TbCertSha256" xml:space="preserve">
<value>服务器证书指纹 (SHA-256)</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>十六进制 SHA-256证书链用逗号分隔</value>
</data>
</root> </root>

View file

@ -1512,4 +1512,10 @@
<data name="TbFakeIPTips" xml:space="preserve"> <data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value> <value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data> </data>
<data name="TbCertSha256" xml:space="preserve">
<value>Server Certificate Fingerprint (SHA-256)</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Hex SHA-256, comma-separated for certificate chain</value>
</data>
</root> </root>

View file

@ -251,6 +251,20 @@ public partial class CoreConfigSingboxService
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
}; };
} }
if (node.CertSha256.IsNotEmpty())
{
// hex to raw to base64
var certSha256List = Utils.ParseCertSha256ToList(node.CertSha256)
.Select(s => Convert.ToBase64String(
Enumerable.Range(0, 32)
.Select(i => Convert.ToByte(s.Substring(i * 2, 2), 16))
.ToArray()))
.ToList();
if (certSha256List.Count > 0)
{
tls.certificate_sha256 = certSha256List;
}
}
if (node.StreamSecurity == Global.StreamSecurityReality) if (node.StreamSecurity == Global.StreamSecurityReality)
{ {
tls.reality = new Reality4Sbox() tls.reality = new Reality4Sbox()

View file

@ -277,6 +277,14 @@ public partial class CoreConfigV2rayService
{ {
tlsSettings.serverName = Utils.String2List(host)?.First(); tlsSettings.serverName = Utils.String2List(host)?.First();
} }
if (node.CertSha256.IsNotEmpty())
{
var certSha256List = Utils.ParseCertSha256ToList(node.CertSha256);
if (certSha256List.Count > 0)
{
tlsSettings.pinnedPeerCertificateSha256 = certSha256List;
}
}
streamSettings.tlsSettings = tlsSettings; streamSettings.tlsSettings = tlsSettings;
} }

View file

@ -709,9 +709,9 @@
<Grid <Grid
x:Name="gridTlsMore" x:Name="gridTlsMore"
Grid.Row="7" Grid.Row="7"
ColumnDefinitions="180,Auto" ColumnDefinitions="180,Auto,Auto"
IsVisible="False" IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto"> RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@ -765,6 +765,26 @@
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertSha256}" />
<TextBox
x:Name="txtCertSha256"
Grid.Row="5"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="5"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertSha256Tips}" />
</Grid> </Grid>
<Grid <Grid
x:Name="gridRealityMore" x:Name="gridRealityMore"

View file

@ -189,6 +189,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.CertSha256, v => v.txtCertSha256.Text).DisposeWith(disposables);
//reality //reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables);

View file

@ -4,19 +4,25 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="480" xmlns:sys="clr-namespace:System;assembly=netstandard"
d:DesignWidth="400" d:DesignHeight="600"
d:DesignWidth="600"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources>
<sys:Double x:Key="QrcodeWidth">500</sys:Double>
</UserControl.Resources>
<Grid Margin="32" RowDefinitions="Auto,Auto"> <Grid Margin="32" RowDefinitions="Auto,Auto">
<Image <Image
Name="imgQrcode" Name="imgQrcode"
Width="300" Width="{StaticResource QrcodeWidth}"
Height="300" /> Height="{StaticResource QrcodeWidth}" />
<TextBox <TextBox
x:Name="txtContent" x:Name="txtContent"
Grid.Row="1" Grid.Row="1"
Width="300" Width="{StaticResource QrcodeWidth}"
MaxHeight="100" MaxHeight="100"
Margin="{StaticResource MarginTb8}" Margin="{StaticResource MarginTb8}"
VerticalAlignment="Center" VerticalAlignment="Center"

View file

@ -23,8 +23,16 @@ public partial class QrcodeView : UserControl
private Bitmap? GetQRCode(string? url) private Bitmap? GetQRCode(string? url)
{ {
var bytes = QRCodeUtils.GenQRCode(url); try
return ByteToBitmap(bytes); {
var bytes = QRCodeUtils.GenQRCode(url);
return ByteToBitmap(bytes);
}
catch (Exception ex)
{
Logging.SaveLog("GetQRCode", ex);
return null;
}
} }
private Bitmap? ByteToBitmap(byte[]? bytes) private Bitmap? ByteToBitmap(byte[]? bytes)

View file

@ -20,8 +20,9 @@ public class QRCodeUtils
var qrCodeImage = ServiceLib.Common.QRCodeUtils.GenQRCode(strContent); var qrCodeImage = ServiceLib.Common.QRCodeUtils.GenQRCode(strContent);
return qrCodeImage is null ? null : ByteToImage(qrCodeImage); return qrCodeImage is null ? null : ByteToImage(qrCodeImage);
} }
catch catch (Exception ex)
{ {
Logging.SaveLog("GetQRCode", ex);
return null; return null;
} }
} }

View file

@ -928,10 +928,12 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /> <ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock <TextBlock
@ -995,6 +997,29 @@
Width="200" Width="200"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbCertSha256}" />
<TextBox
x:Name="txtCertSha256"
Grid.Row="5"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="5"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbCertSha256Tips}" />
</Grid> </Grid>
<Grid <Grid
x:Name="gridRealityMore" x:Name="gridRealityMore"

View file

@ -183,6 +183,7 @@ public partial class AddServerWindow
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.CertSha256, v => v.txtCertSha256.Text).DisposeWith(disposables);
//reality //reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.Text).DisposeWith(disposables);

View file

@ -1,4 +1,4 @@
<UserControl <UserControl
x:Class="v2rayN.Views.QrcodeView" x:Class="v2rayN.Views.QrcodeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@ -6,28 +6,33 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
d:DesignHeight="300" xmlns:sys="clr-namespace:System;assembly=mscorlib"
d:DesignWidth="300" d:DesignHeight="600"
d:DesignWidth="600"
Style="{StaticResource ViewGlobal}" Style="{StaticResource ViewGlobal}"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources>
<sys:Double x:Key="QrcodeWidth">500</sys:Double>
</UserControl.Resources>
<Grid Margin="32"> <Grid Margin="32">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="60" /> <RowDefinition Height="80" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image <Image
x:Name="imgQrcode" x:Name="imgQrcode"
Grid.Row="0" Grid.Row="0"
Width="300" Width="{StaticResource QrcodeWidth}"
Height="300" Height="{StaticResource QrcodeWidth}"
Stretch="UniformToFill" /> Stretch="UniformToFill" />
<TextBox <TextBox
x:Name="txtContent" x:Name="txtContent"
Grid.Row="1" Grid.Row="1"
Width="300" Width="{StaticResource QrcodeWidth}"
Margin="0,8" Margin="0,8"
VerticalAlignment="Center" VerticalAlignment="Center"
IsReadOnly="True" IsReadOnly="True"