mirror of https://github.com/2dust/v2rayN
Feat. custom config
parent
4f560e86ab
commit
c0cebf9cad
|
@ -29,6 +29,7 @@ public enum EViewAction
|
||||||
DNSSettingWindow,
|
DNSSettingWindow,
|
||||||
RoutingSettingWindow,
|
RoutingSettingWindow,
|
||||||
OptionSettingWindow,
|
OptionSettingWindow,
|
||||||
|
CustomConfigWindow,
|
||||||
GlobalHotkeySettingWindow,
|
GlobalHotkeySettingWindow,
|
||||||
SubSettingWindow,
|
SubSettingWindow,
|
||||||
DispatcherSpeedTest,
|
DispatcherSpeedTest,
|
||||||
|
|
|
@ -64,6 +64,7 @@ public sealed class AppHandler
|
||||||
SQLiteHelper.Instance.CreateTable<RoutingItem>();
|
SQLiteHelper.Instance.CreateTable<RoutingItem>();
|
||||||
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
|
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
|
||||||
SQLiteHelper.Instance.CreateTable<DNSItem>();
|
SQLiteHelper.Instance.CreateTable<DNSItem>();
|
||||||
|
SQLiteHelper.Instance.CreateTable<CustomConfigItem>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,6 +204,16 @@ public sealed class AppHandler
|
||||||
return await SQLiteHelper.Instance.TableAsync<DNSItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
|
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
|
#endregion SqliteHelper
|
||||||
|
|
||||||
#region Core Type
|
#region Core Type
|
||||||
|
|
|
@ -2220,6 +2220,54 @@ public class ConfigHandler
|
||||||
|
|
||||||
#endregion Simple DNS
|
#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
|
#region Regional Presets
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// <summary>
|
||||||
/// 查找类似 Please fill in the correct custom DNS 的本地化字符串。
|
/// 查找类似 Please fill in the correct custom DNS 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -852,6 +861,15 @@ namespace ServiceLib.Resx {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Custom Config 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string menuCustomConfig {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("menuCustomConfig", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 DNS Settings 的本地化字符串。
|
/// 查找类似 DNS Settings 的本地化字符串。
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// 查找类似 Display GUI 的本地化字符串。
|
/// 查找类似 Display GUI 的本地化字符串。
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// 查找类似 Alias (remarks) 的本地化字符串。
|
/// 查找类似 Alias (remarks) 的本地化字符串。
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// 查找类似 Encryption method (security) 的本地化字符串。
|
/// 查找类似 Encryption method (security) 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1464,4 +1464,25 @@
|
||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||||
</data>
|
</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>
|
</root>
|
|
@ -1464,4 +1464,25 @@
|
||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||||
</data>
|
</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>
|
</root>
|
|
@ -1464,4 +1464,25 @@
|
||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||||
</data>
|
</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>
|
</root>
|
|
@ -1464,4 +1464,25 @@
|
||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||||
</data>
|
</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>
|
</root>
|
|
@ -1461,4 +1461,25 @@
|
||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>自定义 DNS 已启用,此页面配置将无效</value>
|
<value>自定义 DNS 已启用,此页面配置将无效</value>
|
||||||
</data>
|
</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>
|
</root>
|
|
@ -1461,4 +1461,25 @@
|
||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
||||||
</data>
|
</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>
|
</root>
|
|
@ -4,6 +4,7 @@ using System.Net;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using ServiceLib.Models;
|
using ServiceLib.Models;
|
||||||
|
|
||||||
|
@ -83,7 +84,51 @@ public class CoreConfigSingboxService
|
||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
ret.Success = true;
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -439,7 +484,50 @@ public class CoreConfigSingboxService
|
||||||
await ConvertGeo2Ruleset(singboxConfig);
|
await ConvertGeo2Ruleset(singboxConfig);
|
||||||
|
|
||||||
ret.Success = true;
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Net.NetworkInformation;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using ServiceLib.Models;
|
||||||
|
|
||||||
namespace ServiceLib.Services.CoreConfig;
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
|
@ -68,7 +69,39 @@ public class CoreConfigV2rayService
|
||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
ret.Success = true;
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -197,7 +230,82 @@ public class CoreConfigV2rayService
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.Success = true;
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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> RoutingSettingCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> DNSSettingCmd { get; }
|
public ReactiveCommand<Unit, Unit> DNSSettingCmd { get; }
|
||||||
|
public ReactiveCommand<Unit, Unit> CustomConfigCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> GlobalHotkeySettingCmd { get; }
|
public ReactiveCommand<Unit, Unit> GlobalHotkeySettingCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> RebootAsAdminCmd { get; }
|
public ReactiveCommand<Unit, Unit> RebootAsAdminCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> ClearServerStatisticsCmd { get; }
|
public ReactiveCommand<Unit, Unit> ClearServerStatisticsCmd { get; }
|
||||||
|
@ -169,6 +170,10 @@ public class MainWindowViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
await DNSSettingAsync();
|
await DNSSettingAsync();
|
||||||
});
|
});
|
||||||
|
CustomConfigCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
|
{
|
||||||
|
await CustomConfigAsync();
|
||||||
|
});
|
||||||
GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () =>
|
GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true)
|
if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true)
|
||||||
|
@ -220,6 +225,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||||
|
|
||||||
await ConfigHandler.InitBuiltinRouting(_config);
|
await ConfigHandler.InitBuiltinRouting(_config);
|
||||||
await ConfigHandler.InitBuiltinDNS(_config);
|
await ConfigHandler.InitBuiltinDNS(_config);
|
||||||
|
await ConfigHandler.InitBuiltinCustomConfig(_config);
|
||||||
await ProfileExHandler.Instance.Init();
|
await ProfileExHandler.Instance.Init();
|
||||||
await CoreHandler.Instance.Init(_config, UpdateHandler);
|
await CoreHandler.Instance.Init(_config, UpdateHandler);
|
||||||
TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler);
|
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()
|
public async Task RebootAsAdmin()
|
||||||
{
|
{
|
||||||
ProcUtils.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="menuOptionSetting" Header="{x:Static resx:ResUI.menuOptionSetting}" />
|
||||||
<MenuItem x:Name="menuRoutingSetting" Header="{x:Static resx:ResUI.menuRoutingSetting}" />
|
<MenuItem x:Name="menuRoutingSetting" Header="{x:Static resx:ResUI.menuRoutingSetting}" />
|
||||||
<MenuItem x:Name="menuDNSSetting" Header="{x:Static resx:ResUI.menuDNSSetting}" />
|
<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}" />
|
<MenuItem x:Name="menuGlobalHotkeySetting" Header="{x:Static resx:ResUI.menuGlobalHotkeySetting}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem x:Name="menuRebootAsAdmin" Header="{x:Static resx:ResUI.menuRebootAsAdmin}" />
|
<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.OptionSettingCmd, v => v.menuOptionSetting).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.RoutingSettingCmd, v => v.menuRoutingSetting).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.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.GlobalHotkeySettingCmd, v => v.menuGlobalHotkeySetting).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.RebootAsAdminCmd, v => v.menuRebootAsAdmin).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.RebootAsAdminCmd, v => v.menuRebootAsAdmin).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.ClearServerStatisticsCmd, v => v.menuClearServerStatistics).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:
|
case EViewAction.DNSSettingWindow:
|
||||||
return await new DNSSettingWindow().ShowDialog<bool>(this);
|
return await new DNSSettingWindow().ShowDialog<bool>(this);
|
||||||
|
|
||||||
|
case EViewAction.CustomConfigWindow:
|
||||||
|
return await new CustomConfigWindow().ShowDialog<bool>(this);
|
||||||
|
|
||||||
case EViewAction.RoutingSettingWindow:
|
case EViewAction.RoutingSettingWindow:
|
||||||
return await new RoutingSettingWindow().ShowDialog<bool>(this);
|
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"
|
x:Name="menuDNSSetting"
|
||||||
Height="{StaticResource MenuItemHeight}"
|
Height="{StaticResource MenuItemHeight}"
|
||||||
Header="{x:Static resx:ResUI.menuDNSSetting}" />
|
Header="{x:Static resx:ResUI.menuDNSSetting}" />
|
||||||
|
<MenuItem
|
||||||
|
x:Name="menuCustomConfig"
|
||||||
|
Height="{StaticResource MenuItemHeight}"
|
||||||
|
Header="{x:Static resx:ResUI.menuCustomConfig}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
x:Name="menuGlobalHotkeySetting"
|
x:Name="menuGlobalHotkeySetting"
|
||||||
Height="{StaticResource MenuItemHeight}"
|
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.OptionSettingCmd, v => v.menuOptionSetting).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.RoutingSettingCmd, v => v.menuRoutingSetting).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.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.GlobalHotkeySettingCmd, v => v.menuGlobalHotkeySetting).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.RebootAsAdminCmd, v => v.menuRebootAsAdmin).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.RebootAsAdminCmd, v => v.menuRebootAsAdmin).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.ClearServerStatisticsCmd, v => v.menuClearServerStatistics).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.ClearServerStatisticsCmd, v => v.menuClearServerStatistics).DisposeWith(disposables);
|
||||||
|
@ -186,6 +187,9 @@ public partial class MainWindow
|
||||||
case EViewAction.OptionSettingWindow:
|
case EViewAction.OptionSettingWindow:
|
||||||
return (new OptionSettingWindow().ShowDialog() ?? false);
|
return (new OptionSettingWindow().ShowDialog() ?? false);
|
||||||
|
|
||||||
|
case EViewAction.CustomConfigWindow:
|
||||||
|
return (new CustomConfigWindow().ShowDialog() ?? false);
|
||||||
|
|
||||||
case EViewAction.GlobalHotkeySettingWindow:
|
case EViewAction.GlobalHotkeySettingWindow:
|
||||||
return (new GlobalHotkeySettingWindow().ShowDialog() ?? false);
|
return (new GlobalHotkeySettingWindow().ShowDialog() ?? false);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue