Add Detour Feature

DHR60 2025-07-27 18:34:30 +08:00
parent eb310e2a1e
commit f5e82dd2b0
15 changed files with 369 additions and 215 deletions

View File

@ -13,4 +13,6 @@ public class CustomConfigItem
public ECoreType CoreType { get; set; }
public string? Config { get; set; }
public string? TunConfig { get; set; }
public bool? AddProxyOnly { get; set; } = false;
public string? ProxyDetour { get; set; }
}

View File

@ -2247,6 +2247,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Do Not Add Non-Proxy Protocol Outbound 的本地化字符串。
/// </summary>
public static string TbAddProxyProtocolOutboundOnly {
get {
return ResourceManager.GetString("TbAddProxyProtocolOutboundOnly", resourceCulture);
}
}
/// <summary>
/// 查找类似 Address 的本地化字符串。
/// </summary>
@ -2382,24 +2391,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Enable Custom DNS 的本地化字符串。
/// </summary>
public static string TbCustomDNSEnable {
get {
return ResourceManager.GetString("TbCustomDNSEnable", resourceCulture);
}
}
/// <summary>
/// 查找类似 Custom DNS Enabled, This Page&apos;s Settings Invalid 的本地化字符串。
/// </summary>
public static string TbCustomDNSEnabledPageInvalid {
get {
return ResourceManager.GetString("TbCustomDNSEnabledPageInvalid", resourceCulture);
}
}
/// <summary>
/// 查找类似 Enable Custom Config 的本地化字符串。
/// </summary>
@ -2418,6 +2409,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Enable Custom DNS 的本地化字符串。
/// </summary>
public static string TbCustomDNSEnable {
get {
return ResourceManager.GetString("TbCustomDNSEnable", resourceCulture);
}
}
/// <summary>
/// 查找类似 Custom DNS Enabled, This Page&apos;s Settings Invalid 的本地化字符串。
/// </summary>
public static string TbCustomDNSEnabledPageInvalid {
get {
return ResourceManager.GetString("TbCustomDNSEnabledPageInvalid", resourceCulture);
}
}
/// <summary>
/// 查找类似 Display GUI 的本地化字符串。
/// </summary>
@ -2761,7 +2770,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Override Outbound Config, routing.balancers and routing.rules.outboundTag Only 的本地化字符串。
/// 查找类似 Add Outbound Config Only, routing.balancers and routing.rules.outboundTag 的本地化字符串。
/// </summary>
public static string TbRayCustomConfigDesc {
get {
@ -2895,6 +2904,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Add Outbound and Endpoint Config Only 的本地化字符串。
/// </summary>
public static string TbSBCustomConfigDesc {
get {
return ResourceManager.GetString("TbSBCustomConfigDesc", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box Direct Resolution Strategy 的本地化字符串。
/// </summary>
@ -2958,15 +2976,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Override Outbound And Endpoint Config Only 的本地化字符串。
/// </summary>
public static string TbSBCustomConfigDesc {
get {
return ResourceManager.GetString("TbSBCustomConfigDesc", resourceCulture);
}
}
/// <summary>
/// 查找类似 Encryption method (security) 的本地化字符串。
/// </summary>
@ -3750,6 +3759,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Set Upstream Proxy Tag 的本地化字符串。
/// </summary>
public static string TbSetUpstreamProxyDetour {
get {
return ResourceManager.GetString("TbSetUpstreamProxyDetour", resourceCulture);
}
}
/// <summary>
/// 查找类似 Short Id 的本地化字符串。
/// </summary>

View File

@ -1483,9 +1483,15 @@
<value>sing-box Custom Config</value>
</data>
<data name="TbRayCustomConfigDesc" xml:space="preserve">
<value>Override Outbound Config, routing.balancers and routing.rules.outboundTag Only</value>
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag</value>
</data>
<data name="TbSBCustomConfigDesc" xml:space="preserve">
<value>Override Outbound And Endpoint Config Only</value>
<value>Add Outbound and Endpoint Config Only</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>Do Not Add Non-Proxy Protocol Outbound</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>Set Upstream Proxy Tag</value>
</data>
</root>

View File

@ -1483,9 +1483,15 @@
<value>sing-box Custom Config</value>
</data>
<data name="TbRayCustomConfigDesc" xml:space="preserve">
<value>Override Outbound Config, routing.balancers and routing.rules.outboundTag Only</value>
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag</value>
</data>
<data name="TbSBCustomConfigDesc" xml:space="preserve">
<value>Override Outbound And Endpoint Config Only</value>
<value>Add Outbound and Endpoint Config Only</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>Do Not Add Non-Proxy Protocol Outbound</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>Set Upstream Proxy Tag</value>
</data>
</root>

View File

@ -1483,9 +1483,15 @@
<value>sing-box Custom Config</value>
</data>
<data name="TbRayCustomConfigDesc" xml:space="preserve">
<value>Override Outbound Config, routing.balancers and routing.rules.outboundTag Only</value>
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag</value>
</data>
<data name="TbSBCustomConfigDesc" xml:space="preserve">
<value>Override Outbound And Endpoint Config Only</value>
<value>Add Outbound and Endpoint Config Only</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>Do Not Add Non-Proxy Protocol Outbound</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>Set Upstream Proxy Tag</value>
</data>
</root>

View File

@ -1483,9 +1483,15 @@
<value>sing-box Custom Config</value>
</data>
<data name="TbRayCustomConfigDesc" xml:space="preserve">
<value>Override Outbound Config, routing.balancers and routing.rules.outboundTag Only</value>
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag</value>
</data>
<data name="TbSBCustomConfigDesc" xml:space="preserve">
<value>Override Outbound And Endpoint Config Only</value>
<value>Add Outbound and Endpoint Config Only</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>Do Not Add Non-Proxy Protocol Outbound</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>Set Upstream Proxy Tag</value>
</data>
</root>

View File

@ -1480,9 +1480,15 @@
<value>sing-box 自定义配置</value>
</data>
<data name="TbRayCustomConfigDesc" xml:space="preserve">
<value>仅覆盖出站配置routing.balancers 和 routing.rules.outboundTag</value>
<value>仅添加出站配置routing.balancers 和 routing.rules.outboundTag</value>
</data>
<data name="TbSBCustomConfigDesc" xml:space="preserve">
<value>仅覆盖出站和端点配置</value>
<value>仅添加出站和端点配置</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>不添加非代理协议出站</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>设置上游代理 tag</value>
</data>
</root>

View File

@ -1480,9 +1480,15 @@
<value>sing-box Custom Config</value>
</data>
<data name="TbRayCustomConfigDesc" xml:space="preserve">
<value>Override Outbound Config, routing.balancers and routing.rules.outboundTag Only</value>
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag</value>
</data>
<data name="TbSBCustomConfigDesc" xml:space="preserve">
<value>Override Outbound And Endpoint Config Only</value>
<value>Add Outbound and Endpoint Config Only</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>Do Not Add Non-Proxy Protocol Outbound</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>Set Upstream Proxy Tag</value>
</data>
</root>

View File

@ -86,43 +86,7 @@ public class CoreConfigSingboxService
ret.Success = true;
var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.sing_box);
var customConfigItem = customConfig.Config;
if (_config.TunModeItem.EnableTun)
{
customConfigItem = customConfig.TunConfig;
}
if (customConfig.Enabled && (!customConfigItem.IsNullOrEmpty()))
{
var customConfigNode = JsonNode.Parse(customConfigItem);
if (customConfigNode != null)
{
if (customConfigNode["outbounds"] is JsonArray customOutboundsNode)
{
if (JsonNode.Parse(JsonUtils.Serialize(singboxConfig.outbounds)) is JsonArray newOutbounds)
{
foreach (var outbound in newOutbounds)
{
customOutboundsNode.Add(outbound?.DeepClone());
}
}
}
if (customConfigNode["endpoints"] is JsonArray customEndpointsNode)
{
if (singboxConfig.endpoints != null && JsonNode.Parse(JsonUtils.Serialize(singboxConfig.endpoints)) is JsonArray newEndpoints)
{
foreach (var endpoint in newEndpoints)
{
customEndpointsNode.Add(endpoint?.DeepClone());
}
}
}
ret.Data = JsonUtils.Serialize(customConfigNode);
}
}
else
{
ret.Data = JsonUtils.Serialize(singboxConfig);
}
ret.Data = await ApplyCustomConfig(customConfig, singboxConfig);
return ret;
}
catch (Exception ex)
@ -476,43 +440,7 @@ public class CoreConfigSingboxService
ret.Success = true;
var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.sing_box);
var customConfigItem = customConfig.Config;
if (_config.TunModeItem.EnableTun)
{
customConfigItem = customConfig.TunConfig;
}
if (customConfig.Enabled && (!customConfigItem.IsNullOrEmpty()))
{
var customConfigNode = JsonNode.Parse(customConfigItem);
if (customConfigNode != null)
{
if (customConfigNode["outbounds"] is JsonArray customOutboundsNode)
{
if (JsonNode.Parse(JsonUtils.Serialize(singboxConfig.outbounds)) is JsonArray newOutbounds)
{
foreach (var outbound in newOutbounds)
{
customOutboundsNode.Add(outbound?.DeepClone());
}
}
}
if (customConfigNode["endpoints"] is JsonArray customEndpointsNode)
{
if (singboxConfig.endpoints != null && JsonNode.Parse(JsonUtils.Serialize(singboxConfig.endpoints)) is JsonArray newEndpoints)
{
foreach (var endpoint in newEndpoints)
{
customEndpointsNode.Add(endpoint?.DeepClone());
}
}
}
ret.Data = JsonUtils.Serialize(customConfigNode);
}
}
else
{
ret.Data = JsonUtils.Serialize(singboxConfig);
}
ret.Data = await ApplyCustomConfig(customConfig, singboxConfig);
return ret;
}
catch (Exception ex)
@ -2277,5 +2205,61 @@ public class CoreConfigSingboxService
return 0;
}
private async Task<string> ApplyCustomConfig(CustomConfigItem customConfig, SingboxConfig singboxConfig)
{
var customConfigItem = customConfig.Config;
if (_config.TunModeItem.EnableTun)
{
customConfigItem = customConfig.TunConfig;
}
if (!customConfig.Enabled || customConfigItem.IsNullOrEmpty())
{
return JsonUtils.Serialize(singboxConfig);
}
var customConfigNode = JsonNode.Parse(customConfigItem);
if (customConfigNode == null)
{
return JsonUtils.Serialize(singboxConfig);
}
// Process outbounds
var customOutboundsNode = customConfigNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
foreach (var outbound in singboxConfig.outbounds)
{
if (outbound.type.ToLower() is "direct" or "block")
{
if (customConfig.AddProxyOnly == true)
{
continue;
}
}
else if (outbound.detour.IsNullOrEmpty() && (!customConfig.ProxyDetour.IsNullOrEmpty()))
{
outbound.detour = customConfig.ProxyDetour;
}
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
}
customConfigNode["outbounds"] = customOutboundsNode;
// Process endpoints
if (singboxConfig.endpoints != null && singboxConfig.endpoints.Count > 0)
{
var customEndpointsNode = customConfigNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray();
foreach (var endpoint in singboxConfig.endpoints)
{
if (endpoint.detour.IsNullOrEmpty() && (!customConfig.ProxyDetour.IsNullOrEmpty()))
{
endpoint.detour = customConfig.ProxyDetour;
}
customEndpointsNode.Add(JsonUtils.DeepCopy(endpoint));
}
customConfigNode["endpoints"] = customEndpointsNode;
}
return await Task.FromResult(JsonUtils.Serialize(customConfigNode));
}
#endregion private gen function
}

