Compare commits

...

12 Commits

Author SHA1 Message Date
2dust
e0ad2c46e6 up 7.24.7 2026-08-15 10:14:52 +08:00
2dust
230a2f6773 Bug fix
https://github.com/2dust/v2rayN/issues/9936
2026-08-14 12:08:43 +08:00
白龙
e7cc4c832e Fix tray server selection being cleared after refresh (#9927) (#9928)
* Fix tray server selection after refresh (#9927)

* Update StatusBarViewModel.cs

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-08-14 10:38:27 +08:00
DHR60
fb7033aab5 Add hy2 ech support (#9954) 2026-08-14 10:22:31 +08:00
DHR60
b6cc3913ac Perf hy2 fmt (#9953) 2026-08-14 10:22:10 +08:00
DHR60
bc06a5dc21 Fix (#9951)
* Fix

* Fix

* Add HandleSafe
2026-08-14 10:20:58 +08:00
Chen, Ting-An
9c1951178c fix(i18n): polish Traditional Chinese DNS settings (#9947)
Replace Simplified Chinese and ambiguous translations in the DNS and full configuration template settings with natural Taiwan terminology. Clarify related descriptions without changing resource keys or application behavior.
2026-08-14 10:20:16 +08:00
DHR60
b3df42b9ba Sync finalmask order (#9943) 2026-08-14 10:19:56 +08:00
liuclare
e01717d832 Narrow the TUN self-address drop rule to single addresses (#9935)
The rule added in #9897 takes the TUN inbound's `address` verbatim as `ip_cidr`,
so a /30 or /126 interface prefix becomes the match range.

sing-tun derives the TUN's DNS entry from the address right after the interface's
own and hands it to the system resolver: Windows through luid.SetDNS in
tun_windows.go, Linux through systemd-resolved in tun_linux.go, both guarded only
by AutoRoute && !EXP_DisableDNSHijack. HasNextAddress keeps that address inside
the interface prefix, every preset in Global.TunIPv4Address is a /30 and every
IPv6 preset a /126, and the sing-box system stack rejects single-address
prefixes, so there is no configuration where it falls outside.

Queries from the system resolver then hit the drop rule and time out with no
response and no ICMP. Name resolution fails for the whole system while the proxy
path itself stays healthy, which makes it read as a DNS outage rather than a
routing rule. Reported in #9934 and #9926.

Matching each address on its own keeps what #9897 set out to block - the loop it
diagnosed was addressed to the interface address itself - and leaves the DNS
entry to sing-box.

Also restores the two regression tests #9897 came with, removed by eff58459
(#9817) while its implementation and template fix stayed in place.
ShouldRejectTrafficToTunOwnAddresses now asserts the single-address form and
additionally pins the prefix length, so it covers both the loop it was written
for and the resolver address it must not cover.

Verified on Linux by running sing-box directly from a generated config, changing
only this rule's prefix length between runs:

  ip_cidr ["172.18.0.1/30"]   getent hosts www.google.com -> empty, 3/3
  ip_cidr ["172.18.0.1/32"]   getent hosts www.google.com -> resolved, 3/3

dig against a public resolver, naked-IP HTTPS and the local mixed port were
unaffected in both runs. End to end, a build of this branch emits
drop ip_cidr ["172.18.0.1/32"] and system resolution works while its TUN is up.

Co-authored-by: liuclare <177657698+liuclare@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 15:09:04 +08:00
DHR60
8b4063b44b Set tun "route only" to true (#9933) 2026-08-09 20:53:00 +08:00
DHR60
3136f573dd Revert "Fix Windows TUN restart issue (#9925)" (#9932)
This reverts commit f347fe1c03.
2026-08-09 20:52:36 +08:00
liuclare
e101b1d7b0 Always route IPv6 into Xray TUN regardless of EnableIPv6Address (#9930)
* Always route IPv6 into Xray TUN regardless of EnableIPv6Address

EnableIPv6Address controls whether the TUN interface is assigned an IPv6
address, but it also gated whether ::/0 was added to autoSystemRoutingTable.
With the default (false), IPv6 had no route pointing at the TUN device and
followed the system default route instead, leaving the tunnel unproxied and
exposing the host's real IPv6 address.

The embedded template SampleTunInbound already declares both families; the
generated config discarded it. #9843 restored ::/0 only inside the
EnableIPv6Address == true branch, so the false branch still leaks.

Route both families unconditionally and let the option control only the
interface address. The same conditional existed a second time in the
RouteExcludeAddress branch and is fixed as well.

Fixes #9929

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Add regression tests for IPv6 routing in the Xray TUN inbound

Both assertions fail on 31044f44 and pass with the fix:

  Tun_ShouldRouteIPv6IntoTunnel(enableIPv6Address: False)
    Expected collection {"0.0.0.0/0"} to contain "::/0".

  TunRouteExcludeAddress_ShouldIncludeIPv6Ranges
    Expected collection {...44 IPv4 ranges...} to have an item matching x.Contains(:).

The theory also covers enableIPv6Address: true, which passes on both revisions,
so the tests only fail while the defect is present. The gateway count assertion
pins the intended split of responsibilities: EnableIPv6Address governs the
interface address, never the routing table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: liuclare <177657698+liuclare@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 20:15:18 +08:00
26 changed files with 323 additions and 137 deletions

View File

@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.24.6</Version>
<Version>7.24.7</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -251,4 +251,12 @@ internal static class CoreConfigTestFactory
config.TunModeItem.RouteExcludeAddress = ["10.0.0.1/32", "192.168.1.0/24", "fc00::/7"];
return config;
}
public static Config CreateConfigWithTun(ECoreType coreType, bool enableIPv6Address)
{
var config = CreateConfig(coreType);
config.TunModeItem.EnableTun = true;
config.TunModeItem.EnableIPv6Address = enableIPv6Address;
return config;
}
}

View File

@@ -56,6 +56,85 @@ public class CoreConfigSingboxServiceTests
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]
public void GenerateClientConfigContent_BindInterface_ShouldUseDialBindInterface()
{

View File

@@ -570,6 +570,51 @@ public class CoreConfigV2rayServiceTests
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]
public void GenerateClientConfigContent_TunRouteExcludeAddress()
{

View File

@@ -134,4 +134,25 @@ public static class Extension
.Replace("\r", 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;
}
}
}

View File

@@ -54,12 +54,12 @@ internal static class WindowsUtils
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)
{
try
{
var sum = MD5.HashData(Encoding.UTF8.GetBytes($"wintun{tunName}"));
var sum = MD5.HashData(Encoding.UTF8.GetBytes(tunName));
var guid = new Guid(sum);
var pnpUtilPath = @"C:\Windows\System32\pnputil.exe";
var arg = $$""" /remove-device "SWD\Wintun\{{{guid}}}" """;

View File

@@ -1,5 +1,3 @@
using System.Collections.Specialized;
namespace ServiceLib.Handler.Fmt;
public class Hysteria2Fmt : BaseFmt
@@ -169,7 +167,16 @@ public class Hysteria2Fmt : BaseFmt
if (item.CertSha.IsNullOrEmpty())
{
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
{
Ports = GetQueryDecoded(query, "mport"),
@@ -212,6 +219,10 @@ public class Hysteria2Fmt : BaseFmt
var sha = item.CertSha;
dicQuery.Add("pinSHA256", Utils.UrlEncode(sha));
}
if (!item.EchConfigList.IsNullOrEmpty())
{
dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList));
}
var protocolExtraItem = item.GetProtocolExtra();
var isGecko = !protocolExtraItem.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtraItem.GeckoMaxPacketSize.IsNullOrEmpty();
if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())

View File

@@ -1417,7 +1417,7 @@
<value>新增 [Anytls] 節點</value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>遠 DNS</value>
<value>遠 DNS</value>
</data>
<data name="TbDomesticDNS" xml:space="preserve">
<value>直連 DNS</value>
@@ -1429,16 +1429,16 @@
<value>代理目標解析策略</value>
</data>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>新增常用 DNS Hosts</value>
<value>新增常用 DNS 主機對應</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
<value>阻止 SVCB HTTPS 查詢</value>
<value>封鎖 SVCB HTTPS 查詢</value>
</data>
<data name="TbDNSHostsConfig" xml:space="preserve">
<value>DNS Hosts網域名稱1 ip1 ip2” 一行一個</value>
<value>DNS 主機對應:(每行一組「網域名稱1 ip1 ip2</value>
</data>
<data name="ThBasicDNSSettings" xml:space="preserve">
<value>DNS 基礎設定</value>
@@ -1447,49 +1447,49 @@
<value>DNS 進階設定</value>
</data>
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
<value>校驗相應地區域名 IP</value>
<value>驗證區域網域名稱的 IP</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>配置後,會對相應地區域名(如 geosite:cn - geoip:cn的返回 IP 進行校驗,僅返回期望 IP</value>
<value>設定後,系統會驗證區域網域名稱(例如 geosite:cn - geoip:cn所傳回的 IP並只傳回預期的 IP</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>啟用自訂 DNS</value>
</data>
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
<value>自訂 DNS 已啟用,此頁面配置將無效</value>
<value>已啟用自訂 DNS,此頁面的設定將不會生效</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>開啟後將阻止 ECH HTTP/3 可用性查詢</value>
<value>啟用後將封鎖 ECH HTTP/3 可用性查詢</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>請填寫正確的配置範本</value>
<value>請填寫正確的設定範本</value>
</data>
<data name="menuFullConfigTemplate" xml:space="preserve">
<value>完整配置範本設定</value>
<value>完整設定範本</value>
</data>
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
<value>啟用完整配置範本</value>
<value>啟用完整設定範本</value>
</data>
<data name="TbRayFullConfigTemplate" xml:space="preserve">
<value>v2ray 完整配置範本</value>
<value>v2ray 完整設定範本</value>
</data>
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
<value>僅添加出站配置,routing.balancers routing.rules.outboundTag,點擊查看文檔</value>
<value>僅新增出站設定、routing.balancers routing.rules.outboundTag。點選以查看說明文件</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>不添加非代理協定出站</value>
<value>不新增非代理協定出站</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>設定上游代理 tag</value>
<value>設定上游代理標籤</value>
</data>
<data name="TbSBFullConfigTemplate" xml:space="preserve">
<value>sing-box 完整配置範本</value>
<value>sing-box 完整設定範本</value>
</data>
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
<value>僅添加出站端點配置,點擊查看文檔</value>
<value>僅新增出站端點設定。點選以查看說明文件</value>
</data>
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
<value>此功能供高級用戶和有特殊需求的用戶使用。 啟用此功能後,將忽略 Core 基礎設定DNS 設定 路由設定。你需要保證系統代理的埠和流量統計等功能的配置正確,一切都由你來設定。</value>
<value>此功能適合進階使用者與有特殊需求的使用者。啟用後,Core 基礎設定DNS 設定路由設定將被忽略。請確認系統代理連接埠、流量統計及其他相關設定皆正確;所有項目都必須由您自行設定。</value>
</data>
<data name="MsgStartParsingSubscription" xml:space="preserve">
<value>開始解析和處理訂閱內容</value>
@@ -1498,10 +1498,10 @@
<value>選擇節點</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>默認全局生效,僅在 sing-box 中內置 FakeIP 過濾。</value>
<value>預設會套用至全域;內建 FakeIP 過濾僅支援 sing-box。</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>請至少添加一個節點</value>
<value>請至少新增一個設定檔</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>策略組</value>
@@ -1627,7 +1627,7 @@
<value>提供過期快取Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>行查詢</value>
<value>行查詢</value>
</data>
<data name="TbDomesticDNSTips" xml:space="preserve">
<value>預設僅在路由期間進行解析時調用</value>

View File

@@ -48,12 +48,18 @@ public partial class CoreConfigSingboxService
// 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
// 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;
if (tunAddresses?.Count > 0)
{
_coreConfig.route.rules.Add(new()
{
ip_cidr = [.. tunAddresses],
ip_cidr = [.. tunAddresses.Select(ToSingleAddressPrefix)],
action = "reject",
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()
{
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

View File

@@ -67,12 +67,14 @@ public partial class CoreConfigV2rayService
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
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)
{
var address6 = _config.TunModeItem.IPv6Address.NullIfEmpty() ?? Global.TunIPv6Address.First();
tunInbound.settings.gateway.Add(address6);
tunInbound.settings.autoSystemRoutingTable.Add("::/0");
}
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
@@ -81,7 +83,8 @@ public partial class CoreConfigV2rayService
tunInbound.settings.autoOutboundsInterface = bindInterface;
}
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 })
{
@@ -118,15 +121,8 @@ public partial class CoreConfigV2rayService
includeList = IPNetwork2.Supernet(includeList.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();
}
else
{
tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString()).ToList();
}
tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString())
.Concat(includeListV6.Select(x => x.ToString())).ToList();
}
_coreConfig.inbounds.Add(tunInbound);

View File

@@ -512,6 +512,7 @@ public partial class CoreConfigV2rayService
settings = new MaskSettings4Ray { value = kcpSeed },
});
}
kcpFinalmask.udp?.Reverse();
streamSettings.kcpSettings = kcpSettings;
streamSettings.finalmask = kcpFinalmask;
break;
@@ -666,6 +667,7 @@ public partial class CoreConfigV2rayService
version = 2,
auth = _node.Password,
};
hy2Finalmask.udp?.Reverse();
streamSettings.finalmask = hy2Finalmask;
break;

View File

@@ -26,7 +26,7 @@ public partial class AddServer2ViewModel : MyReactiveObject, ICloseable
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
var fileName = await BrowseConfigFileInteraction.Handle(RxVoid.Default);
var fileName = await BrowseConfigFileInteraction.HandleSafe(RxVoid.Default);
if (fileName.IsNullOrEmpty())
{
return;

View File

@@ -85,8 +85,6 @@ public partial class MainWindowViewModel : MyReactiveObject
#endregion Menu
private readonly SynchronizationContext _uiContext = SynchronizationContext.Current;
#region Init
public MainWindowViewModel()
@@ -302,7 +300,7 @@ public partial class MainWindowViewModel : MyReactiveObject
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async blShow =>
{
await ShowHideWindowInteraction.Handle(blShow);
await ShowHideWindowInteraction.HandleSafe(blShow);
});
StatusBarViewModel.SetDefaultServerRequested
@@ -413,14 +411,25 @@ public partial class MainWindowViewModel : MyReactiveObject
private async Task RefreshServersDispatcherAsync()
{
//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()
{
//await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
_uiContext?.Post(_ => _ = ProfilesViewModel.RefreshSubscriptions(), null);
await Signal.FromAsync(async () =>
{
await ProfilesViewModel.RefreshSubscriptions();
return RxVoid.Default;
})
.SubscribeOn(RxSchedulers.MainThreadScheduler)
.ToTask();
}
#endregion Servers && Groups
@@ -467,7 +476,7 @@ public partial class MainWindowViewModel : MyReactiveObject
var stringData = clipboardData;
if (clipboardData == null)
{
var result = await ReadTextFromClipboardInteraction.Handle(RxVoid.Default);
var result = await ReadTextFromClipboardInteraction.HandleSafe(RxVoid.Default);
if (result.IsNullOrEmpty())
{
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
@@ -490,7 +499,7 @@ public partial class MainWindowViewModel : MyReactiveObject
public async Task AddServerViaScanAsync()
{
var result = await ScanScreenInteraction.Handle(RxVoid.Default);
var result = await ScanScreenInteraction.HandleSafe(RxVoid.Default);
await ScanScreenResult(result);
}
@@ -502,7 +511,7 @@ public partial class MainWindowViewModel : MyReactiveObject
public async Task AddServerViaImageAsync()
{
var imageFileName = await BrowseImageFileInteraction.Handle(RxVoid.Default);
var imageFileName = await BrowseImageFileInteraction.HandleSafe(RxVoid.Default);
await AddScanResultAsync(imageFileName);
}
@@ -691,10 +700,12 @@ public partial class MainWindowViewModel : MyReactiveObject
//{
// await ClashProxiesViewModel.ProxiesReload();
//}, RxSchedulers.MainThreadScheduler);
RxSchedulers.MainThreadScheduler.Schedule(async () =>
{
await ClashProxiesViewModel.ProxiesReload();
});
await Signal.FromAsync(async () =>
{
await ClashProxiesViewModel.ProxiesReload();
return RxVoid.Default;
}).SubscribeOn(RxSchedulers.MainThreadScheduler)
.ToTask();
}
ReloadResult(showClashUI);

View File

@@ -74,7 +74,7 @@ public partial class MsgViewModel : MyReactiveObject
{
try
{
await DispatcherShowMsgInteraction.Handle(sb.ToString());
await DispatcherShowMsgInteraction.HandleSafe(sb.ToString());
}
catch (Exception)
{

View File

@@ -138,13 +138,7 @@ public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable
await RefreshServers();
try
{
await ProfilesFocusInteraction.Handle(RxVoid.Default);
}
catch (UnhandledInteractionException<RxVoid, RxVoid>)
{
}
await ProfilesFocusInteraction.HandleSafe(RxVoid.Default);
}
private async Task ServerFilterChanged(bool c)

View File

@@ -345,13 +345,7 @@ public partial class ProfilesViewModel : MyReactiveObject
await RefreshServers();
try
{
await ProfilesFocusInteraction.Handle(RxVoid.Default);
}
catch (UnhandledInteractionException<RxVoid, RxVoid>)
{
}
await ProfilesFocusInteraction.HandleSafe(RxVoid.Default);
}
private async Task ServerFilterChanged(bool c)
@@ -395,13 +389,7 @@ public partial class ProfilesViewModel : MyReactiveObject
SelectedProfile = selected ?? lstModel.First();
}
try
{
await DispatcherRefreshServersBizInteraction.Handle(RxVoid.Default);
}
catch (UnhandledInteractionException<RxVoid, RxVoid>)
{
}
await DispatcherRefreshServersBizInteraction.HandleSafe(RxVoid.Default);
}
public async Task RefreshSubscriptions()
@@ -419,7 +407,7 @@ public partial class ProfilesViewModel : MyReactiveObject
public async Task AdjustMainLvColWidth()
{
await AdjustMainLvColWidthInteraction.Handle(RxVoid.Default);
await AdjustMainLvColWidthInteraction.HandleSafe(RxVoid.Default);
}
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
@@ -535,7 +523,7 @@ public partial class ProfilesViewModel : MyReactiveObject
{
return;
}
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
{
return;
}
@@ -556,7 +544,7 @@ public partial class ProfilesViewModel : MyReactiveObject
private async Task RemoveDuplicateServer()
{
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
{
return;
}
@@ -631,7 +619,7 @@ public partial class ProfilesViewModel : MyReactiveObject
return;
}
await ShareServerInteraction.Handle(url);
await ShareServerInteraction.HandleSafe(url);
}
private async Task GenGroupAllServer()
@@ -799,13 +787,13 @@ public partial class ProfilesViewModel : MyReactiveObject
}
else
{
await SetClipboardDataInteraction.Handle((string)result.Data);
await SetClipboardDataInteraction.HandleSafe((string)result.Data);
NoticeManager.Instance.SendMessage(ResUI.OperationSuccess);
}
}
else
{
await SaveFileDialogInteraction.Handle(item);
await SaveFileDialogInteraction.HandleSafe(item);
}
}
@@ -854,11 +842,11 @@ public partial class ProfilesViewModel : MyReactiveObject
{
if (blEncode)
{
await SetClipboardDataInteraction.Handle(Utils.Base64Encode(sb.ToString()));
await SetClipboardDataInteraction.HandleSafe(Utils.Base64Encode(sb.ToString()));
}
else
{
await SetClipboardDataInteraction.Handle(sb.ToString());
await SetClipboardDataInteraction.HandleSafe(sb.ToString());
}
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
}
@@ -881,7 +869,7 @@ public partial class ProfilesViewModel : MyReactiveObject
if (!result.IsNullOrEmpty())
{
await SetClipboardDataInteraction.Handle(result);
await SetClipboardDataInteraction.HandleSafe(result);
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
}
else
@@ -925,7 +913,7 @@ public partial class ProfilesViewModel : MyReactiveObject
return;
}
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
{
return;
}

