Added outbound HTTP support (support group prefix proxy)

https://github.com/2dust/v2rayN/discussions/4550
pull/4991/head
2dust 2024-04-08 14:02:56 +08:00
parent b5cb9ce67e
commit 5683df2fc0
15 changed files with 336 additions and 273 deletions

View File

@ -142,7 +142,8 @@ namespace v2rayN
{ {
{EConfigType.VMess,"vmess"}, {EConfigType.VMess,"vmess"},
{EConfigType.Shadowsocks,"shadowsocks"}, {EConfigType.Shadowsocks,"shadowsocks"},
{EConfigType.Socks,"socks"}, {EConfigType.Socks,"socks"},
{EConfigType.Http,"http"},
{EConfigType.VLESS,"vless"}, {EConfigType.VLESS,"vless"},
{EConfigType.Trojan,"trojan"}, {EConfigType.Trojan,"trojan"},
{EConfigType.Hysteria2,"hysteria2"}, {EConfigType.Hysteria2,"hysteria2"},

View File

@ -664,6 +664,23 @@ namespace v2rayN.Handler
return 0; return 0;
} }
/// <summary>
/// Add or edit server
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static int AddHttpServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.configType = EConfigType.Http;
profileItem.address = profileItem.address.TrimEx();
AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary> /// <summary>
/// Add or edit server /// Add or edit server
/// </summary> /// </summary>

View File

