diff --git a/.github/workflows/winget-publish.yml b/.github/workflows/winget-publish.yml index 0c4fdc97..8e172058 100644 --- a/.github/workflows/winget-publish.yml +++ b/.github/workflows/winget-publish.yml @@ -22,10 +22,18 @@ jobs: $github = Invoke-RestMethod -uri "https://api.github.com/repos/cg3s/v2rayN/releases" $targetRelease = $github | Where-Object -Property prerelease -match 'False' | Select -First 1 - $installerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-64\.zip*' | Select -ExpandProperty browser_download_url - + + $x64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-64\.zip' | Select -ExpandProperty browser_download_url + $arm64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-arm64\.zip' | Select -ExpandProperty browser_download_url + $ver = $targetRelease.tag_name # getting latest wingetcreate file iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe - .\wingetcreate.exe update $wingetPackage -s -v $ver -u "$installerUrl|x64" -t $gitToken + + Write-Host "Updating with both x64 and arm64 installers" + Write-Host "Version: $ver" + Write-Host "x64 URL: $x64InstallerUrl" + Write-Host "arm64 URL: $arm64InstallerUrl" + + .\wingetcreate.exe update $wingetPackage -s -v $ver -u "$x64InstallerUrl|x64" "$arm64InstallerUrl|arm64" -t $gitToken diff --git a/v2rayN/Directory.Build.props b/v2rayN/Directory.Build.props index 78438651..d7fd93ac 100644 --- a/v2rayN/Directory.Build.props +++ b/v2rayN/Directory.Build.props @@ -1,7 +1,7 @@ - 7.12.5 + 7.13.3 diff --git a/v2rayN/Directory.Packages.props b/v2rayN/Directory.Packages.props index e9c8d492..57a50cfb 100644 --- a/v2rayN/Directory.Packages.props +++ b/v2rayN/Directory.Packages.props @@ -5,24 +5,24 @@ false - - - - - - + + + + + + - + - - - - + + + + - + diff --git a/v2rayN/ServiceLib/Common/AesUtils.cs b/v2rayN/ServiceLib/Common/AesUtils.cs deleted file mode 100644 index 05f1cb38..00000000 --- a/v2rayN/ServiceLib/Common/AesUtils.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Security.Cryptography; -using System.Text; - -namespace ServiceLib.Common; - -public class AesUtils -{ - private const int KeySize = 256; // AES-256 - private const int IvSize = 16; // AES block size - private const int Iterations = 10000; - private static readonly byte[] Salt = Encoding.ASCII.GetBytes("saltysalt".PadRight(16, ' ')); - private static readonly string DefaultPassword = Utils.GetMd5(Utils.GetHomePath() + "AesUtils"); - - /// - /// Encrypt - /// - /// Plain text - /// Password for key derivation or direct key in ASCII bytes - /// Base64 encoded cipher text with IV - public static string Encrypt(string text, string? password = null) - { - if (string.IsNullOrEmpty(text)) - return string.Empty; - - var plaintext = Encoding.UTF8.GetBytes(text); - var key = GetKey(password); - var iv = GenerateIv(); - - using var aes = Aes.Create(); - aes.Key = key; - aes.IV = iv; - - using var ms = new MemoryStream(); - ms.Write(iv, 0, iv.Length); - - using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write)) - { - cs.Write(plaintext, 0, plaintext.Length); - cs.FlushFinalBlock(); - } - - var cipherTextWithIv = ms.ToArray(); - return Convert.ToBase64String(cipherTextWithIv); - } - - /// - /// Decrypt - /// - /// Base64 encoded cipher text with IV - /// Password for key derivation or direct key in ASCII bytes - /// Plain text - public static string Decrypt(string cipherTextWithIv, string? password = null) - { - if (string.IsNullOrEmpty(cipherTextWithIv)) - return string.Empty; - - var cipherTextWithIvBytes = Convert.FromBase64String(cipherTextWithIv); - var key = GetKey(password); - - var iv = new byte[IvSize]; - Buffer.BlockCopy(cipherTextWithIvBytes, 0, iv, 0, IvSize); - - var cipherText = new byte[cipherTextWithIvBytes.Length - IvSize]; - Buffer.BlockCopy(cipherTextWithIvBytes, IvSize, cipherText, 0, cipherText.Length); - - using var aes = Aes.Create(); - aes.Key = key; - aes.IV = iv; - - using var ms = new MemoryStream(); - using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write)) - { - cs.Write(cipherText, 0, cipherText.Length); - cs.FlushFinalBlock(); - } - - var plainText = ms.ToArray(); - return Encoding.UTF8.GetString(plainText); - } - - private static byte[] GetKey(string? password) - { - if (password.IsNullOrEmpty()) - { - password = DefaultPassword; - } - - using var pbkdf2 = new Rfc2898DeriveBytes(password, Salt, Iterations, HashAlgorithmName.SHA256); - return pbkdf2.GetBytes(KeySize / 8); - } - - private static byte[] GenerateIv() - { - var randomNumber = new byte[IvSize]; - - using var rng = RandomNumberGenerator.Create(); - rng.GetBytes(randomNumber); - return randomNumber; - } -} diff --git a/v2rayN/ServiceLib/Common/DesUtils.cs b/v2rayN/ServiceLib/Common/DesUtils.cs deleted file mode 100644 index aae02206..00000000 --- a/v2rayN/ServiceLib/Common/DesUtils.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Security.Cryptography; -using System.Text; - -namespace ServiceLib.Common; - -public class DesUtils -{ - /// - /// Encrypt - /// - /// - /// /// - /// - public static string Encrypt(string? text, string? key = null) - { - if (text.IsNullOrEmpty()) - { - return string.Empty; - } - GetKeyIv(key ?? GetDefaultKey(), out var rgbKey, out var rgbIv); - var dsp = DES.Create(); - using var memStream = new MemoryStream(); - using var cryStream = new CryptoStream(memStream, dsp.CreateEncryptor(rgbKey, rgbIv), CryptoStreamMode.Write); - using var sWriter = new StreamWriter(cryStream); - sWriter.Write(text); - sWriter.Flush(); - cryStream.FlushFinalBlock(); - memStream.Flush(); - return Convert.ToBase64String(memStream.GetBuffer(), 0, (int)memStream.Length); - } - - /// - /// Decrypt - /// - /// - /// - /// - public static string Decrypt(string? encryptText, string? key = null) - { - if (encryptText.IsNullOrEmpty()) - { - return string.Empty; - } - GetKeyIv(key ?? GetDefaultKey(), out var rgbKey, out var rgbIv); - var dsp = DES.Create(); - var buffer = Convert.FromBase64String(encryptText); - - using var memStream = new MemoryStream(); - using var cryStream = new CryptoStream(memStream, dsp.CreateDecryptor(rgbKey, rgbIv), CryptoStreamMode.Write); - cryStream.Write(buffer, 0, buffer.Length); - cryStream.FlushFinalBlock(); - return Encoding.UTF8.GetString(memStream.ToArray()); - } - - private static void GetKeyIv(string key, out byte[] rgbKey, out byte[] rgbIv) - { - if (key.IsNullOrEmpty()) - { - throw new ArgumentNullException("The key cannot be null"); - } - if (key.Length <= 8) - { - throw new ArgumentNullException("The key length cannot be less than 8 characters."); - } - - rgbKey = Encoding.ASCII.GetBytes(key.Substring(0, 8)); - rgbIv = Encoding.ASCII.GetBytes(key.Insert(0, "w").Substring(0, 8)); - } - - private static string GetDefaultKey() - { - return Utils.GetMd5(Utils.GetHomePath() + "DesUtils"); - } -} diff --git a/v2rayN/ServiceLib/Common/DownloaderHelper.cs b/v2rayN/ServiceLib/Common/DownloaderHelper.cs index 0c4cde0c..073e1ab4 100644 --- a/v2rayN/ServiceLib/Common/DownloaderHelper.cs +++ b/v2rayN/ServiceLib/Common/DownloaderHelper.cs @@ -26,7 +26,7 @@ public class DownloaderHelper var downloadOpt = new DownloadConfiguration() { Timeout = timeout * 1000, - MaxTryAgainOnFailover = 2, + MaxTryAgainOnFailure = 2, RequestConfiguration = { Headers = headers, @@ -64,7 +64,7 @@ public class DownloaderHelper var downloadOpt = new DownloadConfiguration() { Timeout = timeout * 1000, - MaxTryAgainOnFailover = 2, + MaxTryAgainOnFailure = 2, RequestConfiguration = { Timeout= timeout * 1000, @@ -135,7 +135,7 @@ public class DownloaderHelper var downloadOpt = new DownloadConfiguration() { Timeout = timeout * 1000, - MaxTryAgainOnFailover = 2, + MaxTryAgainOnFailure = 2, RequestConfiguration = { Timeout= timeout * 1000, diff --git a/v2rayN/ServiceLib/Common/StringEx.cs b/v2rayN/ServiceLib/Common/Extension.cs similarity index 91% rename from v2rayN/ServiceLib/Common/StringEx.cs rename to v2rayN/ServiceLib/Common/Extension.cs index 1b880b74..49d59534 100644 --- a/v2rayN/ServiceLib/Common/StringEx.cs +++ b/v2rayN/ServiceLib/Common/Extension.cs @@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis; namespace ServiceLib.Common; -public static class StringEx +public static class Extension { public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value) { @@ -79,4 +79,9 @@ public static class StringEx { return int.TryParse(value, out var result) ? result : defaultValue; } + + public static List AppendEmpty(this IEnumerable source) + { + return source.Concat(new[] { string.Empty }).ToList(); + } } diff --git a/v2rayN/ServiceLib/Common/Utils.cs b/v2rayN/ServiceLib/Common/Utils.cs index 03a1a860..c5b670cf 100644 --- a/v2rayN/ServiceLib/Common/Utils.cs +++ b/v2rayN/ServiceLib/Common/Utils.cs @@ -323,6 +323,14 @@ public class Utils return text.Replace(",", ",").Replace(Environment.NewLine, ","); } + public static List GetEnumNames() where TEnum : Enum + { + return Enum.GetValues(typeof(TEnum)) + .Cast() + .Select(e => e.ToString()) + .ToList(); + } + #endregion 转换函数 #region 数据检查 @@ -582,7 +590,7 @@ public class Utils var result = await cmd.ExecuteBufferedAsync(); if (result.IsSuccess) { - return result.StandardOutput.ToString(); + return result.StandardOutput ?? ""; } Logging.SaveLog(result.ToString() ?? ""); @@ -816,18 +824,6 @@ public class Utils return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); } return false; - //else - //{ - // var id = GetLinuxUserId().Result ?? "1000"; - // if (int.TryParse(id, out var userId)) - // { - // return userId == 0; - // } - // else - // { - // return false; - // } - //} } private static async Task GetLinuxUserId() @@ -839,14 +835,46 @@ public class Utils public static async Task SetLinuxChmod(string? fileName) { if (fileName.IsNullOrEmpty()) + { return null; + } + if (SetUnixFileMode(fileName)) + { + Logging.SaveLog($"Successfully set the file execution permission, {fileName}"); + return ""; + } + if (fileName.Contains(' ')) + { fileName = fileName.AppendQuotes(); - //File.SetUnixFileMode(fileName, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); + } var arg = new List() { "-c", $"chmod +x {fileName}" }; return await GetCliWrapOutput(Global.LinuxBash, arg); } + public static bool SetUnixFileMode(string? fileName) + { + try + { + if (fileName.IsNullOrEmpty()) + { + return false; + } + + if (File.Exists(fileName)) + { + var currentMode = File.GetUnixFileMode(fileName); + File.SetUnixFileMode(fileName, currentMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute); + return true; + } + } + catch (Exception ex) + { + Logging.SaveLog("SetUnixFileMode", ex); + } + return false; + } + public static async Task GetLinuxFontFamily(string lang) { // var arg = new List() { "-c", $"fc-list :lang={lang} family" }; diff --git a/v2rayN/ServiceLib/Enums/ECoreType.cs b/v2rayN/ServiceLib/Enums/ECoreType.cs index 6700fe44..e164a2ab 100644 --- a/v2rayN/ServiceLib/Enums/ECoreType.cs +++ b/v2rayN/ServiceLib/Enums/ECoreType.cs @@ -14,5 +14,6 @@ public enum ECoreType hysteria2 = 26, brook = 27, overtls = 28, + shadowquic = 29, v2rayN = 99 } diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index bd2e8863..8436c332 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -507,6 +507,7 @@ public class Global { ECoreType.juicity, "juicity/juicity" }, { ECoreType.brook, "txthinking/brook" }, { ECoreType.overtls, "ShadowsocksR-Live/overtls" }, + { ECoreType.shadowquic, "spongebob888/shadowquic" }, { ECoreType.v2rayN, "cg3s/v2rayN" }, }; @@ -527,5 +528,12 @@ public class Global @"" ]; + public static readonly List OutboundTags = + [ + ProxyTag, + DirectTag, + BlockTag + ]; + #endregion const } diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 65fdc284..fc06751b 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -101,6 +101,7 @@ public class ConfigHandler EnableAutoAdjustMainLvColWidth = true }; config.UiItem.MainColumnItem ??= new(); + config.UiItem.WindowSizeItem ??= new(); if (config.UiItem.CurrentLanguage.IsNullOrEmpty()) { @@ -250,6 +251,7 @@ public class ConfigHandler item.ShortId = profileItem.ShortId; item.SpiderX = profileItem.SpiderX; item.Extra = profileItem.Extra; + item.MuxEnabled = profileItem.MuxEnabled; } var ret = item.ConfigType switch @@ -1859,12 +1861,25 @@ public class ConfigHandler /// 0 if successful public static async Task SetDefaultRouting(Config config, RoutingItem routingItem) { - if (await SQLiteHelper.Instance.TableAsync().Where(t => t.Id == routingItem.Id).CountAsync() > 0) + var items = await AppHandler.Instance.RoutingItems(); + if (items.Any(t => t.Id == routingItem.Id && t.IsActive == true)) { - config.RoutingBasicItem.RoutingIndexId = routingItem.Id; + return -1; } - await SaveConfig(config); + foreach (var item in items) + { + if (item.Id == routingItem.Id) + { + item.IsActive = true; + } + else + { + item.IsActive = false; + } + } + + await SQLiteHelper.Instance.UpdateAllAsync(items); return 0; } @@ -1877,7 +1892,7 @@ public class ConfigHandler /// The default routing item public static async Task GetDefaultRouting(Config config) { - var item = await AppHandler.Instance.GetRoutingItem(config.RoutingBasicItem.RoutingIndexId); + var item = await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.IsActive == true); if (item is null) { var item2 = await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(); @@ -1983,8 +1998,20 @@ public class ConfigHandler items = await AppHandler.Instance.RoutingItems(); } - if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(ver)).ToList().Count > 0) + if (!blImportAdvancedRules && items.Count > 0) { + //migrate + //TODO Temporary code to be removed later + if (config.RoutingBasicItem.RoutingIndexId.IsNotEmpty()) + { + var item = items.FirstOrDefault(t => t.Id == config.RoutingBasicItem.RoutingIndexId); + if (item != null) + { + await SetDefaultRouting(config, item); + } + config.RoutingBasicItem.RoutingIndexId = string.Empty; + } + return 0; } @@ -2177,4 +2204,44 @@ public class ConfigHandler } #endregion Regional Presets + + #region UIItem + + public static WindowSizeItem? GetWindowSizeItem(Config config, string typeName) + { + var sizeItem = config?.UiItem?.WindowSizeItem?.FirstOrDefault(t => t.TypeName == typeName); + if (sizeItem == null || sizeItem.Width <= 0 || sizeItem.Height <= 0) + { + return null; + } + + return sizeItem; + } + + public static int SaveWindowSizeItem(Config config, string typeName, double width, double height) + { + var sizeItem = config?.UiItem?.WindowSizeItem?.FirstOrDefault(t => t.TypeName == typeName); + if (sizeItem == null) + { + sizeItem = new WindowSizeItem { TypeName = typeName }; + config.UiItem.WindowSizeItem.Add(sizeItem); + } + + sizeItem.Width = (int)width; + sizeItem.Height = (int)height; + + return 0; + } + + public static int SaveMainGirdHeight(Config config, double height1, double height2) + { + var uiItem = config.UiItem ?? new(); + + uiItem.MainGirdHeight1 = (int)(height1 + 0.1); + uiItem.MainGirdHeight2 = (int)(height2 + 0.1); + + return 0; + } + + #endregion UIItem } diff --git a/v2rayN/ServiceLib/Handler/CoreAdminHandler.cs b/v2rayN/ServiceLib/Handler/CoreAdminHandler.cs index 7fd07262..3e7d3f93 100644 --- a/v2rayN/ServiceLib/Handler/CoreAdminHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreAdminHandler.cs @@ -9,6 +9,7 @@ public class CoreAdminHandler private static readonly Lazy _instance = new(() => new()); public static CoreAdminHandler Instance => _instance.Value; private Config _config; + private readonly string _sudoAccessText = "SUDO_ACCESS_VERIFIED"; private Action? _updateFunc; private int _linuxSudoPid = -1; @@ -44,33 +45,35 @@ public class CoreAdminHandler RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, - StandardInputEncoding = Encoding.UTF8, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8, } }; - proc.OutputDataReceived += (sender, e) => - { - if (e.Data.IsNotEmpty()) - { - UpdateFunc(false, e.Data + Environment.NewLine); - } - }; - proc.ErrorDataReceived += (sender, e) => + var sudoVerified = false; + DataReceivedEventHandler dataHandler = (sender, e) => { if (e.Data.IsNotEmpty()) { + if (!sudoVerified && e.Data.Contains(_sudoAccessText)) + { + sudoVerified = true; + UpdateFunc(false, ResUI.SudoPwdVerfiedSuccessTip + Environment.NewLine); + return; + } UpdateFunc(false, e.Data + Environment.NewLine); } }; + proc.OutputDataReceived += dataHandler; + proc.ErrorDataReceived += dataHandler; + proc.Start(); proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); await Task.Delay(10); - await proc.StandardInput.WriteLineAsync(); + await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd); await Task.Delay(10); await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd); @@ -115,7 +118,7 @@ public class CoreAdminHandler } else { - sb.AppendLine($"sudo -S {cmdLine}"); + sb.AppendLine($"sudo -S echo \"{_sudoAccessText}\" && sudo -S {cmdLine}"); } await File.WriteAllTextAsync(shFilePath, sb.ToString()); diff --git a/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayN/ServiceLib/Handler/CoreHandler.cs index 92c30171..d1cf43f0 100644 --- a/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -280,20 +280,15 @@ public class CoreHandler if (displayLog) { - proc.OutputDataReceived += (sender, e) => - { - if (e.Data.IsNotEmpty()) - { - UpdateFunc(false, e.Data + Environment.NewLine); - } - }; - proc.ErrorDataReceived += (sender, e) => + DataReceivedEventHandler dataHandler = (sender, e) => { if (e.Data.IsNotEmpty()) { UpdateFunc(false, e.Data + Environment.NewLine); } }; + proc.OutputDataReceived += dataHandler; + proc.ErrorDataReceived += dataHandler; } proc.Start(); diff --git a/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs b/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs index 356d6c1f..6b7e1df2 100644 --- a/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs @@ -200,6 +200,15 @@ public sealed class CoreInfoHandler Arguments = "-r client -c {0}", Url = GetCoreUrl(ECoreType.overtls), AbsolutePath = false, + }, + + new CoreInfo + { + CoreType = ECoreType.shadowquic, + CoreExes = [ "shadowquic", "shadowquic"], + Arguments = "-c {0}", + Url = GetCoreUrl(ECoreType.shadowquic), + AbsolutePath = false, } ]; diff --git a/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs b/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs index dfe58d5b..bc56a302 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs @@ -24,7 +24,7 @@ public class Hysteria2Fmt : BaseFmt item.Path = Utils.UrlDecode(query["obfs-password"] ?? ""); item.AllowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false"; - item.Ports = Utils.UrlDecode(query["mport"] ?? "").Replace('-', ':'); + item.Ports = Utils.UrlDecode(query["mport"] ?? ""); return item; } diff --git a/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayN/ServiceLib/Models/ConfigItems.cs index 194c006e..8b95fdf9 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -89,10 +89,8 @@ public class UIItem { public bool EnableAutoAdjustMainLvColWidth { get; set; } public bool EnableUpdateSubOnlyRemarksExist { get; set; } - public double MainWidth { get; set; } - public double MainHeight { get; set; } - public double MainGirdHeight1 { get; set; } - public double MainGirdHeight2 { get; set; } + public int MainGirdHeight1 { get; set; } + public int MainGirdHeight2 { get; set; } public EGirdOrientation MainGirdOrientation { get; set; } = EGirdOrientation.Vertical; public string? ColorPrimaryName { get; set; } public string? CurrentTheme { get; set; } @@ -103,8 +101,10 @@ public class UIItem public bool DoubleClick2Activate { get; set; } = true; public bool AutoHideStartup { get; set; } public bool Hide2TrayWhenClose { get; set; } - public List MainColumnItem { get; set; } public bool ShowInTaskbar { get; set; } + public bool MacOSShowInDock { get; set; } + public List MainColumnItem { get; set; } + public List WindowSizeItem { get; set; } } [Serializable] @@ -245,3 +245,11 @@ public class Fragment4RayItem public string? Length { get; set; } public string? Interval { get; set; } } + +[Serializable] +public class WindowSizeItem +{ + public string TypeName { get; set; } + public int Width { get; set; } + public int Height { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/IPAPIInfo.cs b/v2rayN/ServiceLib/Models/IPAPIInfo.cs index 7ba16727..86cfe111 100644 --- a/v2rayN/ServiceLib/Models/IPAPIInfo.cs +++ b/v2rayN/ServiceLib/Models/IPAPIInfo.cs @@ -4,7 +4,7 @@ internal class IPAPIInfo { public string? ip { get; set; } public string? clientIp { get; set; } - public string? ip_addr { get; set; } + public string? ip_addr { get; set; } public string? query { get; set; } public string? country { get; set; } public string? country_name { get; set; } diff --git a/v2rayN/ServiceLib/Models/ProfileItem.cs b/v2rayN/ServiceLib/Models/ProfileItem.cs index 58e9414a..bc4358b5 100644 --- a/v2rayN/ServiceLib/Models/ProfileItem.cs +++ b/v2rayN/ServiceLib/Models/ProfileItem.cs @@ -1,9 +1,10 @@ +using ReactiveUI; using SQLite; namespace ServiceLib.Models; [Serializable] -public class ProfileItem +public class ProfileItem : ReactiveObject { public ProfileItem() { @@ -93,4 +94,5 @@ public class ProfileItem public string ShortId { get; set; } public string SpiderX { get; set; } public string Extra { get; set; } + public bool? MuxEnabled { get; set; } } diff --git a/v2rayN/ServiceLib/Models/ProfileItemModel.cs b/v2rayN/ServiceLib/Models/ProfileItemModel.cs index a1e81ca2..40ba3f1d 100644 --- a/v2rayN/ServiceLib/Models/ProfileItemModel.cs +++ b/v2rayN/ServiceLib/Models/ProfileItemModel.cs @@ -1,3 +1,5 @@ +using ReactiveUI.Fody.Helpers; + namespace ServiceLib.Models; [Serializable] @@ -5,13 +7,28 @@ public class ProfileItemModel : ProfileItem { public bool IsActive { get; set; } public string SubRemarks { get; set; } + + [Reactive] public int Delay { get; set; } + public decimal Speed { get; set; } public int Sort { get; set; } + + [Reactive] public string DelayVal { get; set; } + + [Reactive] public string SpeedVal { get; set; } + + [Reactive] public string TodayUp { get; set; } + + [Reactive] public string TodayDown { get; set; } + + [Reactive] public string TotalUp { get; set; } + + [Reactive] public string TotalDown { get; set; } } diff --git a/v2rayN/ServiceLib/Models/RoutingItem.cs b/v2rayN/ServiceLib/Models/RoutingItem.cs index 287e37fb..78d93445 100644 --- a/v2rayN/ServiceLib/Models/RoutingItem.cs +++ b/v2rayN/ServiceLib/Models/RoutingItem.cs @@ -19,4 +19,5 @@ public class RoutingItem public string DomainStrategy { get; set; } public string DomainStrategy4Singbox { get; set; } public int Sort { get; set; } + public bool IsActive { get; set; } } diff --git a/v2rayN/ServiceLib/Models/RoutingItemModel.cs b/v2rayN/ServiceLib/Models/RoutingItemModel.cs index 76b35c55..2326f85b 100644 --- a/v2rayN/ServiceLib/Models/RoutingItemModel.cs +++ b/v2rayN/ServiceLib/Models/RoutingItemModel.cs @@ -3,5 +3,4 @@ namespace ServiceLib.Models; [Serializable] public class RoutingItemModel : RoutingItem { - public bool IsActive { get; set; } } diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 648a633a..afaffebf 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -132,24 +132,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 Download speed 的本地化字符串。 - /// - public static string downloadSpeed { - get { - return ResourceManager.GetString("downloadSpeed", resourceCulture); - } - } - - /// - /// 查找类似 Do you want to download {0}? 的本地化字符串。 - /// - public static string DownloadYesNo { - get { - return ResourceManager.GetString("DownloadYesNo", resourceCulture); - } - } - /// /// 查找类似 Failed to convert configuration file 的本地化字符串。 /// @@ -1338,15 +1320,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 Advanced Function 的本地化字符串。 - /// - public static string menuRoutingAdvanced { - get { - return ResourceManager.GetString("menuRoutingAdvanced", resourceCulture); - } - } - /// /// 查找类似 Add 的本地化字符串。 /// @@ -2220,6 +2193,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Incorrect password, please try again. 的本地化字符串。 + /// + public static string SudoIncorrectPasswordTip { + get { + return ResourceManager.GetString("SudoIncorrectPasswordTip", resourceCulture); + } + } + + /// + /// 查找类似 Sudo password has been verified successfully, please ignore the incorrect password prompts! 的本地化字符串。 + /// + public static string SudoPwdVerfiedSuccessTip { + get { + return ResourceManager.GetString("SudoPwdVerfiedSuccessTip", resourceCulture); + } + } + /// /// 查找类似 Address 的本地化字符串。 /// @@ -2706,33 +2697,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 3. Block Domain or IP 的本地化字符串。 - /// - public static string TbRoutingTabBlock { - get { - return ResourceManager.GetString("TbRoutingTabBlock", resourceCulture); - } - } - - /// - /// 查找类似 2. Direct Domain or IP 的本地化字符串。 - /// - public static string TbRoutingTabDirect { - get { - return ResourceManager.GetString("TbRoutingTabDirect", resourceCulture); - } - } - - /// - /// 查找类似 1. Proxy Domain or IP 的本地化字符串。 - /// - public static string TbRoutingTabProxy { - get { - return ResourceManager.GetString("TbRoutingTabProxy", resourceCulture); - } - } - /// /// 查找类似 Pre-defined Rule Set List 的本地化字符串。 /// @@ -2769,6 +2733,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Can fill in the configuration remarks, please make sure it exist and are unique 的本地化字符串。 + /// + public static string TbRuleOutboundTagTip { + get { + return ResourceManager.GetString("TbRuleOutboundTagTip", resourceCulture); + } + } + /// /// 查找类似 Encryption method (security) 的本地化字符串。 /// @@ -3229,25 +3202,7 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Please set the sudo password in Tun mode settings first 的本地化字符串。 - /// - public static string TbSettingsLinuxSudoPasswordIsEmpty { - get { - return ResourceManager.GetString("TbSettingsLinuxSudoPasswordIsEmpty", resourceCulture); - } - } - - /// - /// 查找类似 Please do not run this app with sudo 的本地化字符串。 - /// - public static string TbSettingsLinuxSudoPasswordNotSudoRunApp { - get { - return ResourceManager.GetString("TbSettingsLinuxSudoPasswordNotSudoRunApp", resourceCulture); - } - } - - /// - /// 查找类似 The password you entered cannot be verified, so make sure you enter it correctly. If the application does not work properly due to an incorrect input, please restart the application. The password will not be stored and you will need to enter it again after each restart. 的本地化字符串。 + /// 查找类似 The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart. 的本地化字符串。 /// public static string TbSettingsLinuxSudoPasswordTip { get { @@ -3939,15 +3894,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 Ungrouped 的本地化字符串。 - /// - public static string UngroupedServers { - get { - return ResourceManager.GetString("UngroupedServers", resourceCulture); - } - } - /// /// 查找类似 Upgrade App does not exist 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index cbe269d8..02e417fe 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -132,12 +132,6 @@ درحال دانلود... - - دانلود - - - آیا می خواهید {0} را دانلود کنید؟ - تبدیل فایل پیکربندی انجام نشد @@ -387,9 +381,6 @@ کلید میانبر جهانی {0} با موفقیت ثبت شد - - گروه بندی نشده - همه سرورها @@ -819,9 +810,6 @@ {0} وب سایت - - عملکرد پیشرفته - اضافه کردن @@ -840,15 +828,6 @@ استراتژی دامنه - - 3. مسدود کردن دامنه یا آیپی - - - 2. دایرکت کردن دامنه یا IP - - - 1. پروکسی کردن دامنه یا IP - لیست مجموعه قوانین از پیش تعریف شده @@ -1336,13 +1315,7 @@ رمز عبور sudo سیستم - رمز عبوری که وارد کرده اید تایید نمی شود، بنابراین مطمئن شوید که آن را به درستی وارد کرده اید. اگر برنامه به دلیل ورودی نادرست به درستی کار نمی کند، لطفاً برنامه را مجدداً راه اندازی کنید. رمز عبور ذخیره نمی شود و پس از هر بار راه اندازی مجدد باید آن را دوباره وارد کنید. - - - لطفاً ابتدا رمز عبور sudo را در تنظیمات حالت Tun تنظیم کنید - - - لطفا این برنامه را با sudo اجرا نکنید + The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart. *حالت xhttp @@ -1416,4 +1389,13 @@ URL آزمایش اطلاعات اتصال فعلی - + + Can fill in the configuration remarks, please make sure it exist and are unique + + + Sudo password has been verified successfully, please ignore the incorrect password prompts! + + + Incorrect password, please try again. + + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 4f8824b7..85ea3335 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -132,12 +132,6 @@ Letöltés... - - Letöltés - - - Le szeretné tölteni? {0} - Nem sikerült a konfigurációs fájl átalakítása @@ -387,9 +381,6 @@ A globális billentyűparancs {0} sikeresen bejegyezve - - Összegyűjtetlen - Összes @@ -822,9 +813,6 @@ {0} Weboldal - - Fejlett funkció - Hozzáadás @@ -843,15 +831,6 @@ Domain stratégia - - 3. Domain vagy IP blokkolása - - - 2. Közvetlen domain vagy IP - - - 1. Proxy domain vagy IP - Előre definiált szabálykészletlista @@ -1339,13 +1318,7 @@ Rendszer sudo jelszó - The password you entered cannot be verified, so make sure you enter it correctly. If the application does not work properly due to an incorrect input, please restart the application. The password will not be stored and you will need to enter it again after each restart. - - - Kérlek, először állítsd be a sudo jelszót a Tun módban - - - Kérlek, ne futtasd ezt az alkalmazást sudo-ként + The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart. *xhttp mód @@ -1419,4 +1392,13 @@ Current connection info test URL + + Can fill in the configuration remarks, please make sure it exist and are unique + + + Sudo password has been verified successfully, please ignore the incorrect password prompts! + + + Incorrect password, please try again. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 5d42790f..78efe585 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -132,12 +132,6 @@ Downloading... - - Download speed - - - Do you want to download {0}? - Failed to convert configuration file @@ -387,9 +381,6 @@ Global hotkey {0} registered successfully - - Ungrouped - All @@ -819,9 +810,6 @@ {0} Website - - Advanced Function - Add @@ -840,15 +828,6 @@ Domain strategy - - 3. Block Domain or IP - - - 2. Direct Domain or IP - - - 1. Proxy Domain or IP - Pre-defined Rule Set List @@ -1336,13 +1315,7 @@ System sudo password - The password you entered cannot be verified, so make sure you enter it correctly. If the application does not work properly due to an incorrect input, please restart the application. The password will not be stored and you will need to enter it again after each restart. - - - Please set the sudo password in Tun mode settings first - - - Please do not run this app with sudo + The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart. *xhttp mode @@ -1416,4 +1389,13 @@ Current connection info test URL + + Can fill in the configuration remarks, please make sure it exist and are unique + + + Sudo password has been verified successfully, please ignore the incorrect password prompts! + + + Incorrect password, please try again. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index a6747246..aaf39391 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -132,12 +132,6 @@ Загрузка... - - Скорость загрузки - - - Хотите загрузить {0}? - Не удалось преобразовать файл конфигурации @@ -387,9 +381,6 @@ Глобальная горячая клавиша {0} зарегистрирована успешно - - Разгруппировано - Все серверы @@ -822,9 +813,6 @@ {0} веб-сайт - - Расширенная функция - Добавить @@ -843,15 +831,6 @@ Доменная стратегия - - 3.Заблокировать домен или IP - - - 2.Прямой домен или IP - - - 1.Прокси домен или IP - Предустановленный список наборов правил @@ -1336,13 +1315,7 @@ Пароль sudo системы - Введенный вами пароль не может быть подтвержден, поэтому убедитесь, что вы ввели его правильно. Если приложение не работает должным образом из-за неправильного ввода, то перезапустите его. Пароль не будет сохранен, и вам придется вводить его заново после каждого перезапуска - - - Сначала задайте пароль sudo в настройках TUN-режима - - - Не запускайте программу как суперпользователь + The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart. *XHTTP-режим @@ -1416,4 +1389,13 @@ URL для тестирования текущего соединения - + + Can fill in the configuration remarks, please make sure it exist and are unique + + + Sudo password has been verified successfully, please ignore the incorrect password prompts! + + + Incorrect password, please try again. + + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 42adf7c5..03a840fd 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -132,12 +132,6 @@ 下载开始... - - 下载 - - - 是否下载?{0} - 转换配置文件失败 @@ -387,9 +381,6 @@ 注册全局热键 {0} 成功 - - 未分组配置文件 - 所有 @@ -709,7 +700,7 @@ 例外:对于下列字符开头的地址,不使用代理配置文件。使用分号 (;) 分隔。 - 显示实时速度(需重启) + 显示实时速度 (需重启) 去重时保留序号较小的项 @@ -745,7 +736,7 @@ 开机启动 (可能会不成功) - 启用流量统计(需重启) + 启用流量统计 (需重启) 订阅转换网址 (可选) @@ -819,9 +810,6 @@ {0} 官网 - - 高级功能 - 添加规则集 @@ -840,15 +828,6 @@ 域名解析策略 - - 3.阻止的 Domain 或 IP - - - 2.直连的 Domain 或 IP - - - 1.代理的 Domain 或 IP - 预定义规则集列表 @@ -904,7 +883,7 @@ 仅限路由 (routeOnly) - 请勿将代理服务器用于本地(Intranet)地址 + 请勿将代理服务器用于本地 (Intranet) 地址 一键多线程测试延迟和速度 (Ctrl+E) @@ -937,7 +916,7 @@ 移至订阅分组 - 启用配置文件拖放排序(需重启) + 启用配置文件拖放排序 (需重启) 自动刷新 @@ -964,10 +943,10 @@ 仅对 tcp/http、ws 协议生效 - 当前字体(需重启) + 当前字体 (需重启) - 拷贝字体 TTF/TTC 文件到目录 guiFonts,重启设置 + 拷贝字体 TTF/TTC 文件到目录 guiFonts,重启生效 Pac 端口 = +3;Xray API 端口 = +4;mihomo API 端口 = +5; @@ -997,10 +976,10 @@ SpiderX - 启用硬件加速(需重启) + 启用硬件加速 (需重启) - 等待测试中(按 ESC 终止)... + 等待测试中 (按 ESC 终止)... 当有异常断流时请关闭 @@ -1093,7 +1072,7 @@ Reserved (2,3,4) - Address (Ipv4,Ipv6) + Address (IPv4,IPv6) 混淆密码 (obfs password) @@ -1123,10 +1102,10 @@ 使用 Xray 且非 Tun 模式启用,和分组前置代理冲突 - 启用分片(Fragment) + 启用分片 (Fragment) - 启用 sing-box(规则集文件)的缓存文件 + 启用 sing-box (规则集文件) 的缓存文件 自定义 sing-box rule-set @@ -1219,7 +1198,7 @@ Outbound 默认解析策略 - 主界面布局方向(需重启) + 主界面布局方向 (需重启) Outbound 域名解析地址 @@ -1321,7 +1300,7 @@ 请不要使用不安全的 HTTP 协议订阅地址 - 安装字体到系统中,选择或填入字体名称,重启设置 + 安装字体到系统中,选择或填入字体名称,重启生效 是否确定退出? @@ -1333,16 +1312,10 @@ 系统的 sudo 密码 - 输入的密码无法校验,所以请确保输入正确。如果因为输入错误导致无法正常运行时,请重启本应用。 密码不会存储,每次重启后都需要再次输入。 - - - 请先在 Tun 模式设置中设置 sudo 密码 - - - 请不要用 sudo 运行本程序 + 密码将调用命令行校验,如果因为校验错误导致无法正常运行时,请重启本应用。 密码不会存储,每次重启后都需要再次输入。 - *xhttp 模式 + *XHTTP 模式 XHTTP Extra 原始 JSON,格式: { XHTTPObject } @@ -1363,7 +1336,7 @@ 开启第二个本地监听端口 - socks:本地端口,socks2:第二个本地端口,socks3:局域网端口 + Socks:本地端口,Socks2:第二个本地端口,Socks3:局域网端口 主题 @@ -1396,13 +1369,13 @@ 多配置文件随机 Xray - 多配置文件负载均衡  Xray + 多配置文件负载均衡 Xray - 多配置文件最低延迟  Xray + 多配置文件最低延迟 Xray - 多配置文件最稳定  Xray + 多配置文件最稳定 Xray 多配置文件最低延迟 sing-box @@ -1413,4 +1386,13 @@ 当前连接信息测试地址 + + 可以填写配置文件别名,请确保存在并唯一 + + + sudo 密码已经验证成功,请忽略错误密码提示! + + + 密码错误,请重试。 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 61d77f9f..811e6b08 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 匯出分享链接至剪貼簿成功 + 匯出分享連結至剪貼簿成功 請先檢查設定檔設定 @@ -132,12 +132,6 @@ 下載開始... - - 下載 - - - 是否下載?{0} - 轉換設定檔失敗 @@ -295,7 +289,7 @@ 成功從剪貼簿匯入 {0} 個設定檔 - 掃描匯入分享链接成功 + 掃描匯入分享連結成功 目前延遲: {0} ms,{1} @@ -387,9 +381,6 @@ 註冊全域快速鍵 {0} 成功 - - 未分組設定檔 - 所有 @@ -478,7 +469,7 @@ 語言 (需重啟) - 從剪貼簿導入分享鏈接 (Ctrl+V) + 從剪貼簿導入分享連結 (Ctrl+V) 掃描螢幕上的二維碼 (Ctrl+S) @@ -514,7 +505,7 @@ 匯出所選設定檔完整設定 - 匯出分享链接至剪貼簿 (多選) (Ctrl+C) + 匯出分享連結至剪貼簿 (多選) (Ctrl+C) 新增自訂設定設定檔 @@ -819,9 +810,6 @@ {0} 官網 - - 進階功能 - 新增規則集 @@ -840,15 +828,6 @@ 域名解析策略 - - 3.阻止的 Domain 或 IP - - - 2.直連的 Domain 或 IP - - - 1.代理的 Domain 或 IP - 預定義規則集列表 @@ -889,7 +868,7 @@ 規則詳細說明檔案 - 支援填寫 DnsObject,JSON 格式,點擊查看説明 + 支援填寫 DnsObject,JSON 格式,點擊查看說明 普通分組此處請留空 @@ -1000,7 +979,7 @@ 啟用硬體加速 (需重啟) - 等待测试中(按 ESC 终止)... + 等待測試中(按 ESC 終止)... 當有異常斷流時請關閉 @@ -1099,10 +1078,10 @@ 混淆密碼 (obfs password) - (Domain 或 IP 或 行程名) 与 Port 与 Protocol 与 InboundTag => OutboundTag + (Domain 或 IP 或 行程名) 與 Port 與 Protocol 與 InboundTag => OutboundTag - 自动滚动到末尾 + 自動滾動到末尾 真連線測試位址 @@ -1198,7 +1177,7 @@ 全局 - 随原配置 + 隨原配置 規則 @@ -1228,7 +1207,7 @@ 自動調整列寬 - 匯出分享链接至剪貼簿 (多選) Base64 编码 + 匯出分享連結至剪貼簿 (多選) Base64 編碼 匯出所選設定檔完整設定至剪貼簿 @@ -1264,7 +1243,7 @@ WebDav 伺服器位址 - WebDav 賬戶 + WebDav 帳戶 WebDav 密碼 @@ -1333,13 +1312,7 @@ 系統的 sudo 密碼 - 輸入的密碼無法校驗,所以請確保輸入正確。如果因為輸入錯誤導致無法正常運作時,請重新啟動本應用。 密碼不會存儲,每次重啟後都需要再次輸入。 - - - 請先在 Tun 模式設定中設定 sudo 密碼 - - - 請不要用 sudo 來運行此 App + 密碼將調用命令行校驗,如果因為校驗錯誤導致無法正常運行時,請重啟本應用。密碼不會存儲,每次重啟後都需要再次輸入。 *xhttp 模式 @@ -1363,7 +1336,7 @@ 開啟第二個本機監聽埠 - socks:本地端口,socks2:第二個本地端口,socks3:區域網路端口 + socks:本地埠,socks2:第二個本地埠,socks3:區域網路埠 主題 @@ -1384,10 +1357,10 @@ 移除無效測試結果 {0} 個。 - 跳躍端口範圍 + 跳躍埠範圍 - 會覆蓋端口,多組時用逗號 (,) 隔開 + 會覆蓋埠,多組時用逗號 (,) 隔開 多設定檔產生自訂配置 (多選) @@ -1413,4 +1386,13 @@ 目前連接資訊測試地址 + + 可以填寫設定檔別名,請確保存在並唯一 + + + sudo 密碼已經驗證成功,請忽略錯誤密碼提示! + + + 密碼錯誤,請重試。 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Sample/linux_autostart_config b/v2rayN/ServiceLib/Sample/linux_autostart_config index c2f2f4e5..07eb0b27 100644 --- a/v2rayN/ServiceLib/Sample/linux_autostart_config +++ b/v2rayN/ServiceLib/Sample/linux_autostart_config @@ -7,4 +7,4 @@ X-GNOME-Autostart-enabled=true Name[en_US]=v2rayN Name=v2rayN Comment[en_US]=v2rayN -Comment=v2rayN \ No newline at end of file +Comment=v2rayN diff --git a/v2rayN/ServiceLib/Sample/proxy_set_linux_sh b/v2rayN/ServiceLib/Sample/proxy_set_linux_sh index 335cce0e..112bb39f 100644 --- a/v2rayN/ServiceLib/Sample/proxy_set_linux_sh +++ b/v2rayN/ServiceLib/Sample/proxy_set_linux_sh @@ -29,9 +29,6 @@ set_gnome_proxy() { echo "Ignored Hosts: $IGNORE_HOSTS" elif [ "$MODE" == "none" ]; then echo "GNOME: Proxy disabled." - else - echo "GNOME: Invalid mode. Use 'none' or 'manual'." - exit 1 fi } @@ -69,9 +66,6 @@ set_kde_proxy() { # Disable proxy $KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key ProxyType 0 echo "KDE: Proxy disabled." - else - echo "KDE: Invalid mode. Use 'none' or 'manual'." - exit 1 fi # Apply changes by restarting KDE's network settings @@ -84,7 +78,7 @@ detect_desktop_environment() { echo "gnome" return fi - + if [[ "$XDG_CURRENT_DESKTOP" == *"XFCE"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"XFCE"* ]]; then echo "gnome" return @@ -94,7 +88,7 @@ detect_desktop_environment() { echo "gnome" return fi - + if [[ "$XDG_CURRENT_DESKTOP" == *"UKUI"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"ukui"* ]]; then echo "gnome" return @@ -117,6 +111,15 @@ detect_desktop_environment() { return fi done + + # Fallback to GNOME method if CLI utility is available. This solves the + # proxy configuration issues on minimal installation systems, like setups + # with only window managers, that borrow some parts from big DEs. + if command -v gsettings >/dev/null 2>&1; then + echo "gnome" + return + fi + echo "unsupported" } @@ -134,6 +137,11 @@ PROXY_IP=$2 PROXY_PORT=$3 IGNORE_HOSTS=$4 +if ! [[ "$MODE" =~ ^(manual|none)$ ]]; then + echo "Invalid mode. Use 'none' or 'manual'." >&2 + exit 1 +fi + # Detect desktop environment DE=$(detect_desktop_environment) @@ -144,6 +152,6 @@ elif [ "$DE" == "kde" ]; then set_gnome_proxy "$MODE" "$PROXY_IP" "$PROXY_PORT" "$IGNORE_HOSTS" set_kde_proxy "$MODE" "$PROXY_IP" "$PROXY_PORT" "$IGNORE_HOSTS" else - echo "Unsupported desktop environment: $DE" + echo "Unsupported desktop environment: $DE" >&2 exit 1 -fi \ No newline at end of file +fi diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 27ed39b9..c9bef4f5 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -336,7 +336,7 @@ public class CoreConfigSingboxService await GenExperimental(singboxConfig); singboxConfig.outbounds.RemoveAt(0); - var tagProxy = new List(); + var proxyProfiles = new List(); foreach (var it in selecteds) { if (it.ConfigType == EConfigType.Custom) @@ -370,42 +370,18 @@ public class CoreConfigSingboxService } //outbound - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(item, outbound); - outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}"; - singboxConfig.outbounds.Insert(0, outbound); - tagProxy.Add(outbound.tag); + proxyProfiles.Add(item); } - if (tagProxy.Count <= 0) + if (proxyProfiles.Count <= 0) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } + await GenOutboundsList(proxyProfiles, singboxConfig); await GenDns(null, singboxConfig); await ConvertGeo2Ruleset(singboxConfig); - //add urltest outbound - var outUrltest = new Outbound4Sbox - { - type = "urltest", - tag = $"{Global.ProxyTag}-auto", - outbounds = tagProxy, - interrupt_exist_connections = false, - }; - singboxConfig.outbounds.Insert(0, outUrltest); - - //add selector outbound - var outSelector = new Outbound4Sbox - { - type = "selector", - tag = Global.ProxyTag, - outbounds = JsonUtils.DeepCopy(tagProxy), - interrupt_exist_connections = false, - }; - outSelector.outbounds.Insert(0, outUrltest.tag); - singboxConfig.outbounds.Insert(0, outSelector); - ret.Success = true; ret.Data = JsonUtils.Serialize(singboxConfig); return ret; @@ -730,12 +706,17 @@ public class CoreConfigSingboxService outbound.up_mbps = _config.HysteriaItem.UpMbps > 0 ? _config.HysteriaItem.UpMbps : null; outbound.down_mbps = _config.HysteriaItem.DownMbps > 0 ? _config.HysteriaItem.DownMbps : null; - if (node.Ports.IsNotEmpty()) + if (node.Ports.IsNotEmpty() && (node.Ports.Contains(':') || node.Ports.Contains('-') || node.Ports.Contains(','))) { outbound.server_port = null; outbound.server_ports = node.Ports.Split(',') - .Where(p => p.Trim().IsNotEmpty()) - .Select(p => p.Replace('-', ':')) + .Select(p => p.Trim()) + .Where(p => p.IsNotEmpty()) + .Select(p => + { + var port = p.Replace('-', ':'); + return port.Contains(':') ? port : $"{port}:{port}"; + }) .ToList(); outbound.hop_interval = _config.HysteriaItem.HopInterval > 0 ? $"{_config.HysteriaItem.HopInterval}s" : null; } @@ -775,7 +756,8 @@ public class CoreConfigSingboxService { try { - if (_config.CoreBasicItem.MuxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty()) + var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; + if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty()) { var mux = new Multiplex4Sbox() { @@ -941,29 +923,21 @@ public class CoreConfigSingboxService //Previous proxy var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + string? prevOutboundTag = null; if (prevNode is not null && prevNode.ConfigType != EConfigType.Custom) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); - prevOutbound.tag = $"{Global.ProxyTag}2"; + prevOutboundTag = $"prev-{Global.ProxyTag}"; + prevOutbound.tag = prevOutboundTag; singboxConfig.outbounds.Add(prevOutbound); - - outbound.detour = prevOutbound.tag; } + var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag); - //Next proxy - var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom) + if (nextOutbound is not null) { - var nextOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(nextNode, nextOutbound); - nextOutbound.tag = Global.ProxyTag; singboxConfig.outbounds.Insert(0, nextOutbound); - - outbound.tag = $"{Global.ProxyTag}1"; - nextOutbound.detour = outbound.tag; } } catch (Exception ex) @@ -974,6 +948,169 @@ public class CoreConfigSingboxService return 0; } + private async Task GenOutboundsList(List nodes, SingboxConfig singboxConfig) + { + try + { + // Get outbound template and initialize lists + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); + if (txtOutbound.IsNullOrEmpty()) + { + return 0; + } + + var resultOutbounds = new List(); + var prevOutbounds = new List(); // Separate list for prev outbounds + var proxyTags = new List(); // For selector and urltest outbounds + + // Cache for chain proxies to avoid duplicate generation + var nextProxyCache = new Dictionary(); + var prevProxyTags = new Dictionary(); // Map from profile name to tag + int prevIndex = 0; // Index for prev outbounds + + // Process each node + int index = 0; + foreach (var node in nodes) + { + index++; + + // Handle proxy chain + string? prevTag = null; + var currentOutbound = JsonUtils.Deserialize(txtOutbound); + var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null); + if (nextOutbound != null) + { + nextOutbound = JsonUtils.DeepCopy(nextOutbound); + } + + var subItem = await AppHandler.Instance.GetSubItem(node.Subid); + + // current proxy + await GenOutbound(node, currentOutbound); + currentOutbound.tag = $"{Global.ProxyTag}-{index}"; + proxyTags.Add(currentOutbound.tag); + + if (!node.Subid.IsNullOrEmpty()) + { + if (prevProxyTags.TryGetValue(node.Subid, out var value)) + { + prevTag = value; // maybe null + } + else + { + var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + if (prevNode is not null + && prevNode.ConfigType != EConfigType.Custom) + { + var prevOutbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(prevNode, prevOutbound); + prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}"; + prevOutbound.tag = prevTag; + prevOutbounds.Add(prevOutbound); + } + prevProxyTags[node.Subid] = prevTag; + } + + nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound); + if (!nextProxyCache.ContainsKey(node.Subid)) + { + nextProxyCache[node.Subid] = nextOutbound; + } + } + + if (nextOutbound is not null) + { + resultOutbounds.Add(nextOutbound); + } + resultOutbounds.Add(currentOutbound); + } + + // Add urltest outbound (auto selection based on latency) + if (proxyTags.Count > 0) + { + var outUrltest = new Outbound4Sbox + { + type = "urltest", + tag = $"{Global.ProxyTag}-auto", + outbounds = proxyTags, + interrupt_exist_connections = false, + }; + + // Add selector outbound (manual selection) + var outSelector = new Outbound4Sbox + { + type = "selector", + tag = Global.ProxyTag, + outbounds = JsonUtils.DeepCopy(proxyTags), + interrupt_exist_connections = false, + }; + outSelector.outbounds.Insert(0, outUrltest.tag); + + // Insert these at the beginning + resultOutbounds.Insert(0, outUrltest); + resultOutbounds.Insert(0, outSelector); + } + + // Merge results: first the selector/urltest/proxies, then other outbounds, and finally prev outbounds + resultOutbounds.AddRange(prevOutbounds); + resultOutbounds.AddRange(singboxConfig.outbounds); + singboxConfig.outbounds = resultOutbounds; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return 0; + } + + /// + /// Generates a chained outbound configuration for the given subItem and outbound. + /// The outbound's tag must be set before calling this method. + /// Returns the next proxy's outbound configuration, which may be null if no next proxy exists. + /// + /// The subscription item containing proxy chain information. + /// The current outbound configuration. Its tag must be set before calling this method. + /// The tag of the previous outbound in the chain, if any. + /// The outbound for the next proxy in the chain, if already created. If null, will be created inside. + /// + /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. + /// + private async Task GenChainOutbounds(SubItem subItem, Outbound4Sbox outbound, string? prevOutboundTag, Outbound4Sbox? nextOutbound = null) + { + try + { + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); + + if (!prevOutboundTag.IsNullOrEmpty()) + { + outbound.detour = prevOutboundTag; + } + + // Next proxy + var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); + if (nextNode is not null + && nextNode.ConfigType != EConfigType.Custom) + { + if (nextOutbound == null) + { + nextOutbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(nextNode, nextOutbound); + } + nextOutbound.tag = outbound.tag; + + outbound.tag = $"mid-{outbound.tag}"; + nextOutbound.detour = outbound.tag; + } + return nextOutbound; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return null; + } + private async Task GenRouting(SingboxConfig singboxConfig) { try @@ -1034,7 +1171,7 @@ public class CoreConfigSingboxService { if (item.Enabled) { - await GenRoutingUserRule(item, singboxConfig.route.rules); + await GenRoutingUserRule(item, singboxConfig); } } } @@ -1074,7 +1211,7 @@ public class CoreConfigSingboxService lstDirectExe = new List(directExeSet); } - private async Task GenRoutingUserRule(RulesItem item, List rules) + private async Task GenRoutingUserRule(RulesItem item, SingboxConfig singboxConfig) { try { @@ -1082,6 +1219,8 @@ public class CoreConfigSingboxService { return 0; } + item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig); + var rules = singboxConfig.route.rules; var rule = new Rule4Sbox() { @@ -1233,6 +1372,29 @@ public class CoreConfigSingboxService return true; } + private async Task GenRoutingUserRuleOutbound(string outboundTag, SingboxConfig singboxConfig) + { + if (Global.OutboundTags.Contains(outboundTag)) + { + return outboundTag; + } + + var node = await AppHandler.Instance.GetProfileItemViaRemarks(outboundTag); + if (node == null + || node.ConfigType == EConfigType.Custom) + { + return Global.ProxyTag; + } + + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); + var outbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(node, outbound); + outbound.tag = Global.ProxyTag + node.IndexId.ToString(); + singboxConfig.outbounds.Add(outbound); + + return outbound.tag; + } + private async Task GenDns(ProfileItem? node, SingboxConfig singboxConfig) { try diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index b1dc1ffd..9be1acf0 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -54,12 +54,12 @@ public class CoreConfigV2rayService await GenInbounds(v2rayConfig); - await GenRouting(v2rayConfig); - await GenOutbound(node, v2rayConfig.outbounds.First()); await GenMoreOutbounds(node, v2rayConfig); + await GenRouting(v2rayConfig); + await GenDns(node, v2rayConfig); await GenStatistic(v2rayConfig); @@ -113,7 +113,7 @@ public class CoreConfigV2rayService await GenStatistic(v2rayConfig); v2rayConfig.outbounds.RemoveAt(0); - var tagProxy = new List(); + var proxyProfiles = new List(); foreach (var it in selecteds) { if (it.ConfigType == EConfigType.Custom) @@ -151,17 +151,14 @@ public class CoreConfigV2rayService } //outbound - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(item, outbound); - outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}"; - v2rayConfig.outbounds.Insert(0, outbound); - tagProxy.Add(outbound.tag); + proxyProfiles.Add(item); } - if (tagProxy.Count <= 0) + if (proxyProfiles.Count <= 0) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } + await GenOutboundsList(proxyProfiles, v2rayConfig); //add balancers await GenBalancer(v2rayConfig, multipleLoad); @@ -559,6 +556,8 @@ public class CoreConfigV2rayService { return 0; } + rule.outboundTag = await GenRoutingUserRuleOutbound(rule.outboundTag, v2rayConfig); + if (rule.port.IsNullOrEmpty()) { rule.port = null; @@ -630,11 +629,36 @@ public class CoreConfigV2rayService return await Task.FromResult(0); } + private async Task GenRoutingUserRuleOutbound(string outboundTag, V2rayConfig v2rayConfig) + { + if (Global.OutboundTags.Contains(outboundTag)) + { + return outboundTag; + } + + var node = await AppHandler.Instance.GetProfileItemViaRemarks(outboundTag); + if (node == null + || node.ConfigType == EConfigType.Custom + || node.ConfigType == EConfigType.Hysteria2 + || node.ConfigType == EConfigType.TUIC) + { + return Global.ProxyTag; + } + + var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); + var outbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(node, outbound); + outbound.tag = Global.ProxyTag + node.IndexId.ToString(); + v2rayConfig.outbounds.Add(outbound); + + return outbound.tag; + } + private async Task GenOutbound(ProfileItem node, Outbounds4Ray outbound) { try { - var muxEnabled = _config.CoreBasicItem.MuxEnabled; + var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; switch (node.ConfigType) { case EConfigType.VMess: @@ -662,7 +686,7 @@ public class CoreConfigV2rayService { usersItem = vnextItem.users.First(); } - + usersItem.id = node.Id; usersItem.alterId = node.AlterId; usersItem.email = Global.UserEMail; @@ -1321,6 +1345,7 @@ public class CoreConfigV2rayService //Previous proxy var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + string? prevOutboundTag = null; if (prevNode is not null && prevNode.ConfigType != EConfigType.Custom && prevNode.ConfigType != EConfigType.Hysteria2 @@ -1328,32 +1353,15 @@ public class CoreConfigV2rayService { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); - prevOutbound.tag = $"{Global.ProxyTag}2"; + prevOutboundTag = $"prev-{Global.ProxyTag}"; + prevOutbound.tag = prevOutboundTag; v2rayConfig.outbounds.Add(prevOutbound); - - outbound.streamSettings.sockopt = new() - { - dialerProxy = prevOutbound.tag - }; } + var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag); - //Next proxy - var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom - && nextNode.ConfigType != EConfigType.Hysteria2 - && nextNode.ConfigType != EConfigType.TUIC) + if (nextOutbound is not null) { - var nextOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(nextNode, nextOutbound); - nextOutbound.tag = Global.ProxyTag; v2rayConfig.outbounds.Insert(0, nextOutbound); - - outbound.tag = $"{Global.ProxyTag}1"; - nextOutbound.streamSettings.sockopt = new() - { - dialerProxy = outbound.tag - }; } } catch (Exception ex) @@ -1364,18 +1372,179 @@ public class CoreConfigV2rayService return 0; } + private async Task GenOutboundsList(List nodes, V2rayConfig v2rayConfig) + { + try + { + // Get template and initialize list + var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); + if (txtOutbound.IsNullOrEmpty()) + { + return 0; + } + + var resultOutbounds = new List(); + var prevOutbounds = new List(); // Separate list for prev outbounds and fragment + + // Cache for chain proxies to avoid duplicate generation + var nextProxyCache = new Dictionary(); + var prevProxyTags = new Dictionary(); // Map from profile name to tag + int prevIndex = 0; // Index for prev outbounds + + // Process nodes + int index = 0; + foreach (var node in nodes) + { + index++; + + // Handle proxy chain + string? prevTag = null; + var currentOutbound = JsonUtils.Deserialize(txtOutbound); + var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null); + if (nextOutbound != null) + { + nextOutbound = JsonUtils.DeepCopy(nextOutbound); + } + + var subItem = await AppHandler.Instance.GetSubItem(node.Subid); + + // current proxy + await GenOutbound(node, currentOutbound); + currentOutbound.tag = $"{Global.ProxyTag}-{index}"; + + if (!node.Subid.IsNullOrEmpty()) + { + if (prevProxyTags.TryGetValue(node.Subid, out var value)) + { + prevTag = value; // maybe null + } + else + { + var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + if (prevNode is not null + && prevNode.ConfigType != EConfigType.Custom + && prevNode.ConfigType != EConfigType.Hysteria2 + && prevNode.ConfigType != EConfigType.TUIC) + { + var prevOutbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(prevNode, prevOutbound); + prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}"; + prevOutbound.tag = prevTag; + prevOutbounds.Add(prevOutbound); + } + prevProxyTags[node.Subid] = prevTag; + } + + nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound); + if (!nextProxyCache.ContainsKey(node.Subid)) + { + nextProxyCache[node.Subid] = nextOutbound; + } + } + + if (nextOutbound is not null) + { + resultOutbounds.Add(nextOutbound); + } + resultOutbounds.Add(currentOutbound); + } + + // Merge results: first the main chain outbounds, then other outbounds, and finally utility outbounds + resultOutbounds.AddRange(prevOutbounds); + resultOutbounds.AddRange(v2rayConfig.outbounds); + v2rayConfig.outbounds = resultOutbounds; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return 0; + } + + /// + /// Generates a chained outbound configuration for the given subItem and outbound. + /// The outbound's tag must be set before calling this method. + /// Returns the next proxy's outbound configuration, which may be null if no next proxy exists. + /// + /// The subscription item containing proxy chain information. + /// The current outbound configuration. Its tag must be set before calling this method. + /// The tag of the previous outbound in the chain, if any. + /// The outbound for the next proxy in the chain, if already created. If null, will be created inside. + /// + /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. + /// + private async Task GenChainOutbounds(SubItem subItem, Outbounds4Ray outbound, string? prevOutboundTag, Outbounds4Ray? nextOutbound = null) + { + try + { + var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); + + if (!prevOutboundTag.IsNullOrEmpty()) + { + outbound.streamSettings.sockopt = new() + { + dialerProxy = prevOutboundTag + }; + } + + // Next proxy + var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); + if (nextNode is not null + && nextNode.ConfigType != EConfigType.Custom + && nextNode.ConfigType != EConfigType.Hysteria2 + && nextNode.ConfigType != EConfigType.TUIC) + { + if (nextOutbound == null) + { + nextOutbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(nextNode, nextOutbound); + } + nextOutbound.tag = outbound.tag; + + outbound.tag = $"mid-{outbound.tag}"; + nextOutbound.streamSettings.sockopt = new() + { + dialerProxy = outbound.tag + }; + } + return nextOutbound; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return null; + } + private async Task GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad) { - if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.LeastPing) + if (multipleLoad == EMultipleLoad.LeastPing) { var observatory = new Observatory4Ray { subjectSelector = [Global.ProxyTag], probeUrl = AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl, - probeInterval = "3m" + probeInterval = "3m", + enableConcurrency = true, }; v2rayConfig.observatory = observatory; } + else if (multipleLoad == EMultipleLoad.LeastLoad) + { + var burstObservatory = new BurstObservatory4Ray + { + subjectSelector = [Global.ProxyTag], + pingConfig = new() + { + destination = AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl, + interval = "5m", + timeout = "30s", + sampling = 2, + } + }; + v2rayConfig.burstObservatory = burstObservatory; + } var strategyType = multipleLoad switch { EMultipleLoad.Random => "random", diff --git a/v2rayN/ServiceLib/Services/SpeedtestService.cs b/v2rayN/ServiceLib/Services/SpeedtestService.cs index f4cd64a2..998cedcc 100644 --- a/v2rayN/ServiceLib/Services/SpeedtestService.cs +++ b/v2rayN/ServiceLib/Services/SpeedtestService.cs @@ -196,6 +196,7 @@ public class SpeedtestService { return false; } + await Task.Delay(1000); var downloadHandle = new DownloadService(); @@ -255,9 +256,13 @@ public class SpeedtestService try { pid = await CoreHandler.Instance.LoadCoreConfigSpeedtest(it); - if (pid > 0) + if (pid < 0) { - await Task.Delay(500); + UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore); + } + else + { + await Task.Delay(1000); var delay = await DoRealPing(downloadHandle, it); if (blSpeedTest) { @@ -271,10 +276,6 @@ public class SpeedtestService } } } - else - { - UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore); - } } catch (Exception ex) { diff --git a/v2rayN/ServiceLib/Services/UpdateService.cs b/v2rayN/ServiceLib/Services/UpdateService.cs index 636f3616..831ef2c3 100644 --- a/v2rayN/ServiceLib/Services/UpdateService.cs +++ b/v2rayN/ServiceLib/Services/UpdateService.cs @@ -485,6 +485,12 @@ public class UpdateService private async Task UpdateOtherFiles(Config config, Action updateFunc) { + //If it is not in China area, no update is required + if (config.ConstItem.GeoSourceUrl.IsNotEmpty()) + { + return; + } + _updateFunc = updateFunc; foreach (var url in Global.OtherGeoUrls) @@ -530,6 +536,11 @@ public class UpdateService } } + //append dns items TODO + geoSiteFiles.Add("cn"); + geoSiteFiles.Add("geolocation-cn"); + geoSiteFiles.Add("category-ads-all"); + var path = Utils.GetBinPath("srss"); if (!Directory.Exists(path)) { diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index 5edab5d4..7bae19be 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -552,6 +552,7 @@ public class MainWindowViewModel : MyReactiveObject { await LoadCore(); await SysProxyHandler.UpdateSysProxy(_config, false); + await Task.Delay(1000); }); Locator.Current.GetService()?.TestServerAvailability(); diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index a955a9aa..f312b890 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -304,7 +304,7 @@ public class ProfilesViewModel : MyReactiveObject { item.SpeedVal = result.Speed ?? string.Empty; } - _profileItems.Replace(item, JsonUtils.DeepCopy(item)); + //_profileItems.Replace(item, JsonUtils.DeepCopy(item)); } public void UpdateStatistics(ServerSpeedItem update) @@ -319,16 +319,16 @@ public class ProfilesViewModel : MyReactiveObject item.TotalDown = Utils.HumanFy(update.TotalDown); item.TotalUp = Utils.HumanFy(update.TotalUp); - if (SelectedProfile?.IndexId == item.IndexId) - { - var temp = JsonUtils.DeepCopy(item); - _profileItems.Replace(item, temp); - SelectedProfile = temp; - } - else - { - _profileItems.Replace(item, JsonUtils.DeepCopy(item)); - } + //if (SelectedProfile?.IndexId == item.IndexId) + //{ + // var temp = JsonUtils.DeepCopy(item); + // _profileItems.Replace(item, temp); + // SelectedProfile = temp; + //} + //else + //{ + // _profileItems.Replace(item, JsonUtils.DeepCopy(item)); + //} } } catch diff --git a/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs index dc2d7eda..73b18fb7 100644 --- a/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs @@ -91,11 +91,9 @@ public class RoutingSettingViewModel : MyReactiveObject var routings = await AppHandler.Instance.RoutingItems(); foreach (var item in routings) { - var def = item.Id == _config.RoutingBasicItem.RoutingIndexId; - var it = new RoutingItemModel() { - IsActive = def, + IsActive = item.IsActive, RuleNum = item.RuleNum, Id = item.Id, Remarks = item.Remarks, diff --git a/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs b/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs index 82b48aff..5c2acab6 100644 --- a/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs @@ -34,6 +34,8 @@ public class StatusBarViewModel : MyReactiveObject public ReactiveCommand SubUpdateViaProxyCmd { get; } public ReactiveCommand CopyProxyCmdToClipboardCmd { get; } public ReactiveCommand NotifyLeftClickCmd { get; } + public ReactiveCommand ShowWindowCmd { get; } + public ReactiveCommand HideWindowCmd { get; } #region System Proxy @@ -91,6 +93,9 @@ public class StatusBarViewModel : MyReactiveObject [Reactive] public bool EnableTun { get; set; } + [Reactive] + public bool BlIsNonWindows { get; set; } + #endregion UI public StatusBarViewModel(Func>? updateView) @@ -100,6 +105,7 @@ public class StatusBarViewModel : MyReactiveObject SelectedServer = new(); RunningServerToolTipText = "-"; BlSystemProxyPacVisible = Utils.IsWindows(); + BlIsNonWindows = Utils.IsNonWindows(); if (_config.TunModeItem.EnableTun && AllowEnableTun()) { @@ -143,11 +149,21 @@ public class StatusBarViewModel : MyReactiveObject Locator.Current.GetService()?.ShowHideWindow(null); await Task.CompletedTask; }); + ShowWindowCmd = ReactiveCommand.CreateFromTask(async () => + { + Locator.Current.GetService()?.ShowHideWindow(true); + await Task.CompletedTask; + }); + HideWindowCmd = ReactiveCommand.CreateFromTask(async () => + { + Locator.Current.GetService()?.ShowHideWindow(false); + await Task.CompletedTask; + }); AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () => - { - await AddServerViaClipboard(); - }); + { + await AddServerViaClipboard(); + }); AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerViaScan(); @@ -372,7 +388,7 @@ public class StatusBarViewModel : MyReactiveObject foreach (var item in routings) { _routingItems.Add(item); - if (item.Id == _config.RoutingBasicItem.RoutingIndexId) + if (item.IsActive) { SelectedRouting = item; } @@ -396,10 +412,6 @@ public class StatusBarViewModel : MyReactiveObject { return; } - if (_config.RoutingBasicItem.RoutingIndexId == item.Id) - { - return; - } if (await ConfigHandler.SetDefaultRouting(_config, item) == 0) { @@ -424,30 +436,34 @@ public class StatusBarViewModel : MyReactiveObject private async Task DoEnableTun(bool c) { - if (_config.TunModeItem.EnableTun != EnableTun) + if (_config.TunModeItem.EnableTun == EnableTun) + { + return; + } + + _config.TunModeItem.EnableTun = EnableTun; + + if (EnableTun && AllowEnableTun() == false) { - _config.TunModeItem.EnableTun = EnableTun; // When running as a non-administrator, reboot to administrator mode - if (EnableTun && AllowEnableTun() == false) + if (Utils.IsWindows()) { - if (Utils.IsWindows()) + _config.TunModeItem.EnableTun = false; + Locator.Current.GetService()?.RebootAsAdmin(); + return; + } + else + { + bool? passwordResult = await _updateView?.Invoke(EViewAction.PasswordInput, null); + if (passwordResult == false) { _config.TunModeItem.EnableTun = false; - Locator.Current.GetService()?.RebootAsAdmin(); return; } - else - { - if (await _updateView?.Invoke(EViewAction.PasswordInput, null) == false) - { - _config.TunModeItem.EnableTun = false; - return; - } - } } - await ConfigHandler.SaveConfig(_config); - Locator.Current.GetService()?.Reload(); } + await ConfigHandler.SaveConfig(_config); + Locator.Current.GetService()?.Reload(); } private bool AllowEnableTun() @@ -500,8 +516,16 @@ public class StatusBarViewModel : MyReactiveObject { try { - SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, Global.ProxyTag, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown)); - SpeedDirectDisplay = string.Format(ResUI.SpeedDisplayText, Global.DirectTag, Utils.HumanFy(update.DirectUp), Utils.HumanFy(update.DirectDown)); + if (_config.IsRunningCore(ECoreType.sing_box)) + { + SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, EInboundProtocol.mixed, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown)); + SpeedDirectDisplay = string.Empty; + } + else + { + SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, Global.ProxyTag, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown)); + SpeedDirectDisplay = string.Format(ResUI.SpeedDisplayText, Global.DirectTag, Utils.HumanFy(update.DirectUp), Utils.HumanFy(update.DirectDown)); + } } catch { diff --git a/v2rayN/v2rayN.Desktop/App.axaml b/v2rayN/v2rayN.Desktop/App.axaml index fb487d1d..8b61125f 100644 --- a/v2rayN/v2rayN.Desktop/App.axaml +++ b/v2rayN/v2rayN.Desktop/App.axaml @@ -6,8 +6,8 @@ xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" xmlns:semi="https://irihi.tech/semi" xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib" - x:DataType="vms:StatusBarViewModel" Name="v2rayN" + x:DataType="vms:StatusBarViewModel" RequestedThemeVariant="Default"> @@ -32,6 +32,16 @@ ToolTipText="{Binding RunningServerToolTipText}"> + + + + - - - - diff --git a/v2rayN/v2rayN.Desktop/App.axaml.cs b/v2rayN/v2rayN.Desktop/App.axaml.cs index 7626edfd..ebd5c29a 100644 --- a/v2rayN/v2rayN.Desktop/App.axaml.cs +++ b/v2rayN/v2rayN.Desktop/App.axaml.cs @@ -11,11 +11,6 @@ public partial class App : Application { public override void Initialize() { - if (!AppHandler.Instance.InitApp()) - { - Environment.Exit(0); - return; - } AvaloniaXamlLoader.Load(this); AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; diff --git a/v2rayN/v2rayN.Desktop/Base/WindowBase.cs b/v2rayN/v2rayN.Desktop/Base/WindowBase.cs new file mode 100644 index 00000000..a84fc86c --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Base/WindowBase.cs @@ -0,0 +1,52 @@ +using Avalonia; +using Avalonia.Interactivity; +using Avalonia.ReactiveUI; + +namespace v2rayN.Desktop.Base; + +public class WindowBase : ReactiveWindow where TViewModel : class +{ + public WindowBase() + { + Loaded += OnLoaded; + } + + private void ReactiveWindowBase_Closed(object? sender, EventArgs e) + { + throw new NotImplementedException(); + } + + protected virtual void OnLoaded(object? sender, RoutedEventArgs e) + { + try + { + var sizeItem = ConfigHandler.GetWindowSizeItem(AppHandler.Instance.Config, GetType().Name); + if (sizeItem == null) + { + return; + } + + Width = sizeItem.Width; + Height = sizeItem.Height; + + var workingArea = (Screens.ScreenFromWindow(this) ?? Screens.Primary).WorkingArea; + var scaling = (Utils.IsOSX() ? null : VisualRoot?.RenderScaling) ?? 1.0; + + var x = workingArea.X + ((workingArea.Width - (Width * scaling)) / 2); + var y = workingArea.Y + ((workingArea.Height - (Height * scaling)) / 2); + + Position = new PixelPoint((int)x, (int)y); + } + catch { } + } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + try + { + ConfigHandler.SaveWindowSizeItem(AppHandler.Instance.Config, GetType().Name, Width, Height); + } + catch { } + } +} diff --git a/v2rayN/v2rayN.Desktop/Program.cs b/v2rayN/v2rayN.Desktop/Program.cs index 15efc023..52fdbf92 100644 --- a/v2rayN/v2rayN.Desktop/Program.cs +++ b/v2rayN/v2rayN.Desktop/Program.cs @@ -14,13 +14,17 @@ internal class Program [STAThread] public static void Main(string[] args) { - OnStartup(args); + if (OnStartup(args) == false) + { + Environment.Exit(0); + return; + } BuildAvaloniaApp() .StartWithClassicDesktopLifetime(args); } - private static void OnStartup(string[]? Args) + private static bool OnStartup(string[]? Args) { if (Utils.IsWindows()) { @@ -30,8 +34,7 @@ internal class Program if (!rebootas && !bCreatedNew) { ProgramStarted.Set(); - Environment.Exit(0); - return; + return false; } } else @@ -39,19 +42,26 @@ internal class Program _ = new Mutex(true, "v2rayN", out var bOnlyOneInstance); if (!bOnlyOneInstance) { - Environment.Exit(0); - return; + return false; } } + + if (!AppHandler.Instance.InitApp()) + { + return false; + } + return true; } // Avalonia configuration, don't remove; also used by visual designer. public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() - .UsePlatformDetect() - //.WithInterFont() - .WithFontByDefault() - .LogToTrace() - .UseReactiveUI() - .With(new MacOSPlatformOptions { ShowInDock = false }); + { + return AppBuilder.Configure() + .UsePlatformDetect() + //.WithInterFont() + .WithFontByDefault() + .LogToTrace() + .UseReactiveUI() + .With(new MacOSPlatformOptions { ShowInDock = AppHandler.Instance.Config.UiItem.MacOSShowInDock }); + } } diff --git a/v2rayN/v2rayN.Desktop/Views/AddServer2Window.axaml.cs b/v2rayN/v2rayN.Desktop/Views/AddServer2Window.axaml.cs index 79b6ce7d..4d378bf9 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServer2Window.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/AddServer2Window.axaml.cs @@ -1,12 +1,12 @@ using System.Reactive.Disposables; using Avalonia.Interactivity; -using Avalonia.ReactiveUI; using ReactiveUI; +using v2rayN.Desktop.Base; using v2rayN.Desktop.Common; namespace v2rayN.Desktop.Views; -public partial class AddServer2Window : ReactiveWindow +public partial class AddServer2Window : WindowBase { public AddServer2Window() { @@ -21,13 +21,7 @@ public partial class AddServer2Window : ReactiveWindow btnCancel.Click += (s, e) => this.Close(); ViewModel = new AddServer2ViewModel(profileItem, UpdateViewHandler); - foreach (ECoreType it in Enum.GetValues(typeof(ECoreType))) - { - if (it == ECoreType.v2rayN) - continue; - cmbCoreType.Items.Add(it.ToString()); - } - cmbCoreType.Items.Add(string.Empty); + cmbCoreType.ItemsSource = Utils.GetEnumNames().Where(t => t != ECoreType.v2rayN.ToString()).ToList().AppendEmpty(); this.WhenActivated(disposables => { diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index ef1885af..ad30f985 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -105,7 +105,7 @@ Grid.Row="2" ColumnDefinitions="180,Auto,Auto" IsVisible="False" - RowDefinitions="Auto,Auto,Auto,Auto"> + RowDefinitions="Auto,Auto,Auto,Auto,Auto"> + + + + RowDefinitions="Auto,Auto,Auto,Auto,Auto"> + + + + RowDefinitions="Auto,Auto,Auto,Auto,Auto"> + + + + RowDefinitions="Auto,Auto,Auto,Auto,Auto"> + + + +public partial class AddServerWindow : WindowBase { public AddServerWindow() { @@ -26,41 +26,22 @@ public partial class AddServerWindow : ReactiveWindow ViewModel = new AddServerViewModel(profileItem, UpdateViewHandler); - Global.CoreTypes.ForEach(it => - { - cmbCoreType.Items.Add(it); - }); - cmbCoreType.Items.Add(string.Empty); + cmbCoreType.ItemsSource = Global.CoreTypes.AppendEmpty(); + cmbNetwork.ItemsSource = Global.Networks; + cmbFingerprint.ItemsSource = Global.Fingerprints; + cmbFingerprint2.ItemsSource = Global.Fingerprints; + cmbAllowInsecure.ItemsSource = Global.AllowInsecure; + cmbAlpn.ItemsSource = Global.Alpns; - cmbStreamSecurity.Items.Add(string.Empty); - cmbStreamSecurity.Items.Add(Global.StreamSecurity); - - Global.Networks.ForEach(it => - { - cmbNetwork.Items.Add(it); - }); - Global.Fingerprints.ForEach(it => - { - cmbFingerprint.Items.Add(it); - cmbFingerprint2.Items.Add(it); - }); - Global.AllowInsecure.ForEach(it => - { - cmbAllowInsecure.Items.Add(it); - }); - Global.Alpns.ForEach(it => - { - cmbAlpn.Items.Add(it); - }); + var lstStreamSecurity = new List(); + lstStreamSecurity.Add(string.Empty); + lstStreamSecurity.Add(Global.StreamSecurity); switch (profileItem.ConfigType) { case EConfigType.VMess: gridVMess.IsVisible = true; - Global.VmessSecurities.ForEach(it => - { - cmbSecurity.Items.Add(it); - }); + cmbSecurity.ItemsSource = Global.VmessSecurities; if (profileItem.Security.IsNullOrEmpty()) { profileItem.Security = Global.DefaultSecurity; @@ -69,10 +50,7 @@ public partial class AddServerWindow : ReactiveWindow case EConfigType.Shadowsocks: gridSs.IsVisible = true; - AppHandler.Instance.GetShadowsocksSecurities(profileItem).ForEach(it => - { - cmbSecurity3.Items.Add(it); - }); + cmbSecurity3.ItemsSource = AppHandler.Instance.GetShadowsocksSecurities(profileItem); break; case EConfigType.SOCKS: @@ -82,11 +60,8 @@ public partial class AddServerWindow : ReactiveWindow case EConfigType.VLESS: gridVLESS.IsVisible = true; - cmbStreamSecurity.Items.Add(Global.StreamSecurityReality); - Global.Flows.ForEach(it => - { - cmbFlow5.Items.Add(it); - }); + lstStreamSecurity.Add(Global.StreamSecurityReality); + cmbFlow5.ItemsSource = Global.Flows; if (profileItem.Security.IsNullOrEmpty()) { profileItem.Security = Global.None; @@ -95,11 +70,8 @@ public partial class AddServerWindow : ReactiveWindow case EConfigType.Trojan: gridTrojan.IsVisible = true; - cmbStreamSecurity.Items.Add(Global.StreamSecurityReality); - Global.Flows.ForEach(it => - { - cmbFlow6.Items.Add(it); - }); + lstStreamSecurity.Add(Global.StreamSecurityReality); + cmbFlow6.ItemsSource = Global.Flows; break; case EConfigType.Hysteria2: @@ -119,10 +91,7 @@ public partial class AddServerWindow : ReactiveWindow cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; - Global.TuicCongestionControls.ForEach(it => - { - cmbHeaderType8.Items.Add(it); - }); + cmbHeaderType8.ItemsSource = Global.TuicCongestionControls; break; case EConfigType.WireGuard: @@ -134,6 +103,7 @@ public partial class AddServerWindow : ReactiveWindow break; } + cmbStreamSecurity.ItemsSource = lstStreamSecurity; gridTlsMore.IsVisible = false; @@ -150,11 +120,13 @@ public partial class AddServerWindow : ReactiveWindow this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables); break; case EConfigType.Shadowsocks: this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId3.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables); break; case EConfigType.SOCKS: @@ -167,11 +139,13 @@ public partial class AddServerWindow : ReactiveWindow this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId5.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity5.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables); break; case EConfigType.Trojan: this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId6.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables); break; case EConfigType.Hysteria2: @@ -268,44 +242,41 @@ public partial class AddServerWindow : ReactiveWindow private void SetHeaderType() { - cmbHeaderType.Items.Clear(); + var lstHeaderType = new List(); var network = cmbNetwork.SelectedItem.ToString(); if (network.IsNullOrEmpty()) { - cmbHeaderType.Items.Add(Global.None); + lstHeaderType.Add(Global.None); + cmbHeaderType.ItemsSource = lstHeaderType; + cmbHeaderType.SelectedIndex = 0; return; } if (network == nameof(ETransport.tcp)) { - cmbHeaderType.Items.Add(Global.None); - cmbHeaderType.Items.Add(Global.TcpHeaderHttp); + lstHeaderType.Add(Global.None); + lstHeaderType.Add(Global.TcpHeaderHttp); } else if (network is nameof(ETransport.kcp) or nameof(ETransport.quic)) { - cmbHeaderType.Items.Add(Global.None); - Global.KcpHeaderTypes.ForEach(it => - { - cmbHeaderType.Items.Add(it); - }); + lstHeaderType.Add(Global.None); + lstHeaderType.AddRange(Global.KcpHeaderTypes); } else if (network is nameof(ETransport.xhttp)) { - Global.XhttpMode.ForEach(it => - { - cmbHeaderType.Items.Add(it); - }); + lstHeaderType.AddRange(Global.XhttpMode); } else if (network == nameof(ETransport.grpc)) { - cmbHeaderType.Items.Add(Global.GrpcGunMode); - cmbHeaderType.Items.Add(Global.GrpcMultiMode); + lstHeaderType.Add(Global.GrpcGunMode); + lstHeaderType.Add(Global.GrpcMultiMode); } else { - cmbHeaderType.Items.Add(Global.None); + lstHeaderType.Add(Global.None); } + cmbHeaderType.ItemsSource = lstHeaderType; cmbHeaderType.SelectedIndex = 0; } diff --git a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml index 96c72c47..efdf4eda 100644 --- a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml @@ -34,7 +34,7 @@ IsCancel="True" /> - + @@ -90,16 +90,18 @@ - + - + MinLines="10" + TextWrapping="Wrap" /> + @@ -144,31 +146,34 @@ - + Header="HTTP/SOCKS"> + + - + Header="{x:Static resx:ResUI.TbSettingsTunMode}"> + + + diff --git a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml.cs index c687e153..8f1a0f5a 100644 --- a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml.cs @@ -1,11 +1,11 @@ using System.Reactive.Disposables; using Avalonia.Interactivity; -using Avalonia.ReactiveUI; using ReactiveUI; +using v2rayN.Desktop.Base; namespace v2rayN.Desktop.Views; -public partial class DNSSettingWindow : ReactiveWindow +public partial class DNSSettingWindow : WindowBase { private static Config _config; @@ -17,22 +17,10 @@ public partial class DNSSettingWindow : ReactiveWindow btnCancel.Click += (s, e) => this.Close(); ViewModel = new DNSSettingViewModel(UpdateViewHandler); - Global.DomainStrategy4Freedoms.ForEach(it => - { - cmbdomainStrategy4Freedom.Items.Add(it); - }); - Global.SingboxDomainStrategy4Out.ForEach(it => - { - cmbdomainStrategy4Out.Items.Add(it); - }); - Global.DomainDNSAddress.ForEach(it => - { - cmbdomainDNSAddress.Items.Add(it); - }); - Global.SingboxDomainDNSAddress.ForEach(it => - { - cmbdomainDNSAddress2.Items.Add(it); - }); + cmbdomainStrategy4Freedom.ItemsSource = Global.DomainStrategy4Freedoms; + cmbdomainStrategy4Out.ItemsSource = Global.SingboxDomainStrategy4Out; + cmbdomainDNSAddress.ItemsSource = Global.DomainDNSAddress; + cmbdomainDNSAddress2.ItemsSource = Global.SingboxDomainDNSAddress; this.WhenActivated(disposables => { diff --git a/v2rayN/v2rayN.Desktop/Views/GlobalHotkeySettingWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/GlobalHotkeySettingWindow.axaml.cs index f73a49d9..a9f79765 100644 --- a/v2rayN/v2rayN.Desktop/Views/GlobalHotkeySettingWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/GlobalHotkeySettingWindow.axaml.cs @@ -3,13 +3,13 @@ using System.Text; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.ReactiveUI; using ReactiveUI; +using v2rayN.Desktop.Base; using v2rayN.Desktop.Handler; namespace v2rayN.Desktop.Views; -public partial class GlobalHotkeySettingWindow : ReactiveWindow +public partial class GlobalHotkeySettingWindow : WindowBase { private readonly List _textBoxKeyEventItem = new(); diff --git a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs index 76ea24d9..8bda5040 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs @@ -5,18 +5,18 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Notifications; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.ReactiveUI; using Avalonia.Threading; using DialogHostAvalonia; using MsBox.Avalonia.Enums; using ReactiveUI; using Splat; +using v2rayN.Desktop.Base; using v2rayN.Desktop.Common; using v2rayN.Desktop.Handler; namespace v2rayN.Desktop.Views; -public partial class MainWindow : ReactiveWindow +public partial class MainWindow : WindowBase { private static Config _config; private WindowNotificationManager? _manager; @@ -29,7 +29,7 @@ public partial class MainWindow : ReactiveWindow InitializeComponent(); _config = AppHandler.Instance.Config; - _manager = new WindowNotificationManager(TopLevel.GetTopLevel(this)) { MaxItems = 3, Position = NotificationPosition.BottomRight }; + _manager = new WindowNotificationManager(TopLevel.GetTopLevel(this)) { MaxItems = 3, Position = NotificationPosition.TopRight }; this.KeyDown += MainWindow_KeyDown; menuSettingsSetUWP.Click += menuSettingsSetUWP_Click; @@ -135,26 +135,23 @@ public partial class MainWindow : ReactiveWindow } }); - this.Title = $"{Utils.GetVersion()}"; if (Utils.IsWindows()) { + this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}"; + ThreadPool.RegisterWaitForSingleObject(Program.ProgramStarted, OnProgramStarted, null, -1, false); HotkeyHandler.Instance.Init(_config, OnHotkeyHandler); } else { - if (Utils.IsAdministrator()) - { - this.Title = $"{Utils.GetVersion()} - {ResUI.TbSettingsLinuxSudoPasswordNotSudoRunApp}"; - NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.TbSettingsLinuxSudoPasswordNotSudoRunApp); - } + this.Title = $"{Utils.GetVersion()}"; + menuRebootAsAdmin.IsVisible = false; menuSettingsSetUWP.IsVisible = false; menuGlobalHotkeySetting.IsVisible = false; } menuAddServerViaScan.IsVisible = false; - RestoreUI(); AddHelpMenuItem(); MessageBus.Current.Listen(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI); } @@ -436,14 +433,14 @@ public partial class MainWindow : ReactiveWindow _config.UiItem.ShowInTaskbar = bl; } + protected override void OnLoaded(object? sender, RoutedEventArgs e) + { + base.OnLoaded(sender, e); + RestoreUI(); + } + private void RestoreUI() { - if (_config.UiItem.MainWidth > 0 && _config.UiItem.MainHeight > 0) - { - Width = _config.UiItem.MainWidth; - Height = _config.UiItem.MainHeight; - } - if (_config.UiItem.MainGirdHeight1 > 0 && _config.UiItem.MainGirdHeight2 > 0) { if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal) @@ -461,18 +458,15 @@ public partial class MainWindow : ReactiveWindow private void StorageUI(string? n = null) { - _config.UiItem.MainWidth = this.Width; - _config.UiItem.MainHeight = this.Height; + ConfigHandler.SaveWindowSizeItem(_config, GetType().Name, Width, Height); if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal) { - _config.UiItem.MainGirdHeight1 = Math.Ceiling(gridMain.ColumnDefinitions[0].ActualWidth + 0.1); - _config.UiItem.MainGirdHeight2 = Math.Ceiling(gridMain.ColumnDefinitions[2].ActualWidth + 0.1); + ConfigHandler.SaveMainGirdHeight(_config, gridMain.ColumnDefinitions[0].ActualWidth, gridMain.ColumnDefinitions[2].ActualWidth); } else if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Vertical) { - _config.UiItem.MainGirdHeight1 = Math.Ceiling(gridMain1.RowDefinitions[0].ActualHeight + 0.1); - _config.UiItem.MainGirdHeight2 = Math.Ceiling(gridMain1.RowDefinitions[2].ActualHeight + 0.1); + ConfigHandler.SaveMainGirdHeight(_config, gridMain1.RowDefinitions[0].ActualHeight, gridMain1.RowDefinitions[2].ActualHeight); } } diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml index 004f68a4..a75ed6fc 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -575,9 +575,9 @@ Grid.Column="0" Margin="{StaticResource Margin4}" VerticalAlignment="Center" - Text="{x:Static resx:ResUI.TbSettingsSubConvert}" /> - + + + + - - - diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs index fd319d92..010488e5 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs @@ -1,11 +1,11 @@ using System.Reactive.Disposables; using Avalonia.Controls; -using Avalonia.ReactiveUI; using ReactiveUI; +using v2rayN.Desktop.Base; namespace v2rayN.Desktop.Views; -public partial class OptionSettingWindow : ReactiveWindow +public partial class OptionSettingWindow : WindowBase { private static Config _config; @@ -19,87 +19,39 @@ public partial class OptionSettingWindow : ReactiveWindow - { - clbdestOverride.Items.Add(it); - }); + clbdestOverride.ItemsSource = Global.destOverrideProtocols; _config.Inbound.First().DestOverride?.ForEach(it => { clbdestOverride.SelectedItems.Add(it); }); - Global.IEProxyProtocols.ForEach(it => - { - cmbsystemProxyAdvancedProtocol.Items.Add(it); - }); - Global.LogLevels.ForEach(it => - { - cmbloglevel.Items.Add(it); - }); - Global.Fingerprints.ForEach(it => - { - cmbdefFingerprint.Items.Add(it); - }); - Global.UserAgent.ForEach(it => - { - cmbdefUserAgent.Items.Add(it); - }); - Global.SingboxMuxs.ForEach(it => - { - cmbmux4SboxProtocol.Items.Add(it); - }); - Global.TunMtus.ForEach(it => - { - cmbMtu.Items.Add(it); - }); - Global.TunStacks.ForEach(it => - { - cmbStack.Items.Add(it); - }); - Global.CoreTypes.ForEach(it => - { - cmbCoreType1.Items.Add(it); - cmbCoreType2.Items.Add(it); - cmbCoreType3.Items.Add(it); - cmbCoreType4.Items.Add(it); - cmbCoreType5.Items.Add(it); - cmbCoreType6.Items.Add(it); - cmbCoreType9.Items.Add(it); - }); + cmbsystemProxyAdvancedProtocol.ItemsSource = Global.IEProxyProtocols; + cmbloglevel.ItemsSource = Global.LogLevels; + cmbdefFingerprint.ItemsSource = Global.Fingerprints; + cmbdefUserAgent.ItemsSource = Global.UserAgent; + cmbmux4SboxProtocol.ItemsSource = Global.SingboxMuxs; + cmbMtu.ItemsSource = Global.TunMtus; + cmbStack.ItemsSource = Global.TunStacks; - for (var i = 2; i <= 8; i++) - { - cmbMixedConcurrencyCount.Items.Add(i); - } - for (var i = 2; i <= 6; i++) - { - cmbSpeedTestTimeout.Items.Add(i * 5); - } + cmbCoreType1.ItemsSource = Global.CoreTypes; + cmbCoreType2.ItemsSource = Global.CoreTypes; + cmbCoreType3.ItemsSource = Global.CoreTypes; + cmbCoreType4.ItemsSource = Global.CoreTypes; + cmbCoreType5.ItemsSource = Global.CoreTypes; + cmbCoreType6.ItemsSource = Global.CoreTypes; + cmbCoreType9.ItemsSource = Global.CoreTypes; + cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(2, 7).ToList(); + cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).ToList(); cmbSpeedTestUrl.ItemsSource = Global.SpeedTestUrls; cmbSpeedPingTestUrl.ItemsSource = Global.SpeedPingTestUrls; cmbSubConvertUrl.ItemsSource = Global.SubConvertUrls; + cmbGetFilesSourceUrl.ItemsSource = Global.GeoFilesSources; + cmbSrsFilesSourceUrl.ItemsSource = Global.SingboxRulesetSources; + cmbRoutingRulesSourceUrl.ItemsSource = Global.RoutingRulesSources; + cmbIPAPIUrl.ItemsSource = Global.IPAPIUrls; - Global.GeoFilesSources.ForEach(it => - { - cmbGetFilesSourceUrl.Items.Add(it); - }); - Global.SingboxRulesetSources.ForEach(it => - { - cmbSrsFilesSourceUrl.Items.Add(it); - }); - Global.RoutingRulesSources.ForEach(it => - { - cmbRoutingRulesSourceUrl.Items.Add(it); - }); - Global.IPAPIUrls.ForEach(it => - { - cmbIPAPIUrl.Items.Add(it); - }); - foreach (EGirdOrientation it in Enum.GetValues(typeof(EGirdOrientation))) - { - cmbMainGirdOrientation.Items.Add(it.ToString()); - } + cmbMainGirdOrientation.ItemsSource = Utils.GetEnumNames(); this.WhenActivated(disposables => { diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs index 9bcdb85a..f72c1f51 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs @@ -215,7 +215,7 @@ public partial class ProfilesView : ReactiveUserControl await ViewModel.RefreshServersBiz(); } - if (lstProfiles.SelectedIndex > 0) + if (lstProfiles.SelectedIndex >= 0) { lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, null); } @@ -347,6 +347,24 @@ public partial class ProfilesView : ReactiveUserControl { try { + //First scroll horizontally to the initial position to avoid the control crash bug + if (lstProfiles.SelectedIndex >= 0) + { + lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, lstProfiles.Columns[0]); + } + else + { + var model = lstProfiles.ItemsSource.Cast(); + if (model.Any()) + { + lstProfiles.ScrollIntoView(model.First(), lstProfiles.Columns[0]); + } + else + { + return; + } + } + foreach (var it in lstProfiles.Columns) { it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto); diff --git a/v2rayN/v2rayN.Desktop/Views/RoutingRuleDetailsWindow.axaml b/v2rayN/v2rayN.Desktop/Views/RoutingRuleDetailsWindow.axaml index 85ea58e8..460f0107 100644 --- a/v2rayN/v2rayN.Desktop/Views/RoutingRuleDetailsWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/RoutingRuleDetailsWindow.axaml @@ -2,6 +2,7 @@ x:Class="v2rayN.Desktop.Views.RoutingRuleDetailsWindow" xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:ctrls="clr-namespace:v2rayN.Desktop.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" @@ -22,86 +23,95 @@ + Width="300" + Margin="{StaticResource Margin4}" + HorizontalAlignment="Left" /> + VerticalAlignment="Center" /> - + Text="{Binding SelectedSource.OutboundTag, Mode=TwoWay}" /> + HorizontalAlignment="Left" + VerticalAlignment="Center" + Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" /> + Width="300" + Margin="{StaticResource Margin4}" + HorizontalAlignment="Left" /> + Text="{x:Static resx:ResUI.TbRuleMatchingTips}" /> + + + + - - - - +public partial class RoutingRuleDetailsWindow : WindowBase { public RoutingRuleDetailsWindow() { @@ -23,21 +23,11 @@ public partial class RoutingRuleDetailsWindow : ReactiveWindow - { - clbProtocol.Items.Add(it); - }); - Global.InboundTags.ForEach(it => - { - clbInboundTag.Items.Add(it); - }); - Global.RuleNetworks.ForEach(it => - { - cmbNetwork.Items.Add(it); - }); + + cmbOutboundTag.ItemsSource = Global.OutboundTags; + clbProtocol.ItemsSource = Global.RuleProtocols; + clbInboundTag.ItemsSource = Global.InboundTags; + cmbNetwork.ItemsSource = Global.RuleNetworks; if (!rulesItem.Id.IsNullOrEmpty()) { @@ -54,7 +44,7 @@ public partial class RoutingRuleDetailsWindow : ReactiveWindow { this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.OutboundTag, v => v.cmbOutboundTag.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Port, v => v.txtPort.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Enabled, v => v.togEnabled.IsChecked).DisposeWith(disposables); @@ -80,7 +70,7 @@ public partial class RoutingRuleDetailsWindow : ReactiveWindow +public partial class RoutingRuleSettingWindow : WindowBase { public RoutingRuleSettingWindow() { @@ -30,15 +30,9 @@ public partial class RoutingRuleSettingWindow : ReactiveWindow - { - cmbdomainStrategy.Items.Add(it); - }); - cmbdomainStrategy.Items.Add(string.Empty); - Global.DomainStrategies4Singbox.ForEach(it => - { - cmbdomainStrategy4Singbox.Items.Add(it); - }); + + cmbdomainStrategy.ItemsSource = Global.DomainStrategies.AppendEmpty(); + cmbdomainStrategy4Singbox.ItemsSource = Global.DomainStrategies4Singbox; this.WhenActivated(disposables => { diff --git a/v2rayN/v2rayN.Desktop/Views/RoutingSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/RoutingSettingWindow.axaml index 59b34a75..fba1863d 100644 --- a/v2rayN/v2rayN.Desktop/Views/RoutingSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/RoutingSettingWindow.axaml @@ -24,36 +24,13 @@ - - - - - - - - - - - - - - - - - - - -