View File

@@ -25,6 +25,21 @@ public partial class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
[Reactive]
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> SaveCmd { get; }
@@ -57,6 +72,11 @@ public partial class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
IP = Utils.List2String(SelectedSource.Ip, true);
Process = Utils.List2String(SelectedSource.Process, true);
RuleType = SelectedSource.RuleType?.ToString();
OutboundTag = SelectedSource.OutboundTag;
Remarks = SelectedSource.Remarks;
Port = SelectedSource.Port;
Network = SelectedSource.Network;
Enabled = SelectedSource.Enabled;
}
private async Task SaveRulesAsync()
@@ -80,6 +100,11 @@ public partial class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
SelectedSource.Protocol = ProtocolItems?.ToList();
SelectedSource.InboundTag = InboundTagItems?.ToList();
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
|| SelectedSource.Ip?.Count > 0
@@ -110,8 +135,7 @@ public partial class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
var profileItem = await profileSelectViewModel.GetProfileItem();
if (profileItem != null)
{
SelectedSource.OutboundTag = profileItem.Remarks;
SelectedSource = JsonUtils.DeepCopy(SelectedSource);
OutboundTag = profileItem.Remarks;
}
}
}

View File

@@ -48,7 +48,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
});
ImportRulesFromFileCmd = ReactiveCommand.CreateFromTask(async () =>
{
var fileName = await BrowseRulesFileInteraction.Handle(RxVoid.Default);
var fileName = await BrowseRulesFileInteraction.HandleSafe(RxVoid.Default);
await ImportRulesFromFileAsync(fileName);
});
ImportRulesFromClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
@@ -156,7 +156,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
return;
}
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
{
return;
}
@@ -199,7 +199,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
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;
if (clipboardData == null)
{
var result = await ReadTextFromClipboardInteraction.Handle(RxVoid.Default);
var result = await ReadTextFromClipboardInteraction.HandleSafe(RxVoid.Default);
if (result.IsNullOrEmpty())
{
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
@@ -315,7 +315,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
private async Task<int> AddBatchRoutingRulesAsync(RoutingItem routingItem, string? clipboardData)
{
var blReplace = false;
if (await ShowYesNoInteraction.Handle(ResUI.AddBatchRoutingRulesYesNo) == false)
if (await ShowYesNoInteraction.HandleSafe(ResUI.AddBatchRoutingRulesYesNo) == false)
{
blReplace = true;
}

View File

@@ -147,7 +147,7 @@ public partial class RoutingSettingViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
return;
}
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
{
return;
}

View File

@@ -243,7 +243,7 @@ public partial class StatusBarViewModel : MyReactiveObject
sb.AppendLine($"{cmd} HTTPS_PROXY={Global.HttpProtocol}{address}");
sb.AppendLine($"{cmd} ALL_PROXY={Global.Socks5Protocol}{address}");
await SetClipboardDataInteraction.Handle(sb.ToString());
await SetClipboardDataInteraction.HandleSafe(sb.ToString());
}
private async Task AddServerViaClipboard()
@@ -297,21 +297,14 @@ public partial class StatusBarViewModel : MyReactiveObject
return;
}
var models = new List<ComboItem>();
BlServers = true;
foreach (var it in lstModel)
{
var name = it.GetSummary();
var models = lstModel.Select(it => new ComboItem { ID = it.IndexId, Text = it.GetSummary() }).ToList();
var item = new ComboItem() { ID = it.IndexId, Text = name };
models.Add(item);
if (_config.IndexId == it.IndexId)
{
SelectedServer = item;
}
}
BlServers = true;
Servers.Clear();
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)
@@ -389,14 +382,7 @@ public partial class StatusBarViewModel : MyReactiveObject
if (blChange)
{
try
{
await DispatcherRefreshIconInteraction.Handle(RxVoid.Default);
}
catch (UnhandledInteractionException<RxVoid, RxVoid>)
{
// Ignore
}
await DispatcherRefreshIconInteraction.HandleSafe(RxVoid.Default);
}
}
@@ -432,7 +418,7 @@ public partial class StatusBarViewModel : MyReactiveObject
{
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
ReloadRequested.Publish();
await DispatcherRefreshIconInteraction.Handle(RxVoid.Default);
await DispatcherRefreshIconInteraction.HandleSafe(RxVoid.Default);
}
}
@@ -469,7 +455,7 @@ public partial class StatusBarViewModel : MyReactiveObject
}
else
{
var password = await PasswordInputInteraction.Handle(RxVoid.Default);
var password = await PasswordInputInteraction.HandleSafe(RxVoid.Default);
if (password.IsNullOrEmpty())
{
_config.TunModeItem.EnableTun = false;

View File

@@ -9,6 +9,11 @@ public partial class SubEditViewModel : MyReactiveObject, ICloseable
[Reactive]
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> SelectNextProfileCmd { get; }
@@ -23,8 +28,7 @@ public partial class SubEditViewModel : MyReactiveObject, ICloseable
var profileItem = await SelectProfileAsync();
if (profileItem != null)
{
SelectedSource?.PrevProfile = profileItem.Remarks;
SelectedSource = JsonUtils.DeepCopy(SelectedSource);
PrevProfile = profileItem.Remarks;
}
});
SelectNextProfileCmd = ReactiveCommand.CreateFromTask(async () =>
@@ -32,8 +36,7 @@ public partial class SubEditViewModel : MyReactiveObject, ICloseable
var profileItem = await SelectProfileAsync();
if (profileItem != null)
{
SelectedSource?.NextProfile = profileItem.Remarks;
SelectedSource = JsonUtils.DeepCopy(SelectedSource);
NextProfile = profileItem.Remarks;
}
});
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
@@ -43,6 +46,8 @@ public partial class SubEditViewModel : MyReactiveObject, ICloseable
SelectedSource = subItem.Id.IsNullOrEmpty() ? subItem : JsonUtils.DeepCopy(subItem);
CustomCoreType = SelectedSource.CustomCoreType?.ToString() ?? string.Empty;
PrevProfile = SelectedSource.PrevProfile;
NextProfile = SelectedSource.NextProfile;
}
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.PrevProfile = PrevProfile;
SelectedSource.NextProfile = NextProfile;
if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0)
{

View File

@@ -40,7 +40,7 @@ public partial class SubSettingViewModel : MyReactiveObject
}, canEditRemove);
SubShareCmd = ReactiveCommand.CreateFromTask(async () =>
{
await ShareSubInteraction.Handle(SelectedSource?.Url);
await ShareSubInteraction.HandleSafe(SelectedSource?.Url);
}, canEditRemove);
_ = Init();
@@ -84,7 +84,7 @@ public partial class SubSettingViewModel : MyReactiveObject
private async Task DeleteSubAsync()
{
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
{
return;
}

View File

@@ -26,12 +26,12 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
.Subscribe(InitializeData)
.DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.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.SelectedSource.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.SelectedSource.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.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Port, v => v.txtPort.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Network, v => v.cmbNetwork.SelectedValue).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.IP, v => v.txtIP.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Process, v => v.txtProcess.Text).DisposeWith(disposables);

View File

@@ -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.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.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.PrevProfile, v => v.txtPrevProfile.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.Memo, v => v.txtMemo.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomCoreType, v => v.cmbCustomCoreType.SelectedValue).DisposeWith(disposables);

View File

@@ -23,11 +23,11 @@ public partial class RoutingRuleDetailsWindow
.Subscribe(InitializeData)
.DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.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.SelectedSource.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.SelectedSource.Enabled, v => v.togEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Port, v => v.txtPort.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Network, v => v.cmbNetwork.Text).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.IP, v => v.txtIP.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Process, v => v.txtProcess.Text).DisposeWith(disposables);

View File

@@ -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.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.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.PrevProfile, v => v.txtPrevProfile.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.Memo, v => v.txtMemo.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomCoreType, v => v.cmbCustomCoreType.Text).DisposeWith(disposables);