View File

@ -71,28 +71,7 @@ public class CoreConfigV2rayService
ret.Success = true;
var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.Xray);
if (customConfig.Enabled && (!customConfig.Config.IsNullOrEmpty()))
{
var customConfigNode = JsonNode.Parse(customConfig.Config);
if (customConfigNode != null)
{
if (customConfigNode["outbounds"] is JsonArray customOutboundsNode)
{
if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.outbounds)) is JsonArray newOutbounds)
{
foreach (var outbound in newOutbounds)
{
customOutboundsNode.Add(outbound?.DeepClone());
}
}
}
ret.Data = JsonUtils.Serialize(customConfigNode);
}
}
else
{
ret.Data = JsonUtils.Serialize(v2rayConfig);
}
ret.Data = await ApplyCustomConfig(customConfig, v2rayConfig);
return ret;
}
catch (Exception ex)
@ -223,64 +202,7 @@ public class CoreConfigV2rayService
ret.Success = true;
var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.Xray);
if (customConfig.Enabled && (!customConfig.Config.IsNullOrEmpty()))
{
var customConfigNode = JsonNode.Parse(customConfig.Config);
if (customConfigNode != null)
{
var rulesNode = customConfigNode["routing"]?["rules"];
if (rulesNode != null)
{
foreach (var rule in rulesNode.AsArray())
{
if (rule["outboundTag"]?.GetValue<string>() == Global.ProxyTag)
{
rule["outboundTag"] = null;
rule["balancerTag"] = balancer.tag;
}
}
}
// Ensure routing node exists
if (customConfigNode["routing"] == null)
{
customConfigNode["routing"] = new JsonObject();
}
// Handle balancers - append instead of override
if (customConfigNode["routing"]["balancers"] is JsonArray customBalancersNode)
{
if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)) is JsonArray newBalancers)
{
foreach (var balancerNode in newBalancers)
{
customBalancersNode.Add(balancerNode?.DeepClone());
}
}
}
else
{
customConfigNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers));
}
// append outbounds instead of override
if (customConfigNode["outbounds"] is JsonArray customOutboundsNode)
{
if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.outbounds)) is JsonArray newOutbounds)
{
foreach (var outbound in newOutbounds)
{
customOutboundsNode.Add(outbound?.DeepClone());
}
}
}
ret.Data = JsonUtils.Serialize(customConfigNode);
}
}
else
{
ret.Data = JsonUtils.Serialize(v2rayConfig);
}
ret.Data = await ApplyCustomConfig(customConfig, v2rayConfig, true);
return ret;
}
catch (Exception ex)
@ -1921,5 +1843,83 @@ public class CoreConfigV2rayService
return await Task.FromResult(0);
}
private async Task<string> ApplyCustomConfig(CustomConfigItem customConfig, V2rayConfig v2rayConfig, bool handleBalancerAndRules = false)
{
if (!customConfig.Enabled || customConfig.Config.IsNullOrEmpty())
{
return JsonUtils.Serialize(v2rayConfig);
}
var customConfigNode = JsonNode.Parse(customConfig.Config);
if (customConfigNode == null)
{
return JsonUtils.Serialize(v2rayConfig);
}
// Handle balancer and rules modifications (for multiple load scenarios)
if (handleBalancerAndRules && v2rayConfig.routing?.balancers?.Count > 0)
{
var balancer = v2rayConfig.routing.balancers.First();
// Modify existing rules in custom config
var rulesNode = customConfigNode["routing"]?["rules"];
if (rulesNode != null)
{
foreach (var rule in rulesNode.AsArray())
{
if (rule["outboundTag"]?.GetValue<string>() == Global.ProxyTag)
{
rule.AsObject().Remove("outboundTag");
rule["balancerTag"] = balancer.tag;
}
}
}
// Ensure routing node exists
if (customConfigNode["routing"] == null)
{
customConfigNode["routing"] = new JsonObject();
}
// Handle balancers - append instead of override
if (customConfigNode["routing"]["balancers"] is JsonArray customBalancersNode)
{
if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)) is JsonArray newBalancers)
{
foreach (var balancerNode in newBalancers)
{
customBalancersNode.Add(balancerNode?.DeepClone());
}
}
}
else
{
customConfigNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers));
}
}
// Handle outbounds - append instead of override
var customOutboundsNode = customConfigNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
foreach (var outbound in v2rayConfig.outbounds)
{
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
{
if (customConfig.AddProxyOnly == true)
{
continue;
}
}
else if ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() == true) && (!customConfig.ProxyDetour.IsNullOrEmpty()))
{
outbound.streamSettings ??= new StreamSettings4Ray();
outbound.streamSettings.sockopt ??= new Sockopt4Ray();
outbound.streamSettings.sockopt.dialerProxy = customConfig.ProxyDetour;
}
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
}
return await Task.FromResult(JsonUtils.Serialize(customConfigNode));
}
#endregion private gen function
}