@ -213,105 +213,111 @@ namespace v2rayN.Handler
{ {
outbound.server = node.address; outbound.server = node.address;
outbound.server_port = node.port; outbound.server_port = node.port;
outbound.type = Global.ProtocolTypes[node.configType];
if (node.configType == EConfigType.VMess) switch (node.configType)
{ {
outbound.type = Global.ProtocolTypes[EConfigType.VMess]; case EConfigType.VMess:
outbound.uuid = node.id;
outbound.alter_id = node.alterId;
if (Global.VmessSecurities.Contains(node.security))
{
outbound.security = node.security;
}
else
{
outbound.security = Global.DefaultSecurity;
}
GenOutboundMux(node, outbound);
}
else if (node.configType == EConfigType.Shadowsocks)
{
outbound.type = Global.ProtocolTypes[EConfigType.Shadowsocks];
outbound.method = LazyConfig.Instance.GetShadowsocksSecurities(node).Contains(node.security) ? node.security : Global.None;
outbound.password = node.id;
GenOutboundMux(node, outbound);
}
else if (node.configType == EConfigType.Socks)
{
outbound.type = Global.ProtocolTypes[EConfigType.Socks];
outbound.version = "5";
if (!Utils.IsNullOrEmpty(node.security)
&& !Utils.IsNullOrEmpty(node.id))
{
outbound.username = node.security;
outbound.password = node.id;
}
}
else if (node.configType == EConfigType.VLESS)
{
outbound.type = Global.ProtocolTypes[EConfigType.VLESS];
outbound.uuid = node.id;
outbound.packet_encoding = "xudp";
if (Utils.IsNullOrEmpty(node.flow))
{
GenOutboundMux(node, outbound);
}
else
{
outbound.flow = node.flow;
}
}
else if (node.configType == EConfigType.Trojan)
{
outbound.type = Global.ProtocolTypes[EConfigType.Trojan];
outbound.password = node.id;
GenOutboundMux(node, outbound);
}
else if (node.configType == EConfigType.Hysteria2)
{
outbound.type = Global.ProtocolTypes[EConfigType.Hysteria2];
outbound.password = node.id;
if (!Utils.IsNullOrEmpty(node.path))
{
outbound.obfs = new()
{ {
type = "salamander", outbound.uuid = node.id;
password = node.path.TrimEx(), outbound.alter_id = node.alterId;
}; if (Global.VmessSecurities.Contains(node.security))
} {
outbound.security = node.security;
}
else
{
outbound.security = Global.DefaultSecurity;
}
outbound.up_mbps = _config.hysteriaItem.up_mbps > 0 ? _config.hysteriaItem.up_mbps : null; GenOutboundMux(node, outbound);
outbound.down_mbps = _config.hysteriaItem.down_mbps > 0 ? _config.hysteriaItem.down_mbps : null; break;
} }
else if (node.configType == EConfigType.Tuic) case EConfigType.Shadowsocks:
{ {
outbound.type = Global.ProtocolTypes[EConfigType.Tuic]; outbound.method = LazyConfig.Instance.GetShadowsocksSecurities(node).Contains(node.security) ? node.security : Global.None;
outbound.password = node.id;
outbound.uuid = node.id; GenOutboundMux(node, outbound);
outbound.password = node.security; break;
outbound.congestion_control = node.headerType; }
} case EConfigType.Socks:
else if (node.configType == EConfigType.Wireguard) {
{ outbound.version = "5";
outbound.type = Global.ProtocolTypes[EConfigType.Wireguard]; if (!Utils.IsNullOrEmpty(node.security)
&& !Utils.IsNullOrEmpty(node.id))
{
outbound.username = node.security;
outbound.password = node.id;
}
break;
}
case EConfigType.Http:
{
if (!Utils.IsNullOrEmpty(node.security)
&& !Utils.IsNullOrEmpty(node.id))
{
outbound.username = node.security;
outbound.password = node.id;
}
break;
}
case EConfigType.VLESS:
{
outbound.uuid = node.id;
outbound.private_key = node.id; outbound.packet_encoding = "xudp";
outbound.peer_public_key = node.publicKey;
outbound.reserved = Utils.String2List(node.path).Select(int.Parse).ToArray(); if (Utils.IsNullOrEmpty(node.flow))
outbound.local_address = [.. Utils.String2List(node.requestHost)]; {
outbound.mtu = Utils.ToInt(node.shortId.IsNullOrEmpty() ? Global.TunMtus.FirstOrDefault() : node.shortId); GenOutboundMux(node, outbound);
}
else
{
outbound.flow = node.flow;
}
break;
}
case EConfigType.Trojan:
{
outbound.password = node.id;
GenOutboundMux(node, outbound);
break;
}
case EConfigType.Hysteria2:
{
outbound.password = node.id;
if (!Utils.IsNullOrEmpty(node.path))
{
outbound.obfs = new()
{
type = "salamander",
password = node.path.TrimEx(),
};
}
outbound.up_mbps = _config.hysteriaItem.up_mbps > 0 ? _config.hysteriaItem.up_mbps : null;
outbound.down_mbps = _config.hysteriaItem.down_mbps > 0 ? _config.hysteriaItem.down_mbps : null;
break;
}
case EConfigType.Tuic:
{
outbound.uuid = node.id;
outbound.password = node.security;
outbound.congestion_control = node.headerType;
break;
}
case EConfigType.Wireguard:
{
outbound.private_key = node.id;
outbound.peer_public_key = node.publicKey;
outbound.reserved = Utils.String2List(node.path).Select(int.Parse).ToArray();
outbound.local_address = [.. Utils.String2List(node.requestHost)];
outbound.mtu = Utils.ToInt(node.shortId.IsNullOrEmpty() ? Global.TunMtus.FirstOrDefault() : node.shortId);
break;
}
} }
GenOutboundTls(node, outbound); GenOutboundTls(node, outbound);

View File

@ -295,182 +295,188 @@ namespace v2rayN.Handler
{ {
try try
{ {
if (node.configType == EConfigType.VMess) switch (node.configType)
{ {
VnextItem4Ray vnextItem; case EConfigType.VMess:
if (outbound.settings.vnext.Count <= 0)
{
vnextItem = new VnextItem4Ray();
outbound.settings.vnext.Add(vnextItem);
}
else
{
vnextItem = outbound.settings.vnext[0];
}
vnextItem.address = node.address;
vnextItem.port = node.port;
UsersItem4Ray usersItem;
if (vnextItem.users.Count <= 0)
{
usersItem = new UsersItem4Ray();
vnextItem.users.Add(usersItem);
}
else
{
usersItem = vnextItem.users[0];
}
//远程服务器用户ID
usersItem.id = node.id;
usersItem.alterId = node.alterId;
usersItem.email = Global.UserEMail;
if (Global.VmessSecurities.Contains(node.security))
{
usersItem.security = node.security;
}
else
{
usersItem.security = Global.DefaultSecurity;
}
GenOutboundMux(node, outbound, _config.coreBasicItem.muxEnabled);
outbound.protocol = Global.ProtocolTypes[EConfigType.VMess];
outbound.settings.servers = null;
}
else if (node.configType == EConfigType.Shadowsocks)
{
ServersItem4Ray serversItem;
if (outbound.settings.servers.Count <= 0)
{
serversItem = new ServersItem4Ray();
outbound.settings.servers.Add(serversItem);
}
else
{
serversItem = outbound.settings.servers[0];
}
serversItem.address = node.address;
serversItem.port = node.port;
serversItem.password = node.id;
serversItem.method = LazyConfig.Instance.GetShadowsocksSecurities(node).Contains(node.security) ? node.security : "none";
serversItem.ota = false;
serversItem.level = 1;
GenOutboundMux(node, outbound, false);
outbound.protocol = Global.ProtocolTypes[EConfigType.Shadowsocks];
outbound.settings.vnext = null;
}
else if (node.configType == EConfigType.Socks)
{
ServersItem4Ray serversItem;
if (outbound.settings.servers.Count <= 0)
{
serversItem = new ServersItem4Ray();
outbound.settings.servers.Add(serversItem);
}
else
{
serversItem = outbound.settings.servers[0];
}
serversItem.address = node.address;
serversItem.port = node.port;
serversItem.method = null;
serversItem.password = null;
if (!Utils.IsNullOrEmpty(node.security)
&& !Utils.IsNullOrEmpty(node.id))
{
SocksUsersItem4Ray socksUsersItem = new()
{ {
user = node.security, VnextItem4Ray vnextItem;
pass = node.id, if (outbound.settings.vnext.Count <= 0)
level = 1 {
}; vnextItem = new VnextItem4Ray();
outbound.settings.vnext.Add(vnextItem);
}
else
{
vnextItem = outbound.settings.vnext[0];
}
vnextItem.address = node.address;
vnextItem.port = node.port;
serversItem.users = new List<SocksUsersItem4Ray>() { socksUsersItem }; UsersItem4Ray usersItem;
} if (vnextItem.users.Count <= 0)
{
usersItem = new UsersItem4Ray();
vnextItem.users.Add(usersItem);
}
else
{
usersItem = vnextItem.users[0];
}
//远程服务器用户ID
usersItem.id = node.id;
usersItem.alterId = node.alterId;
usersItem.email = Global.UserEMail;
if (Global.VmessSecurities.Contains(node.security))
{
usersItem.security = node.security;
}
else
{
usersItem.security = Global.DefaultSecurity;
}
GenOutboundMux(node, outbound, false); GenOutboundMux(node, outbound, _config.coreBasicItem.muxEnabled);
outbound.protocol = Global.ProtocolTypes[EConfigType.Socks]; outbound.settings.servers = null;
outbound.settings.vnext = null; break;
} }
else if (node.configType == EConfigType.VLESS) case EConfigType.Shadowsocks:
{
VnextItem4Ray vnextItem;
if (outbound.settings.vnext.Count <= 0)
{
vnextItem = new VnextItem4Ray();
outbound.settings.vnext.Add(vnextItem);
}
else
{
vnextItem = outbound.settings.vnext[0];
}
vnextItem.address = node.address;
vnextItem.port = node.port;
UsersItem4Ray usersItem;
if (vnextItem.users.Count <= 0)
{
usersItem = new UsersItem4Ray();
vnextItem.users.Add(usersItem);
}
else
{
usersItem = vnextItem.users[0];
}
usersItem.id = node.id;
usersItem.email = Global.UserEMail;
usersItem.encryption = node.security;
GenOutboundMux(node, outbound, _config.coreBasicItem.muxEnabled);
if (node.streamSecurity == Global.StreamSecurityReality
|| node.streamSecurity == Global.StreamSecurity)
{
if (!Utils.IsNullOrEmpty(node.flow))
{ {
usersItem.flow = node.flow; ServersItem4Ray serversItem;
if (outbound.settings.servers.Count <= 0)
{
serversItem = new ServersItem4Ray();
outbound.settings.servers.Add(serversItem);
}
else
{
serversItem = outbound.settings.servers[0];
}
serversItem.address = node.address;
serversItem.port = node.port;
serversItem.password = node.id;
serversItem.method = LazyConfig.Instance.GetShadowsocksSecurities(node).Contains(node.security) ? node.security : "none";
serversItem.ota = false;
serversItem.level = 1;
GenOutboundMux(node, outbound, false); GenOutboundMux(node, outbound, false);
outbound.settings.vnext = null;
break;
} }
} case EConfigType.Socks:
if (node.streamSecurity == Global.StreamSecurityReality && Utils.IsNullOrEmpty(node.flow)) case EConfigType.Http:
{ {
GenOutboundMux(node, outbound, _config.coreBasicItem.muxEnabled); ServersItem4Ray serversItem;
} if (outbound.settings.servers.Count <= 0)
{
serversItem = new ServersItem4Ray();
outbound.settings.servers.Add(serversItem);
}
else
{
serversItem = outbound.settings.servers[0];
}
serversItem.address = node.address;
serversItem.port = node.port;
serversItem.method = null;
serversItem.password = null;
outbound.protocol = Global.ProtocolTypes[EConfigType.VLESS]; if (!Utils.IsNullOrEmpty(node.security)
outbound.settings.servers = null; && !Utils.IsNullOrEmpty(node.id))
{
SocksUsersItem4Ray socksUsersItem = new()
{
user = node.security,
pass = node.id,
level = 1
};
serversItem.users = new List<SocksUsersItem4Ray>() { socksUsersItem };
}
GenOutboundMux(node, outbound, false);
outbound.settings.vnext = null;
break;
}
case EConfigType.VLESS:
{
VnextItem4Ray vnextItem;
if (outbound.settings.vnext?.Count <= 0)
{
vnextItem = new VnextItem4Ray();
outbound.settings.vnext.Add(vnextItem);
}
else
{
vnextItem = outbound.settings.vnext[0];
}
vnextItem.address = node.address;
vnextItem.port = node.port;
UsersItem4Ray usersItem;
if (vnextItem.users.Count <= 0)
{
usersItem = new UsersItem4Ray();
vnextItem.users.Add(usersItem);
}
else
{
usersItem = vnextItem.users[0];
}
usersItem.id = node.id;
usersItem.email = Global.UserEMail;
usersItem.encryption = node.security;
GenOutboundMux(node, outbound, _config.coreBasicItem.muxEnabled);
if (node.streamSecurity == Global.StreamSecurityReality
|| node.streamSecurity == Global.StreamSecurity)
{
if (!Utils.IsNullOrEmpty(node.flow))
{
usersItem.flow = node.flow;
GenOutboundMux(node, outbound, false);
}
}
if (node.streamSecurity == Global.StreamSecurityReality && Utils.IsNullOrEmpty(node.flow))
{
GenOutboundMux(node, outbound, _config.coreBasicItem.muxEnabled);
}
outbound.settings.servers = null;
break;
}
case EConfigType.Trojan:
{
ServersItem4Ray serversItem;
if (outbound.settings.servers.Count <= 0)
{
serversItem = new ServersItem4Ray();
outbound.settings.servers.Add(serversItem);
}
else
{
serversItem = outbound.settings.servers[0];
}
serversItem.address = node.address;
serversItem.port = node.port;
serversItem.password = node.id;
serversItem.ota = false;
serversItem.level = 1;
GenOutboundMux(node, outbound, false);
outbound.settings.vnext = null;
break;
}
} }
else if (node.configType == EConfigType.Trojan)
{
ServersItem4Ray serversItem;
if (outbound.settings.servers.Count <= 0)
{
serversItem = new ServersItem4Ray();
outbound.settings.servers.Add(serversItem);
}
else
{
serversItem = outbound.settings.servers[0];
}
serversItem.address = node.address;
serversItem.port = node.port;
serversItem.password = node.id;
serversItem.ota = false; outbound.protocol = Global.ProtocolTypes[node.configType];
serversItem.level = 1;
GenOutboundMux(node, outbound, false);
outbound.protocol = Global.ProtocolTypes[EConfigType.Trojan];
outbound.settings.vnext = null;
}
GenBoundStreamSettings(node, outbound.streamSettings); GenBoundStreamSettings(node, outbound.streamSettings);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -10,6 +10,7 @@
Trojan = 6, Trojan = 6,
Hysteria2 = 7, Hysteria2 = 7,
Tuic = 8, Tuic = 8,
Wireguard = 9 Wireguard = 9,
Http = 10
} }
} }

View File

@ -232,7 +232,7 @@ namespace v2rayN.Models
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public List<VnextItem4Ray> vnext { get; set; } public List<VnextItem4Ray>? vnext { get; set; }
/// <summary> /// <summary>
/// ///
@ -288,17 +288,17 @@ namespace v2rayN.Models
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string method { get; set; } public string? method { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public bool ota { get; set; } public bool? ota { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string password { get; set; } public string? password { get; set; }
/// <summary> /// <summary>
/// ///
@ -308,7 +308,7 @@ namespace v2rayN.Models
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int level { get; set; } public int? level { get; set; }
/// <summary> /// <summary>
/// trojan /// trojan
@ -336,7 +336,7 @@ namespace v2rayN.Models
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int level { get; set; } public int? level { get; set; }
} }
public class Mux4Ray public class Mux4Ray

View File

@ -618,6 +618,15 @@ namespace v2rayN.Resx {
} }
} }
/// <summary>
/// 查找类似 Add [Http] server 的本地化字符串。
/// </summary>
public static string menuAddHttpServer {
get {
return ResourceManager.GetString("menuAddHttpServer", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Add [Hysteria2] server 的本地化字符串。 /// 查找类似 Add [Hysteria2] server 的本地化字符串。
/// </summary> /// </summary>

View File

@ -1198,4 +1198,7 @@
<data name="TransportRequestHostTip5" xml:space="preserve"> <data name="TransportRequestHostTip5" xml:space="preserve">
<value>*grpc Authority</value> <value>*grpc Authority</value>
</data> </data>
<data name="menuAddHttpServer" xml:space="preserve">
<value>Add [Http] server</value>
</data>
</root> </root>

View File

@ -1195,4 +1195,7 @@
<data name="TransportRequestHostTip5" xml:space="preserve"> <data name="TransportRequestHostTip5" xml:space="preserve">
<value>*grpc Authority</value> <value>*grpc Authority</value>
</data> </data>
<data name="menuAddHttpServer" xml:space="preserve">
<value>添加[Http]服务器</value>
</data>
</root> </root>

View File

@ -1168,4 +1168,7 @@
<data name="TransportRequestHostTip5" xml:space="preserve"> <data name="TransportRequestHostTip5" xml:space="preserve">
<value>*grpc Authority</value> <value>*grpc Authority</value>
</data> </data>
<data name="menuAddHttpServer" xml:space="preserve">
<value>新增[Http]伺服器</value>
</data>
</root> </root>

View File

@ -80,7 +80,8 @@ namespace v2rayN.ViewModels
return; return;
} }
} }
if (SelectedSource.configType != EConfigType.Socks) if (SelectedSource.configType != EConfigType.Socks
&& SelectedSource.configType != EConfigType.Http)
{ {
if (Utils.IsNullOrEmpty(SelectedSource.id)) if (Utils.IsNullOrEmpty(SelectedSource.id))
{ {
@ -127,6 +128,7 @@ namespace v2rayN.ViewModels
EConfigType.VMess => ConfigHandler.AddServer(_config, item), EConfigType.VMess => ConfigHandler.AddServer(_config, item),
EConfigType.Shadowsocks => ConfigHandler.AddShadowsocksServer(_config, item), EConfigType.Shadowsocks => ConfigHandler.AddShadowsocksServer(_config, item),
EConfigType.Socks => ConfigHandler.AddSocksServer(_config, item), EConfigType.Socks => ConfigHandler.AddSocksServer(_config, item),
EConfigType.Http => ConfigHandler.AddHttpServer(_config, item),
EConfigType.Trojan => ConfigHandler.AddTrojanServer(_config, item), EConfigType.Trojan => ConfigHandler.AddTrojanServer(_config, item),
EConfigType.VLESS => ConfigHandler.AddVlessServer(_config, item), EConfigType.VLESS => ConfigHandler.AddVlessServer(_config, item),
EConfigType.Hysteria2 => ConfigHandler.AddHysteria2Server(_config, item), EConfigType.Hysteria2 => ConfigHandler.AddHysteria2Server(_config, item),

View File

@ -86,6 +86,7 @@ namespace v2rayN.ViewModels
public ReactiveCommand<Unit, Unit> AddVlessServerCmd { get; } public ReactiveCommand<Unit, Unit> AddVlessServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddShadowsocksServerCmd { get; } public ReactiveCommand<Unit, Unit> AddShadowsocksServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddSocksServerCmd { get; } public ReactiveCommand<Unit, Unit> AddSocksServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddHttpServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddTrojanServerCmd { get; } public ReactiveCommand<Unit, Unit> AddTrojanServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; } public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; } public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; }
@ -333,6 +334,10 @@ namespace v2rayN.ViewModels
{ {
EditServer(true, EConfigType.Socks); EditServer(true, EConfigType.Socks);
}); });
AddHttpServerCmd = ReactiveCommand.Create(() =>
{
EditServer(true, EConfigType.Http);
});
AddTrojanServerCmd = ReactiveCommand.Create(() => AddTrojanServerCmd = ReactiveCommand.Create(() =>
{ {
EditServer(true, EConfigType.Trojan); EditServer(true, EConfigType.Trojan);

View File

@ -92,6 +92,7 @@ namespace v2rayN.Views
break; break;
case EConfigType.Socks: case EConfigType.Socks:
case EConfigType.Http:
gridSocks.Visibility = Visibility.Visible; gridSocks.Visibility = Visibility.Visible;
break; break;
@ -174,6 +175,7 @@ namespace v2rayN.Views
break; break;
case EConfigType.Socks: case EConfigType.Socks:
case EConfigType.Http:
this.Bind(ViewModel, vm => vm.SelectedSource.id, v => v.txtId4.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.id, v => v.txtId4.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.security, v => v.txtSecurity4.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.security, v => v.txtSecurity4.Text).DisposeWith(disposables);
break; break;

View File

@ -1,17 +1,17 @@
<reactiveui:ReactiveWindow <reactiveui:ReactiveWindow
x:Class="v2rayN.Views.MainWindow" x:Class="v2rayN.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tb="clr-namespace:H.NotifyIcon;assembly=H.NotifyIcon.Wpf"
xmlns:base="clr-namespace:v2rayN.Base" xmlns:base="clr-namespace:v2rayN.Base"
xmlns:conv="clr-namespace:v2rayN.Converters" xmlns:conv="clr-namespace:v2rayN.Converters"
xmlns:resx="clr-namespace:v2rayN.Resx" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vms="clr-namespace:v2rayN.ViewModels"
xmlns:local="clr-namespace:v2rayN.Views" xmlns:local="clr-namespace:v2rayN.Views"
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:v2rayN.Resx"
xmlns:tb="clr-namespace:H.NotifyIcon;assembly=H.NotifyIcon.Wpf"
xmlns:vms="clr-namespace:v2rayN.ViewModels"
Title="v2rayN" Title="v2rayN"
Width="900" Width="900"
Height="700" Height="700"
@ -79,6 +79,10 @@
x:Name="menuAddSocksServer" x:Name="menuAddSocksServer"
Height="{StaticResource MenuItemHeight}" Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddSocksServer}" /> Header="{x:Static resx:ResUI.menuAddSocksServer}" />
<MenuItem
x:Name="menuAddHttpServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddHttpServer}" />
<MenuItem <MenuItem
x:Name="menuAddTrojanServer" x:Name="menuAddTrojanServer"
Height="{StaticResource MenuItemHeight}" Height="{StaticResource MenuItemHeight}"

View File

@ -86,6 +86,7 @@ namespace v2rayN.Views
this.BindCommand(ViewModel, vm => vm.AddVlessServerCmd, v => v.menuAddVlessServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddVlessServerCmd, v => v.menuAddVlessServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddShadowsocksServerCmd, v => v.menuAddShadowsocksServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddShadowsocksServerCmd, v => v.menuAddShadowsocksServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddSocksServerCmd, v => v.menuAddSocksServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddSocksServerCmd, v => v.menuAddSocksServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddHttpServerCmd, v => v.menuAddHttpServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddTrojanServerCmd, v => v.menuAddTrojanServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddTrojanServerCmd, v => v.menuAddTrojanServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables);