mirror of https://github.com/2dust/v2rayN
Feat. custom config
parent
4f560e86ab
commit
c0cebf9cad
|
@ -29,6 +29,7 @@ public enum EViewAction
|
|||
DNSSettingWindow,
|
||||
RoutingSettingWindow,
|
||||
OptionSettingWindow,
|
||||
CustomConfigWindow,
|
||||
GlobalHotkeySettingWindow,
|
||||
SubSettingWindow,
|
||||
DispatcherSpeedTest,
|
||||
|
|
|
@ -64,6 +64,7 @@ public sealed class AppHandler
|
|||
SQLiteHelper.Instance.CreateTable<RoutingItem>();
|
||||
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
|
||||
SQLiteHelper.Instance.CreateTable<DNSItem>();
|
||||
SQLiteHelper.Instance.CreateTable<CustomConfigItem>();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -203,6 +204,16 @@ public sealed class AppHandler
|
|||
return await SQLiteHelper.Instance.TableAsync<DNSItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
|
||||
}
|
||||
|
||||
public async Task<List<CustomConfigItem>?> CustomConfigItem()
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<CustomConfigItem>().ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<CustomConfigItem?> GetCustomConfigItem(ECoreType eCoreType)
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<CustomConfigItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
|
||||
}
|
||||
|
||||
#endregion SqliteHelper
|
||||
|
||||
#region Core Type
|
||||
|
|
|
@ -2220,6 +2220,54 @@ public class ConfigHandler
|
|||
|
||||
#endregion Simple DNS
|
||||
|
||||
#region Custom Config
|
||||
|
||||
public static async Task<int> InitBuiltinCustomConfig(Config config)
|
||||
{
|
||||
var items = await AppHandler.Instance.CustomConfigItem();
|
||||
if (items.Count <= 0)
|
||||
{
|
||||
var item = new CustomConfigItem()
|
||||
{
|
||||
Remarks = "V2ray",
|
||||
CoreType = ECoreType.Xray,
|
||||
};
|
||||
await SaveCustomConfigItem(config, item);
|
||||
|
||||
var item2 = new CustomConfigItem()
|
||||
{
|
||||
Remarks = "sing-box",
|
||||
CoreType = ECoreType.sing_box,
|
||||
};
|
||||
await SaveCustomConfigItem(config, item2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
public static async Task<int> SaveCustomConfigItem(Config config, CustomConfigItem item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (item.Id.IsNullOrEmpty())
|
||||
{
|
||||
item.Id = Utils.GetGuid(false);
|
||||
}
|
||||
|
||||
if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Custom Config
|
||||
|
||||
#region Regional Presets
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
using SQLite;
|
||||
|
||||
namespace ServiceLib.Models;
|
||||
|
||||
[Serializable]
|
||||
public class CustomConfigItem
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Remarks { get; set; }
|
||||
public bool Enabled { get; set; } = false;
|
||||
public ECoreType CoreType { get; set; }
|
||||
public string? Config { get; set; }
|
||||
public string? TunConfig { get; set; }
|
||||
}
|
|
@ -186,6 +186,15 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please fill in the correct custom config 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string FillCorrectConfigText {
|
||||
get {
|
||||
return ResourceManager.GetString("FillCorrectConfigText", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please fill in the correct custom DNS 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -852,6 +861,15 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Custom Config 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuCustomConfig {
|
||||
get {
|
||||
return ResourceManager.GetString("menuCustomConfig", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 DNS Settings 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -2373,6 +2391,24 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Enable Custom Config 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbCustomConfigEnable {
|
||||
get {
|
||||
return ResourceManager.GetString("TbCustomConfigEnable", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 sing-box Custom Config 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbCustomConfigSingbox {
|
||||
get {
|
||||
return ResourceManager.GetString("TbCustomConfigSingbox", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Display GUI 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -2706,6 +2742,24 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 v2ray Custom Config 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbRayCustomConfig {
|
||||
get {
|
||||
return ResourceManager.GetString("TbRayCustomConfig", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Override Outbound Config, routing.balancers and routing.rules.outboundTag Only 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbRayCustomConfigDesc {
|
||||
get {
|
||||
return ResourceManager.GetString("TbRayCustomConfigDesc", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Alias (remarks) 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -2895,6 +2949,15 @@ 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>
|
||||
|
|
|
@ -1464,4 +1464,25 @@
|
|||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||
</data>
|
||||
<data name="FillCorrectConfigText" xml:space="preserve">
|
||||
<value>Please fill in the correct custom config</value>
|
||||
</data>
|
||||
<data name="menuCustomConfig" xml:space="preserve">
|
||||
<value>Custom Config</value>
|
||||
</data>
|
||||
<data name="TbCustomConfigEnable" xml:space="preserve">
|
||||
<value>Enable Custom Config</value>
|
||||
</data>
|
||||
<data name="TbRayCustomConfig" xml:space="preserve">
|
||||
<value>v2ray Custom Config</value>
|
||||
</data>
|
||||
<data name="TbCustomConfigSingbox" xml:space="preserve">
|
||||
<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>
|
||||
</data>
|
||||
<data name="TbSBCustomConfigDesc" xml:space="preserve">
|
||||
<value>Override Outbound And Endpoint Config Only</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1464,4 +1464,25 @@
|
|||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||
</data>
|
||||
<data name="FillCorrectConfigText" xml:space="preserve">
|
||||
<value>Please fill in the correct custom config</value>
|
||||
</data>
|
||||
<data name="menuCustomConfig" xml:space="preserve">
|
||||
<value>Custom Config</value>
|
||||
</data>
|
||||
<data name="TbCustomConfigEnable" xml:space="preserve">
|
||||
<value>Enable Custom Config</value>
|
||||
</data>
|
||||
<data name="TbRayCustomConfig" xml:space="preserve">
|
||||
<value>v2ray Custom Config</value>
|
||||
</data>
|
||||
<data name="TbCustomConfigSingbox" xml:space="preserve">
|
||||
<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>
|
||||
</data>
|
||||
<data name="TbSBCustomConfigDesc" xml:space="preserve">
|
||||
<value>Override Outbound And Endpoint Config Only</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1464,4 +1464,25 @@
|
|||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||
</data>
|
||||
<data name="FillCorrectConfigText" xml:space="preserve">
|
||||
<value>Please fill in the correct custom config</value>
|
||||
</data>
|
||||
<data name="menuCustomConfig" xml:space="preserve">
|
||||
<value>Custom Config</value>
|
||||
</data>
|
||||
<data name="TbCustomConfigEnable" xml:space="preserve">
|
||||
<value>Enable Custom Config</value>
|
||||
</data>
|
||||
<data name="TbRayCustomConfig" xml:space="preserve">
|
||||
<value>v2ray Custom Config</value>
|
||||
</data>
|
||||
<data name="TbCustomConfigSingbox" xml:space="preserve">
|
||||
<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>
|
||||
</data>
|
||||
<data name="TbSBCustomConfigDesc" xml:space="preserve">
|
||||
<value>Override Outbound And Endpoint Config Only</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1464,4 +1464,25 @@
|
|||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||
</data>
|
||||
<data name="FillCorrectConfigText" xml:space="preserve">
|
||||
<value>Please fill in the correct custom config</value>
|
||||
</data>
|
||||
<data name="menuCustomConfig" xml:space="preserve">
|
||||
<value>Custom Config</value>
|
||||
</data>
|
||||
<data name="TbCustomConfigEnable" xml:space="preserve">
|
||||
<value>Enable Custom Config</value>
|
||||
</data>
|
||||
<data name="TbRayCustomConfig" xml:space="preserve">
|
||||
<value>v2ray Custom Config</value>
|
||||
</data>
|
||||
<data name="TbCustomConfigSingbox" xml:space="preserve">
|
||||
<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>
|
||||
</data>
|
||||
<data name="TbSBCustomConfigDesc" xml:space="preserve">
|
||||
<value>Override Outbound And Endpoint Config Only</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1461,4 +1461,25 @@
|
|||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||
<value>自定义 DNS 已启用,此页面配置将无效</value>
|
||||
</data>
|
||||
<data name="FillCorrectConfigText" xml:space="preserve">
|
||||
<value>请填写正确的自定义配置</value>
|
||||
</data>
|
||||
<data name="menuCustomConfig" xml:space="preserve">
|
||||
<value>自定义配置</value>
|
||||
</data>
|
||||
<data name="TbCustomConfigEnable" xml:space="preserve">
|
||||
<value>启用自定义配置</value>
|
||||
</data>
|
||||
<data name="TbRayCustomConfig" xml:space="preserve">
|
||||
<value>v2ray 自定义配置</value>
|
||||
</data>
|
||||
<data name="TbCustomConfigSingbox" xml:space="preserve">
|
||||
<value>sing-box 自定义配置</value>
|
||||
</data>
|
||||
<data name="TbRayCustomConfigDesc" xml:space="preserve">
|
||||
<value>仅覆盖出站配置,routing.balancers 和 routing.rules.outboundTag</value>
|
||||
</data>
|
||||
<data name="TbSBCustomConfigDesc" xml:space="preserve">
|
||||
<value>仅覆盖出站和端点配置</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1461,4 +1461,25 @@
|
|||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||
</data>
|
||||
<data name="FillCorrectConfigText" xml:space="preserve">
|
||||
<value>Please fill in the correct custom config</value>
|
||||
</data>
|
||||
<data name="menuCustomConfig" xml:space="preserve">
|
||||
<value>Custom Config</value>
|
||||
</data>
|
||||
<data name="TbCustomConfigEnable" xml:space="preserve">
|
||||
<value>Enable Custom Config</value>
|
||||
</data>
|
||||
<data name="TbRayCustomConfig" xml:space="preserve">
|
||||
<value>v2ray Custom Config</value>
|
||||
</data>
|
||||
<data name="TbCustomConfigSingbox" xml:space="preserve">
|
||||
<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>
|
||||
</data>
|
||||
<data name="TbSBCustomConfigDesc" xml:space="preserve">
|
||||
<value>Override Outbound And Endpoint Config Only</value>
|
||||
</data>
|
||||
</root>
|
|
@ -4,6 +4,7 @@ using System.Net;
|
|||
using System.Net.NetworkInformation;
|
||||
using System.Reactive;
|
||||
using System.Text;
|
||||
using System.Text.Json.Nodes;
|
||||
using DynamicData;
|
||||
using ServiceLib.Models;
|
||||
|
||||
|
@ -83,7 +84,51 @@ public class CoreConfigSingboxService
|
|||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(singboxConfig);
|
||||
|
||||
var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.sing_box);
|
||||
if (customConfig.Enabled && (!customConfig.Config.IsNullOrEmpty()))
|
||||
{
|
||||
//var customConfigObj = JsonUtils.Deserialize<SingboxConfig>(customConfig.Config);
|
||||
//if (customConfigObj != null)
|
||||
//{
|
||||
// customConfigObj.outbounds = JsonUtils.DeepCopy(singboxConfig.outbounds);
|
||||
// singboxConfig = customConfigObj;
|
||||
//}
|
||||
|
||||
// skip deserialize
|
||||
var customConfigNode = JsonNode.Parse(customConfig.Config);
|
||||
if (customConfigNode != null)
|
||||
{
|
||||
// customConfigNode["outbounds"] = JsonNode.Parse(JsonUtils.Serialize(singboxConfig.outbounds));
|
||||
// append outbounds instead of override
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
// endpoints
|
||||
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 = customConfigNode.ToJsonString(new() { WriteIndented = true });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.Data = JsonUtils.Serialize(singboxConfig);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -439,7 +484,50 @@ public class CoreConfigSingboxService
|
|||
await ConvertGeo2Ruleset(singboxConfig);
|
||||
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(singboxConfig);
|
||||
|
||||
var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.sing_box);
|
||||
if (customConfig.Enabled && (!customConfig.Config.IsNullOrEmpty()))
|
||||
{
|
||||
//var customConfigObj = JsonUtils.Deserialize<SingboxConfig>(customConfig.Config);
|
||||
//if (customConfigObj != null)
|
||||
//{
|
||||
// customConfigObj.outbounds = JsonUtils.DeepCopy(singboxConfig.outbounds);
|
||||
// singboxConfig = customConfigObj;
|
||||
//}
|
||||
// skip deserialize
|
||||
var customConfigNode = JsonNode.Parse(customConfig.Config);
|
||||
if (customConfigNode != null)
|
||||
{
|
||||
// customConfigNode["outbounds"] = JsonNode.Parse(JsonUtils.Serialize(singboxConfig.outbounds));
|
||||
// append outbounds instead of override
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
// endpoints
|
||||
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 = customConfigNode.ToJsonString(new() { WriteIndented = true });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.Data = JsonUtils.Serialize(singboxConfig);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Net.NetworkInformation;
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using ServiceLib.Models;
|
||||
|
||||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
|
@ -68,7 +69,39 @@ public class CoreConfigV2rayService
|
|||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(v2rayConfig);
|
||||
|
||||
var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.Xray);
|
||||
if (customConfig.Enabled && (!customConfig.Config.IsNullOrEmpty()))
|
||||
{
|
||||
//var customConfigObj = JsonUtils.Deserialize<V2rayConfig>(customConfig.Config);
|
||||
//if (customConfigObj != null)
|
||||
//{
|
||||
// customConfigObj.outbounds = JsonUtils.DeepCopy(v2rayConfig.outbounds);
|
||||
// v2rayConfig = customConfigObj;
|
||||
//}
|
||||
// spik deserialize
|
||||
var customConfigNode = JsonNode.Parse(customConfig.Config);
|
||||
if (customConfigNode != null)
|
||||
{
|
||||
//customConfigNode["outbounds"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.outbounds));
|
||||
// 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 = customConfigNode.ToJsonString(new() { WriteIndented = true });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.Data = JsonUtils.Serialize(v2rayConfig);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -197,7 +230,82 @@ public class CoreConfigV2rayService
|
|||
}
|
||||
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(v2rayConfig);
|
||||
|
||||
var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.Xray);
|
||||
if (customConfig.Enabled && (!customConfig.Config.IsNullOrEmpty()))
|
||||
{
|
||||
//var customConfigObj = JsonUtils.Deserialize<V2rayConfig>(customConfig.Config);
|
||||
//if (customConfigObj != null)
|
||||
//{
|
||||
// foreach (var rule in customConfigObj.routing.rules)
|
||||
// {
|
||||
// if (rule.outboundTag == Global.ProxyTag)
|
||||
// {
|
||||
// rule.outboundTag = null;
|
||||
// rule.balancerTag = balancer.tag;
|
||||
// }
|
||||
// }
|
||||
// customConfigObj.routing.balancers = JsonUtils.DeepCopy(v2rayConfig.routing.balancers);
|
||||
// customConfigObj.outbounds = JsonUtils.DeepCopy(v2rayConfig.outbounds);
|
||||
// v2rayConfig = customConfigObj;
|
||||
//}
|
||||
// skip deserialize
|
||||
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 = customConfigNode.ToJsonString(new() { WriteIndented = true });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.Data = JsonUtils.Serialize(v2rayConfig);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
using System.Reactive;
|
||||
using DynamicData.Binding;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
|
||||
namespace ServiceLib.ViewModels;
|
||||
public class CustomConfigViewModel : MyReactiveObject
|
||||
{
|
||||
#region Reactive
|
||||
[Reactive]
|
||||
public bool EnableCustomConfig4Ray { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool EnableCustomConfig4Singbox { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string CustomConfig4Ray { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string CustomConfig4Singbox { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string CustomTunConfig4Singbox { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
#endregion Reactive
|
||||
|
||||
public CustomConfigViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppHandler.Instance.Config;
|
||||
_updateView = updateView;
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SaveSettingAsync();
|
||||
});
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
private async Task Init()
|
||||
{
|
||||
var item = await AppHandler.Instance.GetCustomConfigItem(ECoreType.Xray);
|
||||
EnableCustomConfig4Ray = item?.Enabled ?? false;
|
||||
CustomConfig4Ray = item?.Config ?? 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;
|
||||
}
|
||||
|
||||
private async Task SaveSettingAsync()
|
||||
{
|
||||
if (!await SaveXrayConfigAsync())
|
||||
return;
|
||||
|
||||
if (!await SaveSingboxConfigAsync())
|
||||
return;
|
||||
|
||||
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_ = _updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
}
|
||||
|
||||
private async Task<bool> SaveXrayConfigAsync()
|
||||
{
|
||||
var item = await AppHandler.Instance.GetCustomConfigItem(ECoreType.Xray);
|
||||
item.Enabled = EnableCustomConfig4Ray;
|
||||
item.Config = null;
|
||||
|
||||
if (CustomConfig4Ray.IsNotEmpty())
|
||||
{
|
||||
item.Config = CustomConfig4Ray;
|
||||
}
|
||||
|
||||
await ConfigHandler.SaveCustomConfigItem(_config, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> SaveSingboxConfigAsync()
|
||||
{
|
||||
var item = await AppHandler.Instance.GetCustomConfigItem(ECoreType.sing_box);
|
||||
item.Enabled = EnableCustomConfig4Singbox;
|
||||
item.Config = null;
|
||||
item.TunConfig = null;
|
||||
|
||||
var hasChanges = false;
|
||||
|
||||
if (CustomConfig4Singbox.IsNotEmpty())
|
||||
{
|
||||
item.Config = CustomConfig4Singbox;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
if (CustomTunConfig4Singbox.IsNotEmpty())
|
||||
{
|
||||
item.TunConfig = CustomTunConfig4Singbox;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
if (hasChanges)
|
||||
{
|
||||
await ConfigHandler.SaveCustomConfigItem(_config, item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -39,6 +39,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
|
||||
public ReactiveCommand<Unit, Unit> RoutingSettingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> DNSSettingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> CustomConfigCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GlobalHotkeySettingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RebootAsAdminCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ClearServerStatisticsCmd { get; }
|
||||
|
@ -169,6 +170,10 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
{
|
||||
await DNSSettingAsync();
|
||||
});
|
||||
CustomConfigCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await CustomConfigAsync();
|
||||
});
|
||||
GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true)
|
||||
|
@ -220,6 +225,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
|
||||
await ConfigHandler.InitBuiltinRouting(_config);
|
||||
await ConfigHandler.InitBuiltinDNS(_config);
|
||||
await ConfigHandler.InitBuiltinCustomConfig(_config);
|
||||
await ProfileExHandler.Instance.Init();
|
||||
await CoreHandler.Instance.Init(_config, UpdateHandler);
|
||||
TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler);
|
||||
|
@ -508,6 +514,15 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
}
|
||||
}
|
||||
|
||||
private async Task CustomConfigAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.CustomConfigWindow, null);
|
||||
if (ret == true)
|
||||
{
|
||||
await Reload();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RebootAsAdmin()
|
||||
{
|
||||
ProcUtils.RebootAsAdmin();
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
<Window
|
||||
x:Class="v2rayN.Desktop.Views.CustomConfigWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
Title="{x:Static resx:ResUI.menuCustomConfig}"
|
||||
Width="900"
|
||||
Height="600"
|
||||
x:DataType="vms:CustomConfigViewModel"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Center"
|
||||
DockPanel.Dock="Bottom"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
Cursor="Hand"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
Cursor="Hand"
|
||||
IsCancel="True" />
|
||||
</StackPanel>
|
||||
|
||||
<TabControl HorizontalContentAlignment="Stretch">
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.TbRayCustomConfig}">
|
||||
<Grid Margin="{StaticResource Margin4}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbRayCustomConfigDesc}" />
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbCustomConfigEnable}" />
|
||||
<ToggleSwitch
|
||||
x:Name="rayCustomConfigEnable"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
|
||||
<TextBox
|
||||
x:Name="rayCustomConfig"
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderThickness="1"
|
||||
Classes="TextArea"
|
||||
Margin="{StaticResource Margin4}"
|
||||
TextWrapping="Wrap"
|
||||
Watermark="xray json config" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.TbCustomConfigSingbox}">
|
||||
<Grid Margin="{StaticResource Margin4}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSBCustomConfigDesc}" />
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbCustomConfigEnable}" />
|
||||
<ToggleSwitch
|
||||
x:Name="sbCustomConfigEnable"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2" Margin="{StaticResource Margin4}" ColumnDefinitions="*,10,*">
|
||||
|
||||
<TextBox
|
||||
x:Name="sbCustomConfig"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderThickness="1"
|
||||
Classes="TextArea"
|
||||
Margin="{StaticResource Margin4}"
|
||||
TextWrapping="Wrap"
|
||||
Watermark="sing-box json config" />
|
||||
|
||||
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
|
||||
|
||||
<TextBox
|
||||
x:Name="sbCustomTunConfig"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderThickness="1"
|
||||
Classes="TextArea"
|
||||
Margin="{StaticResource Margin4}"
|
||||
TextWrapping="Wrap"
|
||||
Watermark="sing-box json tun config" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</Window>
|
|
@ -0,0 +1,42 @@
|
|||
using System.Reactive.Disposables;
|
||||
using Avalonia.Interactivity;
|
||||
using ReactiveUI;
|
||||
using v2rayN.Desktop.Base;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class CustomConfigWindow : WindowBase<CustomConfigViewModel>
|
||||
{
|
||||
private static Config _config;
|
||||
|
||||
public CustomConfigWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_config = AppHandler.Instance.Config;
|
||||
btnCancel.Click += (s, e) => this.Close();
|
||||
ViewModel = new CustomConfigViewModel(UpdateViewHandler);
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
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.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.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case EViewAction.CloseWindow:
|
||||
this.Close(true);
|
||||
break;
|
||||
}
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
}
|
|
@ -72,6 +72,7 @@
|
|||
<MenuItem x:Name="menuOptionSetting" Header="{x:Static resx:ResUI.menuOptionSetting}" />
|
||||
<MenuItem x:Name="menuRoutingSetting" Header="{x:Static resx:ResUI.menuRoutingSetting}" />
|
||||
<MenuItem x:Name="menuDNSSetting" Header="{x:Static resx:ResUI.menuDNSSetting}" />
|
||||
<MenuItem x:Name="menuCustomConfig" Header="{x:Static resx:ResUI.menuCustomConfig}" />
|
||||
<MenuItem x:Name="menuGlobalHotkeySetting" Header="{x:Static resx:ResUI.menuGlobalHotkeySetting}" />
|
||||
<Separator />
|
||||
<MenuItem x:Name="menuRebootAsAdmin" Header="{x:Static resx:ResUI.menuRebootAsAdmin}" />
|
||||
|
|
|
@ -100,6 +100,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||
this.BindCommand(ViewModel, vm => vm.OptionSettingCmd, v => v.menuOptionSetting).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.RoutingSettingCmd, v => v.menuRoutingSetting).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.DNSSettingCmd, v => v.menuDNSSetting).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.CustomConfigCmd, v => v.menuCustomConfig).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.GlobalHotkeySettingCmd, v => v.menuGlobalHotkeySetting).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.RebootAsAdminCmd, v => v.menuRebootAsAdmin).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.ClearServerStatisticsCmd, v => v.menuClearServerStatistics).DisposeWith(disposables);
|
||||
|
@ -190,6 +191,9 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||
case EViewAction.DNSSettingWindow:
|
||||
return await new DNSSettingWindow().ShowDialog<bool>(this);
|
||||
|
||||
case EViewAction.CustomConfigWindow:
|
||||
return await new CustomConfigWindow().ShowDialog<bool>(this);
|
||||
|
||||
case EViewAction.RoutingSettingWindow:
|
||||
return await new RoutingSettingWindow().ShowDialog<bool>(this);
|
||||
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.CustomConfigWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
Title="{x:Static resx:ResUI.menuCustomConfig}"
|
||||
Width="1000"
|
||||
Height="700"
|
||||
x:TypeArguments="vms:CustomConfigViewModel"
|
||||
ShowInTaskbar="False"
|
||||
Style="{StaticResource WindowGlobal}"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Center"
|
||||
DockPanel.Dock="Bottom"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
Cursor="Hand"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
Cursor="Hand"
|
||||
IsCancel="true"
|
||||
Style="{StaticResource DefButton}" />
|
||||
</StackPanel>
|
||||
|
||||
<TabControl HorizontalContentAlignment="Left">
|
||||
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.TbRayCustomConfig}">
|
||||
<Grid Margin="{StaticResource Margin8}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbRayCustomConfigDesc}" />
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbCustomConfigEnable}" />
|
||||
<ToggleButton
|
||||
x:Name="rayCustomConfigEnable"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
|
||||
<TextBox
|
||||
x:Name="rayCustomConfig"
|
||||
Grid.Row="2"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
materialDesign:HintAssist.Hint="xray json config"
|
||||
AcceptsReturn="True"
|
||||
BorderThickness="1"
|
||||
Style="{StaticResource MaterialDesignOutlinedTextBox}"
|
||||
TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.TbCustomConfigSingbox}">
|
||||
<Grid Margin="{StaticResource Margin8}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSBCustomConfigDesc}" />
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbCustomConfigEnable}" />
|
||||
<ToggleButton
|
||||
x:Name="sbCustomConfigEnable"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2" Margin="{StaticResource Margin8}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox
|
||||
x:Name="sbCustomConfig"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
materialDesign:HintAssist.Hint="sing-box json config"
|
||||
AcceptsReturn="True"
|
||||
BorderThickness="1"
|
||||
Style="{StaticResource MaterialDesignOutlinedTextBox}"
|
||||
TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto" />
|
||||
|
||||
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
|
||||
|
||||
<TextBox
|
||||
x:Name="sbCustomTunConfig"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
materialDesign:HintAssist.Hint="sing-box json tun config"
|
||||
AcceptsReturn="True"
|
||||
BorderThickness="1"
|
||||
Style="{StaticResource MaterialDesignOutlinedTextBox}"
|
||||
TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto" />
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</base:WindowBase>
|
|
@ -0,0 +1,43 @@
|
|||
using System.Reactive.Disposables;
|
||||
using System.Windows;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace v2rayN.Views;
|
||||
|
||||
public partial class CustomConfigWindow
|
||||
{
|
||||
private static Config _config;
|
||||
|
||||
public CustomConfigWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.Owner = Application.Current.MainWindow;
|
||||
_config = AppHandler.Instance.Config;
|
||||
|
||||
ViewModel = new CustomConfigViewModel(UpdateViewHandler);
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
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.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.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
});
|
||||
WindowsUtils.SetDarkBorder(this, AppHandler.Instance.Config.UiItem.CurrentTheme);
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case EViewAction.CloseWindow:
|
||||
this.DialogResult = true;
|
||||
break;
|
||||
}
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
}
|
|
@ -173,6 +173,10 @@
|
|||
x:Name="menuDNSSetting"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuDNSSetting}" />
|
||||
<MenuItem
|
||||
x:Name="menuCustomConfig"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuCustomConfig}" />
|
||||
<MenuItem
|
||||
x:Name="menuGlobalHotkeySetting"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
|
|
|
@ -97,6 +97,7 @@ public partial class MainWindow
|
|||
this.BindCommand(ViewModel, vm => vm.OptionSettingCmd, v => v.menuOptionSetting).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.RoutingSettingCmd, v => v.menuRoutingSetting).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.DNSSettingCmd, v => v.menuDNSSetting).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.CustomConfigCmd, v => v.menuCustomConfig).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.GlobalHotkeySettingCmd, v => v.menuGlobalHotkeySetting).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.RebootAsAdminCmd, v => v.menuRebootAsAdmin).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.ClearServerStatisticsCmd, v => v.menuClearServerStatistics).DisposeWith(disposables);
|
||||
|
@ -186,6 +187,9 @@ public partial class MainWindow
|
|||
case EViewAction.OptionSettingWindow:
|
||||
return (new OptionSettingWindow().ShowDialog() ?? false);
|
||||
|
||||
case EViewAction.CustomConfigWindow:
|
||||
return (new CustomConfigWindow().ShowDialog() ?? false);
|
||||
|
||||
case EViewAction.GlobalHotkeySettingWindow:
|
||||
return (new GlobalHotkeySettingWindow().ShowDialog() ?? false);
|
||||
|
||||
|
|
Loading…
Reference in New Issue