View File

@ -22,6 +22,18 @@ public class CustomConfigViewModel : MyReactiveObject
[Reactive]
public string CustomTunConfig4Singbox { get; set; }
[Reactive]
public bool AddProxyOnly4Ray { get; set; }
[Reactive]
public bool AddProxyOnly4Singbox { get; set; }
[Reactive]
public string ProxyDetour4Ray { get; set; }
[Reactive]
public string ProxyDetour4Singbox { get; set; }
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
#endregion Reactive
@ -41,11 +53,15 @@ public class CustomConfigViewModel : MyReactiveObject
var item = await AppHandler.Instance.GetCustomConfigItem(ECoreType.Xray);
EnableCustomConfig4Ray = item?.Enabled ?? false;
CustomConfig4Ray = item?.Config ?? string.Empty;
AddProxyOnly4Ray = item?.AddProxyOnly ?? false;
ProxyDetour4Ray = item?.ProxyDetour ?? string.Empty;
var item2 = await AppHandler.Instance.GetCustomConfigItem(ECoreType.sing_box);
EnableCustomConfig4Singbox = item2?.Enabled ?? false;
CustomConfig4Singbox = item2?.Config ?? string.Empty;
CustomTunConfig4Singbox = item2?.TunConfig ?? string.Empty;
AddProxyOnly4Singbox = item2?.AddProxyOnly ?? false;
ProxyDetour4Singbox = item2?.ProxyDetour ?? string.Empty;
}
private async Task SaveSettingAsync()
@ -66,10 +82,10 @@ public class CustomConfigViewModel : MyReactiveObject
item.Enabled = EnableCustomConfig4Ray;
item.Config = null;
if (CustomConfig4Ray.IsNotEmpty())
{
item.Config = CustomConfig4Ray;
}
item.Config = CustomConfig4Ray;
item.AddProxyOnly = AddProxyOnly4Ray;
item.ProxyDetour = ProxyDetour4Ray;
await ConfigHandler.SaveCustomConfigItem(_config, item);
return true;
@ -82,25 +98,13 @@ public class CustomConfigViewModel : MyReactiveObject
item.Config = null;
item.TunConfig = null;
var hasChanges = false;
item.Config = CustomConfig4Singbox;
item.TunConfig = CustomTunConfig4Singbox;
if (CustomConfig4Singbox.IsNotEmpty())
{
item.Config = CustomConfig4Singbox;
hasChanges = true;
}
if (CustomTunConfig4Singbox.IsNotEmpty())
{
item.TunConfig = CustomTunConfig4Singbox;
hasChanges = true;
}
if (hasChanges)
{
await ConfigHandler.SaveCustomConfigItem(_config, item);
}
item.AddProxyOnly = AddProxyOnly4Singbox;
item.ProxyDetour = ProxyDetour4Singbox;
await ConfigHandler.SaveCustomConfigItem(_config, item);
return true;
}
}

View File

@ -59,6 +59,30 @@
</StackPanel>
</Grid>
<WrapPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAddProxyProtocolOutboundOnly}" />
<ToggleSwitch
x:Name="togAddProxyProtocolOutboundOnly4Ray"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSetUpstreamProxyDetour}" />
<TextBox
x:Name="txtProxyDetour4Ray"
Width="200"
Margin="{StaticResource Margin4}" />
</StackPanel>
</WrapPanel>
<HeaderedContentControl
Margin="{StaticResource Margin4}"
BorderBrush="Gray"
@ -97,6 +121,30 @@
</StackPanel>
</Grid>
<WrapPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAddProxyProtocolOutboundOnly}" />
<ToggleSwitch
x:Name="togAddProxyProtocolOutboundOnly4Singbox"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSetUpstreamProxyDetour}" />
<TextBox
x:Name="txtProxyDetour4Singbox"
Width="200"
Margin="{StaticResource Margin4}" />
</StackPanel>
</WrapPanel>
<Grid Margin="{StaticResource Margin4}" ColumnDefinitions="*,10,*">
<HeaderedContentControl

View File

@ -21,9 +21,13 @@ public partial class CustomConfigWindow : WindowBase<CustomConfigViewModel>
{
this.Bind(ViewModel, vm => vm.EnableCustomConfig4Ray, v => v.rayCustomConfigEnable.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomConfig4Ray, v => v.rayCustomConfig.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AddProxyOnly4Ray, v => v.togAddProxyProtocolOutboundOnly4Ray.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ProxyDetour4Ray, v => v.txtProxyDetour4Ray.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableCustomConfig4Singbox, v => v.sbCustomConfigEnable.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomConfig4Singbox, v => v.sbCustomConfig.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomTunConfig4Singbox, v => v.sbCustomTunConfig.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AddProxyOnly4Singbox, v => v.togAddProxyProtocolOutboundOnly4Singbox.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ProxyDetour4Singbox, v => v.txtProxyDetour4Singbox.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});

