mirror of
https://github.com/2dust/v2rayN.git
synced 2026-08-15 19:52:06 +03:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0ad2c46e6 | ||
|
|
230a2f6773 | ||
|
|
e7cc4c832e | ||
|
|
fb7033aab5 | ||
|
|
b6cc3913ac | ||
|
|
bc06a5dc21 | ||
|
|
9c1951178c | ||
|
|
b3df42b9ba | ||
|
|
e01717d832 | ||
|
|
8b4063b44b | ||
|
|
3136f573dd | ||
|
|
e101b1d7b0 |
@@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>7.24.6</Version>
|
<Version>7.24.7</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -251,4 +251,12 @@ internal static class CoreConfigTestFactory
|
|||||||
config.TunModeItem.RouteExcludeAddress = ["10.0.0.1/32", "192.168.1.0/24", "fc00::/7"];
|
config.TunModeItem.RouteExcludeAddress = ["10.0.0.1/32", "192.168.1.0/24", "fc00::/7"];
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Config CreateConfigWithTun(ECoreType coreType, bool enableIPv6Address)
|
||||||
|
{
|
||||||
|
var config = CreateConfig(coreType);
|
||||||
|
config.TunModeItem.EnableTun = true;
|
||||||
|
config.TunModeItem.EnableIPv6Address = enableIPv6Address;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,85 @@ public class CoreConfigSingboxServiceTests
|
|||||||
cfg.inbounds.Should().Contain(i => i.type == "tun");
|
cfg.inbounds.Should().Contain(i => i.type == "tun");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_TunEnabled_ShouldKeepEmbeddedTunRules()
|
||||||
|
{
|
||||||
|
// The embedded tun rules reject local-network noise (NetBIOS/mDNS, multicast).
|
||||||
|
// They are deserialized into List<Rule4Sbox>, so a schema mismatch in the
|
||||||
|
// embedded template makes JsonUtils.Deserialize return null and silently
|
||||||
|
// drops every one of them.
|
||||||
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
|
config.TunModeItem.EnableTun = true;
|
||||||
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
|
|
||||||
|
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.sing_box);
|
||||||
|
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with
|
||||||
|
{
|
||||||
|
IsTunEnabled = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||||
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
|
||||||
|
cfg.route.rules.Should().Contain(
|
||||||
|
r => r.action == "reject"
|
||||||
|
&& r.network != null && r.network.Contains("udp")
|
||||||
|
&& r.port != null && r.port.Contains(5353),
|
||||||
|
"the embedded tun rules must reject mDNS/NetBIOS noise");
|
||||||
|
cfg.route.rules.Should().Contain(
|
||||||
|
r => r.action == "reject"
|
||||||
|
&& r.ip_cidr != null && r.ip_cidr.Contains("224.0.0.0/3"),
|
||||||
|
"the embedded tun rules must reject multicast traffic");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_TunEnabled_ShouldRejectTrafficToTunOwnAddresses()
|
||||||
|
{
|
||||||
|
// Regression test: traffic addressed to the TUN interface's own addresses must
|
||||||
|
// never reach an outbound. auto_route hijacks the default route, so `direct`
|
||||||
|
// writes such a packet straight back into the TUN, which routes it to the
|
||||||
|
// outbound again - an infinite loop that pins a CPU core. Observed in the wild
|
||||||
|
// with WebRTC ICE connectivity checks against the TUN's own fc00::/7 ULA
|
||||||
|
// address, sustaining ~8k packets/s out of the TUN interface.
|
||||||
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
|
config.TunModeItem.EnableTun = true;
|
||||||
|
config.TunModeItem.EnableIPv6Address = true;
|
||||||
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
|
|
||||||
|
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.sing_box);
|
||||||
|
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with
|
||||||
|
{
|
||||||
|
IsTunEnabled = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||||
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
var tun = cfg.inbounds.First(i => i.type == "tun");
|
||||||
|
tun.address.Should().NotBeNullOrEmpty();
|
||||||
|
|
||||||
|
foreach (var address in tun.address!)
|
||||||
|
{
|
||||||
|
var self = IPAddress.Parse(address.Split('/').First());
|
||||||
|
var hostBits = self.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32;
|
||||||
|
var expected = $"{self}/{hostBits}";
|
||||||
|
cfg.route.rules.Should().Contain(
|
||||||
|
r => r.action == "reject" && r.ip_cidr != null && r.ip_cidr.Contains(expected),
|
||||||
|
$"traffic to the TUN's own address '{address}' must be rejected, not routed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The match has to stay on the addresses themselves. sing-tun derives the TUN's DNS
|
||||||
|
// entry from the address right after the interface's own, and every prefix offered
|
||||||
|
// here leaves room for it, so a prefix match would drop system name lookups too.
|
||||||
|
var dropRule = cfg.route.rules.First(r =>
|
||||||
|
r.action == "reject" && r.method == "drop" && r.ip_cidr?.Count > 0);
|
||||||
|
dropRule.ip_cidr!.Should().OnlyContain(c =>
|
||||||
|
c.EndsWith("/32", StringComparison.Ordinal) || c.EndsWith("/128", StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GenerateClientConfigContent_BindInterface_ShouldUseDialBindInterface()
|
public void GenerateClientConfigContent_BindInterface_ShouldUseDialBindInterface()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -570,6 +570,51 @@ public class CoreConfigV2rayServiceTests
|
|||||||
directOutbound!.streamSettings.sockopt!.domainStrategy.Should().Be("UseIPv4");
|
directOutbound!.streamSettings.sockopt!.domainStrategy.Should().Be("UseIPv4");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void GenerateClientConfigContent_Tun_ShouldRouteIPv6IntoTunnel(bool enableIPv6Address)
|
||||||
|
{
|
||||||
|
var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address);
|
||||||
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
|
|
||||||
|
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
|
||||||
|
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray);
|
||||||
|
|
||||||
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue();
|
||||||
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
|
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
|
||||||
|
|
||||||
|
tunInbound.Should().NotBeNull();
|
||||||
|
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("0.0.0.0/0");
|
||||||
|
tunInbound.settings.autoSystemRoutingTable.Should().Contain("::/0");
|
||||||
|
|
||||||
|
// EnableIPv6Address governs the interface address only, never the routing table.
|
||||||
|
tunInbound.settings.gateway.Should().HaveCount(enableIPv6Address ? 2 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_TunRouteExcludeAddress_ShouldIncludeIPv6Ranges()
|
||||||
|
{
|
||||||
|
var config = CoreConfigTestFactory.CreateConfigWithTunRouteExcludeAddress(ECoreType.Xray);
|
||||||
|
config.TunModeItem.EnableIPv6Address = false;
|
||||||
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
|
|
||||||
|
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
|
||||||
|
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray);
|
||||||
|
|
||||||
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue();
|
||||||
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
|
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
|
||||||
|
|
||||||
|
tunInbound.Should().NotBeNull();
|
||||||
|
tunInbound!.settings.autoSystemRoutingTable.Should().Contain(x => x.Contains(':'));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GenerateClientConfigContent_TunRouteExcludeAddress()
|
public void GenerateClientConfigContent_TunRouteExcludeAddress()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -134,4 +134,25 @@ public static class Extension
|
|||||||
.Replace("\r", replacement)
|
.Replace("\r", replacement)
|
||||||
.Replace("\n", replacement);
|
.Replace("\n", replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<TOutput> HandleSafe<TInput, TOutput>(
|
||||||
|
this Interaction<TInput, TOutput> interaction,
|
||||||
|
TInput input,
|
||||||
|
TOutput defaultValue = default!)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await interaction.Handle(input);
|
||||||
|
}
|
||||||
|
catch (UnhandledInteractionException<TInput, TOutput> ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog($"Unhandled interaction exception for input: {input}", ex);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog($"Exception occurred while handling interaction for input: {input}", ex);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ internal static class WindowsUtils
|
|||||||
|
|
||||||
public static async Task RemoveTunDevice()
|
public static async Task RemoveTunDevice()
|
||||||
{
|
{
|
||||||
var tunNameList = new List<string> { "singbox_tun", "xray_tun" };
|
var tunNameList = new List<string> { "wintunsingbox_tun", "xray_tun" };
|
||||||
foreach (var tunName in tunNameList)
|
foreach (var tunName in tunNameList)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var sum = MD5.HashData(Encoding.UTF8.GetBytes($"wintun{tunName}"));
|
var sum = MD5.HashData(Encoding.UTF8.GetBytes(tunName));
|
||||||
var guid = new Guid(sum);
|
var guid = new Guid(sum);
|
||||||
var pnpUtilPath = @"C:\Windows\System32\pnputil.exe";
|
var pnpUtilPath = @"C:\Windows\System32\pnputil.exe";
|
||||||
var arg = $$""" /remove-device "SWD\Wintun\{{{guid}}}" """;
|
var arg = $$""" /remove-device "SWD\Wintun\{{{guid}}}" """;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.Collections.Specialized;
|
|
||||||
|
|
||||||
namespace ServiceLib.Handler.Fmt;
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
public class Hysteria2Fmt : BaseFmt
|
public class Hysteria2Fmt : BaseFmt
|
||||||
@@ -169,7 +167,16 @@ public class Hysteria2Fmt : BaseFmt
|
|||||||
if (item.CertSha.IsNullOrEmpty())
|
if (item.CertSha.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
||||||
|
// NOTE:
|
||||||
|
// To accommodate Xray changes,
|
||||||
|
// some providers issue self-signed cert links with `insecure = false` and a certificate fingerprint,
|
||||||
|
// breaking interoperability between Xray, official Hysteria 2 client, and sing-box.
|
||||||
|
// Since this won't compromise the overall security model,
|
||||||
|
// `insecure = true` is automatically set when a fingerprint is detected,
|
||||||
|
// and the value is restored when generating configurations.
|
||||||
|
item.AllowInsecure = Global.StringTrue;
|
||||||
}
|
}
|
||||||
|
item.EchConfigList = GetQueryDecoded(query, "ech");
|
||||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||||
{
|
{
|
||||||
Ports = GetQueryDecoded(query, "mport"),
|
Ports = GetQueryDecoded(query, "mport"),
|
||||||
@@ -212,6 +219,10 @@ public class Hysteria2Fmt : BaseFmt
|
|||||||
var sha = item.CertSha;
|
var sha = item.CertSha;
|
||||||
dicQuery.Add("pinSHA256", Utils.UrlEncode(sha));
|
dicQuery.Add("pinSHA256", Utils.UrlEncode(sha));
|
||||||
}
|
}
|
||||||
|
if (!item.EchConfigList.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList));
|
||||||
|
}
|
||||||
var protocolExtraItem = item.GetProtocolExtra();
|
var protocolExtraItem = item.GetProtocolExtra();
|
||||||
var isGecko = !protocolExtraItem.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtraItem.GeckoMaxPacketSize.IsNullOrEmpty();
|
var isGecko = !protocolExtraItem.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtraItem.GeckoMaxPacketSize.IsNullOrEmpty();
|
||||||
if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())
|
if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())
|
||||||
|
|||||||
@@ -1417,7 +1417,7 @@
|
|||||||
<value>新增 [Anytls] 節點</value>
|
<value>新增 [Anytls] 節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRemoteDNS" xml:space="preserve">
|
<data name="TbRemoteDNS" xml:space="preserve">
|
||||||
<value>遠程 DNS</value>
|
<value>遠端 DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbDomesticDNS" xml:space="preserve">
|
<data name="TbDomesticDNS" xml:space="preserve">
|
||||||
<value>直連 DNS</value>
|
<value>直連 DNS</value>
|
||||||
@@ -1429,16 +1429,16 @@
|
|||||||
<value>代理目標解析策略</value>
|
<value>代理目標解析策略</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>新增常用 DNS Hosts</value>
|
<value>新增常用 DNS 主機對應</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIP" xml:space="preserve">
|
<data name="TbFakeIP" xml:space="preserve">
|
||||||
<value>FakeIP</value>
|
<value>FakeIP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
||||||
<value>阻止 SVCB 和 HTTPS 查詢</value>
|
<value>封鎖 SVCB 與 HTTPS 查詢</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts:(“網域名稱1 ip1 ip2” 一行一個)</value>
|
<value>DNS 主機對應:(每行一組「網域名稱1 ip1 ip2」)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||||
<value>DNS 基礎設定</value>
|
<value>DNS 基礎設定</value>
|
||||||
@@ -1447,49 +1447,49 @@
|
|||||||
<value>DNS 進階設定</value>
|
<value>DNS 進階設定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
|
||||||
<value>校驗相應地區域名 IP</value>
|
<value>驗證區域網域名稱的 IP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||||
<value>配置後,會對相應地區域名(如 geosite:cn - geoip:cn)的返回 IP 進行校驗,僅返回期望 IP</value>
|
<value>設定後,系統會驗證區域網域名稱(例如 geosite:cn - geoip:cn)所傳回的 IP,並只傳回預期的 IP。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||||
<value>啟用自訂 DNS</value>
|
<value>啟用自訂 DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>自訂 DNS 已啟用,此頁面配置將無效</value>
|
<value>已啟用自訂 DNS,此頁面的設定將不會生效</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
<value>開啟後將阻止 ECH 和 HTTP/3 可用性查詢</value>
|
<value>啟用後將封鎖 ECH 與 HTTP/3 可用性查詢</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
<value>請填寫正確的配置範本</value>
|
<value>請填寫正確的設定範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuFullConfigTemplate" xml:space="preserve">
|
<data name="menuFullConfigTemplate" xml:space="preserve">
|
||||||
<value>完整配置範本設定</value>
|
<value>完整設定範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
|
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
|
||||||
<value>啟用完整配置範本</value>
|
<value>啟用完整設定範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRayFullConfigTemplate" xml:space="preserve">
|
<data name="TbRayFullConfigTemplate" xml:space="preserve">
|
||||||
<value>v2ray 完整配置範本</value>
|
<value>v2ray 完整設定範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>僅添加出站配置,routing.balancers 和 routing.rules.outboundTag,點擊查看文檔</value>
|
<value>僅新增出站設定、routing.balancers 與 routing.rules.outboundTag。點選以查看說明文件</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
|
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
|
||||||
<value>不添加非代理協定出站</value>
|
<value>不新增非代理協定出站</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
|
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
|
||||||
<value>設定上游代理 tag</value>
|
<value>設定上游代理標籤</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFullConfigTemplate" xml:space="preserve">
|
<data name="TbSBFullConfigTemplate" xml:space="preserve">
|
||||||
<value>sing-box 完整配置範本</value>
|
<value>sing-box 完整設定範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>僅添加出站和端點配置,點擊查看文檔</value>
|
<value>僅新增出站與端點設定。點選以查看說明文件</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>此功能供高級用戶和有特殊需求的用戶使用。 啟用此功能後,將忽略 Core 的基礎設定,DNS 設定 ,路由設定。你需要保證系統代理的埠和流量統計等功能的配置正確,一切都由你來設定。</value>
|
<value>此功能適合進階使用者與有特殊需求的使用者。啟用後,Core 基礎設定、DNS 設定與路由設定將被忽略。請確認系統代理連接埠、流量統計及其他相關設定皆正確;所有項目都必須由您自行設定。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>開始解析和處理訂閱內容</value>
|
<value>開始解析和處理訂閱內容</value>
|
||||||
@@ -1498,10 +1498,10 @@
|
|||||||
<value>選擇節點</value>
|
<value>選擇節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIPTips" xml:space="preserve">
|
<data name="TbFakeIPTips" xml:space="preserve">
|
||||||
<value>默認全局生效,僅在 sing-box 中內置 FakeIP 過濾。</value>
|
<value>預設會套用至全域;內建 FakeIP 過濾僅支援 sing-box。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||||
<value>請至少添加一個節點</value>
|
<value>請至少新增一個設定檔</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
|
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
|
||||||
<value>策略組</value>
|
<value>策略組</value>
|
||||||
@@ -1627,7 +1627,7 @@
|
|||||||
<value>提供過期快取(Serve Stale)</value>
|
<value>提供過期快取(Serve Stale)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbParallelQuery" xml:space="preserve">
|
<data name="TbParallelQuery" xml:space="preserve">
|
||||||
<value>并行查詢</value>
|
<value>並行查詢</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbDomesticDNSTips" xml:space="preserve">
|
<data name="TbDomesticDNSTips" xml:space="preserve">
|
||||||
<value>預設僅在路由期間進行解析時調用</value>
|
<value>預設僅在路由期間進行解析時調用</value>
|
||||||
|
|||||||
@@ -48,12 +48,18 @@ public partial class CoreConfigSingboxService
|
|||||||
// packet straight back into the TUN, which hands it to the outbound again -
|
// packet straight back into the TUN, which hands it to the outbound again -
|
||||||
// an infinite loop that pins a CPU core. Drop instead of rejecting so no
|
// an infinite loop that pins a CPU core. Drop instead of rejecting so no
|
||||||
// ICMP unreachable is generated back towards the same addresses.
|
// ICMP unreachable is generated back towards the same addresses.
|
||||||
|
//
|
||||||
|
// Match each address on its own, not the prefix it carries. On Linux sing-tun
|
||||||
|
// registers Inet4Address[0].Addr().Next() with systemd-resolved as a "~." DNS
|
||||||
|
// upstream, and every prefix offered here is a /30 or /126, so carrying the
|
||||||
|
// prefix through would cover that resolver address too and drop every system
|
||||||
|
// name lookup along with the loop.
|
||||||
var tunAddresses = _coreConfig.inbounds.FirstOrDefault(i => i.type == "tun")?.address;
|
var tunAddresses = _coreConfig.inbounds.FirstOrDefault(i => i.type == "tun")?.address;
|
||||||
if (tunAddresses?.Count > 0)
|
if (tunAddresses?.Count > 0)
|
||||||
{
|
{
|
||||||
_coreConfig.route.rules.Add(new()
|
_coreConfig.route.rules.Add(new()
|
||||||
{
|
{
|
||||||
ip_cidr = [.. tunAddresses],
|
ip_cidr = [.. tunAddresses.Select(ToSingleAddressPrefix)],
|
||||||
action = "reject",
|
action = "reject",
|
||||||
method = "drop",
|
method = "drop",
|
||||||
});
|
});
|
||||||
@@ -284,6 +290,14 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ToSingleAddressPrefix(string address)
|
||||||
|
{
|
||||||
|
var addr = address.Split('/').First();
|
||||||
|
return IPAddress.TryParse(addr, out var ip)
|
||||||
|
? $"{addr}/{(ip.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32)}"
|
||||||
|
: address;
|
||||||
|
}
|
||||||
|
|
||||||
private List<string> BuildRoutingDirectExe()
|
private List<string> BuildRoutingDirectExe()
|
||||||
{
|
{
|
||||||
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|||||||
@@ -67,12 +67,14 @@ public partial class CoreConfigV2rayService
|
|||||||
|
|
||||||
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
|
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
|
||||||
tunInbound.settings.gateway = [address];
|
tunInbound.settings.gateway = [address];
|
||||||
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"];
|
// Route both families into the tunnel regardless of EnableIPv6Address. That option only
|
||||||
|
// controls whether the interface gets an IPv6 address; leaving ::/0 out of the routing
|
||||||
|
// table makes IPv6 follow the system default route and bypass the tunnel entirely.
|
||||||
|
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0", "::/0"];
|
||||||
if (_config.TunModeItem.EnableIPv6Address == true)
|
if (_config.TunModeItem.EnableIPv6Address == true)
|
||||||
{
|
{
|
||||||
var address6 = _config.TunModeItem.IPv6Address.NullIfEmpty() ?? Global.TunIPv6Address.First();
|
var address6 = _config.TunModeItem.IPv6Address.NullIfEmpty() ?? Global.TunIPv6Address.First();
|
||||||
tunInbound.settings.gateway.Add(address6);
|
tunInbound.settings.gateway.Add(address6);
|
||||||
tunInbound.settings.autoSystemRoutingTable.Add("::/0");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
|
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
|
||||||
@@ -81,7 +83,8 @@ public partial class CoreConfigV2rayService
|
|||||||
tunInbound.settings.autoOutboundsInterface = bindInterface;
|
tunInbound.settings.autoOutboundsInterface = bindInterface;
|
||||||
}
|
}
|
||||||
tunInbound.sniffing = inbound.sniffing;
|
tunInbound.sniffing = inbound.sniffing;
|
||||||
tunInbound.sniffing.routeOnly = inbound.sniffing.routeOnly;
|
// tunInbound.sniffing.routeOnly = inbound.sniffing.routeOnly;
|
||||||
|
tunInbound.sniffing.routeOnly = true;
|
||||||
|
|
||||||
if (_config.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
if (_config.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
||||||
{
|
{
|
||||||
@@ -118,15 +121,8 @@ public partial class CoreConfigV2rayService
|
|||||||
includeList = IPNetwork2.Supernet(includeList.ToArray()).ToList();
|
includeList = IPNetwork2.Supernet(includeList.ToArray()).ToList();
|
||||||
includeListV6 = IPNetwork2.Supernet(includeListV6.ToArray()).ToList();
|
includeListV6 = IPNetwork2.Supernet(includeListV6.ToArray()).ToList();
|
||||||
|
|
||||||
if (_config.TunModeItem.EnableIPv6Address)
|
tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString())
|
||||||
{
|
.Concat(includeListV6.Select(x => x.ToString())).ToList();
|
||||||
tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString())
|
|
||||||
.Concat(includeListV6.Select(x => x.ToString())).ToList();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString()).ToList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_coreConfig.inbounds.Add(tunInbound);
|
_coreConfig.inbounds.Add(tunInbound);
|
||||||
|
|||||||
@@ -512,6 +512,7 @@ public partial class CoreConfigV2rayService
|
|||||||
settings = new MaskSettings4Ray { value = kcpSeed },
|
settings = new MaskSettings4Ray { value = kcpSeed },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
kcpFinalmask.udp?.Reverse();
|
||||||
streamSettings.kcpSettings = kcpSettings;
|
streamSettings.kcpSettings = kcpSettings;
|
||||||
streamSettings.finalmask = kcpFinalmask;
|
streamSettings.finalmask = kcpFinalmask;
|
||||||
break;
|
break;
|
||||||
@@ -666,6 +667,7 @@ public partial class CoreConfigV2rayService
|
|||||||
version = 2,
|
version = 2,
|
||||||
auth = _node.Password,
|
auth = _node.Password,
|
||||||
};
|
};
|
||||||
|
hy2Finalmask.udp?.Reverse();
|
||||||
streamSettings.finalmask = hy2Finalmask;
|
streamSettings.finalmask = hy2Finalmask;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public partial class AddServer2ViewModel : MyReactiveObject, ICloseable
|
|||||||
|
|
||||||
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
var fileName = await BrowseConfigFileInteraction.Handle(RxVoid.Default);
|
var fileName = await BrowseConfigFileInteraction.HandleSafe(RxVoid.Default);
|
||||||
if (fileName.IsNullOrEmpty())
|
if (fileName.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -85,8 +85,6 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#endregion Menu
|
#endregion Menu
|
||||||
|
|
||||||
private readonly SynchronizationContext _uiContext = SynchronizationContext.Current;
|
|
||||||
|
|
||||||
#region Init
|
#region Init
|
||||||
|
|
||||||
public MainWindowViewModel()
|
public MainWindowViewModel()
|
||||||
@@ -302,7 +300,7 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||||
.Subscribe(async blShow =>
|
.Subscribe(async blShow =>
|
||||||
{
|
{
|
||||||
await ShowHideWindowInteraction.Handle(blShow);
|
await ShowHideWindowInteraction.HandleSafe(blShow);
|
||||||
});
|
});
|
||||||
|
|
||||||
StatusBarViewModel.SetDefaultServerRequested
|
StatusBarViewModel.SetDefaultServerRequested
|
||||||
@@ -413,14 +411,25 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
private async Task RefreshServersDispatcherAsync()
|
private async Task RefreshServersDispatcherAsync()
|
||||||
{
|
{
|
||||||
//await Observable.Start(async () => await RefreshServers(), RxSchedulers.MainThreadScheduler);
|
//await Observable.Start(async () => await RefreshServers(), RxSchedulers.MainThreadScheduler);
|
||||||
_uiContext?.Post(_ => _ = RefreshServers(), null);
|
await Signal.FromAsync(async () =>
|
||||||
|
{
|
||||||
|
await RefreshServers();
|
||||||
|
return RxVoid.Default;
|
||||||
|
})
|
||||||
|
.SubscribeOn(RxSchedulers.MainThreadScheduler)
|
||||||
|
.ToTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RefreshSubscriptions()
|
private async Task RefreshSubscriptions()
|
||||||
{
|
{
|
||||||
//await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
|
//await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
|
||||||
|
await Signal.FromAsync(async () =>
|
||||||
_uiContext?.Post(_ => _ = ProfilesViewModel.RefreshSubscriptions(), null);
|
{
|
||||||
|
await ProfilesViewModel.RefreshSubscriptions();
|
||||||
|
return RxVoid.Default;
|
||||||
|
})
|
||||||
|
.SubscribeOn(RxSchedulers.MainThreadScheduler)
|
||||||
|
.ToTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Servers && Groups
|
#endregion Servers && Groups
|
||||||
@@ -467,7 +476,7 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
var stringData = clipboardData;
|
var stringData = clipboardData;
|
||||||
if (clipboardData == null)
|
if (clipboardData == null)
|
||||||
{
|
{
|
||||||
var result = await ReadTextFromClipboardInteraction.Handle(RxVoid.Default);
|
var result = await ReadTextFromClipboardInteraction.HandleSafe(RxVoid.Default);
|
||||||
if (result.IsNullOrEmpty())
|
if (result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
@@ -490,7 +499,7 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task AddServerViaScanAsync()
|
public async Task AddServerViaScanAsync()
|
||||||
{
|
{
|
||||||
var result = await ScanScreenInteraction.Handle(RxVoid.Default);
|
var result = await ScanScreenInteraction.HandleSafe(RxVoid.Default);
|
||||||
await ScanScreenResult(result);
|
await ScanScreenResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,7 +511,7 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task AddServerViaImageAsync()
|
public async Task AddServerViaImageAsync()
|
||||||
{
|
{
|
||||||
var imageFileName = await BrowseImageFileInteraction.Handle(RxVoid.Default);
|
var imageFileName = await BrowseImageFileInteraction.HandleSafe(RxVoid.Default);
|
||||||
await AddScanResultAsync(imageFileName);
|
await AddScanResultAsync(imageFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,10 +700,12 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
//{
|
//{
|
||||||
// await ClashProxiesViewModel.ProxiesReload();
|
// await ClashProxiesViewModel.ProxiesReload();
|
||||||
//}, RxSchedulers.MainThreadScheduler);
|
//}, RxSchedulers.MainThreadScheduler);
|
||||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
await Signal.FromAsync(async () =>
|
||||||
{
|
{
|
||||||
await ClashProxiesViewModel.ProxiesReload();
|
await ClashProxiesViewModel.ProxiesReload();
|
||||||
});
|
return RxVoid.Default;
|
||||||
|
}).SubscribeOn(RxSchedulers.MainThreadScheduler)
|
||||||
|
.ToTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
ReloadResult(showClashUI);
|
ReloadResult(showClashUI);
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public partial class MsgViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await DispatcherShowMsgInteraction.Handle(sb.ToString());
|
await DispatcherShowMsgInteraction.HandleSafe(sb.ToString());
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -138,13 +138,7 @@ public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
|||||||
|
|
||||||
await RefreshServers();
|
await RefreshServers();
|
||||||
|
|
||||||
try
|
await ProfilesFocusInteraction.HandleSafe(RxVoid.Default);
|
||||||
{
|
|
||||||
await ProfilesFocusInteraction.Handle(RxVoid.Default);
|
|
||||||
}
|
|
||||||
catch (UnhandledInteractionException<RxVoid, RxVoid>)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ServerFilterChanged(bool c)
|
private async Task ServerFilterChanged(bool c)
|
||||||
|
|||||||
@@ -345,13 +345,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
await RefreshServers();
|
await RefreshServers();
|
||||||
|
|
||||||
try
|
await ProfilesFocusInteraction.HandleSafe(RxVoid.Default);
|
||||||
{
|
|
||||||
await ProfilesFocusInteraction.Handle(RxVoid.Default);
|
|
||||||
}
|
|
||||||
catch (UnhandledInteractionException<RxVoid, RxVoid>)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ServerFilterChanged(bool c)
|
private async Task ServerFilterChanged(bool c)
|
||||||
@@ -395,13 +389,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
SelectedProfile = selected ?? lstModel.First();
|
SelectedProfile = selected ?? lstModel.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
await DispatcherRefreshServersBizInteraction.HandleSafe(RxVoid.Default);
|
||||||
{
|
|
||||||
await DispatcherRefreshServersBizInteraction.Handle(RxVoid.Default);
|
|
||||||
}
|
|
||||||
catch (UnhandledInteractionException<RxVoid, RxVoid>)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshSubscriptions()
|
public async Task RefreshSubscriptions()
|
||||||
@@ -419,7 +407,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task AdjustMainLvColWidth()
|
public async Task AdjustMainLvColWidth()
|
||||||
{
|
{
|
||||||
await AdjustMainLvColWidthInteraction.Handle(RxVoid.Default);
|
await AdjustMainLvColWidthInteraction.HandleSafe(RxVoid.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||||
@@ -535,7 +523,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -556,7 +544,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task RemoveDuplicateServer()
|
private async Task RemoveDuplicateServer()
|
||||||
{
|
{
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -631,7 +619,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ShareServerInteraction.Handle(url);
|
await ShareServerInteraction.HandleSafe(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GenGroupAllServer()
|
private async Task GenGroupAllServer()
|
||||||
@@ -799,13 +787,13 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await SetClipboardDataInteraction.Handle((string)result.Data);
|
await SetClipboardDataInteraction.HandleSafe((string)result.Data);
|
||||||
NoticeManager.Instance.SendMessage(ResUI.OperationSuccess);
|
NoticeManager.Instance.SendMessage(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await SaveFileDialogInteraction.Handle(item);
|
await SaveFileDialogInteraction.HandleSafe(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -854,11 +842,11 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (blEncode)
|
if (blEncode)
|
||||||
{
|
{
|
||||||
await SetClipboardDataInteraction.Handle(Utils.Base64Encode(sb.ToString()));
|
await SetClipboardDataInteraction.HandleSafe(Utils.Base64Encode(sb.ToString()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await SetClipboardDataInteraction.Handle(sb.ToString());
|
await SetClipboardDataInteraction.HandleSafe(sb.ToString());
|
||||||
}
|
}
|
||||||
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
||||||
}
|
}
|
||||||
@@ -881,7 +869,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (!result.IsNullOrEmpty())
|
if (!result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
await SetClipboardDataInteraction.Handle(result);
|
await SetClipboardDataInteraction.HandleSafe(result);
|
||||||
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -925,7 +913,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,21 @@ public partial class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
|||||||
[Reactive]
|
[Reactive]
|
||||||
public partial bool AutoSort { get; set; }
|
public partial bool AutoSort { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public partial string OutboundTag { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public partial string Remarks { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public partial string Port { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public partial string Network { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public partial bool Enabled { get; set; }
|
||||||
|
|
||||||
public ReactiveCommand<RxVoid, RxVoid> SelectProfileCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SelectProfileCmd { get; }
|
||||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||||
|
|
||||||
@@ -57,6 +72,11 @@ public partial class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
|||||||
IP = Utils.List2String(SelectedSource.Ip, true);
|
IP = Utils.List2String(SelectedSource.Ip, true);
|
||||||
Process = Utils.List2String(SelectedSource.Process, true);
|
Process = Utils.List2String(SelectedSource.Process, true);
|
||||||
RuleType = SelectedSource.RuleType?.ToString();
|
RuleType = SelectedSource.RuleType?.ToString();
|
||||||
|
OutboundTag = SelectedSource.OutboundTag;
|
||||||
|
Remarks = SelectedSource.Remarks;
|
||||||
|
Port = SelectedSource.Port;
|
||||||
|
Network = SelectedSource.Network;
|
||||||
|
Enabled = SelectedSource.Enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveRulesAsync()
|
private async Task SaveRulesAsync()
|
||||||
@@ -80,6 +100,11 @@ public partial class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
|||||||
SelectedSource.Protocol = ProtocolItems?.ToList();
|
SelectedSource.Protocol = ProtocolItems?.ToList();
|
||||||
SelectedSource.InboundTag = InboundTagItems?.ToList();
|
SelectedSource.InboundTag = InboundTagItems?.ToList();
|
||||||
SelectedSource.RuleType = RuleType.IsNullOrEmpty() ? null : Enum.Parse<ERuleType>(RuleType);
|
SelectedSource.RuleType = RuleType.IsNullOrEmpty() ? null : Enum.Parse<ERuleType>(RuleType);
|
||||||
|
SelectedSource.OutboundTag = OutboundTag;
|
||||||
|
SelectedSource.Remarks = Remarks;
|
||||||
|
SelectedSource.Port = Port;
|
||||||
|
SelectedSource.Network = Network;
|
||||||
|
SelectedSource.Enabled = Enabled;
|
||||||
|
|
||||||
var hasRule = SelectedSource.Domain?.Count > 0
|
var hasRule = SelectedSource.Domain?.Count > 0
|
||||||
|| SelectedSource.Ip?.Count > 0
|
|| SelectedSource.Ip?.Count > 0
|
||||||
@@ -110,8 +135,7 @@ public partial class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
|||||||
var profileItem = await profileSelectViewModel.GetProfileItem();
|
var profileItem = await profileSelectViewModel.GetProfileItem();
|
||||||
if (profileItem != null)
|
if (profileItem != null)
|
||||||
{
|
{
|
||||||
SelectedSource.OutboundTag = profileItem.Remarks;
|
OutboundTag = profileItem.Remarks;
|
||||||
SelectedSource = JsonUtils.DeepCopy(SelectedSource);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
});
|
});
|
||||||
ImportRulesFromFileCmd = ReactiveCommand.CreateFromTask(async () =>
|
ImportRulesFromFileCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
var fileName = await BrowseRulesFileInteraction.Handle(RxVoid.Default);
|
var fileName = await BrowseRulesFileInteraction.HandleSafe(RxVoid.Default);
|
||||||
await ImportRulesFromFileAsync(fileName);
|
await ImportRulesFromFileAsync(fileName);
|
||||||
});
|
});
|
||||||
ImportRulesFromClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
|
ImportRulesFromClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -156,7 +156,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
};
|
};
|
||||||
await SetClipboardDataInteraction.Handle(JsonUtils.Serialize(lst, options));
|
await SetClipboardDataInteraction.HandleSafe(JsonUtils.Serialize(lst, options));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +277,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
var stringData = clipboardData;
|
var stringData = clipboardData;
|
||||||
if (clipboardData == null)
|
if (clipboardData == null)
|
||||||
{
|
{
|
||||||
var result = await ReadTextFromClipboardInteraction.Handle(RxVoid.Default);
|
var result = await ReadTextFromClipboardInteraction.HandleSafe(RxVoid.Default);
|
||||||
if (result.IsNullOrEmpty())
|
if (result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
@@ -315,7 +315,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
private async Task<int> AddBatchRoutingRulesAsync(RoutingItem routingItem, string? clipboardData)
|
private async Task<int> AddBatchRoutingRulesAsync(RoutingItem routingItem, string? clipboardData)
|
||||||
{
|
{
|
||||||
var blReplace = false;
|
var blReplace = false;
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.AddBatchRoutingRulesYesNo) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.AddBatchRoutingRulesYesNo) == false)
|
||||||
{
|
{
|
||||||
blReplace = true;
|
blReplace = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ public partial class RoutingSettingViewModel : MyReactiveObject
|
|||||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ public partial class StatusBarViewModel : MyReactiveObject
|
|||||||
sb.AppendLine($"{cmd} HTTPS_PROXY={Global.HttpProtocol}{address}");
|
sb.AppendLine($"{cmd} HTTPS_PROXY={Global.HttpProtocol}{address}");
|
||||||
sb.AppendLine($"{cmd} ALL_PROXY={Global.Socks5Protocol}{address}");
|
sb.AppendLine($"{cmd} ALL_PROXY={Global.Socks5Protocol}{address}");
|
||||||
|
|
||||||
await SetClipboardDataInteraction.Handle(sb.ToString());
|
await SetClipboardDataInteraction.HandleSafe(sb.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddServerViaClipboard()
|
private async Task AddServerViaClipboard()
|
||||||
@@ -297,21 +297,14 @@ public partial class StatusBarViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var models = new List<ComboItem>();
|
var models = lstModel.Select(it => new ComboItem { ID = it.IndexId, Text = it.GetSummary() }).ToList();
|
||||||
BlServers = true;
|
|
||||||
foreach (var it in lstModel)
|
|
||||||
{
|
|
||||||
var name = it.GetSummary();
|
|
||||||
|
|
||||||
var item = new ComboItem() { ID = it.IndexId, Text = name };
|
BlServers = true;
|
||||||
models.Add(item);
|
|
||||||
if (_config.IndexId == it.IndexId)
|
|
||||||
{
|
|
||||||
SelectedServer = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Servers.Clear();
|
Servers.Clear();
|
||||||
Servers.AddRange(models);
|
Servers.AddRange(models);
|
||||||
|
|
||||||
|
// Update the ItemsSource before SelectedItem so a collection reset does not clear the tray selection.
|
||||||
|
SelectedServer = models.FirstOrDefault(it => it.ID == _config.IndexId) ?? new();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ServerSelectedChanged(bool c)
|
private void ServerSelectedChanged(bool c)
|
||||||
@@ -389,14 +382,7 @@ public partial class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (blChange)
|
if (blChange)
|
||||||
{
|
{
|
||||||
try
|
await DispatcherRefreshIconInteraction.HandleSafe(RxVoid.Default);
|
||||||
{
|
|
||||||
await DispatcherRefreshIconInteraction.Handle(RxVoid.Default);
|
|
||||||
}
|
|
||||||
catch (UnhandledInteractionException<RxVoid, RxVoid>)
|
|
||||||
{
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,7 +418,7 @@ public partial class StatusBarViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
|
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
|
||||||
ReloadRequested.Publish();
|
ReloadRequested.Publish();
|
||||||
await DispatcherRefreshIconInteraction.Handle(RxVoid.Default);
|
await DispatcherRefreshIconInteraction.HandleSafe(RxVoid.Default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,7 +455,7 @@ public partial class StatusBarViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var password = await PasswordInputInteraction.Handle(RxVoid.Default);
|
var password = await PasswordInputInteraction.HandleSafe(RxVoid.Default);
|
||||||
if (password.IsNullOrEmpty())
|
if (password.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
_config.TunModeItem.EnableTun = false;
|
_config.TunModeItem.EnableTun = false;
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ public partial class SubEditViewModel : MyReactiveObject, ICloseable
|
|||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public partial string CustomCoreType { get; set; }
|
public partial string CustomCoreType { get; set; }
|
||||||
|
[Reactive]
|
||||||
|
public partial string PrevProfile { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public partial string NextProfile { get; set; }
|
||||||
|
|
||||||
public ReactiveCommand<RxVoid, RxVoid> SelectPrevProfileCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SelectPrevProfileCmd { get; }
|
||||||
public ReactiveCommand<RxVoid, RxVoid> SelectNextProfileCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SelectNextProfileCmd { get; }
|
||||||
@@ -23,8 +28,7 @@ public partial class SubEditViewModel : MyReactiveObject, ICloseable
|
|||||||
var profileItem = await SelectProfileAsync();
|
var profileItem = await SelectProfileAsync();
|
||||||
if (profileItem != null)
|
if (profileItem != null)
|
||||||
{
|
{
|
||||||
SelectedSource?.PrevProfile = profileItem.Remarks;
|
PrevProfile = profileItem.Remarks;
|
||||||
SelectedSource = JsonUtils.DeepCopy(SelectedSource);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
SelectNextProfileCmd = ReactiveCommand.CreateFromTask(async () =>
|
SelectNextProfileCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -32,8 +36,7 @@ public partial class SubEditViewModel : MyReactiveObject, ICloseable
|
|||||||
var profileItem = await SelectProfileAsync();
|
var profileItem = await SelectProfileAsync();
|
||||||
if (profileItem != null)
|
if (profileItem != null)
|
||||||
{
|
{
|
||||||
SelectedSource?.NextProfile = profileItem.Remarks;
|
NextProfile = profileItem.Remarks;
|
||||||
SelectedSource = JsonUtils.DeepCopy(SelectedSource);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -43,6 +46,8 @@ public partial class SubEditViewModel : MyReactiveObject, ICloseable
|
|||||||
|
|
||||||
SelectedSource = subItem.Id.IsNullOrEmpty() ? subItem : JsonUtils.DeepCopy(subItem);
|
SelectedSource = subItem.Id.IsNullOrEmpty() ? subItem : JsonUtils.DeepCopy(subItem);
|
||||||
CustomCoreType = SelectedSource.CustomCoreType?.ToString() ?? string.Empty;
|
CustomCoreType = SelectedSource.CustomCoreType?.ToString() ?? string.Empty;
|
||||||
|
PrevProfile = SelectedSource.PrevProfile;
|
||||||
|
NextProfile = SelectedSource.NextProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveSubAsync()
|
private async Task SaveSubAsync()
|
||||||
@@ -72,6 +77,8 @@ public partial class SubEditViewModel : MyReactiveObject, ICloseable
|
|||||||
}
|
}
|
||||||
|
|
||||||
SelectedSource.CustomCoreType = Enum.TryParse<ECoreType>(CustomCoreType, out var coreType) ? coreType : null;
|
SelectedSource.CustomCoreType = Enum.TryParse<ECoreType>(CustomCoreType, out var coreType) ? coreType : null;
|
||||||
|
SelectedSource.PrevProfile = PrevProfile;
|
||||||
|
SelectedSource.NextProfile = NextProfile;
|
||||||
|
|
||||||
if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0)
|
if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public partial class SubSettingViewModel : MyReactiveObject
|
|||||||
}, canEditRemove);
|
}, canEditRemove);
|
||||||
SubShareCmd = ReactiveCommand.CreateFromTask(async () =>
|
SubShareCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
await ShareSubInteraction.Handle(SelectedSource?.Url);
|
await ShareSubInteraction.HandleSafe(SelectedSource?.Url);
|
||||||
}, canEditRemove);
|
}, canEditRemove);
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
@@ -84,7 +84,7 @@ public partial class SubSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task DeleteSubAsync()
|
private async Task DeleteSubAsync()
|
||||||
{
|
{
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
|
|||||||
.Subscribe(InitializeData)
|
.Subscribe(InitializeData)
|
||||||
.DisposeWith(disposables);
|
.DisposeWith(disposables);
|
||||||
|
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Port, v => v.txtPort.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Port, v => v.txtPort.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Enabled, v => v.togEnabled.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Enabled, v => v.togEnabled.IsChecked).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.Domain, v => v.txtDomain.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Domain, v => v.txtDomain.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.IP, v => v.txtIP.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.IP, v => v.txtIP.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.Process, v => v.txtProcess.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Process, v => v.txtProcess.Text).DisposeWith(disposables);
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
|
|||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Sort, v => v.txtSort.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Sort, v => v.txtSort.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Filter, v => v.txtFilter.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Filter, v => v.txtFilter.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.ConvertTarget, v => v.cmbConvertTarget.SelectedValue).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.ConvertTarget, v => v.cmbConvertTarget.SelectedValue).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.PrevProfile, v => v.txtPrevProfile.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.PrevProfile, v => v.txtPrevProfile.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.NextProfile, v => v.txtNextProfile.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.NextProfile, v => v.txtNextProfile.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.PreSocksPort, v => v.txtPreSocksPort.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.PreSocksPort, v => v.txtPreSocksPort.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Memo, v => v.txtMemo.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Memo, v => v.txtMemo.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.CustomCoreType, v => v.cmbCustomCoreType.SelectedValue).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.CustomCoreType, v => v.cmbCustomCoreType.SelectedValue).DisposeWith(disposables);
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ public partial class RoutingRuleDetailsWindow
|
|||||||
.Subscribe(InitializeData)
|
.Subscribe(InitializeData)
|
||||||
.DisposeWith(disposables);
|
.DisposeWith(disposables);
|
||||||
|
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Port, v => v.txtPort.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Port, v => v.txtPort.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Network, v => v.cmbNetwork.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Enabled, v => v.togEnabled.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Enabled, v => v.togEnabled.IsChecked).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.Domain, v => v.txtDomain.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Domain, v => v.txtDomain.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.IP, v => v.txtIP.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.IP, v => v.txtIP.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.Process, v => v.txtProcess.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Process, v => v.txtProcess.Text).DisposeWith(disposables);
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ public partial class SubEditWindow
|
|||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Sort, v => v.txtSort.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Sort, v => v.txtSort.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Filter, v => v.txtFilter.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Filter, v => v.txtFilter.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.ConvertTarget, v => v.cmbConvertTarget.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.ConvertTarget, v => v.cmbConvertTarget.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.PrevProfile, v => v.txtPrevProfile.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.PrevProfile, v => v.txtPrevProfile.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.NextProfile, v => v.txtNextProfile.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.NextProfile, v => v.txtNextProfile.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.PreSocksPort, v => v.txtPreSocksPort.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.PreSocksPort, v => v.txtPreSocksPort.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Memo, v => v.txtMemo.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Memo, v => v.txtMemo.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.CustomCoreType, v => v.cmbCustomCoreType.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.CustomCoreType, v => v.cmbCustomCoreType.Text).DisposeWith(disposables);
|
||||||
|
|||||||
Reference in New Issue
Block a user