mirror of https://github.com/2dust/v2rayN
Adding support for the clash proxies interface
parent
63ed2311dc
commit
691b19b5fd
|
@ -1,9 +1,9 @@
|
|||
<Application
|
||||
x:Class="v2rayN.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:conv="clr-namespace:v2rayN.Converters"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
ShutdownMode="OnExplicitShutdown"
|
||||
StartupUri="Views/MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
@ -20,6 +20,7 @@
|
|||
<system:Double x:Key="StdFontSize1">13</system:Double>
|
||||
<system:Double x:Key="StdFontSize2">14</system:Double>
|
||||
<system:Double x:Key="StdFontSizeMsg">11</system:Double>
|
||||
<system:Double x:Key="StdFontSize-1">11</system:Double>
|
||||
|
||||
<conv:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
||||
<Thickness
|
||||
|
@ -68,16 +69,21 @@
|
|||
<Setter Property="Foreground" Value="{DynamicResource MaterialDesignBody}" />
|
||||
</Style>
|
||||
<Style x:Key="lvItemSelected" TargetType="{x:Type ListViewItem}">
|
||||
<Setter Property="Height" Value="20" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource StdFontSize}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="true">
|
||||
<Setter Property="Background" Value="{DynamicResource MaterialDesign.Brush.Primary.Light}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter Property="Background" Value="{DynamicResource MaterialDesign.Brush.Primary}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
<Setter Property="Margin" Value="2" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListViewItem">
|
||||
<materialDesign:Card Name="_Card" SnapsToDevicePixels="true">
|
||||
<ContentPresenter />
|
||||
</materialDesign:Card>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="true">
|
||||
<Setter TargetName="_Card" Property="Background" Value="{DynamicResource MaterialDesign.Brush.Primary.Light}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GridViewColumnHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
|
||||
|
@ -156,6 +162,27 @@
|
|||
<Setter Property="FontSize" Value="{DynamicResource StdFontSize}" />
|
||||
<Setter Property="Padding" Value="{StaticResource OutlinedTextBoxDefaultPadding}" />
|
||||
</Style>
|
||||
<Style x:Key="ListItemChip" TargetType="{x:Type materialDesign:Chip}">
|
||||
<Setter Property="FontSize" Value="{DynamicResource StdFontSize1}" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ListItemTitle"
|
||||
BasedOn="{StaticResource MaterialDesignTextBlock}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="{DynamicResource StdFontSize1}" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ListItemSubTitle"
|
||||
BasedOn="{StaticResource MaterialDesignTextBlock}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="{DynamicResource StdFontSize}" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ListItemSubTitle2"
|
||||
BasedOn="{StaticResource MaterialDesignTextBlock}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="{DynamicResource StdFontSize-1}" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
|
@ -1,5 +1,6 @@
|
|||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
|
||||
|
@ -21,6 +22,22 @@ namespace v2rayN
|
|||
|
||||
private HttpClientHelper(HttpClient httpClient) => this.httpClient = httpClient;
|
||||
|
||||
public async Task<string?> TryGetAsync(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await httpClient.GetAsync(url);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> GetAsync(string url)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(url)) return null;
|
||||
|
@ -41,6 +58,21 @@ namespace v2rayN
|
|||
var result = await httpClient.PutAsync(url, content);
|
||||
}
|
||||
|
||||
public async Task PatchAsync(string url, Dictionary<string, string> headers)
|
||||
{
|
||||
var myContent = JsonUtils.Serialize(headers);
|
||||
var buffer = System.Text.Encoding.UTF8.GetBytes(myContent);
|
||||
var byteContent = new ByteArrayContent(buffer);
|
||||
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||
|
||||
await httpClient.PatchAsync(url, byteContent);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(string url)
|
||||
{
|
||||
await httpClient.DeleteAsync(url);
|
||||
}
|
||||
|
||||
public static async Task DownloadFileAsync(HttpClient client, string url, string fileName, IProgress<double>? progress, CancellationToken token = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(url);
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace v2rayN.Common
|
||||
{
|
||||
internal class YamlUtils
|
||||
{
|
||||
#region YAML
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化成对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static T FromYaml<T>(string str)
|
||||
{
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
T obj = deserializer.Deserialize<T>(str);
|
||||
return obj;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("FromYaml", ex);
|
||||
return deserializer.Deserialize<T>("");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 序列化
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToYaml(Object obj)
|
||||
{
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(HyphenatedNamingConvention.Instance)
|
||||
.Build();
|
||||
|
||||
string result = string.Empty;
|
||||
try
|
||||
{
|
||||
result = serializer.Serialize(obj);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion YAML
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace v2rayN.Enums
|
||||
{
|
||||
public enum ERuleMode
|
||||
{
|
||||
Rule = 0,
|
||||
Global = 1,
|
||||
Direct = 2,
|
||||
Unchanged = 3
|
||||
}
|
||||
}
|
|
@ -183,6 +183,10 @@ namespace v2rayN
|
|||
public static readonly List<string> SingboxMuxs = new() { "h2mux", "smux", "yamux", "" };
|
||||
public static readonly List<string> TuicCongestionControls = new() { "cubic", "new_reno", "bbr" };
|
||||
|
||||
public static readonly List<string> allowSelectType = new List<string> { "selector", "urltest", "loadbalance", "fallback" };
|
||||
public static readonly List<string> notAllowTestType = new List<string> { "selector", "urltest", "direct", "reject", "compatible", "pass", "loadbalance", "fallback" };
|
||||
public static readonly List<string> proxyVehicleType = new List<string> { "file", "http" };
|
||||
|
||||
#endregion const
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
using v2rayN.Models;
|
||||
using static v2rayN.Models.ClashProxies;
|
||||
|
||||
namespace v2rayN.Handler
|
||||
{
|
||||
public sealed class ClashApiHandler
|
||||
{
|
||||
private static readonly Lazy<ClashApiHandler> instance = new(() => new());
|
||||
public static ClashApiHandler Instance => instance.Value;
|
||||
|
||||
private Dictionary<String, ProxiesItem> _proxies;
|
||||
public Dictionary<string, object> ProfileContent { get; set; }
|
||||
public bool ShowInTaskbar { get; set; } = true;
|
||||
|
||||
public void SetProxies(Dictionary<String, ProxiesItem> proxies)
|
||||
{
|
||||
_proxies = proxies;
|
||||
}
|
||||
|
||||
public Dictionary<String, ProxiesItem> GetProxies()
|
||||
{
|
||||
return _proxies;
|
||||
}
|
||||
|
||||
public void GetClashProxies(Config config, Action<ClashProxies, ClashProviders> update)
|
||||
{
|
||||
Task.Run(() => GetClashProxiesAsync(config, update));
|
||||
}
|
||||
|
||||
private async Task GetClashProxiesAsync(Config config, Action<ClashProxies, ClashProviders> update)
|
||||
{
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var url = $"{GetApiUrl()}/proxies";
|
||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||
var clashProxies = JsonUtils.Deserialize<ClashProxies>(result);
|
||||
|
||||
var url2 = $"{GetApiUrl()}/providers/proxies";
|
||||
var result2 = await HttpClientHelper.Instance.TryGetAsync(url2);
|
||||
var clashProviders = JsonUtils.Deserialize<ClashProviders>(result2);
|
||||
|
||||
if (clashProxies != null || clashProviders != null)
|
||||
{
|
||||
update(clashProxies, clashProviders);
|
||||
return;
|
||||
}
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
update(null, null);
|
||||
}
|
||||
|
||||
public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Action<ClashProxyModel?, string> update)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (blAll)
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (GetProxies() != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
var proxies = GetProxies();
|
||||
if (proxies == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lstProxy = new List<ClashProxyModel>();
|
||||
foreach (KeyValuePair<string, ProxiesItem> kv in proxies)
|
||||
{
|
||||
if (Global.notAllowTestType.Contains(kv.Value.type.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
lstProxy.Add(new ClashProxyModel()
|
||||
{
|
||||
name = kv.Value.name,
|
||||
type = kv.Value.type.ToLower(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (lstProxy == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var urlBase = $"{GetApiUrl()}/proxies";
|
||||
urlBase += @"/{0}/delay?timeout=10000&url=" + LazyConfig.Instance.GetConfig().speedTestItem.speedPingTestUrl;
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
foreach (var it in lstProxy)
|
||||
{
|
||||
if (Global.notAllowTestType.Contains(it.type.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var name = it.name;
|
||||
var url = string.Format(urlBase, name);
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||
update(it, result);
|
||||
}));
|
||||
}
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
|
||||
Thread.Sleep(1000);
|
||||
update(null, "");
|
||||
});
|
||||
}
|
||||
|
||||
public List<ProxiesItem>? GetClashProxyGroups()
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileContent = ProfileContent;
|
||||
if (fileContent is null || fileContent?.ContainsKey("proxy-groups") == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return JsonUtils.Deserialize<List<ProxiesItem>>(JsonUtils.Serialize(fileContent["proxy-groups"]));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("GetClashProxyGroups", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async void ClashSetActiveProxy(string name, string nameNode)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"{GetApiUrl()}/proxies/{name}";
|
||||
Dictionary<string, string> headers = new Dictionary<string, string>();
|
||||
headers.Add("name", nameNode);
|
||||
await HttpClientHelper.Instance.PutAsync(url, headers);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClashConfigUpdate(Dictionary<string, string> headers)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var proxies = GetProxies();
|
||||
if (proxies == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var urlBase = $"{GetApiUrl()}/configs";
|
||||
|
||||
await HttpClientHelper.Instance.PatchAsync(urlBase, headers);
|
||||
});
|
||||
}
|
||||
|
||||
public async void ClashConfigReload(string filePath)
|
||||
{
|
||||
ClashConnectionClose("");
|
||||
try
|
||||
{
|
||||
var url = $"{GetApiUrl()}/configs?force=true";
|
||||
Dictionary<string, string> headers = new Dictionary<string, string>();
|
||||
headers.Add("path", filePath);
|
||||
await HttpClientHelper.Instance.PutAsync(url, headers);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetClashConnections(Config config, Action<ClashConnections> update)
|
||||
{
|
||||
Task.Run(() => GetClashConnectionsAsync(config, update));
|
||||
}
|
||||
|
||||
private async Task GetClashConnectionsAsync(Config config, Action<ClashConnections> update)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"{GetApiUrl()}/connections";
|
||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||
var clashConnections = JsonUtils.Deserialize<ClashConnections>(result);
|
||||
|
||||
update(clashConnections);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async void ClashConnectionClose(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"{GetApiUrl()}/connections/{id}";
|
||||
await HttpClientHelper.Instance.DeleteAsync(url);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetApiUrl()
|
||||
{
|
||||
return $"{Global.HttpProtocol}{Global.Loopback}:{LazyConfig.Instance.StatePort2}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -194,6 +194,7 @@ namespace v2rayN.Handler
|
|||
down_mbps = 100
|
||||
};
|
||||
}
|
||||
config.clashUIItem ??= new();
|
||||
|
||||
LazyConfig.Instance.SetConfig(config);
|
||||
return 0;
|
||||
|
|
|
@ -0,0 +1,259 @@
|
|||
using System.IO;
|
||||
using v2rayN.Common;
|
||||
using v2rayN.Enums;
|
||||
using v2rayN.Models;
|
||||
using v2rayN.Resx;
|
||||
|
||||
namespace v2rayN.Handler.CoreConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Core configuration file processing class
|
||||
/// </summary>
|
||||
internal class CoreConfigClash
|
||||
{
|
||||
private Config _config;
|
||||
|
||||
public CoreConfigClash(Config config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成配置文件
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="msg"></param>
|
||||
/// <returns></returns>
|
||||
public int GenerateClientConfig(ProfileItem node, string? fileName, out string msg)
|
||||
{
|
||||
if (node == null || fileName is null)
|
||||
{
|
||||
msg = ResUI.CheckServerSettings;
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg = ResUI.InitialConfiguration;
|
||||
|
||||
try
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
msg = ResUI.CheckServerSettings;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
string addressFileName = node.address;
|
||||
if (string.IsNullOrEmpty(addressFileName))
|
||||
{
|
||||
msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return -1;
|
||||
}
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName);
|
||||
}
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
msg = ResUI.FailedReadConfiguration + "1";
|
||||
return -1;
|
||||
}
|
||||
|
||||
string tagYamlStr1 = "!<str>";
|
||||
string tagYamlStr2 = "__strn__";
|
||||
string tagYamlStr3 = "!!str";
|
||||
var txtFile = File.ReadAllText(addressFileName);
|
||||
txtFile = txtFile.Replace(tagYamlStr1, tagYamlStr2);
|
||||
|
||||
var fileContent = YamlUtils.FromYaml<Dictionary<string, object>>(txtFile);
|
||||
if (fileContent == null)
|
||||
{
|
||||
msg = ResUI.FailedConversionConfiguration;
|
||||
return -1;
|
||||
}
|
||||
|
||||
//port
|
||||
fileContent["port"] = LazyConfig.Instance.GetLocalPort(EInboundProtocol.http);
|
||||
//socks-port
|
||||
fileContent["socks-port"] = LazyConfig.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
//log-level
|
||||
fileContent["log-level"] = _config.coreBasicItem.loglevel;
|
||||
//external-controller
|
||||
fileContent["external-controller"] = $"{Global.Loopback}:{LazyConfig.Instance.StatePort2}";
|
||||
//allow-lan
|
||||
if (_config.inbound[0].allowLANConn)
|
||||
{
|
||||
fileContent["allow-lan"] = "true";
|
||||
fileContent["bind-address"] = "*";
|
||||
}
|
||||
else
|
||||
{
|
||||
fileContent["allow-lan"] = "false";
|
||||
}
|
||||
|
||||
//ipv6
|
||||
//fileContent["ipv6"] = _config.EnableIpv6;
|
||||
|
||||
//mode
|
||||
if (!fileContent.ContainsKey("mode"))
|
||||
{
|
||||
fileContent["mode"] = ERuleMode.Rule.ToString().ToLower();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_config.clashUIItem.ruleMode != ERuleMode.Unchanged)
|
||||
{
|
||||
fileContent["mode"] = _config.clashUIItem.ruleMode.ToString().ToLower();
|
||||
}
|
||||
}
|
||||
|
||||
////enable tun mode
|
||||
//if (config.EnableTun)
|
||||
//{
|
||||
// string tun = Utils.GetEmbedText(Global.SampleTun);
|
||||
// if (!string.IsNullOrEmpty(tun))
|
||||
// {
|
||||
// var tunContent = Utils.FromYaml<Dictionary<string, object>>(tun);
|
||||
// if (tunContent != null)
|
||||
// fileContent["tun"] = tunContent["tun"];
|
||||
// }
|
||||
//}
|
||||
|
||||
//Mixin
|
||||
//try
|
||||
//{
|
||||
// MixinContent(fileContent, config, node);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// Logging.SaveLog("GenerateClientConfigClash-Mixin", ex);
|
||||
//}
|
||||
|
||||
var txtFileNew = YamlUtils.ToYaml(fileContent).Replace(tagYamlStr2, tagYamlStr3);
|
||||
File.WriteAllText(fileName, txtFileNew);
|
||||
//check again
|
||||
if (!File.Exists(fileName))
|
||||
{
|
||||
msg = ResUI.FailedReadConfiguration + "2";
|
||||
return -1;
|
||||
}
|
||||
|
||||
ClashApiHandler.Instance.ProfileContent = fileContent;
|
||||
|
||||
msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("GenerateClientConfigClash", ex);
|
||||
msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//private static void MixinContent(Dictionary<string, object> fileContent, Config config, ProfileItem node)
|
||||
//{
|
||||
// if (!config.EnableMixinContent)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// var path = Utils.GetConfigPath(Global.mixinConfigFileName);
|
||||
// if (!File.Exists(path))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// var txtFile = File.ReadAllText(Utils.GetConfigPath(Global.mixinConfigFileName));
|
||||
// //txtFile = txtFile.Replace("!<str>", "");
|
||||
|
||||
// var mixinContent = YamlUtils.FromYaml<Dictionary<string, object>>(txtFile);
|
||||
// if (mixinContent == null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// foreach (var item in mixinContent)
|
||||
// {
|
||||
// if (!config.EnableTun && item.Key == "tun")
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if (item.Key.StartsWith("prepend-")
|
||||
// || item.Key.StartsWith("append-")
|
||||
// || item.Key.StartsWith("removed-"))
|
||||
// {
|
||||
// ModifyContentMerge(fileContent, item.Key, item.Value);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// fileContent[item.Key] = item.Value;
|
||||
// }
|
||||
// }
|
||||
// return;
|
||||
//}
|
||||
|
||||
private static void ModifyContentMerge(Dictionary<string, object> fileContent, string key, object value)
|
||||
{
|
||||
bool blPrepend = false;
|
||||
bool blRemoved = false;
|
||||
if (key.StartsWith("prepend-"))
|
||||
{
|
||||
blPrepend = true;
|
||||
key = key.Replace("prepend-", "");
|
||||
}
|
||||
else if (key.StartsWith("append-"))
|
||||
{
|
||||
blPrepend = false;
|
||||
key = key.Replace("append-", "");
|
||||
}
|
||||
else if (key.StartsWith("removed-"))
|
||||
{
|
||||
blRemoved = true;
|
||||
key = key.Replace("removed-", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!blRemoved && !fileContent.ContainsKey(key))
|
||||
{
|
||||
fileContent.Add(key, value);
|
||||
return;
|
||||
}
|
||||
var lstOri = (List<object>)fileContent[key];
|
||||
var lstValue = (List<object>)value;
|
||||
|
||||
if (blRemoved)
|
||||
{
|
||||
foreach (var item in lstValue)
|
||||
{
|
||||
lstOri.RemoveAll(t => t.ToString().StartsWith(item.ToString()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (blPrepend)
|
||||
{
|
||||
lstValue.Reverse();
|
||||
foreach (var item in lstValue)
|
||||
{
|
||||
lstOri.Insert(0, item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in lstValue)
|
||||
{
|
||||
lstOri.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,15 @@ namespace v2rayN.Handler.CoreConfig
|
|||
msg = ResUI.InitialConfiguration;
|
||||
if (node.configType == EConfigType.Custom)
|
||||
{
|
||||
return GenerateClientCustomConfig(node, fileName, out msg);
|
||||
if (node.coreType is ECoreType.clash or ECoreType.clash_meta or ECoreType.mihomo)
|
||||
{
|
||||
var configGenClash = new CoreConfigClash(config);
|
||||
return configGenClash.GenerateClientConfig(node, fileName, out msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GenerateClientCustomConfig(node, fileName, out msg);
|
||||
}
|
||||
}
|
||||
else if (LazyConfig.Instance.GetCoreType(node, node.configType) == ECoreType.sing_box)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
namespace v2rayN.Models
|
||||
{
|
||||
public class ClashConnectionModel
|
||||
{
|
||||
public string id { get; set; }
|
||||
public string network { get; set; }
|
||||
public string type { get; set; }
|
||||
public string host { get; set; }
|
||||
public ulong upload { get; set; }
|
||||
public ulong download { get; set; }
|
||||
public string uploadTraffic { get; set; }
|
||||
public string downloadTraffic { get; set; }
|
||||
public double time { get; set; }
|
||||
public string elapsed { get; set; }
|
||||
public string chain { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
namespace v2rayN.Models
|
||||
{
|
||||
public class ClashConnections
|
||||
{
|
||||
public ulong downloadTotal { get; set; }
|
||||
public ulong uploadTotal { get; set; }
|
||||
public List<ConnectionItem>? connections { get; set; }
|
||||
}
|
||||
|
||||
public class ConnectionItem
|
||||
{
|
||||
public string id { get; set; } = string.Empty;
|
||||
public MetadataItem metadata { get; set; }
|
||||
public ulong upload { get; set; }
|
||||
public ulong download { get; set; }
|
||||
public DateTime start { get; set; }
|
||||
public List<string>? chains { get; set; }
|
||||
public string rule { get; set; }
|
||||
public string rulePayload { get; set; }
|
||||
}
|
||||
|
||||
public class MetadataItem
|
||||
{
|
||||
public string network { get; set; }
|
||||
public string type { get; set; }
|
||||
public string sourceIP { get; set; }
|
||||
public string destinationIP { get; set; }
|
||||
public string sourcePort { get; set; }
|
||||
public string destinationPort { get; set; }
|
||||
public string host { get; set; }
|
||||
public string nsMode { get; set; }
|
||||
public object uid { get; set; }
|
||||
public string process { get; set; }
|
||||
public string processPath { get; set; }
|
||||
public string remoteDestination { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
|
||||
using static v2rayN.Models.ClashProxies;
|
||||
|
||||
namespace v2rayN.Models
|
||||
{
|
||||
public class ClashProviders
|
||||
{
|
||||
public Dictionary<String, ProvidersItem> providers { get; set; }
|
||||
|
||||
public class ProvidersItem
|
||||
{
|
||||
public string name { get; set; }
|
||||
public ProxiesItem[] proxies { get; set; }
|
||||
public string type { get; set; }
|
||||
public string vehicleType { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
namespace v2rayN.Models
|
||||
{
|
||||
public class ClashProxies
|
||||
{
|
||||
public Dictionary<String, ProxiesItem> proxies { get; set; }
|
||||
|
||||
public class ProxiesItem
|
||||
{
|
||||
public string[] all { get; set; }
|
||||
public List<HistoryItem> history { get; set; }
|
||||
public string name { get; set; }
|
||||
public string type { get; set; }
|
||||
public bool udp { get; set; }
|
||||
public string now { get; set; }
|
||||
public int delay { get; set; }
|
||||
}
|
||||
|
||||
public class HistoryItem
|
||||
{
|
||||
public string time { get; set; }
|
||||
public int delay { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using ReactiveUI.Fody.Helpers;
|
||||
|
||||
namespace v2rayN.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class ClashProxyModel
|
||||
{
|
||||
[Reactive]
|
||||
public string name { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string type { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string now { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int delay { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string delayName { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool isActive { get; set; }
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ namespace v2rayN.Models
|
|||
public SpeedTestItem speedTestItem { get; set; }
|
||||
public Mux4SboxItem mux4SboxItem { get; set; }
|
||||
public HysteriaItem hysteriaItem { get; set; }
|
||||
public ClashUIItem clashUIItem { get; set; }
|
||||
public List<InItem> inbound { get; set; }
|
||||
public List<KeyEventItem> globalHotkeys { get; set; }
|
||||
public List<CoreTypeItem> coreTypeItem { get; set; }
|
||||
|
|
|
@ -211,4 +211,16 @@ namespace v2rayN.Models
|
|||
public int up_mbps { get; set; }
|
||||
public int down_mbps { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ClashUIItem
|
||||
{
|
||||
public ERuleMode ruleMode { get; set; }
|
||||
public int proxiesSorting { get; set; }
|
||||
public bool proxiesAutoRefresh { get; set; }
|
||||
public int AutoDelayTestInterval { get; set; } = 10;
|
||||
public int connectionsSorting { get; set; }
|
||||
public bool connectionsAutoRefresh { get; set; }
|
||||
|
||||
}
|
||||
}
|
|
@ -753,6 +753,24 @@ namespace v2rayN.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Close Connection 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuConnectionClose {
|
||||
get {
|
||||
return ResourceManager.GetString("menuConnectionClose", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Close All Connection 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuConnectionCloseAll {
|
||||
get {
|
||||
return ResourceManager.GetString("menuConnectionCloseAll", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Clone selected server 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -879,6 +897,42 @@ namespace v2rayN.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Direct 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuModeDirect {
|
||||
get {
|
||||
return ResourceManager.GetString("menuModeDirect", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Global 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuModeGlobal {
|
||||
get {
|
||||
return ResourceManager.GetString("menuModeGlobal", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Do not change 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuModeNothing {
|
||||
get {
|
||||
return ResourceManager.GetString("menuModeNothing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Rule 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuModeRule {
|
||||
get {
|
||||
return ResourceManager.GetString("menuModeRule", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Move to bottom (B) 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -1005,6 +1059,42 @@ namespace v2rayN.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 All Node Latency Test 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuProxiesDelaytest {
|
||||
get {
|
||||
return ResourceManager.GetString("menuProxiesDelaytest", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Part Node Latency Test 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuProxiesDelaytestPart {
|
||||
get {
|
||||
return ResourceManager.GetString("menuProxiesDelaytestPart", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Refresh Proxies (F5) 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuProxiesReload {
|
||||
get {
|
||||
return ResourceManager.GetString("menuProxiesReload", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Select active node (Enter) 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuProxiesSelectActivity {
|
||||
get {
|
||||
return ResourceManager.GetString("menuProxiesSelectActivity", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Test servers real delay (Ctrl+R) 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -1176,6 +1266,15 @@ namespace v2rayN.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Rule mode 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuRulemode {
|
||||
get {
|
||||
return ResourceManager.GetString("menuRulemode", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Remove Rule (Delete) 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -1996,6 +2095,15 @@ namespace v2rayN.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Connections 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbConnections {
|
||||
get {
|
||||
return ResourceManager.GetString("TbConnections", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Core Type 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -2266,6 +2374,15 @@ namespace v2rayN.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Proxies 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbProxies {
|
||||
get {
|
||||
return ResourceManager.GetString("TbProxies", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 PublicKey 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -3139,6 +3256,123 @@ namespace v2rayN.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Sorting 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSorting {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSorting", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Chain 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSortingChain {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSortingChain", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Default 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSortingDefault {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSortingDefault", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Delay 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSortingDelay {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSortingDelay", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Download Speed 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSortingDownSpeed {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSortingDownSpeed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Download Traffic 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSortingDownTraffic {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSortingDownTraffic", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Host 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSortingHost {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSortingHost", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Name 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSortingName {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSortingName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Network 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSortingNetwork {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSortingNetwork", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Time 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSortingTime {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSortingTime", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Type 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSortingType {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSortingType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Upload Speed 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSortingUpSpeed {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSortingUpSpeed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Upload Traffic 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSortingUpTraffic {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSortingUpTraffic", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 SpiderX 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
|
@ -1006,4 +1006,70 @@
|
|||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>فعال کردن کش فایل مجموعه قوانین برای sing-box</value>
|
||||
</data>
|
||||
<data name="TbSorting" xml:space="preserve">
|
||||
<value>مرتب سازی</value>
|
||||
</data>
|
||||
<data name="TbSortingDefault" xml:space="preserve">
|
||||
<value>Default</value>
|
||||
</data>
|
||||
<data name="TbSortingDelay" xml:space="preserve">
|
||||
<value>تاخیر</value>
|
||||
</data>
|
||||
<data name="TbSortingDownSpeed" xml:space="preserve">
|
||||
<value>سرعت دانلود</value>
|
||||
</data>
|
||||
<data name="TbSortingDownTraffic" xml:space="preserve">
|
||||
<value>ترافیک دانلود</value>
|
||||
</data>
|
||||
<data name="TbSortingName" xml:space="preserve">
|
||||
<value>نام</value>
|
||||
</data>
|
||||
<data name="TbSortingTime" xml:space="preserve">
|
||||
<value>زمان</value>
|
||||
</data>
|
||||
<data name="TbSortingUpSpeed" xml:space="preserve">
|
||||
<value>سرعت اپلود</value>
|
||||
</data>
|
||||
<data name="TbSortingUpTraffic" xml:space="preserve">
|
||||
<value>ترافیک آپلود</value>
|
||||
</data>
|
||||
<data name="TbConnections" xml:space="preserve">
|
||||
<value>اتصالات</value>
|
||||
</data>
|
||||
<data name="menuConnectionClose" xml:space="preserve">
|
||||
<value>بستن اتصال</value>
|
||||
</data>
|
||||
<data name="menuConnectionCloseAll" xml:space="preserve">
|
||||
<value>تمام اتصالات را ببندید</value>
|
||||
</data>
|
||||
<data name="TbProxies" xml:space="preserve">
|
||||
<value>پروکسی</value>
|
||||
</data>
|
||||
<data name="menuRulemode" xml:space="preserve">
|
||||
<value>نوع قانون</value>
|
||||
</data>
|
||||
<data name="menuModeDirect" xml:space="preserve">
|
||||
<value>Direct</value>
|
||||
</data>
|
||||
<data name="menuModeGlobal" xml:space="preserve">
|
||||
<value>Global</value>
|
||||
</data>
|
||||
<data name="menuModeNothing" xml:space="preserve">
|
||||
<value>تغییر نده</value>
|
||||
</data>
|
||||
<data name="menuModeRule" xml:space="preserve">
|
||||
<value>قانون</value>
|
||||
</data>
|
||||
<data name="menuProxiesDelaytest" xml:space="preserve">
|
||||
<value>All Node Latency Test</value>
|
||||
</data>
|
||||
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
||||
<value>Part Node Latency Test</value>
|
||||
</data>
|
||||
<data name="menuProxiesReload" xml:space="preserve">
|
||||
<value>Refresh Proxies (F5)</value>
|
||||
</data>
|
||||
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
||||
<value>Select active node (Enter)</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1219,4 +1219,82 @@
|
|||
<data name="menuOpenTheFileLocation" xml:space="preserve">
|
||||
<value>Open the storage location</value>
|
||||
</data>
|
||||
<data name="TbSorting" xml:space="preserve">
|
||||
<value>Sorting</value>
|
||||
</data>
|
||||
<data name="TbSortingChain" xml:space="preserve">
|
||||
<value>Chain</value>
|
||||
</data>
|
||||
<data name="TbSortingDefault" xml:space="preserve">
|
||||
<value>Default</value>
|
||||
</data>
|
||||
<data name="TbSortingDelay" xml:space="preserve">
|
||||
<value>Delay</value>
|
||||
</data>
|
||||
<data name="TbSortingDownSpeed" xml:space="preserve">
|
||||
<value>Download Speed</value>
|
||||
</data>
|
||||
<data name="TbSortingDownTraffic" xml:space="preserve">
|
||||
<value>Download Traffic</value>
|
||||
</data>
|
||||
<data name="TbSortingHost" xml:space="preserve">
|
||||
<value>Host</value>
|
||||
</data>
|
||||
<data name="TbSortingName" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="TbSortingNetwork" xml:space="preserve">
|
||||
<value>Network</value>
|
||||
</data>
|
||||
<data name="TbSortingTime" xml:space="preserve">
|
||||
<value>Time</value>
|
||||
</data>
|
||||
<data name="TbSortingType" xml:space="preserve">
|
||||
<value>Type</value>
|
||||
</data>
|
||||
<data name="TbSortingUpSpeed" xml:space="preserve">
|
||||
<value>Upload Speed</value>
|
||||
</data>
|
||||
<data name="TbSortingUpTraffic" xml:space="preserve">
|
||||
<value>Upload Traffic</value>
|
||||
</data>
|
||||
<data name="TbConnections" xml:space="preserve">
|
||||
<value>Connections</value>
|
||||
</data>
|
||||
<data name="menuConnectionClose" xml:space="preserve">
|
||||
<value>Close Connection</value>
|
||||
</data>
|
||||
<data name="menuConnectionCloseAll" xml:space="preserve">
|
||||
<value>Close All Connection</value>
|
||||
</data>
|
||||
<data name="TbProxies" xml:space="preserve">
|
||||
<value>Proxies</value>
|
||||
</data>
|
||||
<data name="menuRulemode" xml:space="preserve">
|
||||
<value>Rule mode</value>
|
||||
</data>
|
||||
<data name="menuModeDirect" xml:space="preserve">
|
||||
<value>Direct</value>
|
||||
</data>
|
||||
<data name="menuModeGlobal" xml:space="preserve">
|
||||
<value>Global</value>
|
||||
</data>
|
||||
<data name="menuModeNothing" xml:space="preserve">
|
||||
<value>Do not change</value>
|
||||
</data>
|
||||
<data name="menuModeRule" xml:space="preserve">
|
||||
<value>Rule</value>
|
||||
</data>
|
||||
<data name="menuProxiesDelaytest" xml:space="preserve">
|
||||
<value>All Node Latency Test</value>
|
||||
</data>
|
||||
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
||||
<value>Part Node Latency Test</value>
|
||||
</data>
|
||||
<data name="menuProxiesReload" xml:space="preserve">
|
||||
<value>Refresh Proxies (F5)</value>
|
||||
</data>
|
||||
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
||||
<value>Select active node (Enter)</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1216,4 +1216,82 @@
|
|||
<data name="menuOpenTheFileLocation" xml:space="preserve">
|
||||
<value>打开存储所在的位置</value>
|
||||
</data>
|
||||
<data name="TbSorting" xml:space="preserve">
|
||||
<value>排序</value>
|
||||
</data>
|
||||
<data name="TbSortingChain" xml:space="preserve">
|
||||
<value>路由链</value>
|
||||
</data>
|
||||
<data name="TbSortingDefault" xml:space="preserve">
|
||||
<value>默认</value>
|
||||
</data>
|
||||
<data name="TbSortingDelay" xml:space="preserve">
|
||||
<value>延迟</value>
|
||||
</data>
|
||||
<data name="TbSortingDownSpeed" xml:space="preserve">
|
||||
<value>下载速度</value>
|
||||
</data>
|
||||
<data name="TbSortingDownTraffic" xml:space="preserve">
|
||||
<value>下载流量</value>
|
||||
</data>
|
||||
<data name="TbSortingHost" xml:space="preserve">
|
||||
<value>主机</value>
|
||||
</data>
|
||||
<data name="TbSortingName" xml:space="preserve">
|
||||
<value>名称</value>
|
||||
</data>
|
||||
<data name="TbSortingNetwork" xml:space="preserve">
|
||||
<value>网络</value>
|
||||
</data>
|
||||
<data name="TbSortingTime" xml:space="preserve">
|
||||
<value>时间</value>
|
||||
</data>
|
||||
<data name="TbSortingType" xml:space="preserve">
|
||||
<value>类型</value>
|
||||
</data>
|
||||
<data name="TbSortingUpSpeed" xml:space="preserve">
|
||||
<value>上传速度</value>
|
||||
</data>
|
||||
<data name="TbSortingUpTraffic" xml:space="preserve">
|
||||
<value>上传流量</value>
|
||||
</data>
|
||||
<data name="TbConnections" xml:space="preserve">
|
||||
<value>当前连接</value>
|
||||
</data>
|
||||
<data name="menuConnectionClose" xml:space="preserve">
|
||||
<value>关闭连接</value>
|
||||
</data>
|
||||
<data name="menuConnectionCloseAll" xml:space="preserve">
|
||||
<value>关闭所有连接</value>
|
||||
</data>
|
||||
<data name="TbProxies" xml:space="preserve">
|
||||
<value>当前代理</value>
|
||||
</data>
|
||||
<data name="menuRulemode" xml:space="preserve">
|
||||
<value>规则模式</value>
|
||||
</data>
|
||||
<data name="menuModeDirect" xml:space="preserve">
|
||||
<value>直连</value>
|
||||
</data>
|
||||
<data name="menuModeGlobal" xml:space="preserve">
|
||||
<value>全局</value>
|
||||
</data>
|
||||
<data name="menuModeNothing" xml:space="preserve">
|
||||
<value>随原配置</value>
|
||||
</data>
|
||||
<data name="menuModeRule" xml:space="preserve">
|
||||
<value>规则</value>
|
||||
</data>
|
||||
<data name="menuProxiesDelaytest" xml:space="preserve">
|
||||
<value>全部节点延迟测试</value>
|
||||
</data>
|
||||
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
||||
<value>当前部分节点延迟测试</value>
|
||||
</data>
|
||||
<data name="menuProxiesReload" xml:space="preserve">
|
||||
<value>刷新 (F5)</value>
|
||||
</data>
|
||||
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
||||
<value>设为活动节点 (Enter)</value>
|
||||
</data>
|
||||
</root>
|
|
@ -0,0 +1,195 @@
|
|||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Splat;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using v2rayN.Handler;
|
||||
using v2rayN.Models;
|
||||
|
||||
namespace v2rayN.ViewModels
|
||||
{
|
||||
public class ClashConnectionsViewModel : ReactiveObject
|
||||
{
|
||||
private static Config _config;
|
||||
|
||||
static ClashConnectionsViewModel()
|
||||
{
|
||||
_config = LazyConfig.Instance.GetConfig();
|
||||
}
|
||||
|
||||
private IObservableCollection<ClashConnectionModel> _connectionItems = new ObservableCollectionExtended<ClashConnectionModel>();
|
||||
|
||||
public IObservableCollection<ClashConnectionModel> ConnectionItems => _connectionItems;
|
||||
|
||||
[Reactive]
|
||||
public ClashConnectionModel SelectedSource { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ConnectionCloseCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ConnectionCloseAllCmd { get; }
|
||||
|
||||
[Reactive]
|
||||
public int SortingSelected { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
|
||||
private int AutoRefreshInterval;
|
||||
|
||||
public ClashConnectionsViewModel()
|
||||
{
|
||||
AutoRefreshInterval = 10;
|
||||
SortingSelected = _config.clashUIItem.connectionsSorting;
|
||||
AutoRefresh = _config.clashUIItem.connectionsAutoRefresh;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
x => x.SelectedSource,
|
||||
selectedSource => selectedSource != null && !string.IsNullOrEmpty(selectedSource.id));
|
||||
|
||||
this.WhenAnyValue(
|
||||
x => x.SortingSelected,
|
||||
y => y >= 0)
|
||||
.Subscribe(c => DoSortingSelected(c));
|
||||
|
||||
this.WhenAnyValue(
|
||||
x => x.AutoRefresh,
|
||||
y => y == true)
|
||||
.Subscribe(c => { _config.clashUIItem.connectionsAutoRefresh = AutoRefresh; });
|
||||
|
||||
ConnectionCloseCmd = ReactiveCommand.Create(() =>
|
||||
{
|
||||
ClashConnectionClose(false);
|
||||
}, canEditRemove);
|
||||
|
||||
ConnectionCloseAllCmd = ReactiveCommand.Create(() =>
|
||||
{
|
||||
ClashConnectionClose(true);
|
||||
});
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
private void DoSortingSelected(bool c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (SortingSelected != _config.clashUIItem.connectionsSorting)
|
||||
{
|
||||
_config.clashUIItem.connectionsSorting = SortingSelected;
|
||||
}
|
||||
|
||||
GetClashConnections();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
Observable.Interval(TimeSpan.FromSeconds(AutoRefreshInterval))
|
||||
.Subscribe(x =>
|
||||
{
|
||||
if (!(AutoRefresh && ClashApiHandler.Instance.ShowInTaskbar))
|
||||
{
|
||||
return;
|
||||
}
|
||||
GetClashConnections();
|
||||
});
|
||||
}
|
||||
|
||||
private void GetClashConnections()
|
||||
{
|
||||
ClashApiHandler.Instance.GetClashConnections(_config, (it) =>
|
||||
{
|
||||
if (it == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Application.Current?.Dispatcher.Invoke((Action)(() =>
|
||||
{
|
||||
RefreshConnections(it?.connections);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private void RefreshConnections(List<ConnectionItem>? connections)
|
||||
{
|
||||
_connectionItems.Clear();
|
||||
|
||||
var dtNow = DateTime.Now;
|
||||
var lstModel = new List<ClashConnectionModel>();
|
||||
foreach (var item in connections ?? [])
|
||||
{
|
||||
ClashConnectionModel model = new();
|
||||
|
||||
model.id = item.id;
|
||||
model.network = item.metadata.network;
|
||||
model.type = item.metadata.type;
|
||||
model.host = $"{(string.IsNullOrEmpty(item.metadata.host) ? item.metadata.destinationIP : item.metadata.host)}:{item.metadata.destinationPort}";
|
||||
var sp = (dtNow - item.start);
|
||||
model.time = sp.TotalSeconds < 0 ? 1 : sp.TotalSeconds;
|
||||
model.upload = item.upload;
|
||||
model.download = item.download;
|
||||
model.uploadTraffic = $"{Utils.HumanFy((long)item.upload)}";
|
||||
model.downloadTraffic = $"{Utils.HumanFy((long)item.download)}";
|
||||
model.elapsed = sp.ToString(@"hh\:mm\:ss");
|
||||
model.chain = item.chains?.Count > 0 ? item.chains[0] : String.Empty;
|
||||
|
||||
lstModel.Add(model);
|
||||
}
|
||||
if (lstModel.Count <= 0) { return; }
|
||||
|
||||
//sort
|
||||
switch (SortingSelected)
|
||||
{
|
||||
case 0:
|
||||
lstModel = lstModel.OrderBy(t => t.upload / t.time).ToList();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
lstModel = lstModel.OrderBy(t => t.download / t.time).ToList();
|
||||
break;
|
||||
|
||||
case 2:
|
||||
lstModel = lstModel.OrderBy(t => t.upload).ToList();
|
||||
break;
|
||||
|
||||
case 3:
|
||||
lstModel = lstModel.OrderBy(t => t.download).ToList();
|
||||
break;
|
||||
|
||||
case 4:
|
||||
lstModel = lstModel.OrderBy(t => t.time).ToList();
|
||||
break;
|
||||
|
||||
case 5:
|
||||
lstModel = lstModel.OrderBy(t => t.host).ToList();
|
||||
break;
|
||||
}
|
||||
|
||||
_connectionItems.AddRange(lstModel);
|
||||
}
|
||||
|
||||
public void ClashConnectionClose(bool all)
|
||||
{
|
||||
var id = string.Empty;
|
||||
if (!all)
|
||||
{
|
||||
var item = SelectedSource;
|
||||
if (item is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
id = item.id;
|
||||
}
|
||||
else
|
||||
{
|
||||
_connectionItems.Clear();
|
||||
}
|
||||
ClashApiHandler.Instance.ClashConnectionClose(id);
|
||||
GetClashConnections();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,487 @@
|
|||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Splat;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using v2rayN.Enums;
|
||||
using v2rayN.Handler;
|
||||
using v2rayN.Models;
|
||||
using v2rayN.Resx;
|
||||
using static v2rayN.Models.ClashProviders;
|
||||
using static v2rayN.Models.ClashProxies;
|
||||
|
||||
namespace v2rayN.ViewModels
|
||||
{
|
||||
public class ClashProxiesViewModel : ReactiveObject
|
||||
{
|
||||
private static Config _config;
|
||||
private NoticeHandler? _noticeHandler;
|
||||
private Dictionary<String, ProxiesItem>? proxies;
|
||||
private Dictionary<String, ProvidersItem>? providers;
|
||||
private int delayTimeout = 99999999;
|
||||
|
||||
private IObservableCollection<ClashProxyModel> _proxyGroups = new ObservableCollectionExtended<ClashProxyModel>();
|
||||
private IObservableCollection<ClashProxyModel> _proxyDetails = new ObservableCollectionExtended<ClashProxyModel>();
|
||||
|
||||
public IObservableCollection<ClashProxyModel> ProxyGroups => _proxyGroups;
|
||||
public IObservableCollection<ClashProxyModel> ProxyDetails => _proxyDetails;
|
||||
|
||||
[Reactive]
|
||||
public ClashProxyModel SelectedGroup { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public ClashProxyModel SelectedDetail { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ProxiesReloadCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ProxiesDelaytestCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ProxiesDelaytestPartCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ProxiesSelectActivityCmd { get; }
|
||||
|
||||
[Reactive]
|
||||
public int RuleModeSelected { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int SortingSelected { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
|
||||
public ClashProxiesViewModel()
|
||||
{
|
||||
_noticeHandler = Locator.Current.GetService<NoticeHandler>();
|
||||
_config = LazyConfig.Instance.GetConfig();
|
||||
|
||||
SelectedGroup = new();
|
||||
SelectedDetail = new();
|
||||
|
||||
AutoRefresh = _config.clashUIItem.proxiesAutoRefresh;
|
||||
SortingSelected = _config.clashUIItem.proxiesSorting;
|
||||
RuleModeSelected = (int)_config.clashUIItem.ruleMode;
|
||||
|
||||
this.WhenAnyValue(
|
||||
x => x.SelectedGroup,
|
||||
y => y != null && !string.IsNullOrEmpty(y.name))
|
||||
.Subscribe(c => RefreshProxyDetails(c));
|
||||
|
||||
this.WhenAnyValue(
|
||||
x => x.RuleModeSelected,
|
||||
y => y >= 0)
|
||||
.Subscribe(c => DoRulemodeSelected(c));
|
||||
|
||||
this.WhenAnyValue(
|
||||
x => x.SortingSelected,
|
||||
y => y >= 0)
|
||||
.Subscribe(c => DoSortingSelected(c));
|
||||
|
||||
this.WhenAnyValue(
|
||||
x => x.AutoRefresh,
|
||||
y => y == true)
|
||||
.Subscribe(c => { _config.clashUIItem.proxiesAutoRefresh = AutoRefresh; });
|
||||
|
||||
ProxiesReloadCmd = ReactiveCommand.Create(() =>
|
||||
{
|
||||
ProxiesReload();
|
||||
});
|
||||
ProxiesDelaytestCmd = ReactiveCommand.Create(() =>
|
||||
{
|
||||
ProxiesDelayTest(true);
|
||||
});
|
||||
|
||||
ProxiesDelaytestPartCmd = ReactiveCommand.Create(() =>
|
||||
{
|
||||
ProxiesDelayTest(false);
|
||||
});
|
||||
ProxiesSelectActivityCmd = ReactiveCommand.Create(() =>
|
||||
{
|
||||
SetActiveProxy();
|
||||
});
|
||||
|
||||
ProxiesReload();
|
||||
DelayTestTask();
|
||||
}
|
||||
|
||||
private void DoRulemodeSelected(bool c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_config.clashUIItem.ruleMode == (ERuleMode)RuleModeSelected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SetRuleModeCheck((ERuleMode)RuleModeSelected);
|
||||
}
|
||||
|
||||
public void SetRuleModeCheck(ERuleMode mode)
|
||||
{
|
||||
if (_config.clashUIItem.ruleMode == mode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SetRuleMode(mode);
|
||||
}
|
||||
|
||||
private void DoSortingSelected(bool c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (SortingSelected != _config.clashUIItem.proxiesSorting)
|
||||
{
|
||||
_config.clashUIItem.proxiesSorting = SortingSelected;
|
||||
}
|
||||
|
||||
RefreshProxyDetails(c);
|
||||
}
|
||||
|
||||
private void UpdateHandler(bool notify, string msg)
|
||||
{
|
||||
_noticeHandler?.SendMessage(msg, true);
|
||||
}
|
||||
|
||||
public void ProxiesReload()
|
||||
{
|
||||
GetClashProxies(true);
|
||||
}
|
||||
|
||||
public void ProxiesClear()
|
||||
{
|
||||
proxies = null;
|
||||
providers = null;
|
||||
|
||||
ClashApiHandler.Instance.SetProxies(proxies);
|
||||
|
||||
Application.Current?.Dispatcher.Invoke((Action)(() =>
|
||||
{
|
||||
_proxyGroups.Clear();
|
||||
_proxyDetails.Clear();
|
||||
}));
|
||||
}
|
||||
|
||||
public void ProxiesDelayTest()
|
||||
{
|
||||
ProxiesDelayTest(true);
|
||||
}
|
||||
|
||||
#region proxy function
|
||||
|
||||
private void SetRuleMode(ERuleMode mode)
|
||||
{
|
||||
_config.clashUIItem.ruleMode = mode;
|
||||
|
||||
if (mode != ERuleMode.Unchanged)
|
||||
{
|
||||
Dictionary<string, string> headers = new Dictionary<string, string>();
|
||||
headers.Add("mode", mode.ToString().ToLower());
|
||||
ClashApiHandler.Instance.ClashConfigUpdate(headers);
|
||||
}
|
||||
}
|
||||
|
||||
private void GetClashProxies(bool refreshUI)
|
||||
{
|
||||
ClashApiHandler.Instance.GetClashProxies(_config, (it, it2) =>
|
||||
{
|
||||
UpdateHandler(false, "Refresh Clash Proxies");
|
||||
proxies = it?.proxies;
|
||||
providers = it2?.providers;
|
||||
|
||||
ClashApiHandler.Instance.SetProxies(proxies);
|
||||
if (proxies == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (refreshUI)
|
||||
{
|
||||
Application.Current?.Dispatcher.Invoke((Action)(() =>
|
||||
{
|
||||
RefreshProxyGroups();
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void RefreshProxyGroups()
|
||||
{
|
||||
var selectedName = SelectedGroup?.name;
|
||||
_proxyGroups.Clear();
|
||||
|
||||
var proxyGroups = ClashApiHandler.Instance.GetClashProxyGroups();
|
||||
if (proxyGroups != null && proxyGroups.Count > 0)
|
||||
{
|
||||
foreach (var it in proxyGroups)
|
||||
{
|
||||
if (string.IsNullOrEmpty(it.name) || !proxies.ContainsKey(it.name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = proxies[it.name];
|
||||
if (!Global.allowSelectType.Contains(item.type.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
_proxyGroups.Add(new ClashProxyModel()
|
||||
{
|
||||
now = item.now,
|
||||
name = item.name,
|
||||
type = item.type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//from api
|
||||
foreach (KeyValuePair<string, ProxiesItem> kv in proxies)
|
||||
{
|
||||
if (!Global.allowSelectType.Contains(kv.Value.type.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = _proxyGroups.Where(t => t.name == kv.Key).FirstOrDefault();
|
||||
if (item != null && !string.IsNullOrEmpty(item.name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
_proxyGroups.Add(new ClashProxyModel()
|
||||
{
|
||||
now = kv.Value.now,
|
||||
name = kv.Key,
|
||||
type = kv.Value.type
|
||||
});
|
||||
}
|
||||
|
||||
if (_proxyGroups != null && _proxyGroups.Count > 0)
|
||||
{
|
||||
if (selectedName != null && _proxyGroups.Any(t => t.name == selectedName))
|
||||
{
|
||||
SelectedGroup = _proxyGroups.FirstOrDefault(t => t.name == selectedName);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedGroup = _proxyGroups[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedGroup = new();
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshProxyDetails(bool c)
|
||||
{
|
||||
_proxyDetails.Clear();
|
||||
if (!c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var name = SelectedGroup?.name;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (proxies == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
proxies.TryGetValue(name, out ProxiesItem proxy);
|
||||
if (proxy == null || proxy.all == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var lstDetails = new List<ClashProxyModel>();
|
||||
foreach (var item in proxy.all)
|
||||
{
|
||||
var isActive = item == proxy.now;
|
||||
|
||||
var proxy2 = TryGetProxy(item);
|
||||
if (proxy2 == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int delay = -1;
|
||||
if (proxy2.history.Count > 0)
|
||||
{
|
||||
delay = proxy2.history[proxy2.history.Count - 1].delay;
|
||||
}
|
||||
|
||||
lstDetails.Add(new ClashProxyModel()
|
||||
{
|
||||
isActive = isActive,
|
||||
name = item,
|
||||
type = proxy2.type,
|
||||
delay = delay <= 0 ? delayTimeout : delay,
|
||||
delayName = delay <= 0 ? string.Empty : $"{delay}ms",
|
||||
});
|
||||
}
|
||||
//sort
|
||||
switch (SortingSelected)
|
||||
{
|
||||
case 0:
|
||||
lstDetails = lstDetails.OrderBy(t => t.delay).ToList();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
lstDetails = lstDetails.OrderBy(t => t.name).ToList();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_proxyDetails.AddRange(lstDetails);
|
||||
}
|
||||
|
||||
private ProxiesItem? TryGetProxy(string name)
|
||||
{
|
||||
if(proxies is null)
|
||||
return null;
|
||||
proxies.TryGetValue(name, out ProxiesItem proxy2);
|
||||
if (proxy2 != null)
|
||||
{
|
||||
return proxy2;
|
||||
}
|
||||
//from providers
|
||||
if (providers != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, ProvidersItem> kv in providers)
|
||||
{
|
||||
if (Global.proxyVehicleType.Contains(kv.Value.vehicleType.ToLower()))
|
||||
{
|
||||
var proxy3 = kv.Value.proxies.FirstOrDefault(t => t.name == name);
|
||||
if (proxy3 != null)
|
||||
{
|
||||
return proxy3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetActiveProxy()
|
||||
{
|
||||
if (SelectedGroup == null || string.IsNullOrEmpty(SelectedGroup.name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (SelectedDetail == null || string.IsNullOrEmpty(SelectedDetail.name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var name = SelectedGroup.name;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var nameNode = SelectedDetail.name;
|
||||
if (string.IsNullOrEmpty(nameNode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var selectedProxy = TryGetProxy(name);
|
||||
if (selectedProxy == null || selectedProxy.type != "Selector")
|
||||
{
|
||||
_noticeHandler?.Enqueue(ResUI.OperationFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
ClashApiHandler.Instance.ClashSetActiveProxy(name, nameNode);
|
||||
|
||||
selectedProxy.now = nameNode;
|
||||
var group = _proxyGroups.Where(it => it.name == SelectedGroup.name).FirstOrDefault();
|
||||
if (group != null)
|
||||
{
|
||||
group.now = nameNode;
|
||||
var group2 = JsonUtils.DeepCopy(group);
|
||||
_proxyGroups.Replace(group, group2);
|
||||
|
||||
SelectedGroup = group2;
|
||||
|
||||
//var index = _proxyGroups.IndexOf(group);
|
||||
//_proxyGroups.Remove(group);
|
||||
//_proxyGroups.Insert(index, group);
|
||||
}
|
||||
_noticeHandler?.Enqueue(ResUI.OperationSuccess);
|
||||
|
||||
//RefreshProxyDetails(true);
|
||||
//GetClashProxies(true);
|
||||
}
|
||||
|
||||
private void ProxiesDelayTest(bool blAll)
|
||||
{
|
||||
UpdateHandler(false, "Clash Proxies Latency Test");
|
||||
|
||||
ClashApiHandler.Instance.ClashProxiesDelayTest(blAll, _proxyDetails.ToList(), (item, result) =>
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
GetClashProxies(true);
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(result))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Application.Current?.Dispatcher.Invoke((Action)(() =>
|
||||
{
|
||||
//UpdateHandler(false, $"{item.name}={result}");
|
||||
var detail = _proxyDetails.Where(it => it.name == item.name).FirstOrDefault();
|
||||
if (detail != null)
|
||||
{
|
||||
var dicResult = JsonUtils.Deserialize<Dictionary<string, object>>(result);
|
||||
if (dicResult != null && dicResult.ContainsKey("delay"))
|
||||
{
|
||||
detail.delay = Convert.ToInt32(dicResult["delay"]);
|
||||
detail.delayName = $"{dicResult["delay"]}ms";
|
||||
}
|
||||
else if (dicResult != null && dicResult.ContainsKey("message"))
|
||||
{
|
||||
detail.delay = delayTimeout;
|
||||
detail.delayName = $"{dicResult["message"]}";
|
||||
}
|
||||
else
|
||||
{
|
||||
detail.delay = delayTimeout;
|
||||
detail.delayName = String.Empty;
|
||||
}
|
||||
_proxyDetails.Replace(detail, JsonUtils.DeepCopy(detail));
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#endregion proxy function
|
||||
|
||||
#region task
|
||||
|
||||
public void DelayTestTask()
|
||||
{
|
||||
var autoDelayTestTime = DateTime.Now;
|
||||
|
||||
Observable.Interval(TimeSpan.FromSeconds(60))
|
||||
.Subscribe(x =>
|
||||
{
|
||||
if (!(AutoRefresh && ClashApiHandler.Instance.ShowInTaskbar))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var dtNow = DateTime.Now;
|
||||
|
||||
if (_config.clashUIItem.AutoDelayTestInterval > 0)
|
||||
{
|
||||
if ((dtNow - autoDelayTestTime).Minutes % _config.clashUIItem.AutoDelayTestInterval == 0)
|
||||
{
|
||||
ProxiesDelayTest();
|
||||
autoDelayTestTime = dtNow;
|
||||
}
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion task
|
||||
}
|
||||
}
|
|
@ -1502,7 +1502,7 @@ namespace v2rayN.ViewModels
|
|||
Application.Current?.Dispatcher.Invoke((Action)(() =>
|
||||
{
|
||||
BlReloadEnabled = true;
|
||||
}));
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1515,7 +1515,7 @@ namespace v2rayN.ViewModels
|
|||
//ConfigHandler.SaveConfig(_config, false);
|
||||
|
||||
ChangeSystemProxyStatus(_config.sysProxyType, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void CloseCore()
|
||||
|
@ -1775,6 +1775,7 @@ namespace v2rayN.ViewModels
|
|||
Application.Current.Resources["StdFontSize1"] = size + 1;
|
||||
Application.Current.Resources["StdFontSize2"] = size + 2;
|
||||
Application.Current.Resources["StdFontSizeMsg"] = size - 1;
|
||||
Application.Current.Resources["StdFontSize-1"] = size - 1;
|
||||
|
||||
ConfigHandler.SaveConfig(_config);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
<reactiveui:ReactiveUserControl
|
||||
x:Class="v2rayN.Views.ClashConnectionsView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
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:vms="clr-namespace:v2rayN.ViewModels"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
x:TypeArguments="vms:ClashConnectionsViewModel"
|
||||
mc:Ignorable="d">
|
||||
<DockPanel Margin="8">
|
||||
<StackPanel
|
||||
Margin="8,0,8,8"
|
||||
HorizontalAlignment="Left"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock Style="{StaticResource ModuleTitle}" Text="{x:Static resx:ResUI.TbConnections}" />
|
||||
<materialDesign:Chip
|
||||
x:Name="chipCount"
|
||||
Height="20"
|
||||
IsEnabled="False"
|
||||
Style="{StaticResource ListItemChip}" />
|
||||
</StackPanel>
|
||||
<ToolBarTray Margin="0,8,0,8" DockPanel.Dock="Top">
|
||||
<ToolBar ClipToBounds="True" Style="{StaticResource MaterialDesignToolBar}">
|
||||
<Button Width="1" Visibility="Hidden">
|
||||
<materialDesign:PackIcon
|
||||
Margin="0,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Kind="ContentSave" />
|
||||
</Button>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSorting}" />
|
||||
<ComboBox
|
||||
x:Name="cmbSorting"
|
||||
Width="100"
|
||||
Margin="8"
|
||||
Style="{StaticResource DefComboBox}">
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingUpSpeed}" />
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingDownSpeed}" />
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingUpTraffic}" />
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingDownTraffic}" />
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingTime}" />
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingHost}" />
|
||||
</ComboBox>
|
||||
<Separator />
|
||||
<Button x:Name="btnConnectionCloseAll" ToolTip="{x:Static resx:ResUI.menuConnectionCloseAll}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
Margin="0,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Kind="Close" />
|
||||
<TextBlock Style="{StaticResource ToolbarTextBlock}" Text="{x:Static resx:ResUI.menuConnectionCloseAll}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Separator />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbAutoRefresh}" />
|
||||
<ToggleButton
|
||||
x:Name="togAutoRefresh"
|
||||
Margin="8"
|
||||
HorizontalAlignment="Left" />
|
||||
</ToolBar>
|
||||
</ToolBarTray>
|
||||
<DataGrid
|
||||
x:Name="lstConnections"
|
||||
AutoGenerateColumns="False"
|
||||
BorderThickness="1"
|
||||
CanUserAddRows="False"
|
||||
CanUserResizeRows="False"
|
||||
CanUserSortColumns="False"
|
||||
EnableRowVirtualization="True"
|
||||
GridLinesVisibility="All"
|
||||
HeadersVisibility="Column"
|
||||
IsReadOnly="True"
|
||||
Style="{StaticResource DefDataGrid}">
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu Style="{StaticResource DefContextMenu}">
|
||||
<MenuItem x:Name="menuConnectionClose" Header="{x:Static resx:ResUI.menuConnectionClose}" />
|
||||
<MenuItem x:Name="menuConnectionCloseAll" Header="{x:Static resx:ResUI.menuConnectionCloseAll}" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="300"
|
||||
Binding="{Binding host}"
|
||||
Header="{x:Static resx:ResUI.TbSortingHost}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding network}"
|
||||
Header="{x:Static resx:ResUI.TbSortingNetwork}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding type}"
|
||||
Header="{x:Static resx:ResUI.TbSortingType}" />
|
||||
<DataGridTextColumn
|
||||
Width="200"
|
||||
Binding="{Binding chain}"
|
||||
Header="{x:Static resx:ResUI.TbSortingChain}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding uploadTraffic}"
|
||||
Header="{x:Static resx:ResUI.TbSortingUpTraffic}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding downloadTraffic}"
|
||||
Header="{x:Static resx:ResUI.TbSortingDownTraffic}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding elapsed}"
|
||||
Header="{x:Static resx:ResUI.TbSortingTime}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveUserControl>
|
|
@ -0,0 +1,37 @@
|
|||
using v2rayN.ViewModels;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace v2rayN.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ConnectionsView.xaml
|
||||
/// </summary>
|
||||
public partial class ClashConnectionsView
|
||||
{
|
||||
public ClashConnectionsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
ViewModel = new ClashConnectionsViewModel();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.ConnectionItems, v => v.lstConnections.ItemsSource).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource, v => v.lstConnections.SelectedItem).DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ConnectionItems.Count, v => v.chipCount.Content).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.ConnectionCloseCmd, v => v.menuConnectionClose).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.menuConnectionCloseAll).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
private void btnClose_Click(object sender, System.Windows.RoutedEventArgs e)
|
||||
{
|
||||
ViewModel?.ClashConnectionClose(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
<reactiveui:ReactiveUserControl
|
||||
x:Class="v2rayN.Views.ClashProxiesView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:v2rayN.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
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:vms="clr-namespace:v2rayN.ViewModels"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
x:TypeArguments="vms:ClashProxiesViewModel"
|
||||
KeyDown="ProxiesView_KeyDown"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
|
||||
<converters:DelayColorConverter x:Key="DelayColorConverter" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<DockPanel Margin="8">
|
||||
<TextBlock
|
||||
Margin="8,0,8,8"
|
||||
DockPanel.Dock="Top"
|
||||
Style="{StaticResource ModuleTitle}"
|
||||
Text="{x:Static resx:ResUI.TbProxies}" />
|
||||
|
||||
<ToolBarTray DockPanel.Dock="Top">
|
||||
<ToolBar
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ClipToBounds="True"
|
||||
Style="{StaticResource MaterialDesignToolBar}">
|
||||
<Button Width="1" Visibility="Hidden">
|
||||
<materialDesign:PackIcon
|
||||
Margin="0,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Kind="ContentSave" />
|
||||
</Button>
|
||||
<Separator />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.menuRulemode}" />
|
||||
<ComboBox
|
||||
x:Name="cmbRulemode"
|
||||
Width="80"
|
||||
Margin="8"
|
||||
Style="{StaticResource DefComboBox}">
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeRule}" />
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeGlobal}" />
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeDirect}" />
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeNothing}" />
|
||||
</ComboBox>
|
||||
<Separator />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSorting}" />
|
||||
<ComboBox
|
||||
x:Name="cmbSorting"
|
||||
Width="60"
|
||||
Margin="8"
|
||||
Style="{StaticResource DefComboBox}">
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingDelay}" />
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingName}" />
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.TbSortingDefault}" />
|
||||
</ComboBox>
|
||||
<Separator />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbAutoRefresh}" />
|
||||
<ToggleButton
|
||||
x:Name="togAutoRefresh"
|
||||
Margin="8"
|
||||
HorizontalAlignment="Left" />
|
||||
</ToolBar>
|
||||
</ToolBarTray>
|
||||
<DockPanel>
|
||||
<ListView
|
||||
x:Name="lstProxyGroups"
|
||||
Margin="0,0,5,0"
|
||||
BorderThickness="0"
|
||||
DockPanel.Dock="Left"
|
||||
ItemContainerStyle="{StaticResource lvItemSelected}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
Style="{StaticResource MaterialDesignListView}">
|
||||
<ListView.ContextMenu>
|
||||
<ContextMenu Style="{StaticResource DefContextMenu}">
|
||||
<MenuItem x:Name="menuProxiesReload" Header="{x:Static resx:ResUI.menuProxiesReload}" />
|
||||
<MenuItem x:Name="menuProxiesDelaytest" Header="{x:Static resx:ResUI.menuProxiesDelaytest}" />
|
||||
</ContextMenu>
|
||||
</ListView.ContextMenu>
|
||||
<ListView.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListView.ItemsPanel>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Width="200" Padding="0">
|
||||
<DockPanel>
|
||||
<Grid Grid.Column="0" Margin="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal">
|
||||
<TextBlock Style="{StaticResource ListItemTitle}" Text="{Binding name}" />
|
||||
<TextBlock
|
||||
Margin="8,0,0,0"
|
||||
Style="{StaticResource ListItemSubTitle}"
|
||||
Text="{Binding type}" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Style="{StaticResource ListItemSubTitle}"
|
||||
Text="{Binding now}" />
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<ListView
|
||||
x:Name="lstProxyDetails"
|
||||
BorderThickness="0"
|
||||
ItemContainerStyle="{StaticResource lvItemSelected}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
Style="{StaticResource MaterialDesignListView}">
|
||||
<ListView.ContextMenu>
|
||||
<ContextMenu Style="{StaticResource DefContextMenu}">
|
||||
<MenuItem x:Name="menuProxiesDelaytestPart" Header="{x:Static resx:ResUI.menuProxiesDelaytestPart}" />
|
||||
<MenuItem x:Name="menuProxiesSelectActivity" Header="{x:Static resx:ResUI.menuProxiesSelectActivity}" />
|
||||
</ContextMenu>
|
||||
</ListView.ContextMenu>
|
||||
<ListView.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListView.ItemsPanel>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Width="200" Padding="0">
|
||||
<DockPanel>
|
||||
<Border
|
||||
Width="5"
|
||||
Height="auto"
|
||||
Margin="0,1"
|
||||
Background="{DynamicResource MaterialDesign.Brush.Primary.Light}"
|
||||
CornerRadius="4"
|
||||
DockPanel.Dock="Left"
|
||||
Visibility="{Binding Path=isActive, Converter={StaticResource BoolToVisConverter}}" />
|
||||
<Grid Margin="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition Height="1*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Style="{StaticResource ListItemSubTitle}"
|
||||
Text="{Binding name}"
|
||||
TextWrapping="WrapWithOverflow" />
|
||||
<DockPanel Grid.Row="1">
|
||||
<TextBlock
|
||||
DockPanel.Dock="Right"
|
||||
Foreground="{Binding Path=delay, Converter={StaticResource DelayColorConverter}}"
|
||||
Style="{StaticResource ListItemSubTitle2}"
|
||||
Text="{Binding delayName}" />
|
||||
<TextBlock Style="{StaticResource ListItemSubTitle2}" Text="{Binding type}" />
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveUserControl>
|
|
@ -0,0 +1,60 @@
|
|||
using v2rayN.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace v2rayN.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ProxiesView.xaml
|
||||
/// </summary>
|
||||
public partial class ClashProxiesView
|
||||
{
|
||||
public ClashProxiesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
ViewModel = new ClashProxiesViewModel();
|
||||
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(ClashProxiesViewModel));
|
||||
lstProxyDetails.PreviewMouseDoubleClick += lstProxyDetails_PreviewMouseDoubleClick;
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.ProxyGroups, v => v.lstProxyGroups.ItemsSource).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedGroup, v => v.lstProxyGroups.SelectedItem).DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.ProxyDetails, v => v.lstProxyDetails.ItemsSource).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedDetail, v => v.lstProxyDetails.SelectedItem).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.ProxiesReloadCmd, v => v.menuProxiesReload).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.ProxiesDelaytestCmd, v => v.menuProxiesDelaytest).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.ProxiesDelaytestPartCmd, v => v.menuProxiesDelaytestPart).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.ProxiesSelectActivityCmd, v => v.menuProxiesSelectActivity).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.RuleModeSelected, v => v.cmbRulemode.SelectedIndex).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
private void ProxiesView_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.F5:
|
||||
ViewModel?.ProxiesReload();
|
||||
break;
|
||||
|
||||
case Key.Enter:
|
||||
ViewModel?.SetActiveProxy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void lstProxyDetails_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ViewModel?.SetActiveProxy();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@
|
|||
<PackageReference Include="ReactiveUI.Validation" Version="4.0.9" />
|
||||
<PackageReference Include="ReactiveUI.WPF" Version="20.1.1" />
|
||||
<PackageReference Include="Splat.NLog" Version="15.1.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
Loading…
Reference in New Issue