View File

@ -71,6 +71,33 @@
</StackPanel>
</Grid>
<WrapPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbAddProxyProtocolOutboundOnly}" />
<ToggleButton
x:Name="togAddProxyProtocolOutboundOnly4Ray"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSetUpstreamProxyDetour}" />
<TextBox
x:Name="txtProxyDetour4Ray"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefTextBox}" />
</StackPanel>
</WrapPanel>
<TextBox
x:Name="rayCustomConfig"
Margin="{StaticResource Margin8}"
@ -113,6 +140,33 @@
</StackPanel>
</Grid>
<WrapPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbAddProxyProtocolOutboundOnly}" />
<ToggleButton
x:Name="togAddProxyProtocolOutboundOnly4Singbox"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSetUpstreamProxyDetour}" />
<TextBox
x:Name="txtProxyDetour4Singbox"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefTextBox}" />
</StackPanel>
</WrapPanel>
<Grid Margin="{StaticResource Margin8}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />

View File

@ -21,9 +21,13 @@ public partial class CustomConfigWindow
{
this.Bind(ViewModel, vm => vm.EnableCustomConfig4Ray, v => v.rayCustomConfigEnable.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomConfig4Ray, v => v.rayCustomConfig.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AddProxyOnly4Ray, v => v.togAddProxyProtocolOutboundOnly4Ray.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ProxyDetour4Ray, v => v.txtProxyDetour4Ray.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableCustomConfig4Singbox, v => v.sbCustomConfigEnable.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomConfig4Singbox, v => v.sbCustomConfig.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomTunConfig4Singbox, v => v.sbCustomTunConfig.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AddProxyOnly4Singbox, v => v.togAddProxyProtocolOutboundOnly4Singbox.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ProxyDetour4Singbox, v => v.txtProxyDetour4Singbox.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});