├── .gitignore ├── LICENSE ├── README.md └── clashN ├── clashN.sln ├── clashN ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── Base │ ├── HttpClientHelper.cs │ └── StringEx.cs ├── Converters │ ├── DelayColorConverter.cs │ └── MaterialDesignFonts.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── Global.cs ├── Handler │ ├── ConfigProc.cs │ ├── CoreConfigHandler.cs │ ├── CoreHandler.cs │ ├── DownloadHandle.cs │ ├── LazyConfig.cs │ ├── MainFormHandler.cs │ ├── NoticeHandler.cs │ ├── ProxySetting.cs │ ├── QRCodeHelper.cs │ ├── SpeedtestHandler.cs │ ├── StatisticsHandler.cs │ ├── SysProxyHandle.cs │ └── UpdateHandle.cs ├── Mode │ ├── ClashConnections.cs │ ├── ClashProviders.cs │ ├── ClashProxies.cs │ ├── ComboItem.cs │ ├── Config.cs │ ├── ConnectionModel.cs │ ├── CoreInfo.cs │ ├── CoreKind.cs │ ├── EProfileColName.cs │ ├── ERuleMode.cs │ ├── ESpeedActionType.cs │ ├── GlobalHotkeyAction.cs │ ├── KeyShortcut.cs │ ├── MovementTarget.cs │ ├── ProfileItemModel.cs │ ├── ProxyModel.cs │ ├── ServerStatistics.cs │ ├── ServerTestItem.cs │ └── SysProxyType.cs ├── Properties │ ├── Resources.Designer.cs │ └── Resources.resx ├── Resources │ ├── NotifyIcon1.ico │ ├── NotifyIcon2.ico │ ├── NotifyIcon3.ico │ ├── sysproxy.exe.gz │ └── sysproxy64.exe.gz ├── Resx │ ├── ResUI.Designer.cs │ ├── ResUI.fa-IR.resx │ ├── ResUI.resx │ └── ResUI.zh-Hans.resx ├── Sample │ ├── SampleMixin.yaml │ └── SampleTun.yaml ├── Tool │ ├── FileManager.cs │ ├── Job.cs │ ├── Logging.cs │ ├── QueryableExtension.cs │ ├── UI.cs │ └── Utils.cs ├── ViewModels │ ├── ConnectionsViewModel.cs │ ├── DashboardViewModel.cs │ ├── HelpViewModel.cs │ ├── LogsViewModel.cs │ ├── MainWindowViewModel.cs │ ├── ProfileEditViewModel.cs │ ├── ProfilesViewModel.cs │ ├── PromotionViewModel.cs │ ├── ProxiesViewModel.cs │ └── SettingsViewModel.cs ├── Views │ ├── ConnectionsView.xaml │ ├── ConnectionsView.xaml.cs │ ├── DashboardView.xaml │ ├── DashboardView.xaml.cs │ ├── GlobalHotkeySettingWindow.xaml │ ├── GlobalHotkeySettingWindow.xaml.cs │ ├── HelpView.xaml │ ├── HelpView.xaml.cs │ ├── LogsView.xaml │ ├── LogsView.xaml.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── MessageSampleDialog.xaml │ ├── MessageSampleDialog.xaml.cs │ ├── MsgView.xaml │ ├── MsgView.xaml.cs │ ├── PorfileEditWindow.xaml │ ├── PorfileEditWindow.xaml.cs │ ├── ProfileQrcodeView.xaml │ ├── ProfileQrcodeView.xaml.cs │ ├── ProfilesView.xaml │ ├── ProfilesView.xaml.cs │ ├── PromotionView.xaml │ ├── PromotionView.xaml.cs │ ├── ProxiesView.xaml │ ├── ProxiesView.xaml.cs │ ├── SettingsView.xaml │ └── SettingsView.xaml.cs ├── app.manifest ├── clashN.csproj └── clashN.ico └── clashUpgrade ├── MainForm.Designer.cs ├── MainForm.cs ├── MainForm.resx ├── Program.cs ├── Properties ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings └── clashUpgrade.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。 3 | ################################################################################ 4 | ` 5 | /clashN/.vs/ 6 | /clashN/clashN/bin/Debug/app.publish 7 | /clashN/clashN/bin/Debug 8 | /clashN/clashN/bin/Release 9 | /clashN/clashN/obj/ 10 | /clashN/.vs/clashN/DesignTimeBuild 11 | /clashN/packages 12 | .vs/ProjectSettings.json 13 | .vs/slnx.sqlite 14 | .vs/VSWorkspaceState.json 15 | /clashN/clashUpgrade/bin/Debug 16 | /clashN/clashUpgrade/obj/Debug 17 | /clashN/clashUpgrade/bin/Release 18 | /clashN/clashUpgrade/obj/Release 19 | /clashN/clashUpgrade/obj 20 | /clashN/clashUpgrade/Properties/PublishProfiles 21 | /clashN/clashN/Properties/PublishProfiles 22 | *.user 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## [本项目功能将合并到v2rayN项目](https://github.com/2dust/clashN/issues/358) 2 | 3 | 4 | # ClashN 5 | A clash client for Windows, supports [Mihomo core](https://github.com/MetaCubeX/Mihomo) 6 | 7 | [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/clashn)](https://github.com/2dust/clashn/commits/master) 8 | [![GitHub Releases](https://img.shields.io/github/downloads/2dust/clashn/latest/total?logo=github)](https://github.com/2dust/clashn/releases) 9 | 10 | ### How to use 11 | 1. Download `clashN.zip` from [releases](https://github.com/2dust/clashN/releases) 12 | 2. Unzip to any folder you want 13 | 3. Run ClashN.exe 14 | 15 | 16 | 17 | ### Tips 18 | 19 | - You can also add `v2ray` subscription to `ClashN`, just enable `Subcription conversion` when you add a profile. 20 | 21 | 22 | 23 | ### Requirements 24 | - Microsoft [.NET 8.0 Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0/runtime) 25 | - Mihomo core [https://github.com/MetaCubeX/Mihomo/releases](https://github.com/MetaCubeX/Mihomo/releases) 26 | 27 | 28 | 29 | ### Telegram Channel 30 | [github_2dust](https://t.me/github_2dust) 31 | 32 | 33 | 34 | ### Contribute 35 | 36 | To build this project, you should clone both [ClashN](https://github.com/2dust/clashN) and [v2rayN](https://github.com/2dust/v2rayN) to the same folder 37 | ``` 38 | Example folder 39 | ├─ ClashN 40 | | ├─ clashN 41 | | | ├─ clashN.sln 42 | | | └─ ... 43 | | ├─ README.md 44 | | └─ ... 45 | ├─ v2rayN 46 | | ├─ v2rayN 47 | | | ├─ v2rayN.sln 48 | | | └─ ... 49 | | ├─ README.md 50 | | └─ ... 51 | └─ ... 52 | ``` 53 | -------------------------------------------------------------------------------- /clashN/clashN.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32811.315 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClashN", "clashN\ClashN.csproj", "{6DE127CA-1763-4236-B297-D2EF9CB2EC9B}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClashUpgrade", "clashUpgrade\ClashUpgrade.csproj", "{59E0AF2B-9915-47F2-9F5F-9008F47DAE5F}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PacLib", "..\..\v2rayN\v2rayN\PacLib\PacLib.csproj", "{E27F1C24-9A0A-47D5-8723-B17D00F264FB}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {6DE127CA-1763-4236-B297-D2EF9CB2EC9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {6DE127CA-1763-4236-B297-D2EF9CB2EC9B}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {6DE127CA-1763-4236-B297-D2EF9CB2EC9B}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {6DE127CA-1763-4236-B297-D2EF9CB2EC9B}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {59E0AF2B-9915-47F2-9F5F-9008F47DAE5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {59E0AF2B-9915-47F2-9F5F-9008F47DAE5F}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {59E0AF2B-9915-47F2-9F5F-9008F47DAE5F}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {59E0AF2B-9915-47F2-9F5F-9008F47DAE5F}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {E27F1C24-9A0A-47D5-8723-B17D00F264FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {E27F1C24-9A0A-47D5-8723-B17D00F264FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {E27F1C24-9A0A-47D5-8723-B17D00F264FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {E27F1C24-9A0A-47D5-8723-B17D00F264FB}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {43E06CBD-3DA9-40A3-8E4D-F0943CB0DD32} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /clashN/clashN/App.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 16 | 17 | 18 | 11 19 | 12 20 | 13 21 | 14 22 | 28 | 34 | 40 | 46 | 52 | 58 | 64 | 67 | 84 | 90 | 93 | 100 | 107 | 113 | 119 | 125 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /clashN/clashN/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using ClashN.Handler; 2 | using ClashN.Mode; 3 | using ClashN.Tool; 4 | using System.Diagnostics; 5 | using System.Windows; 6 | using System.Windows.Threading; 7 | 8 | namespace ClashN 9 | { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application 14 | { 15 | public static EventWaitHandle? ProgramStarted; 16 | private Config? _config; 17 | 18 | static App() 19 | { 20 | ProgramStarted = new EventWaitHandle(false, EventResetMode.AutoReset, "ProgramStartedEvent", out bool bCreatedNew); 21 | if (!bCreatedNew) 22 | { 23 | ProgramStarted.Set(); 24 | Environment.Exit(-1); 25 | return; 26 | } 27 | } 28 | 29 | public App() 30 | { 31 | // Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetCallingAssembly()); 32 | 33 | this.DispatcherUnhandledException += App_DispatcherUnhandledException; 34 | AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; 35 | TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; 36 | } 37 | 38 | /// 39 | /// 只打开一个进程 40 | /// 41 | /// 42 | protected override void OnStartup(StartupEventArgs e) 43 | { 44 | foreach (string arg in e.Args) 45 | { 46 | Utils.SetClipboardData(arg); 47 | } 48 | 49 | Global.processJob = new Job(); 50 | 51 | Logging.Setup(); 52 | Utils.SaveLog($"ClashN start up | {Utils.GetVersion()} | {Utils.GetExePath()}"); 53 | Logging.ClearLogs(); 54 | 55 | Init(); 56 | 57 | string lang = Utils.RegReadValue(Global.MyRegPath, Global.MyRegKeyLanguage, Global.Languages[0]); 58 | Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(lang); 59 | 60 | base.OnStartup(e); 61 | } 62 | 63 | private void Init() 64 | { 65 | if (ConfigProc.LoadConfig(ref _config) != 0) 66 | { 67 | UI.ShowWarning($"Loading GUI configuration file is abnormal,please restart the application{Environment.NewLine}加载GUI配置文件异常,请重启应用"); 68 | Environment.Exit(0); 69 | return; 70 | } 71 | } 72 | 73 | private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) 74 | { 75 | Utils.SaveLog("App_DispatcherUnhandledException", e.Exception); 76 | e.Handled = true; 77 | } 78 | 79 | private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) 80 | { 81 | if (e.ExceptionObject != null) 82 | { 83 | Utils.SaveLog("CurrentDomain_UnhandledException", (Exception)e.ExceptionObject!); 84 | } 85 | } 86 | 87 | private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e) 88 | { 89 | Utils.SaveLog("TaskScheduler_UnobservedTaskException", e.Exception); 90 | } 91 | protected override void OnExit(ExitEventArgs e) 92 | { 93 | Utils.SaveLog("OnExit"); 94 | base.OnExit(e); 95 | Process.GetCurrentProcess().Kill(); 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /clashN/clashN/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] -------------------------------------------------------------------------------- /clashN/clashN/Base/HttpClientHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Net.Http; 3 | using System.Net.Http.Headers; 4 | 5 | namespace ClashN.Base 6 | { 7 | /// 8 | /// 9 | public class HttpClientHelper 10 | { 11 | private static HttpClientHelper httpClientHelper = null; 12 | private HttpClient httpClient; 13 | private int progressPercentage = -1; 14 | 15 | /// 16 | /// 17 | private HttpClientHelper() 18 | { 19 | httpClient = new HttpClient(new HttpClientHandler() 20 | { 21 | UseCookies = false 22 | }); 23 | } 24 | 25 | /// 26 | /// 27 | /// 28 | public static HttpClientHelper GetInstance() 29 | { 30 | if (httpClientHelper != null) 31 | { 32 | return httpClientHelper; 33 | } 34 | else 35 | { 36 | HttpClientHelper httpClientHelper = new HttpClientHelper(); 37 | 38 | HttpClientHandler handler = new HttpClientHandler() { UseCookies = false }; 39 | httpClientHelper.httpClient = new HttpClient(handler); 40 | return httpClientHelper; 41 | } 42 | } 43 | 44 | public async Task TryGetAsync(string url) 45 | { 46 | if (string.IsNullOrEmpty(url)) 47 | return null; 48 | 49 | try 50 | { 51 | HttpResponseMessage response = await httpClient.GetAsync(url); 52 | return await response.Content.ReadAsStringAsync(); 53 | } 54 | catch 55 | { 56 | return null; 57 | } 58 | } 59 | 60 | public async Task<(string, HttpResponseHeaders)> GetAsync(HttpClient client, string url, CancellationToken token) 61 | { 62 | if (string.IsNullOrEmpty(url)) 63 | { 64 | return (null, null); 65 | } 66 | HttpResponseMessage response = await client.GetAsync(url, token); 67 | if (!response.IsSuccessStatusCode) 68 | { 69 | throw new Exception(string.Format("The request returned with HTTP status code {0}", response.StatusCode)); 70 | } 71 | return (await response.Content.ReadAsStringAsync(), response.Headers); 72 | } 73 | 74 | public async Task PutAsync(string url, Dictionary headers) 75 | { 76 | var myContent = Utils.ToJson(headers); 77 | var buffer = System.Text.Encoding.UTF8.GetBytes(myContent); 78 | var byteContent = new ByteArrayContent(buffer); 79 | byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); 80 | 81 | await httpClient.PutAsync(url, byteContent); 82 | } 83 | 84 | public async Task PatchAsync(string url, Dictionary headers) 85 | { 86 | var myContent = Utils.ToJson(headers); 87 | var buffer = System.Text.Encoding.UTF8.GetBytes(myContent); 88 | var byteContent = new ByteArrayContent(buffer); 89 | byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); 90 | 91 | await httpClient.PatchAsync(url, byteContent); 92 | } 93 | 94 | public async Task DeleteAsync(string url) 95 | { 96 | //var myContent = Utils.ToJson(headers); 97 | //var buffer = System.Text.Encoding.UTF8.GetBytes(myContent); 98 | //var byteContent = new ByteArrayContent(buffer); 99 | //byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); 100 | 101 | await httpClient.DeleteAsync(url); 102 | } 103 | 104 | public async Task DownloadFileAsync(HttpClient client, string url, string fileName, IProgress progress, CancellationToken token) 105 | { 106 | if (string.IsNullOrEmpty(url)) 107 | throw new ArgumentNullException(nameof(url)); 108 | if (string.IsNullOrEmpty(fileName)) 109 | throw new ArgumentNullException(nameof(fileName)); 110 | 111 | HttpResponseMessage response = 112 | await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); 113 | 114 | if (!response.IsSuccessStatusCode) 115 | throw new Exception(string.Format("The request returned with HTTP status code {0}", response.StatusCode)); 116 | 117 | var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L; 118 | var canReportProgress = total != -1 && progress != null; 119 | 120 | using var stream = await response.Content.ReadAsStreamAsync(); 121 | using var file = File.Create(fileName); 122 | 123 | var totalRead = 0L; 124 | var buffer = new byte[1024 * 1024]; 125 | var isMoreToRead = true; 126 | progressPercentage = -1; 127 | 128 | do 129 | { 130 | token.ThrowIfCancellationRequested(); 131 | 132 | var read = await stream.ReadAsync(buffer, 0, buffer.Length, token); 133 | 134 | if (read == 0) 135 | { 136 | isMoreToRead = false; 137 | } 138 | else 139 | { 140 | var data = new byte[read]; 141 | buffer.ToList().CopyTo(0, data, 0, read); 142 | 143 | // TODO: put here the code to write the file to disk 144 | file.Write(data, 0, read); 145 | 146 | totalRead += read; 147 | 148 | if (canReportProgress) 149 | { 150 | var percent = Convert.ToInt32((totalRead * 1d) / (total * 1d) * 100); 151 | if (progressPercentage != percent && percent % 10 == 0) 152 | { 153 | progressPercentage = percent; 154 | progress?.Report(percent); 155 | } 156 | } 157 | } 158 | } while (isMoreToRead); 159 | 160 | if (canReportProgress) 161 | progress?.Report(101); 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /clashN/clashN/Base/StringEx.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace ClashN.Base 4 | { 5 | internal static class StringEx 6 | { 7 | public static bool BeginWithAny(this string? s, IEnumerable chars) 8 | { 9 | if (string.IsNullOrEmpty(s)) 10 | return false; 11 | 12 | return chars.Contains(s[0]); 13 | } 14 | 15 | public static IEnumerable NonWhiteSpaceLines(this TextReader reader) 16 | { 17 | string? line; 18 | while ((line = reader.ReadLine()) != null) 19 | { 20 | if (string.IsNullOrWhiteSpace(line)) 21 | continue; 22 | yield return line; 23 | } 24 | } 25 | 26 | public static string TrimEx(this string? value) 27 | { 28 | return value == null ? string.Empty : value.Trim(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /clashN/clashN/Converters/DelayColorConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Data; 2 | using System.Windows.Media; 3 | 4 | namespace ClashN.Converters 5 | { 6 | public class DelayColorConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 9 | { 10 | var temp = (int)value; 11 | 12 | if (temp <= 200) 13 | return new SolidColorBrush(Colors.Green); 14 | else 15 | return new SolidColorBrush(Colors.IndianRed); 16 | } 17 | 18 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 19 | { 20 | return null; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /clashN/clashN/Converters/MaterialDesignFonts.cs: -------------------------------------------------------------------------------- 1 | using ClashN.Handler; 2 | using System.Windows.Media; 3 | 4 | namespace ClashN.Converters 5 | { 6 | public class MaterialDesignFonts 7 | { 8 | public static FontFamily MyFont { get; } 9 | 10 | static MaterialDesignFonts() 11 | { 12 | try 13 | { 14 | var fontFamily = LazyConfig.Instance.Config.UiItem.currentFontFamily; 15 | if (!string.IsNullOrEmpty(fontFamily)) 16 | { 17 | var fontPath = Utils.GetFontsPath(); 18 | MyFont = new FontFamily(new Uri(@$"file:///{fontPath}\"), $"./#{fontFamily}"); 19 | } 20 | } 21 | catch 22 | { 23 | } 24 | if (MyFont is null) 25 | { 26 | MyFont = new FontFamily("Microsoft YaHei"); 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /clashN/clashN/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /clashN/clashN/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 12 | 13 | 14 | 15 | 16 | A comma-separated list of error codes that can be safely ignored in assembly verification. 17 | 18 | 19 | 20 | 21 | 'false' to turn off automatic generation of the XML Schema file. 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /clashN/clashN/Global.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN 2 | { 3 | internal class Global 4 | { 5 | #region 常量 6 | 7 | public const string AboutUrl = @"https://github.com/2dust/clashN"; 8 | public const string UpdateUrl = AboutUrl + @"/releases"; 9 | public const string NUrl = @"https://github.com/2dust/clashN/releases"; 10 | public const string clashCoreUrl = "https://github.com/Dreamacro/clash/releases"; 11 | public const string clashMetaCoreUrl = "https://github.com/MetaCubeX/Clash.Meta/releases"; 12 | public const string mihomoCoreUrl = "https://github.com/MetaCubeX/mihomo/releases"; 13 | public const string geoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat"; 14 | 15 | /// 16 | /// SpeedTestUrl 17 | /// 18 | public const string SpeedTestUrl = @"http://cachefly.cachefly.net/10mb.test"; 19 | 20 | public const string SpeedPingTestUrl = @"https://www.google.com/generate_204"; 21 | 22 | public static readonly List SubConvertUrls = new List { 23 | @"https://sub.xeton.dev/sub?target=clash&url={0}", 24 | @"https://api.dler.io/sub?target=clash&url={0}", 25 | @"http://127.0.0.1:25500/sub?target=clash&url={0}", 26 | "" 27 | }; 28 | 29 | public static readonly List SubConvertConfig = new List { 30 | @"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini" 31 | }; 32 | 33 | /// 34 | /// PromotionUrl 35 | /// 36 | public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="; 37 | 38 | /// 39 | /// 本软件配置文件名 40 | /// 41 | public const string ConfigFileName = "guiNConfig.json"; 42 | 43 | /// 44 | /// 配置文件名 45 | /// 46 | public const string coreConfigFileName = "config.yaml"; 47 | 48 | public const string mixinConfigFileName = "Mixin.yaml"; 49 | 50 | public const string SampleMixin = "ClashN.Sample.SampleMixin.yaml"; 51 | public const string SampleTun = "ClashN.Sample.SampleTun.yaml"; 52 | 53 | public const string InboundSocks = "socks"; 54 | public const string InboundHttp = "http"; 55 | public const string Loopback = "127.0.0.1"; 56 | 57 | /// 58 | /// http 59 | /// 60 | public const string httpProtocol = "http://"; 61 | 62 | /// 63 | /// https 64 | /// 65 | public const string httpsProtocol = "https://"; 66 | 67 | public const string clashProtocol = "clash://"; 68 | 69 | /// 70 | /// MyRegPath 71 | /// 72 | public const string MyRegPath = "Software\\clashNGUI"; 73 | 74 | /// 75 | /// Language 76 | /// 77 | public const string MyRegKeyLanguage = "CurrentLanguage"; 78 | 79 | /// 80 | /// Font 81 | /// 82 | public const string MyRegKeyFont = "CurrentFont"; 83 | 84 | /// 85 | /// URL Schemes 86 | /// 87 | public const string MyRegPathClasses = "SOFTWARE\\Classes\\clash"; 88 | 89 | /// 90 | /// Icon 91 | /// 92 | public const string CustomIconName = "ClashN.ico"; 93 | 94 | public const int MinFontSize = 10; 95 | 96 | public const string StatisticLogOverall = "StatisticLogOverall.json"; 97 | 98 | public const string IEProxyExceptions = "localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*"; 99 | 100 | public static readonly List IEProxyProtocols = new List { 101 | "{ip}:{http_port}", 102 | "socks={ip}:{socks_port}", 103 | "http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}", 104 | "http=http://{ip}:{http_port};https=http://{ip}:{http_port}", 105 | "" 106 | }; 107 | 108 | public static readonly List coreTypes = new List { "Clash", "ClashPremium", "ClashMeta", "Mihomo" }; 109 | 110 | public static readonly List allowSelectType = new List { "selector", "urltest", "loadbalance", "fallback" }; 111 | 112 | public static readonly List notAllowTestType = new List { "selector", "urltest", "direct", "reject", "compatible", "pass", "loadbalance", "fallback" }; 113 | 114 | public static readonly List proxyVehicleType = new List { "file", "http" }; 115 | 116 | public static readonly List Languages = new List { "zh-Hans", "en", "fa-IR" }; 117 | 118 | public static readonly List LogLevel = new List { "debug", "info", "warning", "error", "silent" }; 119 | 120 | #endregion 常量 121 | 122 | #region 全局变量 123 | 124 | /// 125 | /// 是否需要重启服务 126 | /// 127 | public static bool reloadCore 128 | { 129 | get; set; 130 | } 131 | 132 | public static Job processJob 133 | { 134 | get; set; 135 | } 136 | 137 | public static bool ShowInTaskbar 138 | { 139 | get; set; 140 | } 141 | 142 | #endregion 全局变量 143 | } 144 | } -------------------------------------------------------------------------------- /clashN/clashN/Handler/DownloadHandle.cs: -------------------------------------------------------------------------------- 1 | using ClashN.Base; 2 | using ClashN.Resx; 3 | using System.IO; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Net.Http.Headers; 7 | using System.Net.Sockets; 8 | 9 | namespace ClashN.Handler 10 | { 11 | /// 12 | ///Download 13 | /// 14 | internal class DownloadHandle 15 | { 16 | public event EventHandler? UpdateCompleted; 17 | 18 | public event ErrorEventHandler? Error; 19 | 20 | public class ResultEventArgs : EventArgs 21 | { 22 | public bool Success; 23 | public string Msg; 24 | 25 | public ResultEventArgs(bool success, string msg) 26 | { 27 | this.Success = success; 28 | this.Msg = msg; 29 | } 30 | } 31 | 32 | public void DownloadFileAsync(string url, bool blProxy, int downloadTimeout) 33 | { 34 | try 35 | { 36 | Utils.SetSecurityProtocol(LazyConfig.Instance.Config.EnableSecurityProtocolTls13); 37 | UpdateCompleted?.Invoke(this, new ResultEventArgs(false, ResUI.Downloading)); 38 | 39 | var client = new HttpClient(new SocketsHttpHandler() 40 | { 41 | Proxy = GetWebProxy(blProxy) 42 | }); 43 | 44 | var progress = new Progress(); 45 | progress.ProgressChanged += (sender, value) => 46 | { 47 | if (UpdateCompleted != null) 48 | { 49 | string msg = string.Format("...{0}%", value); 50 | UpdateCompleted(this, new ResultEventArgs(value > 100 ? true : false, msg)); 51 | } 52 | }; 53 | 54 | var cancellationToken = new CancellationTokenSource(); 55 | _ = HttpClientHelper.GetInstance().DownloadFileAsync(client, 56 | url, 57 | Utils.GetTempPath(Utils.GetDownloadFileName(url)), 58 | progress, 59 | cancellationToken.Token); 60 | } 61 | catch (Exception ex) 62 | { 63 | Utils.SaveLog(ex.Message, ex); 64 | 65 | Error?.Invoke(this, new ErrorEventArgs(ex)); 66 | } 67 | } 68 | 69 | /// 70 | /// DownloadString 71 | /// 72 | /// 73 | public async Task<(string, HttpResponseHeaders)?> DownloadStringAsync(string url, bool blProxy, string userAgent) 74 | { 75 | try 76 | { 77 | Utils.SetSecurityProtocol(LazyConfig.Instance.Config.EnableSecurityProtocolTls13); 78 | var webProxy = GetWebProxy(blProxy); 79 | var client = new HttpClient(new SocketsHttpHandler() 80 | { 81 | Proxy = webProxy, 82 | UseProxy = webProxy != null 83 | }); 84 | 85 | if (string.IsNullOrEmpty(userAgent)) 86 | { 87 | userAgent = $"{Utils.GetVersion(false)}"; 88 | } 89 | client.DefaultRequestHeaders.UserAgent.TryParseAdd(userAgent); 90 | 91 | Uri uri = new Uri(url); 92 | //Authorization Header 93 | if (!string.IsNullOrEmpty(uri.UserInfo)) 94 | { 95 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Utils.Base64Encode(uri.UserInfo)); 96 | } 97 | 98 | var cts = new CancellationTokenSource(); 99 | cts.CancelAfter(1000 * 30); 100 | 101 | var result = await HttpClientHelper.GetInstance().GetAsync(client, url, cts.Token); 102 | return result; 103 | } 104 | catch (Exception ex) 105 | { 106 | Utils.SaveLog(ex.Message, ex); 107 | Error?.Invoke(this, new ErrorEventArgs(ex)); 108 | if (ex.InnerException != null) 109 | { 110 | Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); 111 | } 112 | } 113 | 114 | return null; 115 | } 116 | 117 | public async Task UrlRedirectAsync(string url, bool blProxy) 118 | { 119 | Utils.SetSecurityProtocol(LazyConfig.Instance.Config.EnableSecurityProtocolTls13); 120 | SocketsHttpHandler webRequestHandler = new SocketsHttpHandler 121 | { 122 | AllowAutoRedirect = false, 123 | Proxy = GetWebProxy(blProxy) 124 | }; 125 | HttpClient client = new HttpClient(webRequestHandler); 126 | 127 | HttpResponseMessage response = await client.GetAsync(url); 128 | if (response.StatusCode == HttpStatusCode.Found || response.StatusCode == HttpStatusCode.Redirect) 129 | { 130 | return response.Headers.Location?.ToString(); 131 | } 132 | else 133 | { 134 | Utils.SaveLog("StatusCode error: " + url); 135 | return null; 136 | } 137 | } 138 | 139 | private WebProxy GetWebProxy(bool blProxy) 140 | { 141 | if (!blProxy) 142 | { 143 | return null; 144 | } 145 | var socksPort = LazyConfig.Instance.Config.SocksPort; 146 | if (!SocketCheck(Global.Loopback, socksPort)) 147 | { 148 | return null; 149 | } 150 | 151 | return new WebProxy($"socks5://{Global.Loopback}:{socksPort}"); 152 | } 153 | 154 | private bool SocketCheck(string ip, int port) 155 | { 156 | Socket sock = null; 157 | try 158 | { 159 | IPAddress ipa = IPAddress.Parse(ip); 160 | IPEndPoint point = new IPEndPoint(ipa, port); 161 | sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 162 | sock.Connect(point); 163 | return true; 164 | } 165 | catch { } 166 | finally 167 | { 168 | if (sock != null) 169 | { 170 | sock.Close(); 171 | sock.Dispose(); 172 | } 173 | } 174 | return false; 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /clashN/clashN/Handler/LazyConfig.cs: -------------------------------------------------------------------------------- 1 | using ClashN.Mode; 2 | using System.Runtime.Intrinsics.X86; 3 | using static ClashN.Mode.ClashProxies; 4 | 5 | namespace ClashN.Handler 6 | { 7 | public sealed class LazyConfig 8 | { 9 | private static readonly Lazy _instance = new Lazy(() => new LazyConfig()); 10 | private Config _config; 11 | private List coreInfos; 12 | private Dictionary _proxies; 13 | 14 | public static LazyConfig Instance 15 | { 16 | get { return _instance.Value; } 17 | } 18 | 19 | public void SetConfig(Config config) 20 | { 21 | _config = config; 22 | } 23 | 24 | public Config Config => _config; 25 | 26 | public void SetProxies(Dictionary proxies) 27 | { 28 | _proxies = proxies; 29 | } 30 | 31 | public Dictionary GetProxies() 32 | { 33 | return _proxies; 34 | } 35 | 36 | public Dictionary ProfileContent { get; set; } 37 | 38 | public CoreKind GetCoreType(ProfileItem profileItem) 39 | { 40 | if (profileItem != null && profileItem.coreType != null) 41 | { 42 | return (CoreKind)profileItem.coreType; 43 | } 44 | return CoreKind.Clash; 45 | } 46 | 47 | public CoreInfo GetCoreInfo(CoreKind coreType) 48 | { 49 | if (coreInfos == null) 50 | { 51 | InitCoreInfo(); 52 | } 53 | return coreInfos.Where(t => t.coreType == coreType).FirstOrDefault(); 54 | } 55 | 56 | private void InitCoreInfo() 57 | { 58 | coreInfos = new List(); 59 | 60 | coreInfos.Add(new CoreInfo 61 | { 62 | coreType = CoreKind.ClashN, 63 | coreUrl = Global.NUrl, 64 | coreLatestUrl = Global.NUrl + "/latest", 65 | coreDownloadUrl32 = Global.NUrl + "/download/{0}/clashN-32.zip", 66 | coreDownloadUrl64 = Global.NUrl + "/download/{0}/clashN.zip", 67 | }); 68 | 69 | coreInfos.Add(new CoreInfo 70 | { 71 | coreType = CoreKind.Clash, 72 | coreExes = new List { "clash-windows-amd64-v3", "clash-windows-amd64", "clash-windows-386", "clash" }, 73 | arguments = "-f config.yaml", 74 | coreUrl = Global.clashCoreUrl, 75 | coreLatestUrl = Global.clashCoreUrl + "/latest", 76 | coreDownloadUrl32 = Global.clashCoreUrl + "/download/{0}/clash-windows-386-{0}.zip", 77 | coreDownloadUrl64 = Global.clashCoreUrl + "/download/{0}/clash-windows-amd64-{0}.zip", 78 | match = "Clash" 79 | }); 80 | 81 | coreInfos.Add(new CoreInfo 82 | { 83 | coreType = CoreKind.ClashMeta, 84 | coreExes = new List { $"Clash.Meta-windows-amd64{(Avx2.X64.IsSupported ? "" : "-compatible")}", "Clash.Meta-windows-amd64-compatible", "Clash.Meta-windows-amd64", "Clash.Meta-windows-386", "Clash.Meta", "clash" }, 85 | arguments = "-f config.yaml", 86 | coreUrl = Global.clashMetaCoreUrl, 87 | coreLatestUrl = Global.clashMetaCoreUrl + "/latest", 88 | coreDownloadUrl32 = Global.clashMetaCoreUrl + "/download/{0}/Clash.Meta-windows-386-{0}.zip", 89 | coreDownloadUrl64 = Global.clashMetaCoreUrl + "/download/{0}/Clash.Meta-windows-amd64" + (Avx2.X64.IsSupported ? "" : "-compatible") + "-{0}.zip", 90 | match = "Clash Meta" 91 | }); 92 | 93 | coreInfos.Add(new CoreInfo 94 | { 95 | coreType = CoreKind.Mihomo, 96 | coreExes = new List { $"mihomo-windows-amd64{(Avx2.X64.IsSupported ? "" : "-compatible")}", "mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-windows-386", "mihomo", "clash" }, 97 | arguments = "-f config.yaml", 98 | coreUrl = Global.mihomoCoreUrl, 99 | coreLatestUrl = Global.mihomoCoreUrl + "/latest", 100 | coreDownloadUrl32 = Global.mihomoCoreUrl + "/download/{0}/mihomo-windows-386-{0}.zip", 101 | coreDownloadUrl64 = Global.mihomoCoreUrl + "/download/{0}/mihomo-windows-amd64" + (Avx2.X64.IsSupported ? "" : "-compatible") + "-{0}.zip", 102 | match = "Mihomo" 103 | }); 104 | 105 | coreInfos.Add(new CoreInfo 106 | { 107 | coreType = CoreKind.ClashPremium, 108 | coreExes = new List { "clash-windows-amd64-v3", "clash-windows-amd64", "clash-windows-386", "clash" }, 109 | arguments = "-f config.yaml", 110 | coreUrl = Global.clashCoreUrl, 111 | coreLatestUrl = Global.clashCoreUrl + "/latest", 112 | coreDownloadUrl32 = Global.clashCoreUrl + "/download/{0}/clash-windows-386-{0}.zip", 113 | coreDownloadUrl64 = Global.clashCoreUrl + "/download/{0}/clash-windows-amd64-{0}.zip", 114 | match = "Clash" 115 | }); 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /clashN/clashN/Handler/NoticeHandler.cs: -------------------------------------------------------------------------------- 1 | using MaterialDesignThemes.Wpf; 2 | using ReactiveUI; 3 | 4 | namespace ClashN.Handler 5 | { 6 | public class NoticeHandler 7 | { 8 | private readonly ISnackbarMessageQueue _snackbarMessageQueue; 9 | 10 | public NoticeHandler(ISnackbarMessageQueue snackbarMessageQueue) 11 | { 12 | _snackbarMessageQueue = snackbarMessageQueue ?? throw new ArgumentNullException(nameof(snackbarMessageQueue)); 13 | 14 | //_snackbarMessageQueue = snackbarMessageQueue; 15 | } 16 | 17 | public void Enqueue(object content) 18 | { 19 | _snackbarMessageQueue?.Enqueue(content); 20 | } 21 | 22 | public void SendMessage(string msg) 23 | { 24 | MessageBus.Current.SendMessage(msg, "MsgView"); 25 | } 26 | 27 | public void SendMessage(string msg, bool time) 28 | { 29 | msg = $"{DateTime.Now} {msg}"; 30 | MessageBus.Current.SendMessage(msg, "MsgView"); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /clashN/clashN/Handler/QRCodeHelper.cs: -------------------------------------------------------------------------------- 1 | using QRCoder; 2 | using QRCoder.Xaml; 3 | using System.Windows.Media; 4 | 5 | namespace ClashN.Handler 6 | { 7 | /// 8 | /// 含有QR码的描述类和包装编码和渲染 9 | /// 10 | public class QRCodeHelper 11 | { 12 | public static DrawingImage GetQRCode(string strContent) 13 | { 14 | try 15 | { 16 | QRCodeGenerator qrGenerator = new QRCodeGenerator(); 17 | QRCodeData qrCodeData = qrGenerator.CreateQrCode(strContent, QRCodeGenerator.ECCLevel.H); 18 | XamlQRCode qrCode = new XamlQRCode(qrCodeData); 19 | DrawingImage qrCodeAsXaml = qrCode.GetGraphic(40); 20 | return qrCodeAsXaml; 21 | } 22 | catch 23 | { 24 | return null; 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /clashN/clashN/Handler/SpeedtestHandler.cs: -------------------------------------------------------------------------------- 1 | using ClashN.Mode; 2 | using System.Diagnostics; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | 6 | namespace ClashN.Handler 7 | { 8 | internal class SpeedtestHandler 9 | { 10 | private Config _config; 11 | private CoreHandler _coreHandler; 12 | private List _selecteds; 13 | private Action _updateFunc; 14 | 15 | public SpeedtestHandler(ref Config config) 16 | { 17 | _config = config; 18 | } 19 | 20 | public SpeedtestHandler(ref Config config, CoreHandler coreHandler, List selecteds, ESpeedActionType actionType, Action update) 21 | { 22 | _config = config; 23 | _coreHandler = coreHandler; 24 | //_selecteds = Utils.DeepCopy(selecteds); 25 | _updateFunc = update; 26 | 27 | _selecteds = new List(); 28 | foreach (var it in selecteds) 29 | { 30 | _selecteds.Add(new ServerTestItem() 31 | { 32 | IndexId = it.indexId, 33 | Address = it.address 34 | }); 35 | } 36 | 37 | if (actionType == ESpeedActionType.Ping) 38 | { 39 | Task.Run(() => RunPing()); 40 | } 41 | else if (actionType == ESpeedActionType.Tcping) 42 | { 43 | Task.Run(() => RunTcping()); 44 | } 45 | } 46 | 47 | private void RunPingSub(Action updateFun) 48 | { 49 | try 50 | { 51 | foreach (var it in _selecteds) 52 | { 53 | try 54 | { 55 | updateFun(it); 56 | } 57 | catch (Exception ex) 58 | { 59 | Utils.SaveLog(ex.Message, ex); 60 | } 61 | } 62 | 63 | Thread.Sleep(10); 64 | } 65 | catch (Exception ex) 66 | { 67 | Utils.SaveLog(ex.Message, ex); 68 | } 69 | } 70 | 71 | private void RunPing() 72 | { 73 | RunPingSub((ServerTestItem it) => 74 | { 75 | long time = Utils.Ping(it.Address); 76 | 77 | _updateFunc(it.IndexId, FormatOut(time, "ms")); 78 | }); 79 | } 80 | 81 | private void RunTcping() 82 | { 83 | RunPingSub((ServerTestItem it) => 84 | { 85 | int time = GetTcpingTime(it.Address, it.Port); 86 | 87 | _updateFunc(it.IndexId, FormatOut(time, "ms")); 88 | }); 89 | } 90 | 91 | public int RunAvailabilityCheck() // alias: isLive 92 | { 93 | try 94 | { 95 | int httpPort = _config.HttpPort; 96 | 97 | Task t = Task.Run(() => 98 | { 99 | try 100 | { 101 | WebProxy webProxy = new WebProxy(Global.Loopback, httpPort); 102 | int responseTime = -1; 103 | string status = GetRealPingTime(LazyConfig.Instance.Config.ConstItem.speedPingTestUrl, webProxy, out responseTime); 104 | bool noError = string.IsNullOrEmpty(status); 105 | return noError ? responseTime : -1; 106 | } 107 | catch (Exception ex) 108 | { 109 | Utils.SaveLog(ex.Message, ex); 110 | return -1; 111 | } 112 | }); 113 | return t.Result; 114 | } 115 | catch (Exception ex) 116 | { 117 | Utils.SaveLog(ex.Message, ex); 118 | return -1; 119 | } 120 | } 121 | 122 | private int GetTcpingTime(string url, int port) 123 | { 124 | int responseTime = -1; 125 | 126 | try 127 | { 128 | if (!IPAddress.TryParse(url, out IPAddress ipAddress)) 129 | { 130 | IPHostEntry ipHostInfo = System.Net.Dns.GetHostEntry(url); 131 | ipAddress = ipHostInfo.AddressList[0]; 132 | } 133 | 134 | Stopwatch timer = new Stopwatch(); 135 | timer.Start(); 136 | 137 | IPEndPoint endPoint = new IPEndPoint(ipAddress, port); 138 | Socket clientSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 139 | 140 | IAsyncResult result = clientSocket.BeginConnect(endPoint, null, null); 141 | if (!result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5))) 142 | throw new TimeoutException("connect timeout (5s): " + url); 143 | clientSocket.EndConnect(result); 144 | 145 | timer.Stop(); 146 | responseTime = timer.Elapsed.Milliseconds; 147 | clientSocket.Close(); 148 | } 149 | catch (Exception ex) 150 | { 151 | Utils.SaveLog(ex.Message, ex); 152 | } 153 | return responseTime; 154 | } 155 | 156 | private string GetRealPingTime(string url, WebProxy webProxy, out int responseTime) 157 | { 158 | string msg = string.Empty; 159 | responseTime = -1; 160 | try 161 | { 162 | HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); 163 | myHttpWebRequest.Timeout = 5000; 164 | myHttpWebRequest.Proxy = webProxy;//new WebProxy(Global.Loopback, Global.httpPort); 165 | 166 | Stopwatch timer = new Stopwatch(); 167 | timer.Start(); 168 | 169 | HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); 170 | if (myHttpWebResponse.StatusCode != HttpStatusCode.OK 171 | && myHttpWebResponse.StatusCode != HttpStatusCode.NoContent) 172 | { 173 | msg = myHttpWebResponse.StatusDescription; 174 | } 175 | timer.Stop(); 176 | responseTime = timer.Elapsed.Milliseconds; 177 | 178 | myHttpWebResponse.Close(); 179 | } 180 | catch (Exception ex) 181 | { 182 | Utils.SaveLog(ex.Message, ex); 183 | msg = ex.Message; 184 | } 185 | return msg; 186 | } 187 | 188 | private string FormatOut(object time, string unit) 189 | { 190 | if (time.ToString().Equals("-1")) 191 | { 192 | return "Timeout"; 193 | } 194 | return string.Format("{0}{1}", time, unit).PadLeft(8, ' '); 195 | } 196 | } 197 | } -------------------------------------------------------------------------------- /clashN/clashN/Handler/StatisticsHandler.cs: -------------------------------------------------------------------------------- 1 | using ClashN.Mode; 2 | using System.Net.WebSockets; 3 | using System.Text; 4 | 5 | namespace ClashN.Handler 6 | { 7 | internal class StatisticsHandler 8 | { 9 | private Config config_; 10 | 11 | //private ServerStatistics serverStatistics_; 12 | private bool exitFlag_; 13 | 14 | private ClientWebSocket webSocket = null; 15 | private string url = string.Empty; 16 | 17 | private Action updateFunc_; 18 | 19 | private bool Enable 20 | { 21 | get; set; 22 | } 23 | 24 | //private List Statistic 25 | //{ 26 | // get 27 | // { 28 | // return serverStatistics_.profileStat; 29 | // } 30 | //} 31 | 32 | public StatisticsHandler(Config config, Action update) 33 | { 34 | config_ = config; 35 | Enable = config.EnableStatistics; 36 | updateFunc_ = update; 37 | exitFlag_ = false; 38 | 39 | //LoadFromFile(); 40 | 41 | Task.Run(() => Run()); 42 | } 43 | 44 | private async void Init() 45 | { 46 | Thread.Sleep(5000); 47 | 48 | try 49 | { 50 | url = $"ws://{Global.Loopback}:{config_.ApiPort}/traffic"; 51 | 52 | if (webSocket == null) 53 | { 54 | webSocket = new ClientWebSocket(); 55 | await webSocket.ConnectAsync(new Uri(url), CancellationToken.None); 56 | } 57 | } 58 | catch { } 59 | } 60 | 61 | public void Close() 62 | { 63 | try 64 | { 65 | exitFlag_ = true; 66 | if (webSocket != null) 67 | { 68 | webSocket.Abort(); 69 | webSocket = null; 70 | } 71 | } 72 | catch (Exception ex) 73 | { 74 | Utils.SaveLog(ex.Message, ex); 75 | } 76 | } 77 | 78 | public async void Run() 79 | { 80 | Init(); 81 | 82 | while (!exitFlag_) 83 | { 84 | try 85 | { 86 | if (Enable) 87 | { 88 | if (webSocket.State == WebSocketState.Aborted 89 | || webSocket.State == WebSocketState.Closed) 90 | { 91 | webSocket.Abort(); 92 | webSocket = null; 93 | Init(); 94 | } 95 | 96 | if (webSocket.State != WebSocketState.Open) 97 | { 98 | continue; 99 | } 100 | 101 | var buffer = new byte[1024]; 102 | var res = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); 103 | while (!res.CloseStatus.HasValue) 104 | { 105 | var result = Encoding.UTF8.GetString(buffer, 0, res.Count); 106 | if (!string.IsNullOrEmpty(result)) 107 | { 108 | var serverStatItem = config_.GetProfileItem(config_.IndexId); 109 | 110 | ParseOutput(result, out ulong up, out ulong down); 111 | if (serverStatItem != null && (up + down) > 0) 112 | { 113 | serverStatItem.uploadRemote += up; 114 | serverStatItem.downloadRemote += down; 115 | } 116 | updateFunc_(up, down); 117 | } 118 | res = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); 119 | } 120 | } 121 | } 122 | catch 123 | { 124 | } 125 | finally 126 | { 127 | Thread.Sleep(1000); 128 | } 129 | } 130 | } 131 | 132 | //public void LoadFromFile() 133 | //{ 134 | // try 135 | // { 136 | // string result = Utils.LoadResource(Utils.GetConfigPath(Global.StatisticLogOverall)); 137 | // if (!string.IsNullOrEmpty(result)) 138 | // { 139 | // serverStatistics_ = Utils.FromJson(result); 140 | // } 141 | 142 | // if (serverStatistics_ == null) 143 | // { 144 | // serverStatistics_ = new ServerStatistics(); 145 | // } 146 | // if (serverStatistics_.profileStat == null) 147 | // { 148 | // serverStatistics_.profileStat = new List(); 149 | // } 150 | 151 | // long ticks = DateTime.Now.Date.Ticks; 152 | // foreach (ProfileStatItem item in serverStatistics_.profileStat) 153 | // { 154 | // if (item.dateNow != ticks) 155 | // { 156 | // item.todayUp = 0; 157 | // item.todayDown = 0; 158 | // item.dateNow = ticks; 159 | // } 160 | // } 161 | // } 162 | // catch (Exception ex) 163 | // { 164 | // Utils.SaveLog(ex.Message, ex); 165 | // } 166 | //} 167 | 168 | //public void SaveToFile() 169 | //{ 170 | // try 171 | // { 172 | // Utils.ToJsonFile(serverStatistics_, Utils.GetConfigPath(Global.StatisticLogOverall)); 173 | // } 174 | // catch (Exception ex) 175 | // { 176 | // Utils.SaveLog(ex.Message, ex); 177 | // } 178 | //} 179 | 180 | //public void ClearAllServerStatistics() 181 | //{ 182 | // if (serverStatistics_ != null) 183 | // { 184 | // foreach (var item in serverStatistics_.profileStat) 185 | // { 186 | // item.todayUp = 0; 187 | // item.todayDown = 0; 188 | // item.totalUp = 0; 189 | // item.totalDown = 0; 190 | // // update ui display to zero 191 | // updateFunc_(0, 0); 192 | // } 193 | 194 | // // update statistic json file 195 | // //SaveToFile(); 196 | // } 197 | //} 198 | 199 | //public List GetStatistic() 200 | //{ 201 | // return Statistic; 202 | //} 203 | 204 | //private ProfileStatItem GetServerStatItem(string itemId) 205 | //{ 206 | // long ticks = DateTime.Now.Date.Ticks; 207 | // int cur = Statistic.FindIndex(item => item.indexId == itemId); 208 | // if (cur < 0) 209 | // { 210 | // Statistic.Add(new ProfileStatItem 211 | // { 212 | // indexId = itemId, 213 | // totalUp = 0, 214 | // totalDown = 0, 215 | // todayUp = 0, 216 | // todayDown = 0, 217 | // dateNow = ticks 218 | // }); 219 | // cur = Statistic.Count - 1; 220 | // } 221 | // if (Statistic[cur].dateNow != ticks) 222 | // { 223 | // Statistic[cur].todayUp = 0; 224 | // Statistic[cur].todayDown = 0; 225 | // Statistic[cur].dateNow = ticks; 226 | // } 227 | // return Statistic[cur]; 228 | //} 229 | 230 | private void ParseOutput(string source, out ulong up, out ulong down) 231 | { 232 | up = 0; down = 0; 233 | try 234 | { 235 | var trafficItem = Utils.FromJson(source); 236 | if (trafficItem != null) 237 | { 238 | up = trafficItem.up; 239 | down = trafficItem.down; 240 | } 241 | } 242 | catch (Exception) 243 | { 244 | //Utils.SaveLog(ex.Message, ex); 245 | } 246 | } 247 | } 248 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/ClashConnections.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | public class ClashConnections 4 | { 5 | public ulong DownloadTotal { get; set; } 6 | public ulong UploadTotal { get; set; } 7 | public List Connections { get; } = new List(); 8 | } 9 | 10 | public class ConnectionItem 11 | { 12 | public string Id { get; set; } = string.Empty; 13 | public MetadataItem metadata { get; set; } 14 | public ulong upload { get; set; } 15 | public ulong download { get; set; } 16 | public DateTime start { get; set; } 17 | public List Chains { get; } = new List(); 18 | public string rule { get; set; } 19 | public string rulePayload { get; set; } 20 | } 21 | 22 | public class MetadataItem 23 | { 24 | public string Network { get; set; } 25 | public string Type { get; set; } 26 | public string SourceIP { get; set; } 27 | public string DestinationIP { get; set; } 28 | public string SourcePort { get; set; } 29 | public string DestinationPort { get; set; } 30 | public string Host { get; set; } 31 | public string DnsMode { get; set; } 32 | public object Uid { get; set; } 33 | public string Process { get; set; } 34 | public string ProcessPath { get; set; } 35 | public string RemoteDestination { get; set; } 36 | } 37 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/ClashProviders.cs: -------------------------------------------------------------------------------- 1 | using static ClashN.Mode.ClashProxies; 2 | 3 | namespace ClashN.Mode 4 | { 5 | public class ClashProviders 6 | { 7 | public Dictionary providers { get; set; } 8 | 9 | public class ProvidersItem 10 | { 11 | public string name { get; set; } 12 | public ProxiesItem[] proxies { get; set; } 13 | public string type { get; set; } 14 | public string vehicleType { get; set; } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/ClashProxies.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | public class ClashProxies 4 | { 5 | public Dictionary proxies { get; set; } 6 | 7 | public class ProxiesItem 8 | { 9 | public string[] all { get; set; } 10 | public List history { get; set; } 11 | public string name { get; set; } 12 | public string type { get; set; } 13 | public bool udp { get; set; } 14 | public string now { get; set; } 15 | public int delay { get; set; } 16 | } 17 | 18 | public class HistoryItem 19 | { 20 | public string time { get; set; } 21 | public int delay { get; set; } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/ComboItem.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | internal class ComboItem 4 | { 5 | public int ID { get; set; } 6 | public string Text { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/Config.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace ClashN.Mode 4 | { 5 | /// 6 | /// 本软件配置文件实体类 7 | /// 8 | [Serializable] 9 | public class Config 10 | { 11 | #region property 12 | 13 | public int MixedPort { get; set; } = 7888; 14 | 15 | public int HttpPort { get; set; } = 7890; 16 | 17 | public int SocksPort { get; set; } = 7891; 18 | 19 | public int ApiPort { get; set; } = 9090; 20 | 21 | public string LogLevel { get; set; } 22 | 23 | public bool EnableIpv6 { get; set; } 24 | 25 | public string IndexId { get; set; } 26 | 27 | public SysProxyType SysProxyType { get; set; } 28 | 29 | public ERuleMode ruleMode { get; set; } 30 | 31 | public bool AllowLANConn { get; set; } 32 | 33 | public bool AutoRun { get; set; } 34 | 35 | public bool EnableStatistics { get; set; } 36 | 37 | public string SystemProxyExceptions { get; set; } 38 | public string SystemProxyAdvancedProtocol { get; set; } 39 | 40 | public int AutoUpdateSubInterval { get; set; } = 10; 41 | public int AutoDelayTestInterval { get; set; } = 10; 42 | 43 | public bool EnableSecurityProtocolTls13 { get; set; } 44 | 45 | public bool EnableMixinContent { get; set; } 46 | 47 | public int PacPort { get; set; } 48 | 49 | public bool AutoHideStartup { get; set; } 50 | 51 | public bool EnableTun { get; set; } 52 | 53 | #endregion property 54 | 55 | #region other entities 56 | 57 | public List ProfileItems { get; } = new List(); 58 | 59 | public UIItem? UiItem { get; set; } 60 | 61 | public ConstItem? ConstItem { get; set; } 62 | 63 | public List globalHotkeys { get; } = new List(); 64 | 65 | #endregion other entities 66 | 67 | #region function 68 | 69 | public int FindIndexId(string id) 70 | { 71 | if (string.IsNullOrEmpty(id)) 72 | { 73 | return -1; 74 | } 75 | return ProfileItems.FindIndex(it => it.indexId == id); 76 | } 77 | 78 | public ProfileItem? GetProfileItem(string id) 79 | { 80 | if (string.IsNullOrEmpty(id)) 81 | return null; 82 | 83 | return ProfileItems.FirstOrDefault(it => it.indexId == id); 84 | } 85 | 86 | public bool IsActiveNode(ProfileItem item) 87 | { 88 | if (!string.IsNullOrEmpty(item.indexId) && item.indexId == IndexId) 89 | { 90 | return true; 91 | } 92 | 93 | return false; 94 | } 95 | 96 | #endregion function 97 | } 98 | 99 | [Serializable] 100 | public class ProfileItem 101 | { 102 | public ProfileItem() 103 | { 104 | indexId = string.Empty; 105 | sort = 0; 106 | url = string.Empty; 107 | enabled = true; 108 | address = string.Empty; 109 | remarks = string.Empty; 110 | testResult = string.Empty; 111 | groupId = string.Empty; 112 | enableConvert = false; 113 | } 114 | 115 | #region function 116 | 117 | public string GetSummary() 118 | { 119 | string summary = string.Format("{0}", remarks); 120 | return summary; 121 | } 122 | 123 | #endregion function 124 | 125 | public string indexId { get; set; } 126 | 127 | public int sort { get; set; } 128 | 129 | public string address { get; set; } 130 | 131 | public string remarks { get; set; } 132 | 133 | public string testResult { get; set; } 134 | 135 | public string groupId { get; set; } = string.Empty; 136 | public CoreKind? coreType { get; set; } 137 | 138 | public string url { get; set; } 139 | 140 | public bool enabled { get; set; } = true; 141 | 142 | public string userAgent { get; set; } = string.Empty; 143 | 144 | public bool enableConvert { get; set; } 145 | 146 | public long updateTime { get; set; } 147 | public ulong uploadRemote { get; set; } 148 | public ulong downloadRemote { get; set; } 149 | public ulong totalRemote { get; set; } 150 | public long expireRemote { get; set; } 151 | } 152 | 153 | [Serializable] 154 | public class UIItem 155 | { 156 | public Point mainLocation { get; set; } 157 | 158 | public double mainWidth { get; set; } 159 | public double mainHeight { get; set; } 160 | 161 | public bool colorModeDark { get; set; } 162 | public string? colorPrimaryName { get; set; } 163 | public string currentFontFamily { get; set; } = string.Empty; 164 | public int currentFontSize { get; set; } 165 | 166 | public int proxiesSorting { get; set; } 167 | public bool proxiesAutoRefresh { get; set; } 168 | 169 | public int connectionsSorting { get; set; } 170 | public bool connectionsAutoRefresh { get; set; } 171 | } 172 | 173 | [Serializable] 174 | public class ConstItem 175 | { 176 | public string subConvertUrl { get; set; } = string.Empty; 177 | public string speedTestUrl { get; set; } = string.Empty; 178 | public string speedPingTestUrl { get; set; } = string.Empty; 179 | public string defIEProxyExceptions { get; set; } = string.Empty; 180 | } 181 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/ConnectionModel.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | public class ConnectionModel 4 | { 5 | public string id { get; set; } 6 | public string network { get; set; } 7 | public string type { get; set; } 8 | public string host { get; set; } 9 | public ulong upload { get; set; } 10 | public ulong download { get; set; } 11 | public string uploadTraffic { get; set; } 12 | public string downloadTraffic { get; set; } 13 | public double time { get; set; } 14 | public string elapsed { get; set; } 15 | public string chain { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/CoreInfo.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | [Serializable] 4 | public class CoreInfo 5 | { 6 | public CoreKind coreType { get; set; } 7 | 8 | public List coreExes { get; set; } 9 | 10 | public string arguments { get; set; } 11 | 12 | public string coreUrl { get; set; } 13 | 14 | public string coreLatestUrl { get; set; } 15 | 16 | public string coreDownloadUrl32 { get; set; } 17 | 18 | public string coreDownloadUrl64 { get; set; } 19 | 20 | public string match { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/CoreKind.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | public enum CoreKind 4 | { 5 | Clash = 1, 6 | ClashMeta = 2, 7 | ClashPremium = 3, 8 | Mihomo = 4, 9 | ClashN = 99 10 | } 11 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/EProfileColName.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | public enum EProfileColName 4 | { 5 | def = 0, 6 | remarks, 7 | url, 8 | address, 9 | enableUpdateSub, 10 | testResult, 11 | updateTime, 12 | 13 | todayDown, 14 | todayUp, 15 | totalDown, 16 | totalUp 17 | } 18 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/ERuleMode.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | public enum ERuleMode 4 | { 5 | Rule = 0, 6 | Global = 1, 7 | Direct = 2, 8 | Unchanged = 3 9 | } 10 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/ESpeedActionType.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | public enum ESpeedActionType 4 | { 5 | Ping, 6 | Tcping, 7 | Realping, 8 | Speedtest 9 | } 10 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/GlobalHotkeyAction.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | public enum GlobalHotkeyAction 4 | { 5 | ShowForm = 0, 6 | SystemProxyClear = 1, 7 | SystemProxySet = 2, 8 | SystemProxyUnchanged = 3, 9 | SystemProxyPac = 4, 10 | } 11 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/KeyShortcut.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace ClashN.Mode 4 | { 5 | [Serializable] 6 | public struct KeyShortcut 7 | { 8 | public GlobalHotkeyAction GlobalHotkey { get; set; } 9 | 10 | public bool Alt { get; set; } 11 | 12 | public bool Control { get; set; } 13 | 14 | public bool Shift { get; set; } 15 | 16 | public Keys? KeyCode { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/MovementTarget.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | public enum MovementTarget 4 | { 5 | Top = 1, 6 | Up = 2, 7 | Down = 3, 8 | Bottom = 4, 9 | Position = 5 10 | } 11 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/ProfileItemModel.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | public class ProfileItemModel : ProfileItem 4 | { 5 | public bool IsActive { get; set; } 6 | public bool HasUrl => !string.IsNullOrEmpty(url); 7 | public bool HasAddress => !string.IsNullOrEmpty(address); 8 | 9 | public string StrUpdateTime 10 | { 11 | get 12 | { 13 | if (updateTime <= 0) 14 | { 15 | return String.Empty; 16 | } 17 | var dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); 18 | return dateTime.AddSeconds(updateTime).ToLocalTime().ToString("MM-dd HH:mm"); 19 | } 20 | } 21 | 22 | public string TrafficUsed => Utils.HumanFy(uploadRemote + downloadRemote); 23 | public string TrafficTotal => totalRemote <= 0 ? "∞" : Utils.HumanFy(totalRemote); 24 | 25 | public string StrExpireTime 26 | { 27 | get 28 | { 29 | if (expireRemote <= 0) 30 | { 31 | return String.Empty; 32 | } 33 | var dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); 34 | return dateTime.AddSeconds(expireRemote).ToLocalTime().ToString("yyyy-MM-dd"); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/ProxyModel.cs: -------------------------------------------------------------------------------- 1 | using ReactiveUI.Fody.Helpers; 2 | 3 | namespace ClashN.Mode 4 | { 5 | [Serializable] 6 | public class ProxyModel 7 | { 8 | [Reactive] 9 | public string name { get; set; } 10 | 11 | [Reactive] 12 | public string type { get; set; } 13 | 14 | [Reactive] 15 | public string now { get; set; } 16 | 17 | [Reactive] 18 | public int delay { get; set; } 19 | 20 | [Reactive] 21 | public string delayName { get; set; } 22 | 23 | [Reactive] 24 | public bool isActive { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/ServerStatistics.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | [Serializable] 4 | public class ServerStatistics 5 | { 6 | public List profileStat 7 | { 8 | get; set; 9 | } 10 | } 11 | 12 | [Serializable] 13 | public class ProfileStatItem 14 | { 15 | public string indexId 16 | { 17 | get; set; 18 | } 19 | 20 | public ulong totalUp 21 | { 22 | get; set; 23 | } 24 | 25 | public ulong totalDown 26 | { 27 | get; set; 28 | } 29 | 30 | public ulong todayUp 31 | { 32 | get; set; 33 | } 34 | 35 | public ulong todayDown 36 | { 37 | get; set; 38 | } 39 | 40 | public long dateNow 41 | { 42 | get; set; 43 | } 44 | } 45 | 46 | [Serializable] 47 | public class TrafficItem 48 | { 49 | public ulong up 50 | { 51 | get; set; 52 | } 53 | 54 | public ulong down 55 | { 56 | get; set; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/ServerTestItem.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | [Serializable] 4 | internal class ServerTestItem 5 | { 6 | public string IndexId { get; set; } 7 | public string Address { get; set; } 8 | public int Port { get; set; } 9 | public bool AllowTest { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /clashN/clashN/Mode/SysProxyType.cs: -------------------------------------------------------------------------------- 1 | namespace ClashN.Mode 2 | { 3 | public enum SysProxyType 4 | { 5 | ForcedClear = 0, 6 | ForcedChange = 1, 7 | Unchanged = 2, 8 | Pac = 3 9 | } 10 | } -------------------------------------------------------------------------------- /clashN/clashN/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\resources\sysproxy.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 123 | 124 | 125 | ..\resources\sysproxy64.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 126 | 127 | 128 | ..\Resources\NotifyIcon1.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 129 | 130 | 131 | ..\Resources\NotifyIcon2.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 132 | 133 | 134 | ..\Resources\NotifyIcon3.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 135 | 136 | -------------------------------------------------------------------------------- /clashN/clashN/Resources/NotifyIcon1.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2dust/clashN/0b498424d3bc1ef3a9fa1b9a6c05b4b216303984/clashN/clashN/Resources/NotifyIcon1.ico -------------------------------------------------------------------------------- /clashN/clashN/Resources/NotifyIcon2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2dust/clashN/0b498424d3bc1ef3a9fa1b9a6c05b4b216303984/clashN/clashN/Resources/NotifyIcon2.ico -------------------------------------------------------------------------------- /clashN/clashN/Resources/NotifyIcon3.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2dust/clashN/0b498424d3bc1ef3a9fa1b9a6c05b4b216303984/clashN/clashN/Resources/NotifyIcon3.ico -------------------------------------------------------------------------------- /clashN/clashN/Resources/sysproxy.exe.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2dust/clashN/0b498424d3bc1ef3a9fa1b9a6c05b4b216303984/clashN/clashN/Resources/sysproxy.exe.gz -------------------------------------------------------------------------------- /clashN/clashN/Resources/sysproxy64.exe.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2dust/clashN/0b498424d3bc1ef3a9fa1b9a6c05b4b216303984/clashN/clashN/Resources/sysproxy64.exe.gz -------------------------------------------------------------------------------- /clashN/clashN/Sample/SampleMixin.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # 配置文件内容不会被修改,混合行为只会发生在内存中 3 | # 4 | # 注意下面缩进,请用支持yaml显示的编辑器打开 5 | # 6 | # 使用clash配置文件关键字则覆盖原配置 7 | # 8 | # removed-rules 循环匹配rules数组每行,符合则移除当前行 (此规则请放最前面) 9 | # 10 | # append-rules 数组合并至原配置rules数组后 11 | # prepend-rules 数组合并至原配置rules数组前 12 | # append-proxies 数组合并至原配置proxies数组后 13 | # prepend-proxies 数组合并至原配置proxies数组前 14 | # append-proxy-groups 数组合并至原配置proxy-groups数组后 15 | # prepend-proxy-groups 数组合并至原配置proxy-groups数组前 16 | # append-rule-providers 数组合并至原配置rule-providers数组后 17 | # prepend-rule-providers 数组合并至原配置rule-providers数组前 18 | # 19 | 20 | dns: 21 | enable: true 22 | enhanced-mode: fake-ip 23 | nameserver: 24 | - 114.114.114.114 25 | - 223.5.5.5 26 | - 8.8.8.8 27 | fallback: [] 28 | fake-ip-filter: 29 | - +.stun.*.* 30 | - +.stun.*.*.* 31 | - +.stun.*.*.*.* 32 | - +.stun.*.*.*.*.* 33 | - "*.n.n.srv.nintendo.net" 34 | - +.stun.playstation.net 35 | - xbox.*.*.microsoft.com 36 | - "*.*.xboxlive.com" 37 | - "*.msftncsi.com" 38 | - "*.msftconnecttest.com" 39 | - WORKGROUP -------------------------------------------------------------------------------- /clashN/clashN/Sample/SampleTun.yaml: -------------------------------------------------------------------------------- 1 | tun: 2 | enable: true 3 | stack: gvisor 4 | dns-hijack: 5 | - 0.0.0.0:53 6 | auto-route: true 7 | auto-detect-interface: true 8 | -------------------------------------------------------------------------------- /clashN/clashN/Tool/FileManager.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Compression; 3 | using System.Text; 4 | 5 | namespace ClashN.Tool 6 | { 7 | public static class FileManager 8 | { 9 | public static bool ByteArrayToFile(string fileName, byte[] content) 10 | { 11 | try 12 | { 13 | using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) 14 | fs.Write(content, 0, content.Length); 15 | return true; 16 | } 17 | catch (Exception ex) 18 | { 19 | Utils.SaveLog(ex.Message, ex); 20 | } 21 | return false; 22 | } 23 | 24 | public static void UncompressFile(string fileName, byte[] content) 25 | { 26 | try 27 | { 28 | // Because the uncompressed size of the file is unknown, 29 | // we are using an arbitrary buffer size. 30 | byte[] buffer = new byte[4096]; 31 | int n; 32 | 33 | using (FileStream fs = File.Create(fileName)) 34 | using (GZipStream input = new GZipStream(new MemoryStream(content), 35 | CompressionMode.Decompress, false)) 36 | { 37 | while ((n = input.Read(buffer, 0, buffer.Length)) > 0) 38 | { 39 | fs.Write(buffer, 0, n); 40 | } 41 | } 42 | } 43 | catch (Exception ex) 44 | { 45 | Utils.SaveLog(ex.Message, ex); 46 | } 47 | } 48 | 49 | public static string NonExclusiveReadAllText(string path) 50 | { 51 | return NonExclusiveReadAllText(path, Encoding.Default); 52 | } 53 | 54 | public static string NonExclusiveReadAllText(string path, Encoding encoding) 55 | { 56 | try 57 | { 58 | using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 59 | using (StreamReader sr = new StreamReader(fs, encoding)) 60 | { 61 | return sr.ReadToEnd(); 62 | } 63 | } 64 | catch (Exception ex) 65 | { 66 | Utils.SaveLog(ex.Message, ex); 67 | throw ex; 68 | } 69 | } 70 | 71 | public static bool ZipExtractToFile(string fileName, string toPath, string ignoredName) 72 | { 73 | try 74 | { 75 | using (ZipArchive archive = ZipFile.OpenRead(fileName)) 76 | { 77 | foreach (ZipArchiveEntry entry in archive.Entries) 78 | { 79 | if (entry.Length == 0) 80 | { 81 | continue; 82 | } 83 | try 84 | { 85 | if (!string.IsNullOrEmpty(ignoredName) && entry.Name.Contains(ignoredName)) 86 | { 87 | continue; 88 | } 89 | entry.ExtractToFile(Path.Combine(toPath, entry.Name), true); 90 | } 91 | catch (IOException ex) 92 | { 93 | Utils.SaveLog(ex.Message, ex); 94 | } 95 | } 96 | } 97 | } 98 | catch (Exception ex) 99 | { 100 | Utils.SaveLog(ex.Message, ex); 101 | return false; 102 | } 103 | return true; 104 | } 105 | 106 | public static bool ZipExtractToFullFile(string fileName) 107 | { 108 | try 109 | { 110 | using (ZipArchive archive = ZipFile.OpenRead(fileName)) 111 | { 112 | foreach (ZipArchiveEntry entry in archive.Entries) 113 | { 114 | if (entry.Length == 0) 115 | continue; 116 | 117 | string entryOuputPath = Utils.GetPath(entry.FullName); 118 | FileInfo fileInfo = new FileInfo(entryOuputPath); 119 | fileInfo.Directory.Create(); 120 | entry.ExtractToFile(entryOuputPath, true); 121 | } 122 | } 123 | } 124 | catch (Exception ex) 125 | { 126 | Utils.SaveLog(ex.Message, ex); 127 | return false; 128 | } 129 | return true; 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /clashN/clashN/Tool/Job.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace ClashN 5 | { 6 | /* 7 | * See: 8 | * http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net 9 | */ 10 | 11 | public class Job : IDisposable 12 | { 13 | private IntPtr handle = IntPtr.Zero; 14 | 15 | public Job() 16 | { 17 | handle = CreateJobObject(IntPtr.Zero, null); 18 | IntPtr extendedInfoPtr = IntPtr.Zero; 19 | JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION 20 | { 21 | LimitFlags = 0x2000 22 | }; 23 | 24 | JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION 25 | { 26 | BasicLimitInformation = info 27 | }; 28 | 29 | try 30 | { 31 | int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); 32 | extendedInfoPtr = Marshal.AllocHGlobal(length); 33 | Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); 34 | 35 | if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, 36 | (uint)length)) 37 | throw new Exception(string.Format("Unable to set information. Error: {0}", 38 | Marshal.GetLastWin32Error())); 39 | } 40 | finally 41 | { 42 | if (extendedInfoPtr != IntPtr.Zero) 43 | { 44 | Marshal.FreeHGlobal(extendedInfoPtr); 45 | } 46 | } 47 | } 48 | 49 | public bool AddProcess(IntPtr processHandle) 50 | { 51 | bool succ = AssignProcessToJobObject(handle, processHandle); 52 | 53 | if (!succ) 54 | { 55 | Utils.SaveLog("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error()); 56 | } 57 | 58 | return succ; 59 | } 60 | 61 | public bool AddProcess(int processId) 62 | { 63 | return AddProcess(Process.GetProcessById(processId).Handle); 64 | } 65 | 66 | #region IDisposable 67 | 68 | private bool disposed; 69 | 70 | public void Dispose() 71 | { 72 | Dispose(true); 73 | GC.SuppressFinalize(this); 74 | } 75 | 76 | protected virtual void Dispose(bool disposing) 77 | { 78 | if (disposed) return; 79 | disposed = true; 80 | 81 | if (disposing) 82 | { 83 | // no managed objects to free 84 | } 85 | 86 | if (handle != IntPtr.Zero) 87 | { 88 | CloseHandle(handle); 89 | handle = IntPtr.Zero; 90 | } 91 | } 92 | 93 | ~Job() 94 | { 95 | Dispose(false); 96 | } 97 | 98 | #endregion IDisposable 99 | 100 | #region Interop 101 | 102 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] 103 | private static extern IntPtr CreateJobObject(IntPtr a, string lpName); 104 | 105 | [DllImport("kernel32.dll", SetLastError = true)] 106 | private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength); 107 | 108 | [DllImport("kernel32.dll", SetLastError = true)] 109 | private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); 110 | 111 | [DllImport("kernel32.dll", SetLastError = true)] 112 | [return: MarshalAs(UnmanagedType.Bool)] 113 | private static extern bool CloseHandle(IntPtr hObject); 114 | 115 | #endregion Interop 116 | } 117 | 118 | #region Helper classes 119 | 120 | [StructLayout(LayoutKind.Sequential)] 121 | internal struct IO_COUNTERS 122 | { 123 | public UInt64 ReadOperationCount; 124 | public UInt64 WriteOperationCount; 125 | public UInt64 OtherOperationCount; 126 | public UInt64 ReadTransferCount; 127 | public UInt64 WriteTransferCount; 128 | public UInt64 OtherTransferCount; 129 | } 130 | 131 | [StructLayout(LayoutKind.Sequential)] 132 | internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION 133 | { 134 | public Int64 PerProcessUserTimeLimit; 135 | public Int64 PerJobUserTimeLimit; 136 | public UInt32 LimitFlags; 137 | public UIntPtr MinimumWorkingSetSize; 138 | public UIntPtr MaximumWorkingSetSize; 139 | public UInt32 ActiveProcessLimit; 140 | public UIntPtr Affinity; 141 | public UInt32 PriorityClass; 142 | public UInt32 SchedulingClass; 143 | } 144 | 145 | [StructLayout(LayoutKind.Sequential)] 146 | public struct SECURITY_ATTRIBUTES 147 | { 148 | public UInt32 nLength; 149 | public IntPtr lpSecurityDescriptor; 150 | public Int32 bInheritHandle; 151 | } 152 | 153 | [StructLayout(LayoutKind.Sequential)] 154 | internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION 155 | { 156 | public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; 157 | public IO_COUNTERS IoInfo; 158 | public UIntPtr ProcessMemoryLimit; 159 | public UIntPtr JobMemoryLimit; 160 | public UIntPtr PeakProcessMemoryUsed; 161 | public UIntPtr PeakJobMemoryUsed; 162 | } 163 | 164 | public enum JobObjectInfoType 165 | { 166 | AssociateCompletionPortInformation = 7, 167 | BasicLimitInformation = 2, 168 | BasicUIRestrictions = 4, 169 | EndOfJobTimeInformation = 6, 170 | ExtendedLimitInformation = 9, 171 | SecurityLimitInformation = 5, 172 | GroupInformation = 11 173 | } 174 | 175 | #endregion Helper classes 176 | } -------------------------------------------------------------------------------- /clashN/clashN/Tool/Logging.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | using NLog.Config; 3 | using NLog.Targets; 4 | using System.IO; 5 | 6 | namespace ClashN.Tool 7 | { 8 | public class Logging 9 | { 10 | public static void Setup() 11 | { 12 | LoggingConfiguration config = new LoggingConfiguration(); 13 | FileTarget fileTarget = new FileTarget(); 14 | config.AddTarget("file", fileTarget); 15 | fileTarget.Layout = "${longdate}-${level:uppercase=true} ${message}"; 16 | fileTarget.FileName = Utils.GetPath(@"guiLogs/") + "${shortdate}.txt"; 17 | config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget)); 18 | LogManager.Configuration = config; 19 | } 20 | 21 | public static void ClearLogs() 22 | { 23 | Task.Run(() => 24 | { 25 | try 26 | { 27 | var now = DateTime.Now.AddMonths(-1); 28 | var dir = Utils.GetPath(@"guiLogs\"); 29 | var files = Directory.GetFiles(dir, "*.txt"); 30 | foreach (var filePath in files) 31 | { 32 | var file = new FileInfo(filePath); 33 | if (file.CreationTime < now) 34 | { 35 | try 36 | { 37 | file.Delete(); 38 | } 39 | catch { } 40 | } 41 | } 42 | } 43 | catch { } 44 | }); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /clashN/clashN/Tool/QueryableExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using System.Reflection; 3 | 4 | namespace ClashN.Tool 5 | { 6 | public static class QueryableExtension 7 | { 8 | public static IOrderedQueryable OrderBy(this IQueryable query, string propertyName) 9 | { 10 | return OrderBy(query, propertyName, false); 11 | } 12 | 13 | public static IOrderedQueryable OrderByDescending(this IQueryable query, string propertyName) 14 | { 15 | return OrderBy(query, propertyName, true); 16 | } 17 | 18 | private static IOrderedQueryable OrderBy(IQueryable query, string propertyName, bool isDesc) 19 | { 20 | string methodname = (isDesc) ? "OrderByDescendingInternal" : "OrderByInternal"; 21 | 22 | var memberProp = typeof(T).GetProperty(propertyName); 23 | 24 | var method = typeof(QueryableExtension).GetMethod(methodname) 25 | .MakeGenericMethod(typeof(T), memberProp.PropertyType); 26 | 27 | return (IOrderedQueryable)method.Invoke(null, new object[] { query, memberProp }); 28 | } 29 | 30 | public static IOrderedQueryable OrderByInternal(IQueryable query, PropertyInfo memberProperty) 31 | { 32 | return query.OrderBy(GetLambda(memberProperty)); 33 | } 34 | 35 | public static IOrderedQueryable OrderByDescendingInternal(IQueryable query, PropertyInfo memberProperty) 36 | { 37 | return query.OrderByDescending(GetLambda(memberProperty)); 38 | } 39 | 40 | private static Expression> GetLambda(PropertyInfo memberProperty) 41 | { 42 | if (memberProperty.PropertyType != typeof(TProp)) 43 | throw new Exception(); 44 | 45 | var thisArg = Expression.Parameter(typeof(T)); 46 | var lamba = Expression.Lambda>(Expression.Property(thisArg, memberProperty), thisArg); 47 | 48 | return lamba; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /clashN/clashN/Tool/UI.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace ClashN 4 | { 5 | internal class UI 6 | { 7 | public static void Show(string msg) 8 | { 9 | MessageBox.Show(msg, "ClashN", MessageBoxButtons.OK, MessageBoxIcon.Information); 10 | } 11 | 12 | public static void ShowWarning(string msg) 13 | { 14 | MessageBox.Show(msg, "ClashN", MessageBoxButtons.OK, MessageBoxIcon.Warning); 15 | } 16 | 17 | public static void ShowError(string msg) 18 | { 19 | MessageBox.Show(msg, "ClashN", MessageBoxButtons.OK, MessageBoxIcon.Error); 20 | } 21 | 22 | public static DialogResult ShowYesNo(string msg) 23 | { 24 | return MessageBox.Show(msg, "ClashN", MessageBoxButtons.YesNo, MessageBoxIcon.Question); 25 | } 26 | 27 | //public static string GetResourseString(string key) 28 | //{ 29 | // CultureInfo cultureInfo = null; 30 | // try 31 | // { 32 | // string languageCode = this.LanguageCode; 33 | // cultureInfo = new CultureInfo(languageCode); 34 | // return Common.ResourceManager.GetString(key, cultureInfo); 35 | // } 36 | // catch (Exception) 37 | // { 38 | // //默认读取英文的多语言 39 | // cultureInfo = new CultureInfo(MKey.kDefaultLanguageCode); 40 | // return Common.ResourceManager.GetString(key, cultureInfo); 41 | // } 42 | //} 43 | } 44 | } -------------------------------------------------------------------------------- /clashN/clashN/ViewModels/ConnectionsViewModel.cs: -------------------------------------------------------------------------------- 1 | using ClashN.Handler; 2 | using ClashN.Mode; 3 | using DynamicData; 4 | using DynamicData.Binding; 5 | using ReactiveUI; 6 | using ReactiveUI.Fody.Helpers; 7 | using Splat; 8 | using System.Reactive; 9 | using System.Reactive.Linq; 10 | using System.Windows; 11 | 12 | namespace ClashN.ViewModels 13 | { 14 | public class ConnectionsViewModel : ReactiveObject 15 | { 16 | private static Config _config; 17 | 18 | static ConnectionsViewModel() 19 | { 20 | _config = LazyConfig.Instance.Config; 21 | } 22 | 23 | private NoticeHandler? _noticeHandler; 24 | private IObservableCollection _connectionItems = new ObservableCollectionExtended(); 25 | 26 | public IObservableCollection ConnectionItems => _connectionItems; 27 | 28 | [Reactive] 29 | public ConnectionModel SelectedSource { get; set; } 30 | 31 | public ReactiveCommand ConnectionCloseCmd { get; } 32 | public ReactiveCommand ConnectionCloseAllCmd { get; } 33 | 34 | [Reactive] 35 | public int SortingSelected { get; set; } 36 | 37 | [Reactive] 38 | public bool AutoRefresh { get; set; } 39 | 40 | private int AutoRefreshInterval; 41 | 42 | public ConnectionsViewModel() 43 | { 44 | _noticeHandler = Locator.Current.GetService(); 45 | 46 | AutoRefreshInterval = 10; 47 | SortingSelected = _config.UiItem.connectionsSorting; 48 | AutoRefresh = _config.UiItem.connectionsAutoRefresh; 49 | 50 | var canEditRemove = this.WhenAnyValue( 51 | x => x.SelectedSource, 52 | selectedSource => selectedSource != null && !string.IsNullOrEmpty(selectedSource.id)); 53 | 54 | this.WhenAnyValue( 55 | x => x.SortingSelected, 56 | y => y >= 0) 57 | .Subscribe(c => DoSortingSelected(c)); 58 | 59 | this.WhenAnyValue( 60 | x => x.AutoRefresh, 61 | y => y == true) 62 | .Subscribe(c => { _config.UiItem.connectionsAutoRefresh = AutoRefresh; }); 63 | 64 | ConnectionCloseCmd = ReactiveCommand.Create(() => 65 | { 66 | ClashConnectionClose(false); 67 | }, canEditRemove); 68 | 69 | ConnectionCloseAllCmd = ReactiveCommand.Create(() => 70 | { 71 | ClashConnectionClose(true); 72 | }); 73 | 74 | Init(); 75 | } 76 | 77 | private void DoSortingSelected(bool c) 78 | { 79 | if (!c) 80 | { 81 | return; 82 | } 83 | if (SortingSelected != _config.UiItem.connectionsSorting) 84 | { 85 | _config.UiItem.connectionsSorting = SortingSelected; 86 | } 87 | 88 | GetClashConnections(); 89 | } 90 | 91 | private void Init() 92 | { 93 | Observable.Interval(TimeSpan.FromSeconds(AutoRefreshInterval)) 94 | .Subscribe(x => 95 | { 96 | if (!(AutoRefresh && Global.ShowInTaskbar)) 97 | { 98 | return; 99 | } 100 | GetClashConnections(); 101 | }); 102 | 103 | //Task.Run(() => 104 | //{ 105 | // while (true) 106 | // { 107 | // if (AutoRefresh) 108 | // { 109 | // GetClashConnections(); 110 | // } 111 | // Thread.Sleep(1000 * AutoRefreshInterval); 112 | // } 113 | //}); 114 | } 115 | 116 | private void GetClashConnections() 117 | { 118 | MainFormHandler.Instance.GetClashConnections(_config, (it) => 119 | { 120 | //_noticeHandler?.SendMessage("Refresh Clash Connections", true); 121 | if (it == null) 122 | { 123 | return; 124 | } 125 | 126 | Application.Current?.Dispatcher.Invoke((Action)(() => 127 | { 128 | RefreshConnections(it?.Connections!); 129 | })); 130 | }); 131 | } 132 | 133 | private void RefreshConnections(List connections) 134 | { 135 | _connectionItems.Clear(); 136 | 137 | var dtNow = DateTime.Now; 138 | var lstModel = new List(); 139 | foreach (var item in connections) 140 | { 141 | ConnectionModel model = new(); 142 | 143 | model.id = item.Id; 144 | model.network = item.metadata.Network; 145 | model.type = item.metadata.Type; 146 | model.host = $"{(string.IsNullOrEmpty(item.metadata.Host) ? item.metadata.DestinationIP : item.metadata.Host)}:{item.metadata.DestinationPort}"; 147 | var sp = (dtNow - item.start); 148 | model.time = sp.TotalSeconds < 0 ? 1 : sp.TotalSeconds; 149 | model.upload = item.upload; 150 | model.download = item.download; 151 | model.uploadTraffic = $"{Utils.HumanFy(item.upload)}"; 152 | model.downloadTraffic = $"{Utils.HumanFy(item.download)}"; 153 | model.elapsed = sp.ToString(@"hh\:mm\:ss"); 154 | model.chain = item.Chains.Count > 0 ? item.Chains[0] : String.Empty; 155 | 156 | lstModel.Add(model); 157 | } 158 | if (lstModel.Count <= 0) { return; } 159 | 160 | //sort 161 | switch (SortingSelected) 162 | { 163 | case 0: 164 | lstModel = lstModel.OrderBy(t => t.upload / t.time).ToList(); 165 | break; 166 | 167 | case 1: 168 | lstModel = lstModel.OrderBy(t => t.download / t.time).ToList(); 169 | break; 170 | 171 | case 2: 172 | lstModel = lstModel.OrderBy(t => t.upload).ToList(); 173 | break; 174 | 175 | case 3: 176 | lstModel = lstModel.OrderBy(t => t.download).ToList(); 177 | break; 178 | 179 | case 4: 180 | lstModel = lstModel.OrderBy(t => t.time).ToList(); 181 | break; 182 | 183 | case 5: 184 | lstModel = lstModel.OrderBy(t => t.host).ToList(); 185 | break; 186 | } 187 | 188 | _connectionItems.AddRange(lstModel); 189 | } 190 | 191 | public void ClashConnectionClose(bool all) 192 | { 193 | var id = string.Empty; 194 | if (!all) 195 | { 196 | var item = SelectedSource; 197 | if (item is null) 198 | { 199 | return; 200 | } 201 | id = item.id; 202 | } 203 | else 204 | { 205 | _connectionItems.Clear(); 206 | } 207 | MainFormHandler.Instance.ClashConnectionClose(id); 208 | GetClashConnections(); 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /clashN/clashN/ViewModels/DashboardViewModel.cs: -------------------------------------------------------------------------------- 1 | using ReactiveUI; 2 | 3 | namespace ClashN.ViewModels 4 | { 5 | public class DashboardViewModel : ReactiveObject 6 | { 7 | public DashboardViewModel() 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /clashN/clashN/ViewModels/HelpViewModel.cs: -------------------------------------------------------------------------------- 1 | using ClashN.Handler; 2 | using ClashN.Mode; 3 | using ClashN.Resx; 4 | using ClashN.Tool; 5 | using ReactiveUI; 6 | using Splat; 7 | using System.Reactive; 8 | 9 | namespace ClashN.ViewModels 10 | { 11 | public class HelpViewModel : ReactiveObject 12 | { 13 | private static Config _config; 14 | private NoticeHandler? _noticeHandler; 15 | 16 | public ReactiveCommand CheckUpdateCmd { get; } 17 | 18 | //public ReactiveCommand CheckUpdateClashCoreCmd { get; } 19 | public ReactiveCommand CheckUpdateMihomoCoreCmd { get; } 20 | 21 | //public ReactiveCommand CheckUpdateGeoDataCmd { get; } 22 | 23 | public HelpViewModel() 24 | { 25 | _config = LazyConfig.Instance.Config; 26 | _noticeHandler = Locator.Current.GetService(); 27 | 28 | CheckUpdateCmd = ReactiveCommand.Create(() => 29 | { 30 | CheckUpdateN(); 31 | }); 32 | //CheckUpdateClashCoreCmd = ReactiveCommand.Create(() => 33 | //{ 34 | // CheckUpdateCore(CoreKind.Clash); 35 | //}); 36 | CheckUpdateMihomoCoreCmd = ReactiveCommand.Create(() => 37 | { 38 | CheckUpdateCore(CoreKind.Mihomo); 39 | }); 40 | //CheckUpdateGeoDataCmd = ReactiveCommand.Create(() => 41 | //{ 42 | // CheckUpdateGeoData(); 43 | //}); 44 | } 45 | 46 | //private void CheckUpdateGeoData() 47 | //{ 48 | // void _updateUI(bool success, string msg) 49 | // { 50 | // _noticeHandler?.SendMessage(msg); 51 | // if (success) 52 | // { 53 | // Locator.Current.GetService()?.MyAppExit(false); 54 | // } 55 | // }; 56 | // UpdateHandle update = new UpdateHandle(); 57 | // update.UpdateGeoFile(GeoKind.GEO_IP, _config, _updateUI); 58 | // update.UpdateGeoFile(GeoKind.GEO_SITE, _config, _updateUI); 59 | //} 60 | 61 | private void CheckUpdateN() 62 | { 63 | void _updateUI(bool success, string msg) 64 | { 65 | _noticeHandler?.SendMessage(msg); 66 | if (success) 67 | { 68 | Locator.Current.GetService()?.MyAppExit(false); 69 | } 70 | }; 71 | (new UpdateHandle()).CheckUpdateGuiN(_config, _updateUI); 72 | } 73 | 74 | private void CheckUpdateCore(CoreKind type) 75 | { 76 | void _updateUI(bool success, string msg) 77 | { 78 | _noticeHandler?.SendMessage(msg); 79 | if (success) 80 | { 81 | Locator.Current.GetService()?.CloseCore(); 82 | 83 | string fileName = Utils.GetTempPath(Utils.GetDownloadFileName(msg)); 84 | string toPath = Utils.GetBinPath("", type); 85 | if (FileManager.ZipExtractToFile(fileName, toPath, "") == false) 86 | { 87 | Global.reloadCore = true; 88 | _ = Locator.Current.GetService()?.LoadCore(); 89 | _noticeHandler?.Enqueue(ResUI.MsgUpdateCoreCoreFailed); 90 | } 91 | else 92 | { 93 | _noticeHandler?.Enqueue(ResUI.MsgUpdateCoreCoreSuccessfullyMore); 94 | 95 | Global.reloadCore = true; 96 | _ = Locator.Current.GetService()?.LoadCore(); 97 | _noticeHandler?.Enqueue(ResUI.MsgUpdateCoreCoreSuccessfully); 98 | } 99 | } 100 | }; 101 | (new UpdateHandle()).CheckUpdateCore(type, _config, _updateUI); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /clashN/clashN/ViewModels/LogsViewModel.cs: -------------------------------------------------------------------------------- 1 | using ReactiveUI; 2 | using ReactiveUI.Fody.Helpers; 3 | 4 | namespace ClashN.ViewModels 5 | { 6 | public class LogsViewModel : ReactiveObject 7 | { 8 | [Reactive] 9 | public int SortingSelected { get; set; } 10 | 11 | [Reactive] 12 | public bool AutoRefresh { get; set; } 13 | 14 | [Reactive] 15 | public string MsgFilter { get; set; } 16 | 17 | [Reactive] 18 | public int LineCount { get; set; } 19 | 20 | public LogsViewModel() 21 | { 22 | AutoRefresh = true; 23 | MsgFilter = string.Empty; 24 | LineCount = 1000; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /clashN/clashN/ViewModels/ProfileEditViewModel.cs: -------------------------------------------------------------------------------- 1 | using ClashN.Handler; 2 | using ClashN.Mode; 3 | using ClashN.Resx; 4 | using ClashN.Views; 5 | using ReactiveUI; 6 | using ReactiveUI.Fody.Helpers; 7 | using ReactiveUI.Validation.Helpers; 8 | using Splat; 9 | using System.IO; 10 | using System.Reactive; 11 | using System.Windows.Forms; 12 | 13 | namespace ClashN.ViewModels 14 | { 15 | public class ProfileEditViewModel : ReactiveValidationObject 16 | { 17 | private static Config _config; 18 | private NoticeHandler? _noticeHandler; 19 | private PorfileEditWindow _view; 20 | 21 | [Reactive] 22 | public ProfileItem SelectedSource { get; set; } 23 | 24 | [Reactive] 25 | public string CoreType { get; set; } 26 | 27 | public ReactiveCommand BrowseProfileCmd { get; } 28 | public ReactiveCommand EditProfileCmd { get; } 29 | public ReactiveCommand SaveProfileCmd { get; } 30 | 31 | public ProfileEditViewModel(ProfileItem profileItem, PorfileEditWindow view) 32 | { 33 | _noticeHandler = Locator.Current.GetService(); 34 | _config = LazyConfig.Instance.Config; 35 | 36 | if (string.IsNullOrEmpty(profileItem.indexId)) 37 | { 38 | SelectedSource = profileItem; 39 | } 40 | else 41 | { 42 | SelectedSource = Utils.DeepCopy(profileItem); 43 | } 44 | 45 | _view = view; 46 | CoreType = (SelectedSource.coreType ?? CoreKind.Clash).ToString(); 47 | 48 | BrowseProfileCmd = ReactiveCommand.Create(() => 49 | { 50 | BrowseProfile(); 51 | }); 52 | 53 | EditProfileCmd = ReactiveCommand.Create(() => 54 | { 55 | EditProfile(); 56 | }); 57 | 58 | SaveProfileCmd = ReactiveCommand.Create(() => 59 | { 60 | SaveProfile(); 61 | }); 62 | 63 | Utils.SetDarkBorder(view, _config.UiItem.colorModeDark); 64 | } 65 | 66 | private void SaveProfile() 67 | { 68 | string remarks = SelectedSource.remarks; 69 | if (string.IsNullOrEmpty(remarks)) 70 | { 71 | _noticeHandler?.Enqueue(ResUI.PleaseFillRemarks); 72 | return; 73 | } 74 | 75 | if (string.IsNullOrEmpty(CoreType)) 76 | { 77 | SelectedSource.coreType = null; 78 | } 79 | else 80 | { 81 | SelectedSource.coreType = (CoreKind)Enum.Parse(typeof(CoreKind), CoreType); 82 | } 83 | 84 | var item = _config.GetProfileItem(SelectedSource.indexId); 85 | if (item is null) 86 | { 87 | item = SelectedSource; 88 | } 89 | else 90 | { 91 | item.remarks = SelectedSource.remarks; 92 | item.url = SelectedSource.url; 93 | item.address = SelectedSource.address; 94 | item.userAgent = SelectedSource.userAgent; 95 | item.coreType = SelectedSource.coreType; 96 | item.enabled = SelectedSource.enabled; 97 | item.enableConvert = SelectedSource.enableConvert; 98 | } 99 | 100 | if (ConfigProc.EditProfile(ref _config, item) == 0) 101 | { 102 | Locator.Current.GetService()?.RefreshProfiles(); 103 | _noticeHandler?.Enqueue(ResUI.OperationSuccess); 104 | _view?.Close(); 105 | } 106 | else 107 | { 108 | _noticeHandler?.Enqueue(ResUI.OperationFailed); 109 | } 110 | } 111 | 112 | private void BrowseProfile() 113 | { 114 | OpenFileDialog fileDialog = new OpenFileDialog 115 | { 116 | Multiselect = false, 117 | Filter = "YAML|*.yaml;*.yml|All|*.*" 118 | }; 119 | 120 | IWin32Window parent = App.Current.MainWindow.WpfWindow2WinFormWin32Window(); 121 | if (fileDialog.ShowDialog(parent) != DialogResult.OK) 122 | { 123 | return; 124 | } 125 | if (UI.ShowYesNo(ResUI.MsgSureContinue) == DialogResult.No) 126 | { 127 | return; 128 | } 129 | string fileName = fileDialog.FileName; 130 | if (string.IsNullOrEmpty(fileName)) 131 | { 132 | return; 133 | } 134 | var item = _config.GetProfileItem(SelectedSource.indexId); 135 | if (item is null) 136 | { 137 | item = SelectedSource; 138 | } 139 | if (ConfigProc.AddProfileViaPath(ref _config, item, fileName) == 0) 140 | { 141 | _noticeHandler?.Enqueue(ResUI.SuccessfullyImportedCustomProfile); 142 | Locator.Current.GetService()?.RefreshProfiles(); 143 | _view?.Close(); 144 | } 145 | else 146 | { 147 | _noticeHandler?.Enqueue(ResUI.FailedImportedCustomProfile); 148 | } 149 | } 150 | 151 | private void EditProfile() 152 | { 153 | var address = SelectedSource.address; 154 | if (string.IsNullOrEmpty(address)) 155 | { 156 | _noticeHandler?.Enqueue(ResUI.FillProfileAddressCustom); 157 | return; 158 | } 159 | 160 | address = Path.Combine(Utils.GetConfigPath(), address); 161 | if (File.Exists(address)) 162 | { 163 | Utils.ProcessStart(address); 164 | } 165 | else 166 | { 167 | _noticeHandler?.Enqueue(ResUI.FailedReadConfiguration); 168 | } 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /clashN/clashN/ViewModels/PromotionViewModel.cs: -------------------------------------------------------------------------------- 1 | using ReactiveUI; 2 | 3 | namespace ClashN.ViewModels 4 | { 5 | public class PromotionViewModel : ReactiveObject 6 | { 7 | public PromotionViewModel() 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /clashN/clashN/Views/ConnectionsView.xaml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 28 | 29 | 30 | 31 | 37 | 41 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 63 | 64 | 68 | 72 | 73 | 74 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 97 | 101 | 105 | 109 | 113 | 117 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /clashN/clashN/Views/ConnectionsView.xaml.cs: -------------------------------------------------------------------------------- 1 | using ClashN.ViewModels; 2 | using ReactiveUI; 3 | using System.Reactive.Disposables; 4 | 5 | namespace ClashN.Views 6 | { 7 | /// 8 | /// Interaction logic for ConnectionsView.xaml 9 | /// 10 | public partial class ConnectionsView 11 | { 12 | public ConnectionsView() 13 | { 14 | InitializeComponent(); 15 | ViewModel = new ConnectionsViewModel(); 16 | 17 | this.WhenActivated(disposables => 18 | { 19 | this.OneWayBind(ViewModel, vm => vm.ConnectionItems, v => v.lstConnections.ItemsSource).DisposeWith(disposables); 20 | this.Bind(ViewModel, vm => vm.SelectedSource, v => v.lstConnections.SelectedItem).DisposeWith(disposables); 21 | this.OneWayBind(ViewModel, vm => vm.ConnectionItems.Count, v => v.chipCount.Content).DisposeWith(disposables); 22 | 23 | this.BindCommand(ViewModel, vm => vm.ConnectionCloseCmd, v => v.menuConnectionClose).DisposeWith(disposables); 24 | this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.menuConnectionCloseAll).DisposeWith(disposables); 25 | 26 | this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables); 27 | this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables); 28 | this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); 29 | }); 30 | } 31 | 32 | private void btnClose_Click(object sender, System.Windows.RoutedEventArgs e) 33 | { 34 | ViewModel?.ClashConnectionClose(false); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /clashN/clashN/Views/DashboardView.xaml: -------------------------------------------------------------------------------- 1 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | Dashboard 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /clashN/clashN/Views/DashboardView.xaml.cs: -------------------------------------------------------------------------------- 1 | using ReactiveUI; 2 | 3 | namespace ClashN.Views 4 | { 5 | /// 6 | /// Interaction logic for DashboardView.xaml 7 | /// 8 | public partial class DashboardView 9 | { 10 | public DashboardView() 11 | { 12 | InitializeComponent(); 13 | this.WhenActivated(disposables => 14 | { 15 | }); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /clashN/clashN/Views/GlobalHotkeySettingWindow.xaml: -------------------------------------------------------------------------------- 1 |  27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 57 | 58 | 65 | 66 | 75 | 76 | 83 | 92 | 93 | 100 | 109 | 116 | 125 | 132 | 141 | 142 | 143 | 149 | 150 | 154 | 155 | 156 | 157 | 158 | 159 | 32 | 36 | 40 | 41 | 49 | 50 | 54 | 58 | 59 | 63 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 82 | 83 | -------------------------------------------------------------------------------- /clashN/clashN/Views/LogsView.xaml.cs: -------------------------------------------------------------------------------- 1 | using ClashN.ViewModels; 2 | using ReactiveUI; 3 | using System.Reactive.Disposables; 4 | using System.Text.RegularExpressions; 5 | using System.Windows; 6 | using System.Windows.Threading; 7 | 8 | namespace ClashN.Views 9 | { 10 | /// 11 | /// Interaction logic for LogsView.xaml 12 | /// 13 | public partial class LogsView 14 | { 15 | public LogsView() 16 | { 17 | InitializeComponent(); 18 | ViewModel = new LogsViewModel(); 19 | 20 | MessageBus.Current.Listen("MsgView").Subscribe(x => DelegateAppendText(x)); 21 | 22 | this.WhenActivated(disposables => 23 | { 24 | this.Bind(ViewModel, vm => vm.MsgFilter, v => v.txtFilter.Text).DisposeWith(disposables); 25 | this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); 26 | this.Bind(ViewModel, vm => vm.LineCount, v => v.cmbLineCount.Text).DisposeWith(disposables); 27 | }); 28 | } 29 | 30 | private void DelegateAppendText(string msg) 31 | { 32 | Dispatcher.BeginInvoke(new Action(AppendText), DispatcherPriority.Send, msg); 33 | } 34 | 35 | public void AppendText(string msg) 36 | { 37 | if (ViewModel?.AutoRefresh == false) 38 | { 39 | return; 40 | } 41 | string? msgFilter = ViewModel?.MsgFilter; 42 | if (!string.IsNullOrEmpty(msgFilter)) 43 | { 44 | if (!Regex.IsMatch(msg, msgFilter)) 45 | { 46 | return; 47 | } 48 | } 49 | 50 | ShowMsg(msg); 51 | } 52 | 53 | private void ShowMsg(string msg) 54 | { 55 | if (txtMsg.LineCount > ViewModel?.LineCount) 56 | { 57 | ClearMsg(); 58 | } 59 | this.txtMsg.AppendText(msg); 60 | if (!msg.EndsWith(Environment.NewLine)) 61 | { 62 | this.txtMsg.AppendText(Environment.NewLine); 63 | } 64 | txtMsg.ScrollToEnd(); 65 | } 66 | 67 | public void ClearMsg() 68 | { 69 | Dispatcher.Invoke((Action)(() => 70 | { 71 | txtMsg.Clear(); 72 | })); 73 | } 74 | 75 | private void btnDelete_Click(object sender, RoutedEventArgs e) 76 | { 77 | ClearMsg(); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /clashN/clashN/Views/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using ClashN.Resx; 2 | using ClashN.ViewModels; 3 | using ReactiveUI; 4 | using Splat; 5 | using System.ComponentModel; 6 | using System.Reactive.Disposables; 7 | using System.Windows; 8 | 9 | namespace ClashN.Views 10 | { 11 | /// 12 | /// Interaction logic for MainWindow.xaml 13 | /// 14 | public partial class MainWindow 15 | { 16 | public MainWindow() 17 | { 18 | InitializeComponent(); 19 | this.Closing += MainWindow_Closing; 20 | App.Current.SessionEnding += Current_SessionEnding; 21 | 22 | ViewModel = new MainWindowViewModel(MainSnackbar.MessageQueue!); 23 | Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(MainWindowViewModel)); 24 | 25 | this.WhenActivated(disposables => 26 | { 27 | //this.OneWayBind(ViewModel, vm => vm.GetDashboardView, v => v.dashboardTabItem.Content).DisposeWith(disposables); 28 | this.OneWayBind(ViewModel, vm => vm.GetProxyView, v => v.proxiesTabItem.Content).DisposeWith(disposables); 29 | this.OneWayBind(ViewModel, vm => vm.GetProfilesView, v => v.profilesTabItem.Content).DisposeWith(disposables); 30 | this.OneWayBind(ViewModel, vm => vm.GetLogsView, v => v.logsTabItem.Content).DisposeWith(disposables); 31 | this.OneWayBind(ViewModel, vm => vm.GetConnectionsView, v => v.connectionsTabItem.Content).DisposeWith(disposables); 32 | 33 | this.OneWayBind(ViewModel, vm => vm.GetSettingsView, v => v.settingsTabItem.Content).DisposeWith(disposables); 34 | this.OneWayBind(ViewModel, vm => vm.GetHelpView, v => v.helpTabItem.Content).DisposeWith(disposables); 35 | this.OneWayBind(ViewModel, vm => vm.GetPromotionView, v => v.promotionTabItem.Content).DisposeWith(disposables); 36 | this.OneWayBind(ViewModel, vm => vm.SpeedUpload, v => v.txtSpeedUpload.Text).DisposeWith(disposables); 37 | this.OneWayBind(ViewModel, vm => vm.SpeedDownload, v => v.txtSpeedDownload.Text).DisposeWith(disposables); 38 | 39 | this.OneWayBind(ViewModel, vm => vm.BlSystemProxyClear, v => v.menuSystemProxyClear2.Visibility, conversionHint: BooleanToVisibilityHint.UseHidden, vmToViewConverterOverride: new BooleanToVisibilityTypeConverter()).DisposeWith(disposables); 40 | this.OneWayBind(ViewModel, vm => vm.BlSystemProxySet, v => v.menuSystemProxySet2.Visibility, conversionHint: BooleanToVisibilityHint.UseHidden, vmToViewConverterOverride: new BooleanToVisibilityTypeConverter()).DisposeWith(disposables); 41 | this.OneWayBind(ViewModel, vm => vm.BlSystemProxyNothing, v => v.menuSystemProxyNothing2.Visibility, conversionHint: BooleanToVisibilityHint.UseHidden, vmToViewConverterOverride: new BooleanToVisibilityTypeConverter()).DisposeWith(disposables); 42 | this.OneWayBind(ViewModel, vm => vm.BlSystemProxyPac, v => v.menuSystemProxyPac2.Visibility, conversionHint: BooleanToVisibilityHint.UseHidden, vmToViewConverterOverride: new BooleanToVisibilityTypeConverter()).DisposeWith(disposables); 43 | this.BindCommand(ViewModel, vm => vm.SystemProxyClearCmd, v => v.menuSystemProxyClear).DisposeWith(disposables); 44 | this.BindCommand(ViewModel, vm => vm.SystemProxySetCmd, v => v.menuSystemProxySet).DisposeWith(disposables); 45 | this.BindCommand(ViewModel, vm => vm.SystemProxyPacCmd, v => v.menuSystemProxyPac).DisposeWith(disposables); 46 | this.BindCommand(ViewModel, vm => vm.SystemProxyNothingCmd, v => v.menuSystemProxyNothing).DisposeWith(disposables); 47 | 48 | this.OneWayBind(ViewModel, vm => vm.BlModeRule, v => v.menuModeRule2.Visibility, conversionHint: BooleanToVisibilityHint.UseHidden, vmToViewConverterOverride: new BooleanToVisibilityTypeConverter()).DisposeWith(disposables); 49 | this.OneWayBind(ViewModel, vm => vm.BlModeGlobal, v => v.menuModeGlobal2.Visibility, conversionHint: BooleanToVisibilityHint.UseHidden, vmToViewConverterOverride: new BooleanToVisibilityTypeConverter()).DisposeWith(disposables); 50 | this.OneWayBind(ViewModel, vm => vm.BlModeDirect, v => v.menuModeDirect2.Visibility, conversionHint: BooleanToVisibilityHint.UseHidden, vmToViewConverterOverride: new BooleanToVisibilityTypeConverter()).DisposeWith(disposables); 51 | this.OneWayBind(ViewModel, vm => vm.BlModeNothing, v => v.menuModeNothing2.Visibility, conversionHint: BooleanToVisibilityHint.UseHidden, vmToViewConverterOverride: new BooleanToVisibilityTypeConverter()).DisposeWith(disposables); 52 | this.BindCommand(ViewModel, vm => vm.ModeRuleCmd, v => v.menuModeRule).DisposeWith(disposables); 53 | this.BindCommand(ViewModel, vm => vm.ModeGlobalCmd, v => v.menuModeGlobal).DisposeWith(disposables); 54 | this.BindCommand(ViewModel, vm => vm.ModeDirectCmd, v => v.menuModeDirect).DisposeWith(disposables); 55 | this.BindCommand(ViewModel, vm => vm.ModeNothingCmd, v => v.menuModeNothing).DisposeWith(disposables); 56 | 57 | this.BindCommand(ViewModel, vm => vm.AddProfileViaScanCmd, v => v.menuAddProfileViaScan).DisposeWith(disposables); 58 | this.BindCommand(ViewModel, vm => vm.SubUpdateCmd, v => v.menuSubUpdate).DisposeWith(disposables); 59 | this.BindCommand(ViewModel, vm => vm.SubUpdateViaProxyCmd, v => v.menuSubUpdateViaProxy).DisposeWith(disposables); 60 | 61 | //this.BindCommand(ViewModel, vm => vm.ExitCmd, v => v.menuExit).DisposeWith(disposables); 62 | this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.btnReload).DisposeWith(disposables); 63 | 64 | this.OneWayBind(ViewModel, vm => vm.NotifyIcon, v => v.tbNotify.Icon).DisposeWith(disposables); 65 | this.OneWayBind(ViewModel, vm => vm.NotifyLeftClickCmd, v => v.tbNotify.LeftClickCommand).DisposeWith(disposables); 66 | }); 67 | 68 | this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}"; 69 | } 70 | 71 | private void MainWindow_Closing(object? sender, CancelEventArgs e) 72 | { 73 | e.Cancel = true; 74 | ViewModel?.ShowHideWindow(false); 75 | } 76 | 77 | private void menuExit_Click(object sender, RoutedEventArgs e) 78 | { 79 | tbNotify.Dispose(); 80 | ViewModel?.MyAppExit(false); 81 | } 82 | 83 | private void Current_SessionEnding(object sender, SessionEndingCancelEventArgs e) 84 | { 85 | Utils.SaveLog("Current_SessionEnding"); 86 | ViewModel?.MyAppExit(true); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /clashN/clashN/Views/MessageSampleDialog.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 15 | 16 | 17 | 18 | 22 |