mirror of
https://github.com/BeyondDimension/SteamTools.git
synced 2026-10-19 15:21:26 +08:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d0b16895f | |||
| 65d73c2e50 | |||
| 83c390e446 | |||
| 31f5fa5ea6 | |||
| f42c7cbd30 | |||
| 3cb592ce7e | |||
| d5fa02e9a4 | |||
| cbb2fac6ea | |||
| 35747253b8 | |||
| 17c45fdd65 | |||
| 88f02e3faf | |||
| 35a0c4d05f | |||
| ca7fa0bc97 | |||
| a3811a97a0 | |||
| dc6723f2e5 | |||
| e14f169cb0 | |||
| 235a1abf34 | |||
| 95c5a9e143 | |||
| 94f47b0165 | |||
| 08beb69865 | |||
| 0505158281 | |||
| 1fe7986e08 | |||
| a469e29438 | |||
| ec028a4a5c | |||
| b2031998d8 | |||
| 8affd6a3f5 | |||
| 923f2eaf94 | |||
| 3a48c00ec6 | |||
| e3f40a3e40 |
@@ -0,0 +1,203 @@
|
||||
using Avalonia.Reactive;
|
||||
using Avalonia.Xaml.Interactions.Responsive;
|
||||
using Avalonia.Xaml.Interactivity;
|
||||
|
||||
namespace BD.WTTS.Behaviors;
|
||||
|
||||
/// <summary>
|
||||
/// Observes <see cref="Behavior{T}.AssociatedObject"/> control or <see cref="SourceControl"/> control <see cref="Visual.Bounds"/> property changes and if triggered sets or removes style classes when conditions from <see cref="AdaptiveClassSetter"/> are met.
|
||||
/// </summary>
|
||||
public class AdaptiveBehavior : Behavior<Control>
|
||||
{
|
||||
private IDisposable? _disposable;
|
||||
private AvaloniaList<AdaptiveClassSetter>? _setters;
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <seealso cref="SourceControl"/> avalonia property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<Control?> SourceControlProperty =
|
||||
AvaloniaProperty.Register<AdaptiveBehavior, Control?>(nameof(SourceControl));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <seealso cref="TargetControl"/> avalonia property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<Control?> TargetControlProperty =
|
||||
AvaloniaProperty.Register<AdaptiveBehavior, Control?>(nameof(TargetControl));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <seealso cref="Setters"/> avalonia property.
|
||||
/// </summary>
|
||||
public static readonly DirectProperty<AdaptiveBehavior, AvaloniaList<AdaptiveClassSetter>> SettersProperty =
|
||||
AvaloniaProperty.RegisterDirect<AdaptiveBehavior, AvaloniaList<AdaptiveClassSetter>>(nameof(Setters), t => t.Setters);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the the source control that <see cref="Visual.BoundsProperty"/> property are observed from, if not set <see cref="Behavior{T}.AssociatedObject"/> is used. This is a avalonia property.
|
||||
/// </summary>
|
||||
[ResolveByName]
|
||||
public Control? SourceControl
|
||||
{
|
||||
get => GetValue(SourceControlProperty);
|
||||
set => SetValue(SourceControlProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target control that class name that should be added or removed when triggered, if not set <see cref="Behavior{T}.AssociatedObject"/> is used or <see cref="AdaptiveClassSetter.TargetControl"/> from <see cref="AdaptiveClassSetter"/>. This is a avalonia property.
|
||||
/// </summary>
|
||||
[ResolveByName]
|
||||
public Control? TargetControl
|
||||
{
|
||||
get => GetValue(TargetControlProperty);
|
||||
set => SetValue(TargetControlProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets adaptive class setters collection. This is a avalonia property.
|
||||
/// </summary>
|
||||
[Content]
|
||||
public AvaloniaList<AdaptiveClassSetter> Setters => _setters ??= new AvaloniaList<AdaptiveClassSetter>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnAttachedToVisualTree()
|
||||
{
|
||||
base.OnAttachedToVisualTree();
|
||||
|
||||
StopObserving();
|
||||
StartObserving();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnDetachedFromVisualTree()
|
||||
{
|
||||
base.OnDetachedFromVisualTree();
|
||||
|
||||
StopObserving();
|
||||
}
|
||||
|
||||
private void StartObserving()
|
||||
{
|
||||
var sourceControl = GetValue(SourceControlProperty) is not null
|
||||
? SourceControl
|
||||
: AssociatedObject;
|
||||
|
||||
if (sourceControl is not null)
|
||||
{
|
||||
_disposable = ObserveBounds(sourceControl);
|
||||
}
|
||||
}
|
||||
|
||||
private void StopObserving()
|
||||
{
|
||||
_disposable?.Dispose();
|
||||
}
|
||||
|
||||
private IDisposable ObserveBounds(Control sourceControl)
|
||||
{
|
||||
if (sourceControl is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sourceControl));
|
||||
}
|
||||
|
||||
return sourceControl.GetObservable(Visual.BoundsProperty)
|
||||
.Subscribe(new AnonymousObserver<Rect>(bounds => ValueChanged(sourceControl, Setters, bounds)));
|
||||
}
|
||||
|
||||
private void ValueChanged(Control? sourceControl, AvaloniaList<AdaptiveClassSetter>? setters, Rect bounds)
|
||||
{
|
||||
if (sourceControl is null || setters is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var setter in setters)
|
||||
{
|
||||
var isMinOrMaxWidthSet = setter.IsSet(AdaptiveClassSetter.MinWidthProperty)
|
||||
|| setter.IsSet(AdaptiveClassSetter.MaxWidthProperty);
|
||||
var widthConditionTriggered = GetResult(setter.MinWidthOperator, bounds.Width, setter.MinWidth)
|
||||
&& GetResult(setter.MaxWidthOperator, bounds.Width, setter.MaxWidth);
|
||||
|
||||
var isMinOrMaxHeightSet = setter.IsSet(AdaptiveClassSetter.MinHeightProperty)
|
||||
|| setter.IsSet(AdaptiveClassSetter.MaxHeightProperty);
|
||||
var heightConditionTriggered = GetResult(setter.MinHeightOperator, bounds.Height, setter.MinHeight)
|
||||
&& GetResult(setter.MaxHeightOperator, bounds.Height, setter.MaxHeight);
|
||||
|
||||
var isAddClassTriggered = isMinOrMaxWidthSet switch
|
||||
{
|
||||
true when !isMinOrMaxHeightSet => widthConditionTriggered,
|
||||
false when isMinOrMaxHeightSet => heightConditionTriggered,
|
||||
true when isMinOrMaxHeightSet => widthConditionTriggered && heightConditionTriggered,
|
||||
_ => false
|
||||
};
|
||||
|
||||
var targetControl = setter.GetValue(AdaptiveClassSetter.TargetControlProperty) is not null
|
||||
? setter.TargetControl
|
||||
: GetValue(TargetControlProperty) is not null
|
||||
? TargetControl
|
||||
: AssociatedObject;
|
||||
|
||||
if (targetControl is not null)
|
||||
{
|
||||
var className = setter.ClassName;
|
||||
var isPseudoClass = setter.IsPseudoClass;
|
||||
|
||||
if (isAddClassTriggered)
|
||||
{
|
||||
Add(targetControl, className, isPseudoClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(targetControl, className, isPseudoClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetResult(ComparisonConditionType comparisonConditionType, double property, double value)
|
||||
{
|
||||
return comparisonConditionType switch
|
||||
{
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
ComparisonConditionType.Equal => property == value,
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
ComparisonConditionType.NotEqual => property != value,
|
||||
ComparisonConditionType.LessThan => property < value,
|
||||
ComparisonConditionType.LessThanOrEqual => property <= value,
|
||||
ComparisonConditionType.GreaterThan => property > value,
|
||||
ComparisonConditionType.GreaterThanOrEqual => property >= value,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
private static void Add(Control targetControl, string? className, bool isPseudoClass)
|
||||
{
|
||||
if (className is null || string.IsNullOrEmpty(className) || targetControl.Classes.Contains(className))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPseudoClass)
|
||||
{
|
||||
((IPseudoClasses)targetControl.Classes).Add(className);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetControl.Classes.Add(className);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Remove(Control targetControl, string? className, bool isPseudoClass)
|
||||
{
|
||||
if (className is null || string.IsNullOrEmpty(className) || !targetControl.Classes.Contains(className))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPseudoClass)
|
||||
{
|
||||
((IPseudoClasses)targetControl.Classes).Remove(className);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetControl.Classes.Remove(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.UI.Views.Windows")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Converters")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Markup")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/ui", "BD.WTTS.Behaviors")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Services")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Plugins")]
|
||||
[assembly: XmlnsDefinition("https://steampp.net/services", "BD.WTTS.Plugins.Abstractions")]
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
</Design.PreviewWith>
|
||||
|
||||
<Styles.Resources>
|
||||
<Thickness x:Key="AppPageMargin">20</Thickness>
|
||||
<Thickness x:Key="AppPageMargin">20,10,20,20</Thickness>
|
||||
</Styles.Resources>
|
||||
|
||||
<Style Selector="spp|PageBase">
|
||||
@@ -313,7 +313,7 @@
|
||||
<Setter Property="Grid.Row" Value="2" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="Margin" Value="0 12" />
|
||||
<Setter Property="Margin" Value="0 12 0 0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^ /template/ Panel#PageDetails">
|
||||
|
||||
+11
-11
@@ -8,18 +8,18 @@ public partial interface IDnsAnalysisService // 公共常量定义
|
||||
{
|
||||
#region DNS 常量
|
||||
|
||||
const string DNS_Ali_DohAddres = "https://dns.alidns.com";
|
||||
const string DNS_Ali_DohAddres2 = "https://223.6.6.6/dns-query";
|
||||
const string DNS_Ali_DohAddres3 = "https://223.5.5.5/dns-query";
|
||||
const string DNS_Ali_DohAddres = "https://dns.alidns.com/resolve";
|
||||
const string DNS_Ali_DohAddres2 = "https://223.6.6.6/resolve";
|
||||
const string DNS_Ali_DohAddres3 = "https://223.5.5.5/resolve";
|
||||
|
||||
const string Dnspod_DohAddres = "https://1.12.12.12/dns-query";
|
||||
const string Dnspod_DohAddres2 = "https://doh.pub/dns-query";
|
||||
const string Dnspod_DohAddres3 = "https://120.53.53.53/dns-query";
|
||||
const string Dnspod_DohAddres = "https://1.12.12.12/resolve";
|
||||
const string Dnspod_DohAddres2 = "https://doh.pub/resolve";
|
||||
const string Dnspod_DohAddres3 = "https://120.53.53.53/resolve";
|
||||
|
||||
const string Google_DohAddres = "https://dns.google";
|
||||
const string Cloudflare_DohAddres = "https://cloudflare-dns.com";
|
||||
const string DohAddres_360 = "https://doh.360.cn/dns-query";
|
||||
const string TUNA_DohAddres = "https://101.6.6.6:8443/dns-query";
|
||||
const string Google_DohAddres = "https://dns.google/resolve";
|
||||
const string Cloudflare_DohAddres = "https://cloudflare-dns.com/resolve";
|
||||
const string DohAddres_360 = "https://doh.360.cn/resolve";
|
||||
const string TUNA_DohAddres = "https://101.6.6.6:8443/resolve";
|
||||
|
||||
const string PrimaryDNS_IPV6_Ali = "2400:3200::1";
|
||||
|
||||
@@ -60,5 +60,5 @@ public partial interface IDnsAnalysisService // 公共常量定义
|
||||
public static readonly IPEndPoint Cloudflare2 = new(IPAddress.Parse("1.0.0.1"), 53);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion DNS 常量
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace BD.WTTS.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// 网络环境检查步骤
|
||||
/// </summary>
|
||||
public enum NetworkEnvCheckStep
|
||||
{
|
||||
/// <summary>
|
||||
/// Hosts文件检查
|
||||
/// </summary>
|
||||
HostsFileCheck = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 网卡检查
|
||||
/// </summary>
|
||||
NetworkInterfaceCheck = 2,
|
||||
|
||||
/// <summary>
|
||||
/// LSP检查
|
||||
/// </summary>
|
||||
LSPCheck = 3
|
||||
}
|
||||
@@ -66,6 +66,9 @@ public sealed class Plugin : PluginBase<Plugin>, IPlugin
|
||||
services.AddSingleton(_ => certificateManager.Task.GetAwaiter().GetResult());
|
||||
#endif
|
||||
}
|
||||
|
||||
services.AddSingleton<INetworkTestService, NetworkTestService>();
|
||||
|
||||
//if (startup.HasIPCRoot)
|
||||
//{
|
||||
// services.AddSingleton<IAcceleratorService, BackendAcceleratorServiceImpl>();
|
||||
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace BD.WTTS.Services.Implementation;
|
||||
|
||||
public class NetworkEnvCheckService
|
||||
{
|
||||
const string HOST_FILE_PATH =
|
||||
#if WINDOWS
|
||||
@"C:\Windows\System32\drivers\etc\hosts";
|
||||
#else
|
||||
"/etc/hosts";
|
||||
#endif
|
||||
|
||||
public async Task CheckNetworkEnvAsync(IProgress<(NetworkEnvCheckStep Step, bool HasError, string Msg)> progress, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Parallel.ForAsync(
|
||||
(int)NetworkEnvCheckStep.HostsFileCheck,
|
||||
(int)NetworkEnvCheckStep.LSPCheck + 1,
|
||||
cancellationToken,
|
||||
async (step, ct) =>
|
||||
{
|
||||
(bool pass, string msg) = step switch
|
||||
{
|
||||
(int)NetworkEnvCheckStep.HostsFileCheck => await CheckHostsAsync(),
|
||||
(int)NetworkEnvCheckStep.NetworkInterfaceCheck => CheckNetworkInterfaces(),
|
||||
(int)NetworkEnvCheckStep.LSPCheck =>
|
||||
#if WINDOWS
|
||||
CheckLSP(HandleWSAProtocolInfo),
|
||||
#else
|
||||
(true, string.Empty),
|
||||
#endif
|
||||
_ => (true, string.Empty)
|
||||
};
|
||||
|
||||
progress.Report(((NetworkEnvCheckStep)step, pass, msg));
|
||||
}
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static Task<(bool Pass, string Msg)> CheckHostsAsync()
|
||||
{
|
||||
bool hostFileExist = File.Exists(HOST_FILE_PATH);
|
||||
|
||||
if (!hostFileExist)
|
||||
return Task.FromResult((false, "未找到 hosts 文件"));
|
||||
|
||||
return Task.FromResult((hostFileExist, string.Empty));
|
||||
}
|
||||
|
||||
private static (bool Pass, string ErrorMsg) CheckNetworkInterfaces(NetworkInterface[]? interfaces = null)
|
||||
{
|
||||
interfaces ??= NetworkInterface.GetAllNetworkInterfaces();
|
||||
|
||||
if (interfaces == null || interfaces.Length == 0)
|
||||
return (false, "未获取到网络适配器信息!");
|
||||
|
||||
// 如何判断虚拟网卡?
|
||||
|
||||
var useableNetworkInterfaces =
|
||||
interfaces.Where(x =>
|
||||
!x.Description.Contains("Virtual", StringComparison.OrdinalIgnoreCase) &&
|
||||
(x.NetworkInterfaceType == NetworkInterfaceType.Ethernet || x.NetworkInterfaceType == NetworkInterfaceType.Wireless80211));
|
||||
|
||||
if (!useableNetworkInterfaces.Any())
|
||||
return (false, "未获取到可用的以太网,无线网络适配器");
|
||||
|
||||
var checkResults =
|
||||
useableNetworkInterfaces.Select(x => new
|
||||
{
|
||||
Source = x,
|
||||
Result = CheckNetworkInterface(x)
|
||||
});
|
||||
|
||||
if (checkResults.Any(x => x.Result.Pass))
|
||||
return (true, string.Empty);
|
||||
|
||||
var firstError = checkResults.First(x => !x.Result.Pass);
|
||||
|
||||
return (firstError.Result.Pass, $"{firstError.Source.Name}|{firstError.Source.Description} {firstError.Result.ErrorMsg}");
|
||||
}
|
||||
|
||||
private static (bool Pass, string ErrorMsg) CheckNetworkInterface(NetworkInterface @interface)
|
||||
{
|
||||
var name = @interface.Name;
|
||||
var desc = @interface.Description;
|
||||
|
||||
var ipProps = @interface.GetIPProperties();
|
||||
|
||||
if (@interface.OperationalStatus != OperationalStatus.Up)
|
||||
return (false, "未启用网卡");
|
||||
|
||||
if (ipProps == null)
|
||||
return (false, $"获取网络信息失败!");
|
||||
|
||||
bool hasIPv4 = ipProps.UnicastAddresses
|
||||
.Any(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
|
||||
|
||||
bool hasIPv6 = ipProps.UnicastAddresses
|
||||
.Any(x => x.Address.AddressFamily == AddressFamily.InterNetworkV6);
|
||||
|
||||
if (!hasIPv4 && !hasIPv6)
|
||||
return (false, "网络适配器未启用TCP/IP服务");
|
||||
|
||||
// 如何判断虚拟网卡?
|
||||
|
||||
var dns = ipProps.DnsAddresses;
|
||||
|
||||
#if WINDOWS
|
||||
var isDHCPEnable = ipProps.GetIPv4Properties().IsDhcpEnabled;
|
||||
#endif
|
||||
return (true, string.Empty);
|
||||
}
|
||||
|
||||
private static (bool Pass, string ErrorMsg) CheckLSP(CheckLSPPredicate predicate)
|
||||
{
|
||||
int bufferSize = 0;
|
||||
int error = 0;
|
||||
|
||||
// 获取 buffer 的大小
|
||||
_ = WSCEnumProtocols(null, IntPtr.Zero, ref bufferSize, ref error);
|
||||
|
||||
IntPtr protocolBuffer = Marshal.AllocHGlobal(bufferSize);
|
||||
|
||||
try
|
||||
{
|
||||
int result = WSCEnumProtocols(null, protocolBuffer, ref bufferSize, ref error);
|
||||
if (result == -1)
|
||||
{
|
||||
return (false, "获取LSP信息失败!");
|
||||
}
|
||||
|
||||
int structSize = Marshal.SizeOf(typeof(WSAPROTOCOL_INFO));
|
||||
int count = bufferSize / structSize;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
IntPtr item = new(protocolBuffer + (i * structSize));
|
||||
|
||||
WSAPROTOCOL_INFO protocolInfo = Marshal.PtrToStructure<WSAPROTOCOL_INFO>(item);
|
||||
|
||||
#if DEBUG
|
||||
// Console.WriteLine($"Protocol: {protocolInfo.szProtocol}");
|
||||
// Console.WriteLine($"Address Family: {protocolInfo.iAddressFamily}");
|
||||
// Console.WriteLine($"Socket Type: {protocolInfo.iSocketType}");
|
||||
// Console.WriteLine($"Protocol: {protocolInfo.iProtocol}");
|
||||
// Console.WriteLine($"Provider ID: {protocolInfo.ProviderId}");
|
||||
// Console.WriteLine();
|
||||
#endif
|
||||
var handle = predicate(in protocolInfo);
|
||||
|
||||
if (!handle)
|
||||
return (false, "LSP响应异常!");
|
||||
}
|
||||
|
||||
return (true, string.Empty);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(protocolBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
#region P/Invoke
|
||||
|
||||
/// <summary>
|
||||
/// 处理判断 协议信息是否有效
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <returns></returns>
|
||||
static bool HandleWSAProtocolInfo(in WSAPROTOCOL_INFO info)
|
||||
{
|
||||
const int MAX = 255;
|
||||
|
||||
StringBuilder pathBuilder = new StringBuilder(MAX);
|
||||
int pathLength = MAX;
|
||||
|
||||
Guid queryProviderId = info.ProviderId;
|
||||
|
||||
int result = WSCGetProviderPath(ref queryProviderId, pathBuilder, ref pathLength, out int errorCode);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
// 获取失败
|
||||
}
|
||||
|
||||
// path 可能包含系统环境变量例如 %SystemRoot%
|
||||
// 是否需要解析
|
||||
string path = pathBuilder.ToString();
|
||||
|
||||
Console.WriteLine("path:" + path);
|
||||
return true;
|
||||
}
|
||||
|
||||
[DllImport("Ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
static extern int WSCEnumProtocols(
|
||||
int[]? lpiProtocols,
|
||||
IntPtr lpProtocolBuffer,
|
||||
ref int lpdwBufferLength,
|
||||
ref int lpErrno);
|
||||
|
||||
[DllImport("Ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
static extern int WSCGetProviderPath(
|
||||
[In] ref Guid providerId,
|
||||
[Out][MarshalAs(UnmanagedType.LPWStr)] StringBuilder providerDllPath,
|
||||
[In, Out] ref int providerDllPathLength,
|
||||
[Out] out int lpErrno
|
||||
);
|
||||
|
||||
private delegate bool CheckLSPPredicate(in WSAPROTOCOL_INFO protocolInfo);
|
||||
|
||||
/// <summary>
|
||||
/// WSAPROTOCOL_INFO 结构用于存储或检索给定协议的完整信息。
|
||||
/// <see cref="https://learn.microsoft.com/zh-cn/windows/win32/api/winsock2/ns-winsock2-wsaprotocol_infoa"/>
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "<挂起>")]
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct WSAPROTOCOL_INFO
|
||||
{
|
||||
/// <summary>
|
||||
/// 描述协议提供的服务的位掩码
|
||||
/// </summary>
|
||||
public uint dwServiceFlags1;
|
||||
|
||||
/// <summary>
|
||||
/// 保留给其他协议属性定义。
|
||||
/// </summary>
|
||||
public uint dwServiceFlags2;
|
||||
|
||||
/// <summary>
|
||||
/// 保留给其他协议属性定义。
|
||||
/// </summary>
|
||||
public uint dwServiceFlags3;
|
||||
|
||||
/// <summary>
|
||||
/// 保留给其他协议属性定义。
|
||||
/// </summary>
|
||||
public uint dwServiceFlags4;
|
||||
|
||||
/// <summary>
|
||||
/// 一组标志,提供有关如何在 Winsock 目录中表示此协议的信息
|
||||
/// </summary>
|
||||
public uint dwProviderFlags;
|
||||
|
||||
/// <summary>
|
||||
/// 服务提供商供应商) 分配给提供程序的 GUID
|
||||
/// </summary>
|
||||
public Guid ProviderId;
|
||||
|
||||
/// <summary>
|
||||
/// 由 WS2_32.DLL 为每个WSAPROTOCOL_INFO结构分配的唯 一 标识符。
|
||||
/// </summary>
|
||||
public uint dwCatalogEntryId;
|
||||
|
||||
public WSAPROTOCOLCHAIN ProtocolChain;
|
||||
|
||||
/// <summary>
|
||||
/// 协议版本标识符。
|
||||
/// </summary>
|
||||
public int iVersion;
|
||||
|
||||
/// <summary>
|
||||
/// 要作为地址系列参数传递给 套接字 或 WSASocket 函数的
|
||||
/// </summary>
|
||||
public int iAddressFamily;
|
||||
|
||||
/// <summary>
|
||||
/// 最大地址长度(以字节为单位)。
|
||||
/// </summary>
|
||||
public int iMaxSockAddr;
|
||||
|
||||
/// <summary>
|
||||
/// 最小地址长度(以字节为单位)。
|
||||
/// </summary>
|
||||
public int iMinSockAddr;
|
||||
|
||||
public int iSocketType;
|
||||
public int iProtocol;
|
||||
public int iProtocolMaxOffset;
|
||||
|
||||
/// <summary>
|
||||
/// 目前,这些值是 BIGENDIAN 和 LITTLEENDIAN) (清单常量,分别指示值为 0 和 1 的 big-endian 或 little-endian
|
||||
/// </summary>
|
||||
public int iNetworkByteOrder;
|
||||
|
||||
public int iSecurityScheme;
|
||||
|
||||
/// <summary>
|
||||
/// 协议支持的最大消息大小(以字节为单位)
|
||||
/// </summary>
|
||||
public uint dwMessageSize;
|
||||
|
||||
public uint dwProviderReserved;
|
||||
|
||||
/// <summary>
|
||||
/// 一个字符数组,其中包含标识协议的可读名称,例如“MSAFD Tcpip [UDP/IP]”。 允许的最大字符数为 WSAPROTOCOL_LEN,定义为 255。
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
|
||||
public string szProtocol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WSAPROTOCOLCHAIN 结构包含构成协议链的目录条目标识符的计数列表。
|
||||
/// <see cref="https://learn.microsoft.com/zh-cn/windows/win32/api/winsock2/ns-winsock2-wsaprotocolchain"/>
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct WSAPROTOCOLCHAIN
|
||||
{
|
||||
/// <summary>
|
||||
/// 链的长度(以字节为单位)
|
||||
/// 将 ChainLen 设置为零表示分层协议
|
||||
/// 将 ChainLen 设置为 1 指示基本协议
|
||||
/// 将 ChainLen 设置为大于 1 表示协议链
|
||||
/// ChainEntries[MAX_PROTOCOL_CHAIN]
|
||||
/// </summary>
|
||||
public int ChainLen;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
|
||||
public uint[] ChainEntries;
|
||||
}
|
||||
|
||||
#endregion P/Invoke
|
||||
}
|
||||
+32
-17
@@ -50,9 +50,30 @@ internal partial class NetworkTestService : INetworkTestService
|
||||
|
||||
#endregion Ping
|
||||
|
||||
#region Upload/Download Speed Test
|
||||
#region Open/Upload/Download Speed Test
|
||||
|
||||
public async Task<(bool success, double? rate)> TestUploadSpeedAsync(
|
||||
public async Task<(bool Success, long? DelayMs)> TestOpenUrlAsync(string url, Func<HttpClient>? httpClientFunc = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var testClient = httpClientFunc?.Invoke() ?? new HttpClient();
|
||||
|
||||
Stopwatch watch = Stopwatch.StartNew();
|
||||
|
||||
try
|
||||
{
|
||||
var resp = await testClient.GetAsync(url, HttpCompletionOption.ResponseContentRead, cancellationToken);
|
||||
|
||||
watch.Stop();
|
||||
|
||||
return (true, watch.ElapsedMilliseconds);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(nameof(NetworkTestService), ex, "测试打开 URL 异常");
|
||||
return (false, default);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Success, double? Rate)> TestUploadSpeedAsync(
|
||||
string uploadServerUrl,
|
||||
byte[] uploadBytes,
|
||||
CancellationToken cancellationToken = default
|
||||
@@ -70,15 +91,15 @@ internal partial class NetworkTestService : INetworkTestService
|
||||
|
||||
static MultipartFormDataContent BuildUploadContent(byte[] data)
|
||||
{
|
||||
MultipartFormDataContent cotent = new MultipartFormDataContent();
|
||||
MultipartFormDataContent content = new MultipartFormDataContent();
|
||||
|
||||
cotent.Add(new ByteArrayContent(data));
|
||||
content.Add(new ByteArrayContent(data));
|
||||
|
||||
return cotent;
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool success, double? rate)> TestDownloadSpeedAsync(string downloadUrl, CancellationToken cancellationToken = default)
|
||||
public async Task<(bool Success, double? Rate)> TestDownloadSpeedAsync(string downloadUrl, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using HttpClient testClient = new HttpClient();
|
||||
|
||||
@@ -94,7 +115,7 @@ internal partial class NetworkTestService : INetworkTestService
|
||||
/// <param name="uploadBytes"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
static async Task<(bool success, double? rate)> TestUploadSpeedCoreAsync(
|
||||
static async Task<(bool Success, double? Rate)> TestUploadSpeedCoreAsync(
|
||||
Func<HttpClient> httpClientFunc,
|
||||
Func<byte[], HttpContent> uploadContentFunc,
|
||||
string uploadServerUrl,
|
||||
@@ -134,7 +155,7 @@ internal partial class NetworkTestService : INetworkTestService
|
||||
/// <param name="url"></param>
|
||||
/// <param name="httpClientFunc"></param>
|
||||
/// <returns></returns>
|
||||
static async Task<(bool success, double? rate)> TestDownloadSpeedCoreAsync(string url, Func<HttpClient>? httpClientFunc = null, CancellationToken cancellationToken = default)
|
||||
static async Task<(bool Success, double? Rate)> TestDownloadSpeedCoreAsync(string url, Func<HttpClient>? httpClientFunc = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var httpClient = httpClientFunc?.Invoke() ?? new HttpClient();
|
||||
|
||||
@@ -175,7 +196,7 @@ internal partial class NetworkTestService : INetworkTestService
|
||||
return megabits / seconds; // 返回速率,单位是 Mbps
|
||||
}
|
||||
|
||||
#endregion Upload/Download Speed Test
|
||||
#endregion Open/Upload/Download Speed Test
|
||||
|
||||
#region STUN 测试
|
||||
|
||||
@@ -354,13 +375,7 @@ internal partial class NetworkTestService : INetworkTestService
|
||||
DnsQueryAnswerRecord.DnsRecordType dnsRecordType = DnsQueryAnswerRecord.DnsRecordType.A,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var handler = new HttpClientHandler
|
||||
{
|
||||
UseCookies = false,
|
||||
UseProxy = false,
|
||||
Proxy = HttpNoProxy.Instance,
|
||||
};
|
||||
using var client = new HttpClient(handler);
|
||||
using HttpClient client = new HttpClient();
|
||||
|
||||
string queryUrl = $"{dohServer}?name={testDomain}&type={dnsRecordType}";
|
||||
|
||||
@@ -750,7 +765,7 @@ internal partial class NetworkTestService : INetworkTestService
|
||||
}
|
||||
}
|
||||
|
||||
class DnsQueryAnswerRecordConverter : System.Text.Json.Serialization.JsonConverter<DnsQueryAnswerRecord>
|
||||
private class DnsQueryAnswerRecordConverter : System.Text.Json.Serialization.JsonConverter<DnsQueryAnswerRecord>
|
||||
{
|
||||
public override DnsQueryAnswerRecord? Read(ref Utf8JsonReader reader, Type typeToConvert, SystemTextJsonSerializerOptions options)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace BD.WTTS.Services;
|
||||
|
||||
internal interface INetworkTestService : IHttpRequestTestService, IStunTestService
|
||||
{
|
||||
static INetworkTestService Instance => Ioc.Get<INetworkTestService>();
|
||||
|
||||
/// <summary>
|
||||
/// Ping
|
||||
/// </summary>
|
||||
@@ -50,6 +52,15 @@ internal interface INetworkTestService : IHttpRequestTestService, IStunTestServi
|
||||
|
||||
internal interface IHttpRequestTestService
|
||||
{
|
||||
/// <summary>
|
||||
/// 测试打开网址
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="httpClientFunc"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<(bool Success, long? DelayMs)> TestOpenUrlAsync(string url, Func<HttpClient>? httpClientFunc = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 测试上传速度
|
||||
/// </summary>
|
||||
@@ -57,7 +68,7 @@ internal interface IHttpRequestTestService
|
||||
/// <param name="uploadBytes"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<(bool success, double? rate)> TestUploadSpeedAsync(
|
||||
Task<(bool Success, double? Rate)> TestUploadSpeedAsync(
|
||||
string uploadServerUrl,
|
||||
byte[] uploadBytes,
|
||||
CancellationToken cancellationToken = default
|
||||
@@ -69,7 +80,7 @@ internal interface IHttpRequestTestService
|
||||
/// <param name="downloadUrl"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<(bool success, double? rate)> TestDownloadSpeedAsync(string downloadUrl, CancellationToken cancellationToken = default);
|
||||
Task<(bool Success, double? Rate)> TestDownloadSpeedAsync(string downloadUrl, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
internal interface IStunTestService
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
using Avalonia.Data;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Org.BouncyCastle.Bcpg.OpenPgp;
|
||||
using System.Linq;
|
||||
|
||||
namespace BD.WTTS.Services;
|
||||
@@ -151,6 +152,7 @@ public sealed partial class ProxyService
|
||||
|
||||
ProxyStarting = false;
|
||||
ProxyStatus = startOrStop;
|
||||
IsAnyProxyScripts = ProxyStatus && IsEnableScript;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,6 +262,16 @@ public sealed partial class ProxyService
|
||||
[Reactive]
|
||||
public bool ProxyStatus { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool IsAnyProxyScripts { get; set; }
|
||||
|
||||
private bool CheckProxyScriptsEnable()
|
||||
{
|
||||
if (!ProxyScripts.Items.Any_Nullable())
|
||||
return false;
|
||||
return ProxyScripts.Items!.Any(w => !w.Disable);
|
||||
}
|
||||
|
||||
#endregion 代理状态启动退出
|
||||
|
||||
static bool IsProgramStartupRunProxy()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using AppResources = BD.WTTS.Client.Resources.Strings;
|
||||
|
||||
using BD.WTTS.Client.Resources;
|
||||
using BD.WTTS.UI.Views.Pages;
|
||||
using STUN.StunResult;
|
||||
using STUN.Enums;
|
||||
|
||||
namespace BD.WTTS.UI.ViewModels;
|
||||
|
||||
@@ -12,6 +13,7 @@ public sealed partial class AcceleratorPageViewModel
|
||||
readonly IPlatformService platformService = IPlatformService.Instance;
|
||||
readonly IReverseProxyService reverseProxyService = IReverseProxyService.Constants.Instance;
|
||||
readonly ICertificateManager certificateManager = ICertificateManager.Constants.Instance;
|
||||
readonly INetworkTestService networkTestService = INetworkTestService.Instance;
|
||||
|
||||
public AcceleratorPageViewModel()
|
||||
{
|
||||
@@ -22,6 +24,29 @@ public sealed partial class AcceleratorPageViewModel
|
||||
#endif
|
||||
//ProxyService.Current.ProxyStatus = !ProxyService.Current.ProxyStatus;
|
||||
await ProxyService.Current.StartOrStopProxyService(!ProxyService.Current.ProxyStatus);
|
||||
|
||||
if (ProxyService.Current.ProxyStatus == false)
|
||||
return;
|
||||
|
||||
// Create new ProxyEnableDomain for 加速服务 page
|
||||
var enableGroupDomain = ProxyService.Current.ProxyDomainsList
|
||||
.Where(list => list.ThreeStateEnable == true || list.ThreeStateEnable == null)
|
||||
.Select(list => new ProxyDomainGroupViewModel
|
||||
{
|
||||
Name = list.Name,
|
||||
IconUrl = list.IconUrl ?? string.Empty,
|
||||
EnableProxyDomainVMs = new(
|
||||
list.Items!
|
||||
.Where(i => i.ThreeStateEnable == true)
|
||||
.Select(i => new ProxyDomainViewModel(i.Name, i.ProxyType, "https://" + i.ListenDomainNames.Split(";")[0],
|
||||
i.Items?
|
||||
.Select(c => new ProxyDomainViewModel(c.Name, c.ProxyType, "https://" + c.ListenDomainNames.Split(';')[0]))
|
||||
.ToList()))
|
||||
.ToList()),
|
||||
})
|
||||
.ToList();
|
||||
|
||||
EnableProxyDomainGroupVMs = enableGroupDomain.AsReadOnly();
|
||||
});
|
||||
|
||||
RefreshCommand = ReactiveCommand.Create(async () =>
|
||||
@@ -29,6 +54,7 @@ public sealed partial class AcceleratorPageViewModel
|
||||
if (_initializeTime > DateTime.Now.AddSeconds(-2))
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, Strings.Warning_DoNotOperateFrequently);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -43,10 +69,65 @@ public sealed partial class AcceleratorPageViewModel
|
||||
GameAcceleratorService.Current.LoadGames();
|
||||
});
|
||||
|
||||
SelectedSTUNAddress = STUNAddress[0];
|
||||
|
||||
NATCheckCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var natCheckResult = await networkTestService.TestStunClient3489Async(testServerHostName: SelectedSTUNAddress) ?? new ClassicStunResult { NatType = NatType.Unknown };
|
||||
var (netSucc, _) = await networkTestService.TestOpenUrlAsync("https://www.baidu.com");
|
||||
|
||||
var natStatus = natCheckResult.NatType switch
|
||||
{
|
||||
NatType.OpenInternet or NatType.FullCone => NatTypeSimple.Open,
|
||||
NatType.RestrictedCone or NatType.PortRestrictedCone or NatType.SymmetricUdpFirewall => NatTypeSimple.Moderate,
|
||||
NatType.Symmetric or NatType.UdpBlocked => NatTypeSimple.Strict,
|
||||
NatType.Unknown or NatType.UnsupportedServer or _ => NatTypeSimple.Unknown,
|
||||
};
|
||||
|
||||
PublicEndPoint = natCheckResult.PublicEndPoint?.Address.ToString() ?? "Unknown";
|
||||
LocalEndPoint = natCheckResult.LocalEndPoint?.Address.ToString() ?? "Unknown";
|
||||
|
||||
return (natStatus, netSucc);
|
||||
});
|
||||
NATCheckCommand
|
||||
.IsExecuting
|
||||
.ToPropertyEx(this, x => x.IsNATChecking);
|
||||
|
||||
DNSCheckCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var testDomain = DomainPendingTest == string.Empty ? "steamcommunity.com" : DomainPendingTest;
|
||||
try
|
||||
{
|
||||
long delayMs;
|
||||
IPAddress[] address;
|
||||
if (ProxySettings.UseDoh)
|
||||
{
|
||||
var configDoh = ProxySettings.CustomDohAddres.Value ?? string.Empty;
|
||||
(delayMs, address) = await networkTestService.TestDNSOverHttpsAsync(testDomain, configDoh);
|
||||
}
|
||||
else
|
||||
{
|
||||
var configDns = ProxySettings.ProxyMasterDns.Value ?? string.Empty;
|
||||
(delayMs, address) = await networkTestService.TestDNSAsync(testDomain, configDns, 53);
|
||||
}
|
||||
DNSTestDelay = delayMs + "ms ";
|
||||
DNSTestResult = "" + address.FirstOrDefault() ?? "0.0.0.0";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(nameof(AcceleratorPageViewModel), ex.ToString());
|
||||
DNSTestDelay = string.Empty;
|
||||
DNSTestResult = "error";
|
||||
}
|
||||
});
|
||||
DNSCheckCommand
|
||||
.IsExecuting
|
||||
.ToPropertyEx(this, x => x.IsDNSChecking);
|
||||
|
||||
ProxySettingsCommand = ReactiveCommand.Create(() =>
|
||||
{
|
||||
var vm = new ProxySettingsWindowViewModel();
|
||||
IWindowManager.Instance.ShowTaskDialogAsync(vm, vm.Title, pageContent: new ProxySettingsPage(), isOkButton: false);
|
||||
_ = IWindowManager.Instance.ShowTaskDialogAsync(vm, vm.Title, pageContent: new ProxySettingsPage(), isOkButton: false);
|
||||
});
|
||||
|
||||
if (IApplication.IsDesktop())
|
||||
@@ -73,6 +154,7 @@ public sealed partial class AcceleratorPageViewModel
|
||||
}
|
||||
|
||||
#if LINUX
|
||||
|
||||
public bool EnvironmentCheck()
|
||||
{
|
||||
try
|
||||
@@ -93,6 +175,7 @@ public sealed partial class AcceleratorPageViewModel
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public void TrustCer_OnClick()
|
||||
|
||||
+135
-2
@@ -1,12 +1,145 @@
|
||||
using BD.WTTS.Client.Resources;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using System.Reactive;
|
||||
|
||||
namespace BD.WTTS.UI.ViewModels;
|
||||
|
||||
public class ProxyDomainGroupViewModel : ReactiveObject
|
||||
{
|
||||
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string IconUrl { get; set; } = string.Empty;
|
||||
|
||||
public ReadOnlyCollection<ProxyDomainViewModel>? EnableProxyDomainVMs { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ConnectTestCommand { get; set; }
|
||||
|
||||
public ProxyDomainGroupViewModel()
|
||||
{
|
||||
ConnectTestCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var tasks = EnableProxyDomainVMs!.Select(async enableDomain =>
|
||||
{
|
||||
enableDomain.DelayMillseconds = "- ms";
|
||||
var (success, delayMs) = await networkTestService.TestOpenUrlAsync(enableDomain.Url);
|
||||
enableDomain.DelayMillseconds = success switch
|
||||
{
|
||||
true when delayMs > 20000 => "Timeout",
|
||||
true => delayMs.ToString() + " ms",
|
||||
false => "error",
|
||||
};
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class ProxyDomainViewModel : ReactiveObject
|
||||
{
|
||||
private readonly INetworkTestService networkTestService = INetworkTestService.Instance;
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public ProxyType ProxyType { get; }
|
||||
|
||||
public string Url { get; }
|
||||
|
||||
[Reactive]
|
||||
public string DelayMillseconds { get; set; } = string.Empty;
|
||||
|
||||
[ObservableAsProperty]
|
||||
public bool RetestButtonVisible { get; }
|
||||
|
||||
[ObservableAsProperty]
|
||||
public IBrush DelayColor { get; } = null!;
|
||||
|
||||
public ReadOnlyCollection<ProxyDomainViewModel>? Children { get; }
|
||||
|
||||
public ProxyDomainViewModel(string name, ProxyType proxyType, string url, List<ProxyDomainViewModel>? children = null)
|
||||
{
|
||||
Name = name;
|
||||
ProxyType = proxyType;
|
||||
Url = url;
|
||||
Children = children?.AsReadOnly();
|
||||
|
||||
this.WhenAnyValue(x => x.DelayMillseconds)
|
||||
.Subscribe(delay => Children?.ForEach(child => child.DelayMillseconds = delay));
|
||||
|
||||
this.WhenAnyValue(x => x.DelayMillseconds)
|
||||
.Select(d => d != string.Empty && d != "-")
|
||||
.ToPropertyEx(this, x => x.RetestButtonVisible);
|
||||
|
||||
const int DelayMiddle = 1000;
|
||||
this.WhenAnyValue(x => x.DelayMillseconds)
|
||||
.Select(d => d switch
|
||||
{
|
||||
"Timeout" or "error" => Brushes.Red,
|
||||
var s when s.Split(' ') is [var num, "ms"] && int.TryParse(num, out int ms)
|
||||
=> ms switch
|
||||
{
|
||||
<= DelayMiddle => Brushes.Green,
|
||||
> DelayMiddle => Brushes.Orange,
|
||||
},
|
||||
_ => (IBrush?)App.Instance.FindResource("TextFillColorPrimaryBrush"),
|
||||
})
|
||||
.ToPropertyEx(this, x => x.DelayColor);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class AcceleratorPageViewModel : TabItemViewModel
|
||||
{
|
||||
public enum NatTypeSimple
|
||||
{
|
||||
Unknown,
|
||||
Open,
|
||||
Moderate,
|
||||
Strict,
|
||||
}
|
||||
|
||||
public override string Name => Strings.Welcome;
|
||||
|
||||
[Reactive]
|
||||
public string SelectedSTUNAddress { get; set; }
|
||||
|
||||
public List<string> STUNAddress { get; } =
|
||||
[
|
||||
"stun.syncthing.net",
|
||||
"stun.hot-chilli.net",
|
||||
"stun.fitauto.ru",
|
||||
"stun.miwifi.com",
|
||||
];
|
||||
|
||||
[ObservableAsProperty]
|
||||
public bool IsNATChecking { get; }
|
||||
|
||||
[ObservableAsProperty]
|
||||
public bool IsDNSChecking { get; }
|
||||
|
||||
[Reactive]
|
||||
public string DomainPendingTest { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public ReadOnlyCollection<ProxyDomainGroupViewModel>? EnableProxyDomainGroupVMs { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string LocalEndPoint { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string PublicEndPoint { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string DNSTestDelay { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string DNSTestResult { get; set; } = string.Empty;
|
||||
|
||||
public ReactiveCommand<Unit, (NatTypeSimple Nat, bool PingSuccess)> NATCheckCommand { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> DNSCheckCommand { get; }
|
||||
|
||||
public ICommand StartProxyCommand { get; }
|
||||
|
||||
public ICommand RefreshCommand { get; }
|
||||
@@ -32,4 +165,4 @@ public sealed partial class AcceleratorPageViewModel : TabItemViewModel
|
||||
public ICommand? TrustCerCommand { get; }
|
||||
|
||||
public ICommand? OpenLogFileCommand { get; }
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -14,7 +14,7 @@ public sealed class ProxySettingsWindowViewModel : WindowViewModel
|
||||
//public IEnumerable<ExternalProxyType> ProxyTypes { get; }
|
||||
// = Enum2.GetAll<ExternalProxyType>();
|
||||
|
||||
public IEnumerable<string> ProxyDNSs { get; } = new[]
|
||||
public static IEnumerable<string> ProxyDNSs { get; } = new[]
|
||||
{
|
||||
"System Default",
|
||||
PrimaryDNS_114,
|
||||
@@ -31,7 +31,7 @@ public sealed class ProxySettingsWindowViewModel : WindowViewModel
|
||||
"127.0.0.1",
|
||||
};
|
||||
|
||||
public IEnumerable<string> DohAddress { get; }
|
||||
public static IEnumerable<string> DohAddress { get; }
|
||||
= new[] {
|
||||
Dnspod_DohAddres,
|
||||
Dnspod_DohAddres2,
|
||||
@@ -44,4 +44,4 @@ public sealed class ProxySettingsWindowViewModel : WindowViewModel
|
||||
DohAddres_360,
|
||||
TUNA_DohAddres,
|
||||
};
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
<UserControl
|
||||
x:Class="BD.WTTS.UI.Views.Pages.AcceleratorServiceStatus"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:enum="using:BD.WTTS.Enums"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="spp:AcceleratorPageViewModel"
|
||||
mc:Ignorable="d">
|
||||
<ScrollViewer
|
||||
Padding="0,0,10,0"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl ItemsSource="{Binding EnableProxyDomainGroupVMs}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical" Spacing="{StaticResource SettingsSpacing}" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border>
|
||||
<ui:SettingsExpander IsClickEnabled="False" IsExpanded="True">
|
||||
<ui:SettingsExpander.Header>
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<spp:Image2
|
||||
Width="30"
|
||||
Height="30"
|
||||
VerticalAlignment="Center"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
Source="{Binding IconUrl}"
|
||||
Stretch="UniformToFill" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
|
||||
</StackPanel>
|
||||
</ui:SettingsExpander.Header>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
Click="ConnectTestButtonOnClick"
|
||||
Command="{Binding ConnectTestCommand}"
|
||||
Content="连通性测试"
|
||||
DockPanel.Dock="Right" />
|
||||
</ui:SettingsExpander.Footer>
|
||||
<ui:SettingsExpanderItem Theme="{StaticResource SettingsExpanderItem2}">
|
||||
<TreeView ItemsSource="{Binding EnableProxyDomainVMs}" SelectionMode="Single">
|
||||
<TreeView.ItemContainerTheme>
|
||||
<ControlTheme BasedOn="{StaticResource {x:Type TreeViewItem}}" TargetType="TreeViewItem">
|
||||
<Setter Property="IsExpanded" Value="True" />
|
||||
</ControlTheme>
|
||||
</TreeView.ItemContainerTheme>
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate ItemsSource="{Binding Children}">
|
||||
<DockPanel Margin="8,0">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
|
||||
<Border
|
||||
Padding="5,0"
|
||||
CornerRadius="5"
|
||||
IsVisible="{Binding ProxyType, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:ProxyType.ServerAccelerate}}"
|
||||
ToolTip.Tip="Beta 测试加速项,不稳定且随时可能调整">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0.0" Color="#43cbff" />
|
||||
<GradientStop Offset="1.0" Color="#9708cc" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Text="Beta" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"
|
||||
DockPanel.Dock="Right"
|
||||
Foreground="{Binding DelayColor}"
|
||||
Text="{Binding DelayMillseconds}"
|
||||
ToolTip.Tip="此数值代表测试网页打开访问所需时间,仅供参考" />
|
||||
</DockPanel>
|
||||
</TreeDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
</ui:SettingsExpanderItem>
|
||||
</ui:SettingsExpander>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
</UserControl>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.VisualTree;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace BD.WTTS.UI.Views.Pages;
|
||||
|
||||
public partial class AcceleratorServiceStatus : UserControl
|
||||
{
|
||||
public AcceleratorServiceStatus()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ConnectTestButtonOnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button)
|
||||
{
|
||||
var expander = button.FindAncestorOfType<SettingsExpander>();
|
||||
if (expander != null) expander.IsExpanded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
<UserControl
|
||||
x:Class="BD.WTTS.UI.Views.Pages.NetworkCheck"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ms="https://steampp.net/settings"
|
||||
xmlns:s="https://steampp.net/services"
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
xmlns:sys="using:System"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
x:DataType="spp:AcceleratorPageViewModel"
|
||||
mc:Ignorable="d">
|
||||
<ScrollViewer
|
||||
Padding="0,0,10,0"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="8">
|
||||
<ui:SettingsExpander IsClickEnabled="False" IsExpanded="True">
|
||||
<ui:SettingsExpander.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ui:FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="26"
|
||||
Glyph="" />
|
||||
<TextBlock
|
||||
Margin="12,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="NAT 类型检测" />
|
||||
</StackPanel>
|
||||
</ui:SettingsExpander.Header>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<WrapPanel>
|
||||
<ui:FAComboBox
|
||||
x:Name="STUNServer"
|
||||
Margin="4"
|
||||
x:FieldModifier="public"
|
||||
IsEditable="False"
|
||||
IsEnabled="{Binding !IsNATChecking}"
|
||||
ItemsSource="{Binding STUNAddress}"
|
||||
SelectedItem="{Binding SelectedSTUNAddress}"
|
||||
ToolTip.Tip="STUN 服务器地址">
|
||||
<ui:FAComboBox.ItemTemplate>
|
||||
<DataTemplate DataType="sys:String">
|
||||
<TextBlock Text="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ui:FAComboBox.ItemTemplate>
|
||||
</ui:FAComboBox>
|
||||
<Button
|
||||
x:Name="NATCheckButton"
|
||||
Width="100"
|
||||
Margin="4"
|
||||
x:FieldModifier="public"
|
||||
Content="检测"
|
||||
IsEnabled="True" />
|
||||
</WrapPanel>
|
||||
</ui:SettingsExpander.Footer>
|
||||
|
||||
<StackPanel
|
||||
Margin="24,0,0,0"
|
||||
Background="Transparent"
|
||||
Spacing="8">
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock VerticalAlignment="Center" Text="NAT 类型:" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsNATChecking}" />
|
||||
|
||||
<DockPanel IsVisible="{Binding !IsNATChecking}">
|
||||
<SelectableTextBlock
|
||||
x:Name="NATTextBlock"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="Gray"
|
||||
Text="未知" />
|
||||
<ui:FontIcon
|
||||
Margin="3,0"
|
||||
FontSize="20"
|
||||
Glyph="">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock x:Name="NATTypeTip" Text="未知类型" />
|
||||
</ToolTip.Tip>
|
||||
</ui:FontIcon>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<Separator Margin="-64,0" Classes="Separator" />
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock VerticalAlignment="Center" Text="本地IP地址:" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsNATChecking}" />
|
||||
<SelectableTextBlock
|
||||
x:Name="LocalIPAddress"
|
||||
Foreground="Gray"
|
||||
IsVisible="{Binding !IsNATChecking}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<Separator Margin="-64,0" Classes="Separator" />
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="对外IP地址:"
|
||||
ToolTip.Tip="若与您路由器 WAN IP 地址相同,则为公共网络" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsNATChecking}" />
|
||||
<SelectableTextBlock
|
||||
Foreground="Gray"
|
||||
IsVisible="{Binding !IsNATChecking}"
|
||||
Text="{Binding PublicEndPoint}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<Separator Margin="-64,0" Classes="Separator" />
|
||||
|
||||
<!--<Border Classes="paper" Margin="-64 0" />-->
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock VerticalAlignment="Center" Text="互联网连通情况:" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsNATChecking}" />
|
||||
|
||||
<ui:FontIcon
|
||||
x:Name="PingOK"
|
||||
HorizontalAlignment="Right"
|
||||
x:FieldModifier="public"
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
FontSize="26"
|
||||
Foreground="Green"
|
||||
Glyph="" />
|
||||
|
||||
<Grid x:Name="PingError" x:FieldModifier="public">
|
||||
<ui:FontIcon
|
||||
Margin="3"
|
||||
HorizontalAlignment="Right"
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="20"
|
||||
Foreground="Red"
|
||||
Glyph="" />
|
||||
<ui:FontIcon
|
||||
HorizontalAlignment="Right"
|
||||
FontSize="26"
|
||||
Foreground="White"
|
||||
Glyph="" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<!--<Separator Classes="Separator" Margin="-64 0" />-->
|
||||
<Separator Margin="-64,0" Classes="Horizontal" />
|
||||
|
||||
<ui:SettingsExpanderItem Margin="-28,0,0,-4" Background="Transparent">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Text="相关链接" />
|
||||
<HyperlinkButton
|
||||
VerticalAlignment="Center"
|
||||
Content="解决NAT错误"
|
||||
NavigateUri="https://support.xbox.com/zh-CN/help/hardware-network/connect-network/xbox-one-nat-error" />
|
||||
</StackPanel>
|
||||
</ui:SettingsExpanderItem>
|
||||
</StackPanel>
|
||||
</ui:SettingsExpander>
|
||||
|
||||
<!-- DNS -->
|
||||
<ui:SettingsExpander IsClickEnabled="False" IsExpanded="True">
|
||||
<ui:SettingsExpander.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ui:FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="26"
|
||||
Glyph="" />
|
||||
<TextBlock
|
||||
Margin="12,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="DNS 检测" />
|
||||
</StackPanel>
|
||||
</ui:SettingsExpander.Header>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<Button
|
||||
x:Name="DNSCheckButton"
|
||||
Width="100"
|
||||
Content="检测"
|
||||
IsEnabled="True" />
|
||||
</ui:SettingsExpander.Footer>
|
||||
|
||||
<StackPanel Margin="24,0,0,0">
|
||||
|
||||
<ui:SettingsExpanderItem Margin="-28,0,0,-4" Background="Transparent">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Text="检测项目" />
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<Separator Margin="-64,0" Classes="Separator" />
|
||||
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock Text="待检测的域名" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<TextBox
|
||||
x:Name="DomainTextBox"
|
||||
Width="200"
|
||||
Watermark="steamcommunity.com" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock Text="{Binding Path=Res.Settings_Proxy_CustomDNS, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ui:FAComboBox
|
||||
x:Name="dummy"
|
||||
Width="200"
|
||||
IsEditable="False"
|
||||
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=!Value}"
|
||||
ItemsSource="{Binding Source={x:Static spp:ProxySettingsWindowViewModel.ProxyDNSs}}"
|
||||
Text="{Binding Source={x:Static ms:ProxySettings.ProxyMasterDns}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock Text="{Binding Path=Res.CommunityFix_EnableUseDoh, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock Text="{Binding Path=Res.CommunityFix_CustomDohAddres, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<ui:FAComboBox
|
||||
Width="200"
|
||||
IsEditable="False"
|
||||
IsEnabled="{Binding Source={x:Static ms:ProxySettings.UseDoh}, Path=Value}"
|
||||
ItemsSource="{Binding Source={x:Static spp:ProxySettingsWindowViewModel.DohAddress}}"
|
||||
PlaceholderText="{Binding Source={x:Static s:IDnsAnalysisService.Dnspod_DohAddres}, Mode=OneWay}"
|
||||
Text="{Binding Source={x:Static ms:ProxySettings.CustomDohAddres}, Path=Value, Mode=TwoWay}" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem Margin="-28,0,0,-4" Background="Transparent">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Text="测试结果" />
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<Separator Margin="-64,0" Classes="Separator" />
|
||||
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock Text="延迟" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
IsVisible="{Binding IsDNSChecking}" />
|
||||
<SelectableTextBlock Text="{Binding DNSTestDelay}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
<ui:SettingsExpanderItem Background="Transparent">
|
||||
<TextBlock Text="解析地址" />
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<Grid>
|
||||
<ui:ProgressRing
|
||||
Width="22"
|
||||
Margin="0,-4,0,-10"
|
||||
IsVisible="{Binding IsDNSChecking}" />
|
||||
<SelectableTextBlock Text="{Binding DNSTestResult}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
|
||||
</StackPanel>
|
||||
</ui:SettingsExpander>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,17 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Org.BouncyCastle.Asn1.Utilities;
|
||||
|
||||
namespace BD.WTTS.UI.Views.Pages;
|
||||
|
||||
public partial class NetworkCheck : UserControl
|
||||
{
|
||||
public NetworkCheck()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.dummy.SelectedItem = ProxySettings.ProxyMasterDns.Value;
|
||||
this.dummy.Text = ProxySettings.ProxyMasterDns.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<UserControl
|
||||
x:Class="BD.WTTS.UI.Views.Pages.ProxyServiceStatus"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:s="https://steampp.net/services"
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
x:DataType="spp:AcceleratorPageViewModel"
|
||||
mc:Ignorable="d">
|
||||
<ScrollViewer
|
||||
Padding="0,0,10,0"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl Grid.Column="1" ItemsSource="{Binding EnableProxyScripts^, Source={x:Static s:ProxyService.Current}}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type spp:ScriptDTO}">
|
||||
<spp:AppItem
|
||||
Title="{Binding Name}"
|
||||
MaxWidth="135"
|
||||
Margin="4"
|
||||
Classes="Vertical"
|
||||
Description="{Binding AuthorName}"
|
||||
ImageMargin="20"
|
||||
ImageStretch="None"
|
||||
ToolTip.Tip="{Binding Describe}">
|
||||
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2
|
||||
FallbackSource="avares://BD.WTTS.Client.Plugins.Accelerator/UI/Assets/script.ico"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
Source="{Binding IconUrl}"
|
||||
Stretch="Uniform" />
|
||||
</spp:AppItem.Image>
|
||||
</spp:AppItem>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace BD.WTTS.UI.Views.Pages;
|
||||
|
||||
public partial class ProxyServiceStatus : UserControl
|
||||
{
|
||||
public ProxyServiceStatus()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,15 @@
|
||||
xmlns:enum="using:BD.WTTS.Enums"
|
||||
xmlns:i="using:Avalonia.Xaml.Interactivity"
|
||||
xmlns:ia="using:Avalonia.Xaml.Interactions.Core"
|
||||
xmlns:ir="using:Avalonia.Xaml.Interactions.Responsive"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ms="https://steampp.net/settings"
|
||||
xmlns:s="https://steampp.net/services"
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
xmlns:sys="using:System"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
Title="{Binding Name, Source={x:Static s:Plugin.Instance}, Mode=OneWay}"
|
||||
d:DesignHeight="450"
|
||||
d:DesignHeight="650"
|
||||
d:DesignWidth="800"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="spp:AcceleratorPageViewModel"
|
||||
@@ -391,7 +393,6 @@
|
||||
<Button Content="打开文件夹" />
|
||||
<Button Content="选择路径" />
|
||||
</StackPanel>
|
||||
|
||||
</ui:SettingsExpander>-->
|
||||
|
||||
<!--<ItemsControl>
|
||||
@@ -421,12 +422,45 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</spp:PageBase.PaneContent>
|
||||
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,*">
|
||||
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto,*">
|
||||
<Grid.Resources>
|
||||
<sys:Double x:Key="AdaptiveWidth">800</sys:Double>
|
||||
</Grid.Resources>
|
||||
<Grid.Styles>
|
||||
<Style Selector="StackPanel.small">
|
||||
<Setter Property="Grid.Row" Value="1" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="StackPanel.wide">
|
||||
<Setter Property="Grid.Row" Value="0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TabStrip.small">
|
||||
<Setter Property="Grid.ColumnSpan" Value="2" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="TabStrip.wide">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<TabStrip
|
||||
x:Name="AcceleratorTabs"
|
||||
VerticalAlignment="Top"
|
||||
ItemContainerTheme="{StaticResource TabItemStyle}"
|
||||
Theme="{StaticResource TabStripStyle}">
|
||||
<i:Interaction.Behaviors>
|
||||
<spp:AdaptiveBehavior SourceControl="{Binding $parent[TopLevel]}">
|
||||
<ir:AdaptiveClassSetter
|
||||
MinWidth="0"
|
||||
MaxWidth="{StaticResource AdaptiveWidth}"
|
||||
ClassName="small" />
|
||||
<ir:AdaptiveClassSetter
|
||||
MinWidth="{StaticResource AdaptiveWidth}"
|
||||
MaxWidth="Infinity"
|
||||
ClassName="wide" />
|
||||
</spp:AdaptiveBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
<TabStripItem Content="平台加速" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=!ProxyStatus, Mode=OneWay}">
|
||||
<!--<WrapPanel>-->
|
||||
<TextBlock Text="平台加速" />
|
||||
@@ -435,13 +469,13 @@
|
||||
Padding="5,1"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0.0" Color="#48C6EF" />
|
||||
<GradientStop Offset="1.0" Color="#6F86D6" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0.0" Color="#48C6EF" />
|
||||
<GradientStop Offset="1.0" Color="#6F86D6" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Foreground="White"
|
||||
@@ -449,9 +483,10 @@
|
||||
</Border>
|
||||
</WrapPanel>-->
|
||||
</TabStripItem>
|
||||
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_Services}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
|
||||
<TabStripItem Content="加速脚本" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=IsAnyProxyScripts, Mode=OneWay}" />
|
||||
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_ProxyLog}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
|
||||
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_FlowStatistics}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
|
||||
<TabStripItem Content="{x:Static spp:Strings.CommunityFix_Services}" IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}" />
|
||||
<TabStripItem Name="GameAccTab">
|
||||
<!--<WrapPanel>-->
|
||||
<TextBlock Text="游戏加速" />
|
||||
@@ -460,13 +495,13 @@
|
||||
Padding="5,1"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="2">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0.0" Color="#EEAD92" />
|
||||
<GradientStop Offset="1.0" Color="#6018DC" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0.0" Color="#EEAD92" />
|
||||
<GradientStop Offset="1.0" Color="#6018DC" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Foreground="White"
|
||||
@@ -474,9 +509,11 @@
|
||||
</Border>-->
|
||||
<!--</WrapPanel>-->
|
||||
</TabStripItem>
|
||||
<TabStripItem Content="网络检测" IsVisible="True" />
|
||||
<TabStripItem Content="dummy_tab_for_transisit_animation" IsVisible="False" />
|
||||
</TabStrip>
|
||||
<Carousel
|
||||
Grid.Row="1"
|
||||
Grid.Row="2"
|
||||
Margin="0,10,0,0"
|
||||
Classes="AnimeSlide"
|
||||
SelectedIndex="{Binding #AcceleratorTabs.SelectedIndex}">
|
||||
@@ -558,87 +595,19 @@
|
||||
</ItemsControl>
|
||||
</spp:ContentLoader>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- 加速服务 -->
|
||||
<spp:AcceleratorServiceStatus />
|
||||
|
||||
<!-- 加速脚本 -->
|
||||
<spp:ProxyServiceStatus />
|
||||
|
||||
<!-- 加速日志 -->
|
||||
<spp:ProxyLog Margin="5,0" />
|
||||
|
||||
<!-- 流量统计 -->
|
||||
<spp:ProxyChartView Margin="5,0" />
|
||||
|
||||
<!-- 加速服务 -->
|
||||
<Grid Margin="0,0,10,0" ColumnDefinitions="*,20,*">
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
Padding="10"
|
||||
Classes="paper">
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
Margin="0,0,0,10"
|
||||
DockPanel.Dock="Top"
|
||||
FontSize="16"
|
||||
Text="{x:Static spp:Strings.CommunityFix_AccelerationsEnable}" />
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding EnableProxyDomains, Source={x:Static s:ProxyService.Current}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type spp:AccelerateProjectDTO}">
|
||||
<StackPanel Margin="10">
|
||||
<DockPanel>
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
<Border
|
||||
Padding="5,0"
|
||||
HorizontalAlignment="Left"
|
||||
CornerRadius="5"
|
||||
IsVisible="{Binding ProxyType, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static enum:ProxyType.ServerAccelerate}}"
|
||||
ToolTip.Tip="Beta 测试加速项,不稳定且随时可能调整">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0.0" Color="#43cbff" />
|
||||
<GradientStop Offset="1.0" Color="#9708cc" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="10"
|
||||
Text="Beta" />
|
||||
</Border>
|
||||
</DockPanel>
|
||||
<Separator Classes="Separator" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Padding="10"
|
||||
Classes="paper">
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
Margin="0,0,0,10"
|
||||
DockPanel.Dock="Top"
|
||||
FontSize="16"
|
||||
Text="{x:Static spp:Strings.CommunityFix_ScriptsEnable}" />
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding EnableProxyScripts^, Source={x:Static s:ProxyService.Current}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type spp:ScriptDTO}">
|
||||
<StackPanel Margin="10">
|
||||
<DockPanel>
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</DockPanel>
|
||||
<Separator Classes="Separator" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
|
||||
<!-- 游戏加速 -->
|
||||
<Panel x:DataType="spp:GameAcceleratorPageViewModel">
|
||||
<Panel.DataContext>
|
||||
@@ -699,20 +668,30 @@
|
||||
IsVisible="{Binding Source={x:Static s:GameAcceleratorService.Current}, Path=CurrentAcceleratorGame.IsAccelerated}" />
|
||||
</Panel>
|
||||
|
||||
<!--<TabItem Header="加速日志">
|
||||
<!-- 网络检测 -->
|
||||
<spp:NetworkCheck x:Name="NetworkCheckControl" />
|
||||
|
||||
</TabItem>-->
|
||||
<!--<TabItem Header="AD">
|
||||
|
||||
</TabItem>-->
|
||||
</Carousel>
|
||||
|
||||
<!-- Side tab of paid game spped up -->
|
||||
<StackPanel
|
||||
Grid.ColumnSpan="2"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding #GameAccTab.IsSelected}"
|
||||
Orientation="Horizontal"
|
||||
Spacing="5">
|
||||
|
||||
<i:Interaction.Behaviors>
|
||||
<spp:AdaptiveBehavior SourceControl="{Binding $parent[TopLevel]}">
|
||||
<ir:AdaptiveClassSetter
|
||||
MinWidth="0"
|
||||
MaxWidth="{StaticResource AdaptiveWidth}"
|
||||
ClassName="small" />
|
||||
<ir:AdaptiveClassSetter
|
||||
MinWidth="{StaticResource AdaptiveWidth}"
|
||||
MaxWidth="Infinity"
|
||||
ClassName="wide" />
|
||||
</spp:AdaptiveBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
<Border
|
||||
Padding="5,1"
|
||||
VerticalAlignment="Center"
|
||||
@@ -784,14 +763,26 @@
|
||||
</AutoCompleteBox>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<!-- Side tab after speed up -->
|
||||
<StackPanel
|
||||
Grid.ColumnSpan="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding Source={x:Static s:ProxyService.Current}, Path=ProxyStatus, Mode=OneWay}"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
|
||||
<i:Interaction.Behaviors>
|
||||
<spp:AdaptiveBehavior SourceControl="{Binding $parent[TopLevel]}">
|
||||
<ir:AdaptiveClassSetter
|
||||
MinWidth="0"
|
||||
MaxWidth="{StaticResource AdaptiveWidth}"
|
||||
ClassName="small" />
|
||||
<ir:AdaptiveClassSetter
|
||||
MinWidth="{StaticResource AdaptiveWidth}"
|
||||
MaxWidth="Infinity"
|
||||
ClassName="wide" />
|
||||
</spp:AdaptiveBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<CompiledBinding
|
||||
@@ -829,7 +820,7 @@
|
||||
</StackPanel>
|
||||
|
||||
<spp:AdControl
|
||||
Grid.Row="1"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="165"
|
||||
Height="390"
|
||||
@@ -837,4 +828,4 @@
|
||||
VerticalAlignment="Top"
|
||||
Standard="Vertical" />
|
||||
</Grid>
|
||||
</spp:PageBase>
|
||||
</spp:PageBase>
|
||||
@@ -1,8 +1,8 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Interactivity;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
using Avalonia.VisualTree;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace BD.WTTS.UI.Views.Pages;
|
||||
|
||||
@@ -73,6 +73,36 @@ public partial class AcceleratorPage2 : PageBase<AcceleratorPageViewModel>
|
||||
GameAccTab.IsVisible = false;
|
||||
AcceleratorTabs.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedSTUNAddress, v => v.NetworkCheckControl.STUNServer.SelectedItem).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.NATCheckCommand, v => v.NetworkCheckControl.NATCheckButton).DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.LocalEndPoint, v => v.NetworkCheckControl.LocalIPAddress.Text).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.DNSCheckCommand, v => v.NetworkCheckControl.DNSCheckButton).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.DomainPendingTest, v => v.NetworkCheckControl.DomainTextBox.Text).DisposeWith(disposables);
|
||||
|
||||
NetworkCheckControl.PingOK.IsVisible = false;
|
||||
NetworkCheckControl.PingError.IsVisible = false;
|
||||
NetworkCheckControl.NATCheckButton.Click += (_, _) => NetworkCheckControl.PingError.IsVisible = NetworkCheckControl.PingOK.IsVisible = false;
|
||||
ViewModel!.NATCheckCommand
|
||||
.Subscribe(result =>
|
||||
{
|
||||
(NetworkCheckControl.NATTextBlock.Text, NetworkCheckControl.NATTypeTip.Text) = result.Nat switch
|
||||
{
|
||||
AcceleratorPageViewModel.NatTypeSimple.Open => ("开放 NAT", "您可与在其网络上具有任意 NAT 类型的用户玩多人游戏和发起多人游戏。"),
|
||||
AcceleratorPageViewModel.NatTypeSimple.Moderate => ("中等 NAT", "您可与一些用户玩多人游戏;但是,并且通常你将不会被选为比赛的主持人。"),
|
||||
AcceleratorPageViewModel.NatTypeSimple.Strict => ("严格 NAT", "您只能与具有开放 NAT 类型的用户玩多人游戏。您不能被选为比赛的主持人。"),
|
||||
_ => ("不可用 NAT", "如果 NAT 不可用,您将无法使用群聊天或连接到某些 Xbox 游戏的多人游戏。"),
|
||||
};
|
||||
|
||||
if (result.PingSuccess)
|
||||
{
|
||||
NetworkCheckControl.PingOK.IsVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkCheckControl.PingError.IsVisible = true;
|
||||
}
|
||||
}).DisposeWith(disposables);
|
||||
});
|
||||
|
||||
SearchGameBox.DropDownClosed += SearchGameBox_DropDownClosed;
|
||||
@@ -150,4 +180,4 @@ public partial class AcceleratorPage2 : PageBase<AcceleratorPageViewModel>
|
||||
// // Log.Error(nameof(AcceleratorPage), ex, "ForceSave fail.");
|
||||
// //}
|
||||
//}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user