mirror of
https://github.com/2dust/v2rayN.git
synced 2025-07-01 12:32:10 +00:00
Compare commits
65 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7a9ee6e9e2 | ||
![]() |
cb28c31519 | ||
![]() |
84f93f2ae6 | ||
![]() |
30c09a7b54 | ||
![]() |
b3874a78b9 | ||
![]() |
3e71965cd4 | ||
![]() |
3df57f74ba | ||
![]() |
7972cb8e1f | ||
![]() |
0d74452c6c | ||
![]() |
f947f63e6d | ||
![]() |
fefa7ded5a | ||
![]() |
a46a4ad7c1 | ||
![]() |
e46f680651 | ||
![]() |
93a20852f5 | ||
![]() |
298bb64e66 | ||
![]() |
0e5ac65f55 | ||
![]() |
cb6122f872 | ||
![]() |
06500e0218 | ||
![]() |
9ddf0b42e7 | ||
![]() |
2056377f55 | ||
![]() |
7065dabc94 | ||
![]() |
9e2336a71e | ||
![]() |
d239c627f3 | ||
![]() |
984b36d34e | ||
![]() |
c81bc2f536 | ||
![]() |
87f7e65076 | ||
![]() |
9985b68b6b | ||
![]() |
fb4b8b2789 | ||
![]() |
3812ccc780 | ||
![]() |
608a6c387a | ||
![]() |
4875b37f70 | ||
![]() |
2f3fba73de | ||
![]() |
2ab1b9068f | ||
![]() |
b9613875ce | ||
![]() |
5d2aea6b4f | ||
![]() |
5824e18ed6 | ||
![]() |
4f8648cbc9 | ||
![]() |
01b021b2c3 | ||
![]() |
331e8ce960 | ||
![]() |
a2cfe6fa51 | ||
![]() |
8381fefb78 | ||
![]() |
d3b95d781a | ||
![]() |
3a4a96f87a | ||
![]() |
3d462c4be3 | ||
![]() |
82b366cd9b | ||
![]() |
897a4e5635 | ||
![]() |
8ea76fd318 | ||
![]() |
693a96fff2 | ||
![]() |
8b4e2f8f23 | ||
![]() |
13b164acac | ||
![]() |
e590547b30 | ||
![]() |
5a0fdd971a | ||
![]() |
514dce960a | ||
![]() |
6ee6fb1706 | ||
![]() |
6985328653 | ||
![]() |
41c406b84d | ||
![]() |
30f7f2c563 | ||
![]() |
be3dbfb8e3 | ||
![]() |
9b92259e80 | ||
![]() |
1c04144573 | ||
![]() |
adf3b955d6 | ||
![]() |
0032a3d27a | ||
![]() |
c6d347d49a | ||
![]() |
ea42246d1b | ||
![]() |
3f19958c75 |
85 changed files with 2116 additions and 1232 deletions
14
.github/workflows/winget-publish.yml
vendored
14
.github/workflows/winget-publish.yml
vendored
|
@ -22,10 +22,18 @@ jobs:
|
|||
$github = Invoke-RestMethod -uri "https://api.github.com/repos/2dust/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
|
||||
|
|
4
LICENSE
4
LICENSE
|
@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least
|
|||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
Copyright (C) 2019-Present 2dust
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
|
|||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
v2rayN Copyright (C) 2019-Present 2dust
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
# v2rayN
|
||||
A GUI client for Windows, Linux and macOS, support [Xray](https://github.com/XTLS/Xray-core) and [sing-box](https://github.com/SagerNet/sing-box/releases) and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
|
||||
|
||||
A GUI client for Windows, Linux and macOS, support [Xray](https://github.com/XTLS/Xray-core)
|
||||
and [sing-box](https://github.com/SagerNet/sing-box)
|
||||
and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
|
||||
|
||||
[](https://github.com/2dust/v2rayN/commits/master)
|
||||
[](https://www.codefactor.io/repository/github/2dust/v2rayn)
|
||||
[](https://github.com/2dust/v2rayN/releases)
|
||||
[](https://t.me/v2rayn)
|
||||
|
||||
|
||||
## How to use
|
||||
|
||||
Read the [Wiki](https://github.com/2dust/v2rayN/wiki) for details.
|
||||
|
||||
## Telegram Channel
|
||||
|
||||
[github_2dust](https://t.me/github_2dust)
|
||||
|
|
|
@ -5,21 +5,83 @@ internal static class Program
|
|||
[STAThread]
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
try
|
||||
{
|
||||
Console.WriteLine(Resx.Resource.Guidelines);
|
||||
Thread.Sleep(5000);
|
||||
return;
|
||||
}
|
||||
// If no arguments are provided, display usage guidelines and exit
|
||||
if (args.Length == 0)
|
||||
{
|
||||
ShowHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
var argData = Uri.UnescapeDataString(string.Join(" ", args));
|
||||
if (argData.Equals("rebootas"))
|
||||
// Log all arguments for debugging purposes
|
||||
foreach (var arg in args)
|
||||
{
|
||||
Console.WriteLine(arg);
|
||||
}
|
||||
|
||||
// Parse command based on first argument
|
||||
switch (args[0].ToLowerInvariant())
|
||||
{
|
||||
case "rebootas":
|
||||
// Handle application restart
|
||||
HandleRebootAsync();
|
||||
break;
|
||||
|
||||
case "help":
|
||||
case "--help":
|
||||
case "-h":
|
||||
case "/?":
|
||||
// Display help information
|
||||
ShowHelp();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Default behavior: handle as upgrade data
|
||||
// Maintain backward compatibility with existing usage pattern
|
||||
var argData = Uri.UnescapeDataString(string.Join(" ", args));
|
||||
HandleUpgrade(argData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
Utils.StartV2RayN();
|
||||
return;
|
||||
// Global exception handling
|
||||
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||
Console.WriteLine("Press any key to exit...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
|
||||
UpgradeApp.Upgrade(argData);
|
||||
/// <summary>
|
||||
/// Display help information and usage guidelines
|
||||
/// </summary>
|
||||
private static void ShowHelp()
|
||||
{
|
||||
Console.WriteLine(Resx.Resource.Guidelines);
|
||||
Console.WriteLine("Available commands:");
|
||||
Console.WriteLine(" rebootas - Restart the application");
|
||||
Console.WriteLine(" help - Display this help information");
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle application restart
|
||||
/// </summary>
|
||||
private static void HandleRebootAsync()
|
||||
{
|
||||
Console.WriteLine("Restarting application...");
|
||||
Thread.Sleep(1000);
|
||||
Utils.StartV2RayN();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle application upgrade with the provided data
|
||||
/// </summary>
|
||||
/// <param name="upgradeData">Data for the upgrade process</param>
|
||||
private static void HandleUpgrade(string upgradeData)
|
||||
{
|
||||
Console.WriteLine("Upgrading application...");
|
||||
UpgradeApp.Upgrade(upgradeData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.11.3</Version>
|
||||
<Version>7.12.7</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
|
|
@ -5,21 +5,21 @@
|
|||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.2.8" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.2.8" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.2.8" />
|
||||
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.2.8" />
|
||||
<PackageVersion Include="CliWrap" Version="3.8.2" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.1" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.1" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.1" />
|
||||
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.1" />
|
||||
<PackageVersion Include="CliWrap" Version="3.9.0" />
|
||||
<PackageVersion Include="Downloader" Version="3.3.4" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
|
||||
<PackageVersion Include="MaterialDesignThemes" Version="5.2.1" />
|
||||
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" />
|
||||
<PackageVersion Include="QRCoder" Version="1.6.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="20.2.45" />
|
||||
<PackageVersion Include="ReactiveUI" Version="20.3.1" />
|
||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="20.2.45" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.6" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.6" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="20.3.1" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.8" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.8" />
|
||||
<PackageVersion Include="Splat.NLog" Version="15.3.1" />
|
||||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.1" />
|
||||
|
|
|
@ -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");
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt
|
||||
/// </summary>
|
||||
/// <param name="text">Plain text</param>
|
||||
/// <param name="password">Password for key derivation or direct key in ASCII bytes</param>
|
||||
/// <returns>Base64 encoded cipher text with IV</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt
|
||||
/// </summary>
|
||||
/// <param name="cipherTextWithIv">Base64 encoded cipher text with IV</param>
|
||||
/// <param name="password">Password for key derivation or direct key in ASCII bytes</param>
|
||||
/// <returns>Plain text</returns>
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace ServiceLib.Common;
|
||||
|
||||
public class DesUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Encrypt
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// /// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt
|
||||
/// </summary>
|
||||
/// <param name="encryptText"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
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");
|
||||
}
|
||||
}
|
|
@ -582,7 +582,7 @@ public class Utils
|
|||
var result = await cmd.ExecuteBufferedAsync();
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
return result.StandardOutput.ToString();
|
||||
return result.StandardOutput ?? "";
|
||||
}
|
||||
|
||||
Logging.SaveLog(result.ToString() ?? "");
|
||||
|
@ -839,14 +839,46 @@ public class Utils
|
|||
public static async Task<string?> 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<string>() { "-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<string?> GetLinuxFontFamily(string lang)
|
||||
{
|
||||
// var arg = new List<string>() { "-c", $"fc-list :lang={lang} family" };
|
||||
|
|
|
@ -20,6 +20,7 @@ public enum EViewAction
|
|||
BrowseServer,
|
||||
ImportRulesFromFile,
|
||||
InitSettingFont,
|
||||
PasswordInput,
|
||||
SubEditWindow,
|
||||
RoutingRuleSettingWindow,
|
||||
RoutingRuleDetailsWindow,
|
||||
|
|
|
@ -8,9 +8,7 @@ public class Global
|
|||
public const string GithubUrl = "https://github.com";
|
||||
public const string GithubApiUrl = "https://api.github.com/repos";
|
||||
public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat";
|
||||
public const string SpeedPingTestUrl = @"https://www.google.com/generate_204";
|
||||
public const string SingboxRulesetUrl = @"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-{0}/{1}.srs";
|
||||
public const string IPAPIUrl = "https://api.ip.sb/geoip";
|
||||
|
||||
public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw=";
|
||||
public const string ConfigFileName = "guiNConfig.json";
|
||||
|
@ -519,5 +517,15 @@ public class Global
|
|||
@"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb"
|
||||
];
|
||||
|
||||
public static readonly List<string> IPAPIUrls =
|
||||
[
|
||||
@"https://speed.cloudflare.com/meta",
|
||||
@"https://api.ip.sb/geoip",
|
||||
@"https://api-ipv4.ip.sb/geoip",
|
||||
@"https://api-ipv6.ip.sb/geoip",
|
||||
@"https://api.ipapi.is",
|
||||
@""
|
||||
];
|
||||
|
||||
#endregion const
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ public sealed class AppHandler
|
|||
private int? _statePort;
|
||||
private int? _statePort2;
|
||||
private Job? _processJob;
|
||||
private bool? _isAdministrator;
|
||||
public static AppHandler Instance => _instance.Value;
|
||||
public Config Config => _config;
|
||||
|
||||
|
@ -31,14 +30,7 @@ public sealed class AppHandler
|
|||
}
|
||||
}
|
||||
|
||||
public bool IsAdministrator
|
||||
{
|
||||
get
|
||||
{
|
||||
_isAdministrator ??= Utils.IsAdministrator();
|
||||
return _isAdministrator.Value;
|
||||
}
|
||||
}
|
||||
public string LinuxSudoPwd { get; set; }
|
||||
|
||||
#endregion Property
|
||||
|
||||
|
|
|
@ -101,6 +101,7 @@ public class ConfigHandler
|
|||
EnableAutoAdjustMainLvColWidth = true
|
||||
};
|
||||
config.UiItem.MainColumnItem ??= new();
|
||||
config.UiItem.WindowSizeItem ??= new();
|
||||
|
||||
if (config.UiItem.CurrentLanguage.IsNullOrEmpty())
|
||||
{
|
||||
|
@ -122,7 +123,7 @@ public class ConfigHandler
|
|||
}
|
||||
if (config.SpeedTestItem.SpeedPingTestUrl.IsNullOrEmpty())
|
||||
{
|
||||
config.SpeedTestItem.SpeedPingTestUrl = Global.SpeedPingTestUrl;
|
||||
config.SpeedTestItem.SpeedPingTestUrl = Global.SpeedPingTestUrls.First();
|
||||
}
|
||||
if (config.SpeedTestItem.MixedConcurrencyCount < 1)
|
||||
{
|
||||
|
@ -246,6 +247,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
|
||||
|
@ -799,8 +801,11 @@ public class ConfigHandler
|
|||
{
|
||||
return -1;
|
||||
}
|
||||
var lstServerStat = (config.GuiItem.EnableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? [];
|
||||
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs();
|
||||
var lstProfile = (from t in lstModel
|
||||
join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b
|
||||
from t22 in t2b.DefaultIfEmpty()
|
||||
join t3 in lstProfileExs on t.IndexId equals t3.IndexId into t3b
|
||||
from t33 in t3b.DefaultIfEmpty()
|
||||
select new ProfileItemModel
|
||||
|
@ -815,7 +820,11 @@ public class ConfigHandler
|
|||
StreamSecurity = t.StreamSecurity,
|
||||
Delay = t33?.Delay ?? 0,
|
||||
Speed = t33?.Speed ?? 0,
|
||||
Sort = t33?.Sort ?? 0
|
||||
Sort = t33?.Sort ?? 0,
|
||||
TodayDown = (t22?.TodayDown ?? 0).ToString("D16"),
|
||||
TodayUp = (t22?.TodayUp ?? 0).ToString("D16"),
|
||||
TotalDown = (t22?.TotalDown ?? 0).ToString("D16"),
|
||||
TotalUp = (t22?.TotalUp ?? 0).ToString("D16"),
|
||||
}).ToList();
|
||||
|
||||
Enum.TryParse(colName, true, out EServerColName name);
|
||||
|
@ -833,6 +842,10 @@ public class ConfigHandler
|
|||
EServerColName.DelayVal => lstProfile.OrderBy(t => t.Delay).ToList(),
|
||||
EServerColName.SpeedVal => lstProfile.OrderBy(t => t.Speed).ToList(),
|
||||
EServerColName.SubRemarks => lstProfile.OrderBy(t => t.Subid).ToList(),
|
||||
EServerColName.TodayDown => lstProfile.OrderBy(t => t.TodayDown).ToList(),
|
||||
EServerColName.TodayUp => lstProfile.OrderBy(t => t.TodayUp).ToList(),
|
||||
EServerColName.TotalDown => lstProfile.OrderBy(t => t.TotalDown).ToList(),
|
||||
EServerColName.TotalUp => lstProfile.OrderBy(t => t.TotalUp).ToList(),
|
||||
_ => lstProfile
|
||||
};
|
||||
}
|
||||
|
@ -849,6 +862,10 @@ public class ConfigHandler
|
|||
EServerColName.DelayVal => lstProfile.OrderByDescending(t => t.Delay).ToList(),
|
||||
EServerColName.SpeedVal => lstProfile.OrderByDescending(t => t.Speed).ToList(),
|
||||
EServerColName.SubRemarks => lstProfile.OrderByDescending(t => t.Subid).ToList(),
|
||||
EServerColName.TodayDown => lstProfile.OrderByDescending(t => t.TodayDown).ToList(),
|
||||
EServerColName.TodayUp => lstProfile.OrderByDescending(t => t.TodayUp).ToList(),
|
||||
EServerColName.TotalDown => lstProfile.OrderByDescending(t => t.TotalDown).ToList(),
|
||||
EServerColName.TotalUp => lstProfile.OrderByDescending(t => t.TotalUp).ToList(),
|
||||
_ => lstProfile
|
||||
};
|
||||
}
|
||||
|
@ -1964,7 +1981,7 @@ 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)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
@ -2158,4 +2175,34 @@ 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 = width;
|
||||
sizeItem.Height = height;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion UIItem
|
||||
}
|
||||
|
|
42
v2rayN/ServiceLib/Handler/ConnectionHandler.cs
Normal file
42
v2rayN/ServiceLib/Handler/ConnectionHandler.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
namespace ServiceLib.Handler;
|
||||
|
||||
public class ConnectionHandler
|
||||
{
|
||||
private static readonly Lazy<ConnectionHandler> _instance = new(() => new());
|
||||
public static ConnectionHandler Instance => _instance.Value;
|
||||
|
||||
public async Task<string> RunAvailabilityCheck()
|
||||
{
|
||||
var downloadHandle = new DownloadService();
|
||||
var time = await downloadHandle.RunAvailabilityCheck(null);
|
||||
var ip = time > 0 ? await GetIPInfo(downloadHandle) ?? Global.None : Global.None;
|
||||
|
||||
return string.Format(ResUI.TestMeOutput, time, ip);
|
||||
}
|
||||
|
||||
private async Task<string?> GetIPInfo(DownloadService downloadHandle)
|
||||
{
|
||||
var url = AppHandler.Instance.Config.SpeedTestItem.IPAPIUrl;
|
||||
if (url.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = await downloadHandle.TryDownloadString(url, true, "");
|
||||
if (result == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var ipInfo = JsonUtils.Deserialize<IPAPIInfo>(result);
|
||||
if (ipInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var ip = ipInfo.ip ?? ipInfo.clientIp ?? ipInfo.ip_addr ?? ipInfo.query;
|
||||
var country = ipInfo.country_code ?? ipInfo.country ?? ipInfo.countryCode ?? ipInfo.location?.country_code;
|
||||
|
||||
return $"({country ?? "unknown"}) {ip}";
|
||||
}
|
||||
}
|
126
v2rayN/ServiceLib/Handler/CoreAdminHandler.cs
Normal file
126
v2rayN/ServiceLib/Handler/CoreAdminHandler.cs
Normal file
|
@ -0,0 +1,126 @@
|
|||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using CliWrap;
|
||||
|
||||
namespace ServiceLib.Handler;
|
||||
|
||||
public class CoreAdminHandler
|
||||
{
|
||||
private static readonly Lazy<CoreAdminHandler> _instance = new(() => new());
|
||||
public static CoreAdminHandler Instance => _instance.Value;
|
||||
private Config _config;
|
||||
private Action<bool, string>? _updateFunc;
|
||||
private int _linuxSudoPid = -1;
|
||||
|
||||
public async Task Init(Config config, Action<bool, string> updateFunc)
|
||||
{
|
||||
if (_config != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_config = config;
|
||||
_updateFunc = updateFunc;
|
||||
}
|
||||
|
||||
private void UpdateFunc(bool notify, string msg)
|
||||
{
|
||||
_updateFunc?.Invoke(notify, msg);
|
||||
}
|
||||
|
||||
public async Task<Process?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
|
||||
{
|
||||
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
|
||||
var shFilePath = await CreateLinuxShellFile(cmdLine, "run_as_sudo.sh");
|
||||
|
||||
Process proc = new()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
FileName = shFilePath,
|
||||
Arguments = "",
|
||||
WorkingDirectory = Utils.GetBinConfigPath(),
|
||||
UseShellExecute = false,
|
||||
RedirectStandardInput = true,
|
||||
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) =>
|
||||
{
|
||||
if (e.Data.IsNotEmpty())
|
||||
{
|
||||
UpdateFunc(false, e.Data + Environment.NewLine);
|
||||
}
|
||||
};
|
||||
|
||||
proc.Start();
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
|
||||
await Task.Delay(10);
|
||||
await proc.StandardInput.WriteLineAsync();
|
||||
await Task.Delay(10);
|
||||
await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd);
|
||||
|
||||
await Task.Delay(100);
|
||||
if (proc is null or { HasExited: true })
|
||||
{
|
||||
throw new Exception(ResUI.FailedToRunCore);
|
||||
}
|
||||
|
||||
_linuxSudoPid = proc.Id;
|
||||
|
||||
return proc;
|
||||
}
|
||||
|
||||
public async Task KillProcessAsLinuxSudo()
|
||||
{
|
||||
if (_linuxSudoPid < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cmdLine = $"pkill -P {_linuxSudoPid} ; kill {_linuxSudoPid}";
|
||||
var shFilePath = await CreateLinuxShellFile(cmdLine, "kill_as_sudo.sh");
|
||||
|
||||
await Cli.Wrap(shFilePath)
|
||||
.WithStandardInputPipe(PipeSource.FromString(AppHandler.Instance.LinuxSudoPwd))
|
||||
.ExecuteAsync();
|
||||
|
||||
_linuxSudoPid = -1;
|
||||
}
|
||||
|
||||
private async Task<string> CreateLinuxShellFile(string cmdLine, string fileName)
|
||||
{
|
||||
var shFilePath = Utils.GetBinConfigPath(fileName);
|
||||
File.Delete(shFilePath);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("#!/bin/sh");
|
||||
if (Utils.IsAdministrator())
|
||||
{
|
||||
sb.AppendLine($"{cmdLine}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"sudo -S {cmdLine}");
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(shFilePath, sb.ToString());
|
||||
await Utils.SetLinuxChmod(shFilePath);
|
||||
|
||||
return shFilePath;
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ public class CoreHandler
|
|||
private Config _config;
|
||||
private Process? _process;
|
||||
private Process? _processPre;
|
||||
private int _linuxSudoPid = -1;
|
||||
private bool _linuxSudo = false;
|
||||
private Action<bool, string>? _updateFunc;
|
||||
private const string _tag = "CoreHandler";
|
||||
|
||||
|
@ -155,23 +155,23 @@ public class CoreHandler
|
|||
{
|
||||
try
|
||||
{
|
||||
if (_linuxSudo)
|
||||
{
|
||||
await CoreAdminHandler.Instance.KillProcessAsLinuxSudo();
|
||||
_linuxSudo = false;
|
||||
}
|
||||
|
||||
if (_process != null)
|
||||
{
|
||||
await ProcUtils.ProcessKill(_process, true);
|
||||
await ProcUtils.ProcessKill(_process, Utils.IsWindows());
|
||||
_process = null;
|
||||
}
|
||||
|
||||
if (_processPre != null)
|
||||
{
|
||||
await ProcUtils.ProcessKill(_processPre, true);
|
||||
await ProcUtils.ProcessKill(_processPre, Utils.IsWindows());
|
||||
_processPre = null;
|
||||
}
|
||||
|
||||
if (_linuxSudoPid > 0)
|
||||
{
|
||||
await KillProcessAsLinuxSudo();
|
||||
}
|
||||
_linuxSudoPid = -1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -225,15 +225,6 @@ public class CoreHandler
|
|||
_updateFunc?.Invoke(notify, msg);
|
||||
}
|
||||
|
||||
private bool IsNeedSudo(ECoreType eCoreType)
|
||||
{
|
||||
return _config.TunModeItem.EnableTun
|
||||
&& eCoreType == ECoreType.sing_box
|
||||
&& (Utils.IsNonWindows())
|
||||
//&& _config.TunModeItem.LinuxSudoPwd.IsNotEmpty()
|
||||
;
|
||||
}
|
||||
|
||||
#endregion Private
|
||||
|
||||
#region Process
|
||||
|
@ -249,69 +240,17 @@ public class CoreHandler
|
|||
|
||||
try
|
||||
{
|
||||
Process proc = new()
|
||||
if (mayNeedSudo
|
||||
&& _config.TunModeItem.EnableTun
|
||||
&& coreInfo.CoreType == ECoreType.sing_box
|
||||
&& Utils.IsNonWindows())
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath),
|
||||
WorkingDirectory = Utils.GetBinConfigPath(),
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = displayLog,
|
||||
RedirectStandardError = displayLog,
|
||||
CreateNoWindow = true,
|
||||
StandardOutputEncoding = displayLog ? Encoding.UTF8 : null,
|
||||
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
|
||||
}
|
||||
};
|
||||
|
||||
var isNeedSudo = mayNeedSudo && IsNeedSudo(coreInfo.CoreType);
|
||||
if (isNeedSudo)
|
||||
{
|
||||
await RunProcessAsLinuxSudo(proc, fileName, coreInfo, configPath);
|
||||
_linuxSudo = true;
|
||||
await CoreAdminHandler.Instance.Init(_config, _updateFunc);
|
||||
return await CoreAdminHandler.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath);
|
||||
}
|
||||
|
||||
if (displayLog)
|
||||
{
|
||||
proc.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data.IsNullOrEmpty())
|
||||
return;
|
||||
UpdateFunc(false, e.Data + Environment.NewLine);
|
||||
};
|
||||
proc.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data.IsNullOrEmpty())
|
||||
return;
|
||||
UpdateFunc(false, e.Data + Environment.NewLine);
|
||||
};
|
||||
}
|
||||
proc.Start();
|
||||
|
||||
if (isNeedSudo && _config.TunModeItem.LinuxSudoPwd.IsNotEmpty())
|
||||
{
|
||||
var pwd = DesUtils.Decrypt(_config.TunModeItem.LinuxSudoPwd);
|
||||
await Task.Delay(10);
|
||||
await proc.StandardInput.WriteLineAsync(pwd);
|
||||
await Task.Delay(10);
|
||||
await proc.StandardInput.WriteLineAsync(pwd);
|
||||
}
|
||||
if (isNeedSudo)
|
||||
_linuxSudoPid = proc.Id;
|
||||
|
||||
if (displayLog)
|
||||
{
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
}
|
||||
|
||||
await Task.Delay(500);
|
||||
AppHandler.Instance.AddProcess(proc.Handle);
|
||||
if (proc is null or { HasExited: true })
|
||||
{
|
||||
throw new Exception(ResUI.FailedToRunCore);
|
||||
}
|
||||
return proc;
|
||||
return await RunProcessNormal(fileName, coreInfo, configPath, displayLog);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -321,89 +260,57 @@ public class CoreHandler
|
|||
}
|
||||
}
|
||||
|
||||
#endregion Process
|
||||
|
||||
#region Linux
|
||||
|
||||
private async Task RunProcessAsLinuxSudo(Process proc, string fileName, CoreInfo coreInfo, string configPath)
|
||||
private async Task<Process?> RunProcessNormal(string fileName, CoreInfo? coreInfo, string configPath, bool displayLog)
|
||||
{
|
||||
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
|
||||
|
||||
var shFilePath = await CreateLinuxShellFile(cmdLine, "run_as_sudo.sh");
|
||||
proc.StartInfo.FileName = shFilePath;
|
||||
proc.StartInfo.Arguments = "";
|
||||
proc.StartInfo.WorkingDirectory = "";
|
||||
if (_config.TunModeItem.LinuxSudoPwd.IsNotEmpty())
|
||||
{
|
||||
proc.StartInfo.StandardInputEncoding = Encoding.UTF8;
|
||||
proc.StartInfo.RedirectStandardInput = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task KillProcessAsLinuxSudo()
|
||||
{
|
||||
var cmdLine = $"kill {_linuxSudoPid}";
|
||||
var shFilePath = await CreateLinuxShellFile(cmdLine, "kill_as_sudo.sh");
|
||||
Process proc = new()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
FileName = shFilePath,
|
||||
FileName = fileName,
|
||||
Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath),
|
||||
WorkingDirectory = Utils.GetBinConfigPath(),
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = displayLog,
|
||||
RedirectStandardError = displayLog,
|
||||
CreateNoWindow = true,
|
||||
StandardInputEncoding = Encoding.UTF8,
|
||||
RedirectStandardInput = true
|
||||
StandardOutputEncoding = displayLog ? Encoding.UTF8 : null,
|
||||
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
|
||||
}
|
||||
};
|
||||
|
||||
if (displayLog)
|
||||
{
|
||||
proc.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data.IsNotEmpty())
|
||||
{
|
||||
UpdateFunc(false, e.Data + Environment.NewLine);
|
||||
}
|
||||
};
|
||||
proc.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data.IsNotEmpty())
|
||||
{
|
||||
UpdateFunc(false, e.Data + Environment.NewLine);
|
||||
}
|
||||
};
|
||||
}
|
||||
proc.Start();
|
||||
|
||||
if (_config.TunModeItem.LinuxSudoPwd.IsNotEmpty())
|
||||
if (displayLog)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pwd = DesUtils.Decrypt(_config.TunModeItem.LinuxSudoPwd);
|
||||
await Task.Delay(10);
|
||||
await proc.StandardInput.WriteLineAsync(pwd);
|
||||
await Task.Delay(10);
|
||||
await proc.StandardInput.WriteLineAsync(pwd);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
}
|
||||
|
||||
var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||
await proc.WaitForExitAsync(timeout.Token);
|
||||
await Task.Delay(3000);
|
||||
await Task.Delay(100);
|
||||
AppHandler.Instance.AddProcess(proc.Handle);
|
||||
if (proc is null or { HasExited: true })
|
||||
{
|
||||
throw new Exception(ResUI.FailedToRunCore);
|
||||
}
|
||||
return proc;
|
||||
}
|
||||
|
||||
private async Task<string> CreateLinuxShellFile(string cmdLine, string fileName)
|
||||
{
|
||||
//Shell scripts
|
||||
var shFilePath = Utils.GetBinConfigPath(AppHandler.Instance.IsAdministrator ? "root_" + fileName : fileName);
|
||||
File.Delete(shFilePath);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("#!/bin/sh");
|
||||
if (AppHandler.Instance.IsAdministrator)
|
||||
{
|
||||
sb.AppendLine($"{cmdLine}");
|
||||
}
|
||||
else if (_config.TunModeItem.LinuxSudoPwd.IsNullOrEmpty())
|
||||
{
|
||||
sb.AppendLine($"pkexec {cmdLine}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"sudo -S {cmdLine}");
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(shFilePath, sb.ToString());
|
||||
await Utils.SetLinuxChmod(shFilePath);
|
||||
Logging.SaveLog(shFilePath);
|
||||
|
||||
return shFilePath;
|
||||
}
|
||||
|
||||
#endregion Linux
|
||||
#endregion Process
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ public class VmessFmt : BaseFmt
|
|||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
ProfileItem? item;
|
||||
if (str.IndexOf('?') > 0 && str.IndexOf('&') > 0)
|
||||
if (str.IndexOf('@') > 0)
|
||||
{
|
||||
item = ResolveStdVmess(str);
|
||||
item = ResolveStdVmess(str) ?? ResolveVmess(str, out msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -89,8 +89,6 @@ 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 EGirdOrientation MainGirdOrientation { get; set; } = EGirdOrientation.Vertical;
|
||||
|
@ -103,8 +101,10 @@ public class UIItem
|
|||
public bool DoubleClick2Activate { get; set; }
|
||||
public bool AutoHideStartup { get; set; }
|
||||
public bool Hide2TrayWhenClose { get; set; }
|
||||
public List<ColumnItem> MainColumnItem { get; set; }
|
||||
public bool ShowInTaskbar { get; set; }
|
||||
public bool MacOSShowInDock { get; set; }
|
||||
public List<ColumnItem> MainColumnItem { get; set; }
|
||||
public List<WindowSizeItem> WindowSizeItem { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
|
@ -147,7 +147,6 @@ public class TunModeItem
|
|||
public int Mtu { get; set; }
|
||||
public bool EnableExInbound { get; set; }
|
||||
public bool EnableIPv6Address { get; set; }
|
||||
public string? LinuxSudoPwd { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
|
@ -157,6 +156,7 @@ public class SpeedTestItem
|
|||
public string SpeedTestUrl { get; set; }
|
||||
public string SpeedPingTestUrl { get; set; }
|
||||
public int MixedConcurrencyCount { get; set; }
|
||||
public string IPAPIUrl { 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 double Width { get; set; }
|
||||
public double Height { get; set; }
|
||||
}
|
||||
|
|
|
@ -3,10 +3,17 @@ namespace ServiceLib.Models;
|
|||
internal class IPAPIInfo
|
||||
{
|
||||
public string? ip { get; set; }
|
||||
public string? city { get; set; }
|
||||
public string? region { get; set; }
|
||||
public string? region_code { get; set; }
|
||||
public string? clientIp { get; set; }
|
||||
public string? ip_addr { get; set; }
|
||||
public string? query { get; set; }
|
||||
public string? country { get; set; }
|
||||
public string? country_name { get; set; }
|
||||
public string? country_code { get; set; }
|
||||
public string? countryCode { get; set; }
|
||||
public LocationInfo? location { get; set; }
|
||||
}
|
||||
|
||||
public class LocationInfo
|
||||
{
|
||||
public string? country_code { get; set; }
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
|
|
11
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
11
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
|
@ -3201,6 +3201,15 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Current connection info test URL 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsIPAPIUrl {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsIPAPIUrl", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Keep older entries when de-duplicating 的本地化字符串。
|
||||
/// </summary>
|
||||
|
@ -3247,7 +3256,7 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 The password is encrypted and stored only in local files 的本地化字符串。
|
||||
/// 查找类似 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. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsLinuxSudoPasswordTip {
|
||||
get {
|
||||
|
|
|
@ -1339,7 +1339,7 @@
|
|||
<value>رمز عبور sudo سیستم</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
||||
<value>رمز عبور رمزگذاری شده و فقط در فایل های محلی ذخیره می شود.</value>
|
||||
<value>رمز عبوری که وارد کرده اید تایید نمی شود، بنابراین مطمئن شوید که آن را به درستی وارد کرده اید. اگر برنامه به دلیل ورودی نادرست به درستی کار نمی کند، لطفاً برنامه را مجدداً راه اندازی کنید. رمز عبور ذخیره نمی شود و پس از هر بار راه اندازی مجدد باید آن را دوباره وارد کنید.</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
|
||||
<value>لطفاً ابتدا رمز عبور sudo را در تنظیمات حالت Tun تنظیم کنید</value>
|
||||
|
@ -1416,4 +1416,7 @@
|
|||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>صادر کردن سرور</value>
|
||||
</data>
|
||||
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
|
||||
<value>URL آزمایش اطلاعات اتصال فعلی</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
@ -1339,7 +1339,7 @@
|
|||
<value>Rendszer sudo jelszó</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
||||
<value>A jelszó titkosítva és csak a helyi fájlokban tárolva.</value>
|
||||
<value>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.</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
|
||||
<value>Kérlek, először állítsd be a sudo jelszót a Tun módban</value>
|
||||
|
@ -1416,4 +1416,7 @@
|
|||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>Export server</value>
|
||||
</data>
|
||||
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
|
||||
<value>Current connection info test URL</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1339,7 +1339,7 @@
|
|||
<value>System sudo password</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
||||
<value>The password is encrypted and stored only in local files</value>
|
||||
<value>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.</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
|
||||
<value>Please set the sudo password in Tun mode settings first</value>
|
||||
|
@ -1416,4 +1416,7 @@
|
|||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>Export Configuration</value>
|
||||
</data>
|
||||
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
|
||||
<value>Current connection info test URL</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
@ -118,16 +118,16 @@
|
|||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="BatchExportURLSuccessfully" xml:space="preserve">
|
||||
<value>Экспортирование URL в буфер обмена успешно завершено</value>
|
||||
<value>Ссылка успешно скопирована в буфер обмена</value>
|
||||
</data>
|
||||
<data name="CheckServerSettings" xml:space="preserve">
|
||||
<value>Пожалуйста, сначала проверьте настройки сервера</value>
|
||||
<value>Сначала проверьте настройки сервера</value>
|
||||
</data>
|
||||
<data name="ConfigurationFormatIncorrect" xml:space="preserve">
|
||||
<value>Недопустимый формат конфигурации</value>
|
||||
</data>
|
||||
<data name="CustomServerTips" xml:space="preserve">
|
||||
<value>Обратите внимание, что пользовательская конфигурация полностью зависит от вашей собственной конфигурации и работает не со всеми настройками. Если вы хотите использовать системный прокси, пожалуйста, измените порт прослушивания вручную.</value>
|
||||
<value>Обратите внимание, что пользовательская конфигурация полностью зависит от вашей собственной конфигурации и работает не со всеми настройками. Если вы хотите использовать системный прокси, то измените порт прослушивания вручную</value>
|
||||
</data>
|
||||
<data name="Downloading" xml:space="preserve">
|
||||
<value>Загрузка...</value>
|
||||
|
@ -136,13 +136,13 @@
|
|||
<value>Скорость загрузки</value>
|
||||
</data>
|
||||
<data name="DownloadYesNo" xml:space="preserve">
|
||||
<value>Хотите загрузить? {0}</value>
|
||||
<value>Хотите загрузить {0}?</value>
|
||||
</data>
|
||||
<data name="FailedConversionConfiguration" xml:space="preserve">
|
||||
<value>Не удалось преобразовать файл конфигурации</value>
|
||||
</data>
|
||||
<data name="FailedGenDefaultConfiguration" xml:space="preserve">
|
||||
<value>Не удалось создать файл конфигурации по умолчаниюe</value>
|
||||
<value>Не удалось создать файл конфигурации по умолчанию</value>
|
||||
</data>
|
||||
<data name="FailedGetDefaultConfiguration" xml:space="preserve">
|
||||
<value>Не удалось получить конфигурацию по умолчанию</value>
|
||||
|
@ -154,37 +154,37 @@
|
|||
<value>Не удалось прочитать файл конфигурации</value>
|
||||
</data>
|
||||
<data name="FillCorrectServerPort" xml:space="preserve">
|
||||
<value>Пожалуйста, укажите порт сервера в правильном формате</value>
|
||||
<value>Укажите порт сервера в правильном формате</value>
|
||||
</data>
|
||||
<data name="FillLocalListeningPort" xml:space="preserve">
|
||||
<value>Пожалуйста, укажите локальный порт прослушивания</value>
|
||||
<value>Введите локальный порт для прослушивания</value>
|
||||
</data>
|
||||
<data name="FillPassword" xml:space="preserve">
|
||||
<value>Пожалуйста, введите пароль</value>
|
||||
<value>Введите пароль</value>
|
||||
</data>
|
||||
<data name="FillServerAddress" xml:space="preserve">
|
||||
<value>Пожалуйста, заполните адрес сервера</value>
|
||||
<value>Введите адрес сервера</value>
|
||||
</data>
|
||||
<data name="FillUUID" xml:space="preserve">
|
||||
<value>Пожалуйста, заполните идентификатор пользователя</value>
|
||||
<value>Введите идентификатор пользователя</value>
|
||||
</data>
|
||||
<data name="Incorrectconfiguration" xml:space="preserve">
|
||||
<value>Некорректная конфигурация, пожалуйста, проверьте</value>
|
||||
<value>Некорректная конфигурация. Проверьте</value>
|
||||
</data>
|
||||
<data name="InitialConfiguration" xml:space="preserve">
|
||||
<value>Исходная конфигурация</value>
|
||||
</data>
|
||||
<data name="IsLatestCore" xml:space="preserve">
|
||||
<value>{0} {1} является последней версией.</value>
|
||||
<value>{0} {1} является последней версией</value>
|
||||
</data>
|
||||
<data name="IsLatestN" xml:space="preserve">
|
||||
<value>{0} {1} является последней версией.</value>
|
||||
<value>{0} {1} является последней версией</value>
|
||||
</data>
|
||||
<data name="LvAddress" xml:space="preserve">
|
||||
<value>Адрес</value>
|
||||
</data>
|
||||
<data name="LvEncryptionMethod" xml:space="preserve">
|
||||
<value>Безопасность</value>
|
||||
<value>Метод шифрования</value>
|
||||
</data>
|
||||
<data name="LvPort" xml:space="preserve">
|
||||
<value>Порт</value>
|
||||
|
@ -199,7 +199,7 @@
|
|||
<value>Загружено трафика сегодня</value>
|
||||
</data>
|
||||
<data name="LvTodayUploadDataAmount" xml:space="preserve">
|
||||
<value>Отдано трафика сегодня</value>
|
||||
<value>Отправлено сегодня</value>
|
||||
</data>
|
||||
<data name="LvTotalDownloadDataAmount" xml:space="preserve">
|
||||
<value>Всего загружено</value>
|
||||
|
@ -214,13 +214,13 @@
|
|||
<value>Ядро успешно загружено</value>
|
||||
</data>
|
||||
<data name="MsgFailedImportSubscription" xml:space="preserve">
|
||||
<value>Не удалось импортировать содержимое подписки</value>
|
||||
<value>Не удалось импортировать подписку</value>
|
||||
</data>
|
||||
<data name="MsgGetSubscriptionSuccessfully" xml:space="preserve">
|
||||
<value>Содержимое подписки успешно импортировано</value>
|
||||
</data>
|
||||
<data name="MsgNoValidSubscription" xml:space="preserve">
|
||||
<value>Нет установлены подписки</value>
|
||||
<value>Нет установленных подписок</value>
|
||||
</data>
|
||||
<data name="MsgParsingSuccessfully" xml:space="preserve">
|
||||
<value>Парсинг {0} прошел успешно</value>
|
||||
|
@ -235,7 +235,7 @@
|
|||
<value>Некорректное содержимое подписки</value>
|
||||
</data>
|
||||
<data name="MsgUnpacking" xml:space="preserve">
|
||||
<value>распаковывается...</value>
|
||||
<value>Распаковка…</value>
|
||||
</data>
|
||||
<data name="MsgUpdateSubscriptionEnd" xml:space="preserve">
|
||||
<value>Обновление подписки закончено</value>
|
||||
|
@ -244,37 +244,37 @@
|
|||
<value>Обновление подписки начинается</value>
|
||||
</data>
|
||||
<data name="MsgUpdateV2rayCoreSuccessfully" xml:space="preserve">
|
||||
<value>Успешное обновление ядра</value>
|
||||
<value>Ядро успешно обновлено</value>
|
||||
</data>
|
||||
<data name="MsgUpdateV2rayCoreSuccessfullyMore" xml:space="preserve">
|
||||
<value>Успешное обновление ядра! Перезапуск службы...</value>
|
||||
</data>
|
||||
<data name="NonvmessOrssProtocol" xml:space="preserve">
|
||||
<value>Не является протоколом Vmess или SS</value>
|
||||
<value>Не является протоколом VMess или Shadowsocks</value>
|
||||
</data>
|
||||
<data name="NotFoundCore" xml:space="preserve">
|
||||
<value>Файл Core (имя файла: {1}) не найден в папке ({0}), загрузите и поместите его в папку, адрес загрузки: {2}</value>
|
||||
<value>Файл ядра ({1}) не найден в папке {0}. Скачайте по адресу {2} и поместите его туда</value>
|
||||
</data>
|
||||
<data name="NoValidQRcodeFound" xml:space="preserve">
|
||||
<value>Сканирование завершено, не найден корректный QR код</value>
|
||||
</data>
|
||||
<data name="OperationFailed" xml:space="preserve">
|
||||
<value> операция безуспешна, проверьте и попробуйте ещё раз</value>
|
||||
<value>Операция безуспешна, проверьте и попробуйте ещё раз</value>
|
||||
</data>
|
||||
<data name="PleaseFillRemarks" xml:space="preserve">
|
||||
<value>Пожалуйста, заполните примечания</value>
|
||||
<value>Введите примечания</value>
|
||||
</data>
|
||||
<data name="PleaseSelectEncryption" xml:space="preserve">
|
||||
<value>Пожалуйста, выберите метод шифрования</value>
|
||||
<value>Выберите метод шифрования</value>
|
||||
</data>
|
||||
<data name="PleaseSelectProtocol" xml:space="preserve">
|
||||
<value>Пожалуйста, выберите протокол</value>
|
||||
<value>Выберите протокол</value>
|
||||
</data>
|
||||
<data name="PleaseSelectServer" xml:space="preserve">
|
||||
<value>Сначала выберите сервер</value>
|
||||
</data>
|
||||
<data name="RemoveDuplicateServerResult" xml:space="preserve">
|
||||
<value>Удаление дублей завершено. Старая: {0}, Новая: {1}.</value>
|
||||
<value>Удаление дублей завершено. Старая: {0}, Новая: {1}</value>
|
||||
</data>
|
||||
<data name="RemoveServer" xml:space="preserve">
|
||||
<value>Вы уверены, что хотите удалить сервер?</value>
|
||||
|
@ -286,16 +286,16 @@
|
|||
<value>Запуск сервиса ({0})...</value>
|
||||
</data>
|
||||
<data name="SuccessfulConfiguration" xml:space="preserve">
|
||||
<value>Конфигурация успешна {0}</value>
|
||||
<value>Конфигурация выполнена успешно {0}</value>
|
||||
</data>
|
||||
<data name="SuccessfullyImportedCustomServer" xml:space="preserve">
|
||||
<value>Пользовательский сервер конфигурации успешно импортирован.</value>
|
||||
<value>Пользовательская конфигурация сервера успешно импортирована</value>
|
||||
</data>
|
||||
<data name="SuccessfullyImportedServerViaClipboard" xml:space="preserve">
|
||||
<value>{0} серверов импортировано из буфера обмена.</value>
|
||||
<value>{0} серверов импортировано из буфера обмена</value>
|
||||
</data>
|
||||
<data name="SuccessfullyImportedServerViaScan" xml:space="preserve">
|
||||
<value>Сканирование URL-адреса импорта успешна.</value>
|
||||
<value>Сканирование URL-адреса импорта прошло успешно</value>
|
||||
</data>
|
||||
<data name="TestMeOutput" xml:space="preserve">
|
||||
<value>Задержка текущего сервера: {0} мс, {1}</value>
|
||||
|
@ -310,7 +310,7 @@
|
|||
<value>Вы уверены, что хотите удалить правила?</value>
|
||||
</data>
|
||||
<data name="RoutingRuleDetailRequiredTips" xml:space="preserve">
|
||||
<value>{0}, Один из обязательных..</value>
|
||||
<value>{0}: одно из обязательных полей</value>
|
||||
</data>
|
||||
<data name="LvRemarks" xml:space="preserve">
|
||||
<value>Примечания</value>
|
||||
|
@ -322,13 +322,13 @@
|
|||
<value>Количество</value>
|
||||
</data>
|
||||
<data name="MsgNeedUrl" xml:space="preserve">
|
||||
<value>Пожалуйста, заполните адрес (Url)</value>
|
||||
<value>Введите URL-адрес</value>
|
||||
</data>
|
||||
<data name="AddBatchRoutingRulesYesNo" xml:space="preserve">
|
||||
<value>Вы хотите добавить правила? Выберите «Да», чтобы добавить, выберите иное, чтобы заменить</value>
|
||||
<value>Хотите добавить правила? Выберите «Да» для добавления или «Нет» для замены</value>
|
||||
</data>
|
||||
<data name="MsgDownloadGeoFileSuccessfully" xml:space="preserve">
|
||||
<value>Загрузка GeoFile: {0} успешна</value>
|
||||
<value>Загрузка GeoFile {0} прошла успешно</value>
|
||||
</data>
|
||||
<data name="MsgInformationTitle" xml:space="preserve">
|
||||
<value>Информация</value>
|
||||
|
@ -337,49 +337,49 @@
|
|||
<value>Пользовательская иконка</value>
|
||||
</data>
|
||||
<data name="FillCorrectDNSText" xml:space="preserve">
|
||||
<value>Пожалуйста, заполните правильный пользовательский DNS</value>
|
||||
<value>Введите корректный пользовательский DNS</value>
|
||||
</data>
|
||||
<data name="TransportPathTip1" xml:space="preserve">
|
||||
<value>*ws путь</value>
|
||||
<value>*WebSocket-путь</value>
|
||||
</data>
|
||||
<data name="TransportPathTip2" xml:space="preserve">
|
||||
<value>*h2 путь</value>
|
||||
<value>*HTTP2-путь</value>
|
||||
</data>
|
||||
<data name="TransportPathTip3" xml:space="preserve">
|
||||
<value>*QUIC ключ/Kcp seed</value>
|
||||
<value>*QUIC-ключ / KCP-seed</value>
|
||||
</data>
|
||||
<data name="TransportPathTip4" xml:space="preserve">
|
||||
<value>*grpc serviceName</value>
|
||||
<value>Имя сервиса *gRPC</value>
|
||||
</data>
|
||||
<data name="TransportRequestHostTip1" xml:space="preserve">
|
||||
<value>*http хосты разделенные запятыми (,)</value>
|
||||
<value>*http-хосты, разделённые запятыми (,)</value>
|
||||
</data>
|
||||
<data name="TransportRequestHostTip2" xml:space="preserve">
|
||||
<value>*ws хост</value>
|
||||
<value>*WebSocket-хост</value>
|
||||
</data>
|
||||
<data name="TransportRequestHostTip3" xml:space="preserve">
|
||||
<value>*h2 хосты разделенные запятыми (,)</value>
|
||||
<value>*HTTP2-хосты, разделённые запятыми (,)</value>
|
||||
</data>
|
||||
<data name="TransportRequestHostTip4" xml:space="preserve">
|
||||
<value>* безопасность QUIC</value>
|
||||
<value>Безопасность *QUIC</value>
|
||||
</data>
|
||||
<data name="TransportHeaderTypeTip1" xml:space="preserve">
|
||||
<value>* тип TCP камуфляжа</value>
|
||||
<value>Тип *TCP-камуфляжа</value>
|
||||
</data>
|
||||
<data name="TransportHeaderTypeTip2" xml:space="preserve">
|
||||
<value>* тип KCP камуфляжа</value>
|
||||
<value>Тип *KCP-камуфляжа</value>
|
||||
</data>
|
||||
<data name="TransportHeaderTypeTip3" xml:space="preserve">
|
||||
<value>* тип QUIC камуфляжа</value>
|
||||
<value>Тип *QUIC-камуфляжа</value>
|
||||
</data>
|
||||
<data name="TransportHeaderTypeTip4" xml:space="preserve">
|
||||
<value>* режим grpc</value>
|
||||
<value>Режим *gRPC</value>
|
||||
</data>
|
||||
<data name="LvTLS" xml:space="preserve">
|
||||
<value>TLS</value>
|
||||
</data>
|
||||
<data name="TransportPathTip5" xml:space="preserve">
|
||||
<value>*Kcp seed</value>
|
||||
<value>*KCP-seed</value>
|
||||
</data>
|
||||
<data name="RegisterGlobalHotkeyFailed" xml:space="preserve">
|
||||
<value>Не удалось зарегистрировать глобальную горячую клавишу {0}, причина: {1}</value>
|
||||
|
@ -391,10 +391,10 @@
|
|||
<value>Разгруппировано</value>
|
||||
</data>
|
||||
<data name="AllGroupServers" xml:space="preserve">
|
||||
<value>Все сервера</value>
|
||||
<value>Все серверы</value>
|
||||
</data>
|
||||
<data name="FillServerAddressCustom" xml:space="preserve">
|
||||
<value>Пожалуйста, просмотрите, чтобы импортировать конфигурацию сервера</value>
|
||||
<value>Выберите файл конфигурации сервера для импорта</value>
|
||||
</data>
|
||||
<data name="Speedtesting" xml:space="preserve">
|
||||
<value>Тестирование...</value>
|
||||
|
@ -436,7 +436,7 @@
|
|||
<value>Настройки маршрутизации</value>
|
||||
</data>
|
||||
<data name="menuServers" xml:space="preserve">
|
||||
<value>Сервера</value>
|
||||
<value>Серверы</value>
|
||||
</data>
|
||||
<data name="menuSetting" xml:space="preserve">
|
||||
<value>Настройки</value>
|
||||
|
@ -445,7 +445,7 @@
|
|||
<value>Обновить текущую подписку без прокси</value>
|
||||
</data>
|
||||
<data name="menuSubGroupUpdateViaProxy" xml:space="preserve">
|
||||
<value>Обновить текущую подписку с прокси</value>
|
||||
<value>Обновить подписку через прокси</value>
|
||||
</data>
|
||||
<data name="menuSubscription" xml:space="preserve">
|
||||
<value>Группа подписки</value>
|
||||
|
@ -571,7 +571,7 @@
|
|||
<value>Сортировка</value>
|
||||
</data>
|
||||
<data name="LvUserAgent" xml:space="preserve">
|
||||
<value>User Agent</value>
|
||||
<value>Заголовок User-Agent</value>
|
||||
</data>
|
||||
<data name="TbCancel" xml:space="preserve">
|
||||
<value>Отмена</value>
|
||||
|
@ -628,7 +628,7 @@
|
|||
<value>TLS</value>
|
||||
</data>
|
||||
<data name="TipNetwork" xml:space="preserve">
|
||||
<value>* По-умолчанию TCP</value>
|
||||
<value>*По-умолчанию TCP</value>
|
||||
</data>
|
||||
<data name="TbCoreType" xml:space="preserve">
|
||||
<value>Ядро</value>
|
||||
|
@ -658,10 +658,10 @@
|
|||
<value>Шифрования</value>
|
||||
</data>
|
||||
<data name="TbPreSocksPort" xml:space="preserve">
|
||||
<value>txtPreSocksPort</value>
|
||||
<value>Порт SOCKS</value>
|
||||
</data>
|
||||
<data name="TipPreSocksPort" xml:space="preserve">
|
||||
<value>* После установки этого значения служба socks будет запущена с использованием Xray/sing-box(Tun) для обеспечения таких функций, как отображение скорости</value>
|
||||
<value>* После установки этого значения служба SOCKS будет запущена с использованием Xray/sing-box(TUN) для обеспечения таких функций, как отображение скорости</value>
|
||||
</data>
|
||||
<data name="TbBrowse" xml:space="preserve">
|
||||
<value>Просмотр</value>
|
||||
|
@ -697,7 +697,7 @@
|
|||
<value>Разрешить небезопасные</value>
|
||||
</data>
|
||||
<data name="TbSettingsDomainStrategy4Freedom" xml:space="preserve">
|
||||
<value>Outbound Freedom domainStrategy</value>
|
||||
<value>«Freedom»: стратегия обработки доменов исходящего трафика</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableAutoAdjustMainLvColWidth" xml:space="preserve">
|
||||
<value>Автоматически регулировать ширину столбца после обновления подписки</value>
|
||||
|
@ -712,7 +712,7 @@
|
|||
<value>Исключение. Не используйте прокси-сервер для адресов, начинающихся с (,), используйте точку с запятой (;)</value>
|
||||
</data>
|
||||
<data name="TbSettingsDisplayRealTimeSpeed" xml:space="preserve">
|
||||
<value>Display real-time speed</value>
|
||||
<value>Показывать скорость в реальном времени</value>
|
||||
</data>
|
||||
<data name="TbSettingsKeepOlderDedupl" xml:space="preserve">
|
||||
<value>Сохранить старые при удалении дублей</value>
|
||||
|
@ -721,10 +721,10 @@
|
|||
<value>Записывать логи</value>
|
||||
</data>
|
||||
<data name="TbSettingsLogLevel" xml:space="preserve">
|
||||
<value>Уровень логгирования</value>
|
||||
<value>Уровень записи логов</value>
|
||||
</data>
|
||||
<data name="TbSettingsMuxEnabled" xml:space="preserve">
|
||||
<value>Включите мультиплексирование Mux</value>
|
||||
<value>Включить мультиплексирование Mux</value>
|
||||
</data>
|
||||
<data name="TbSettingsN" xml:space="preserve">
|
||||
<value>Настройки v2rayN</value>
|
||||
|
@ -733,16 +733,16 @@
|
|||
<value>Пароль аутентификации</value>
|
||||
</data>
|
||||
<data name="TbSettingsRemoteDNS" xml:space="preserve">
|
||||
<value>Пользовательский DNS (если несколько делите с запятыми (,))</value>
|
||||
<value>Пользовательский DNS (если несколько, то делите запятыми (,))</value>
|
||||
</data>
|
||||
<data name="TbSettingsSetUWP" xml:space="preserve">
|
||||
<value>Set Win10 UWP Loopback</value>
|
||||
<value>Разрешить loopback для приложений UWP (Win10)</value>
|
||||
</data>
|
||||
<data name="TbSettingsSniffingEnabled" xml:space="preserve">
|
||||
<value>Включить сниффинг</value>
|
||||
</data>
|
||||
<data name="TbSettingsSocksPort" xml:space="preserve">
|
||||
<value>Mixed Port</value>
|
||||
<value>Смешанный порт</value>
|
||||
</data>
|
||||
<data name="TbSettingsStartBoot" xml:space="preserve">
|
||||
<value>Автозапуск</value>
|
||||
|
@ -751,7 +751,7 @@
|
|||
<value>Включить статистику (требуется перезагрузка)</value>
|
||||
</data>
|
||||
<data name="TbSettingsSubConvert" xml:space="preserve">
|
||||
<value>URL-адрес конверсии подписки</value>
|
||||
<value>URL конвертации подписок</value>
|
||||
</data>
|
||||
<data name="TbSettingsSystemproxy" xml:space="preserve">
|
||||
<value>Настройки системного прокси</value>
|
||||
|
@ -766,7 +766,7 @@
|
|||
<value>Включить UDP</value>
|
||||
</data>
|
||||
<data name="TbSettingsUser" xml:space="preserve">
|
||||
<value>Auth user</value>
|
||||
<value>Имя пользователя (логин)</value>
|
||||
</data>
|
||||
<data name="TbClearSystemProxy" xml:space="preserve">
|
||||
<value>Очистить системный прокси</value>
|
||||
|
@ -816,6 +816,9 @@
|
|||
<data name="menuMoveUp" xml:space="preserve">
|
||||
<value>Вверх (U)</value>
|
||||
</data>
|
||||
<data name="menuMoveTo" xml:space="preserve">
|
||||
<value>Переместить вверх/вниз</value>
|
||||
</data>
|
||||
<data name="MsgFilterTitle" xml:space="preserve">
|
||||
<value>Фильтр, поддерживает regex</value>
|
||||
</data>
|
||||
|
@ -850,13 +853,13 @@
|
|||
<value>2.Прямой домен или IP</value>
|
||||
</data>
|
||||
<data name="TbRoutingTabProxy" xml:space="preserve">
|
||||
<value>1.Прокси-домен или IP</value>
|
||||
<value>1.Прокси домен или IP</value>
|
||||
</data>
|
||||
<data name="TbRoutingTabRuleList" xml:space="preserve">
|
||||
<value>Предустановленный список наборов правил</value>
|
||||
</data>
|
||||
<data name="TbRoutingTips" xml:space="preserve">
|
||||
<value>* Установите правила, разделенные запятыми (,); Запятая в регулярке заменена на <COMMA></value>
|
||||
<value>*Разделяйте правила запятыми (,). Литерал «,» — <COMMA>. Префикс # — отключить правило</value>
|
||||
</data>
|
||||
<data name="menuImportRulesFromClipboard" xml:space="preserve">
|
||||
<value>Импорт правил из буфера обмена</value>
|
||||
|
@ -883,16 +886,16 @@
|
|||
<value>Удалить правила (Delete)</value>
|
||||
</data>
|
||||
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
|
||||
<value>RoutingRuleDetailsSetting</value>
|
||||
<value>Детальные настройки правил маршрутизации</value>
|
||||
</data>
|
||||
<data name="TbAutoSort" xml:space="preserve">
|
||||
<value>Домен и IP автоматически сортируются при сохранении</value>
|
||||
</data>
|
||||
<data name="TbRuleobjectDoc" xml:space="preserve">
|
||||
<value>Ruleobject Doc</value>
|
||||
<value>Документация RuleObject</value>
|
||||
</data>
|
||||
<data name="TbDnsObjectDoc" xml:space="preserve">
|
||||
<value>Поддержка DnsObject</value>
|
||||
<value>Поддерживаются DNS-объекты, нажмите для просмотра документации</value>
|
||||
</data>
|
||||
<data name="SubUrlTips" xml:space="preserve">
|
||||
<value>Необязательное поле</value>
|
||||
|
@ -913,16 +916,16 @@
|
|||
<value>Тест задержки и скорости всех серверов (Ctrl+E)</value>
|
||||
</data>
|
||||
<data name="LvTestDelay" xml:space="preserve">
|
||||
<value>Задержка (ms)</value>
|
||||
<value>Задержка (мс)</value>
|
||||
</data>
|
||||
<data name="LvTestSpeed" xml:space="preserve">
|
||||
<value>Скорость (M/s)</value>
|
||||
<value>Скорость (МБ/с)</value>
|
||||
</data>
|
||||
<data name="FailedToRunCore" xml:space="preserve">
|
||||
<value>Не удалось запустить ядро, посмотрите логи</value>
|
||||
</data>
|
||||
<data name="LvFilter" xml:space="preserve">
|
||||
<value>Remarks regular filter</value>
|
||||
<value>Фильтр примечаний (Regex)</value>
|
||||
</data>
|
||||
<data name="TbDisplayLog" xml:space="preserve">
|
||||
<value>Показать логи</value>
|
||||
|
@ -934,7 +937,7 @@
|
|||
<value>Новый порт для локальной сети</value>
|
||||
</data>
|
||||
<data name="TbSettingsTunMode" xml:space="preserve">
|
||||
<value>Настройки TunMode</value>
|
||||
<value>Настройки режима TUN</value>
|
||||
</data>
|
||||
<data name="menuMoveToGroup" xml:space="preserve">
|
||||
<value>Перейти в группу</value>
|
||||
|
@ -958,22 +961,22 @@
|
|||
<value>Тест завершен</value>
|
||||
</data>
|
||||
<data name="TbSettingsDefFingerprint" xml:space="preserve">
|
||||
<value>TLS отпечаток по умлочанию</value>
|
||||
<value>TLS отпечаток по умолчанию</value>
|
||||
</data>
|
||||
<data name="TbSettingsDefUserAgent" xml:space="preserve">
|
||||
<value>User-Agent</value>
|
||||
</data>
|
||||
<data name="TbSettingsDefUserAgentTips" xml:space="preserve">
|
||||
<value>Этот параметр действителен только для TCP/HTTP и WS</value>
|
||||
<value>Параметр действует только для TCP/HTTP и WebSocket (WS)</value>
|
||||
</data>
|
||||
<data name="TbSettingsCurrentFontFamily" xml:space="preserve">
|
||||
<value>Шрифт (требуется перезагрузка)</value>
|
||||
</data>
|
||||
<data name="TbSettingsCurrentFontFamilyTip" xml:space="preserve">
|
||||
<value>Скопируйте файл шрифта TTF/TTC в каталог guiFonts, перезапустите настройки</value>
|
||||
<value>Скопируйте файл шрифта TTF/TTC в каталог guiFonts и заново откройте окно настроек</value>
|
||||
</data>
|
||||
<data name="TbSettingsSocksPortTip" xml:space="preserve">
|
||||
<value>Pac порт = +3; Xray API порт = +4; mihomo API порт = +5;</value>
|
||||
<value>Pac порт = +3,Xray API порт = +4, mihomo API порт = +5</value>
|
||||
</data>
|
||||
<data name="TbSettingsStartBootTip" xml:space="preserve">
|
||||
<value>Установите это с правами администратора</value>
|
||||
|
@ -982,13 +985,10 @@
|
|||
<value>Размер шрифта</value>
|
||||
</data>
|
||||
<data name="TbSettingsSpeedTestTimeout" xml:space="preserve">
|
||||
<value>Таймаут одиночного спидтеста</value>
|
||||
<value>Тайм-аут одиночного тестирования скорости</value>
|
||||
</data>
|
||||
<data name="TbSettingsSpeedTestUrl" xml:space="preserve">
|
||||
<value>URL спидтеста</value>
|
||||
</data>
|
||||
<data name="menuMoveTo" xml:space="preserve">
|
||||
<value>Move up and down</value>
|
||||
<value>URL для тестирования скорости</value>
|
||||
</data>
|
||||
<data name="TbPublicKey" xml:space="preserve">
|
||||
<value>PublicKey</value>
|
||||
|
@ -1003,19 +1003,19 @@
|
|||
<value>Включить аппаратное ускорение (требуется перезагрузка)</value>
|
||||
</data>
|
||||
<data name="SpeedtestingWait" xml:space="preserve">
|
||||
<value>Ожидание тестирования (нажмите ESC для отмены)...</value>
|
||||
<value>Ожидание тестирования (нажмите ESC для отмены)…</value>
|
||||
</data>
|
||||
<data name="TipDisplayLog" xml:space="preserve">
|
||||
<value>Please turn off when there is an abnormal disconnection</value>
|
||||
<value>Отключите при аномальном разрыве соединения</value>
|
||||
</data>
|
||||
<data name="MsgSkipSubscriptionUpdate" xml:space="preserve">
|
||||
<value>Updates are not enabled, skip this subscription</value>
|
||||
<value>Обновления не включены — подписка пропущена</value>
|
||||
</data>
|
||||
<data name="menuRebootAsAdmin" xml:space="preserve">
|
||||
<value>Перезагрузить как администратор</value>
|
||||
</data>
|
||||
<data name="LvMoreUrl" xml:space="preserve">
|
||||
<value>More URLs, separated by commas; Subscription conversion will be invalid</value>
|
||||
<value>Дополнительные URL через запятую, конвертация подписки недоступна</value>
|
||||
</data>
|
||||
<data name="SpeedDisplayText" xml:space="preserve">
|
||||
<value>{0} : {1}/s↑ | {2}/s↓</value>
|
||||
|
@ -1024,13 +1024,13 @@
|
|||
<value>Интервал автоматического обновления в минутах</value>
|
||||
</data>
|
||||
<data name="TbSettingsLogEnabledToFile" xml:space="preserve">
|
||||
<value>Включить логгирование в файл</value>
|
||||
<value>Включить запись логов в файл</value>
|
||||
</data>
|
||||
<data name="LvConvertTarget" xml:space="preserve">
|
||||
<value>Преобразовать тип цели</value>
|
||||
</data>
|
||||
<data name="LvConvertTargetTip" xml:space="preserve">
|
||||
<value>Если преобразование не требуется, оставьте поле пустым.</value>
|
||||
<value>Если преобразование не требуется, оставьте поле пустым</value>
|
||||
</data>
|
||||
<data name="menuDNSSetting" xml:space="preserve">
|
||||
<value>Настройки DNS</value>
|
||||
|
@ -1039,7 +1039,7 @@
|
|||
<value>Настройки DNS sing-box</value>
|
||||
</data>
|
||||
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
|
||||
<value>Пожалуйста, заполните структуру DNS. Нажмите, чтобы просмотреть документ.</value>
|
||||
<value>Заполните структуру DNS, нажмите, чтобы открыть документ</value>
|
||||
</data>
|
||||
<data name="TbSettingDnsImportDefConfig" xml:space="preserve">
|
||||
<value>Нажмите, чтобы импортировать конфигурацию DNS по умолчанию</value>
|
||||
|
@ -1048,88 +1048,88 @@
|
|||
<value>Стратегия домена для sing-box</value>
|
||||
</data>
|
||||
<data name="TbSettingsMux4SboxProtocol" xml:space="preserve">
|
||||
<value>sing-box Mux Protocol</value>
|
||||
<value>Протокол Mux для sing-box</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||
<value>Full process name (Tun mode)</value>
|
||||
<value>Полное имя процесса (режим TUN)</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||
<value>IP or IP CIDR</value>
|
||||
<value>IP-адрес или сеть CIDR</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleDomain" xml:space="preserve">
|
||||
<value>Домен</value>
|
||||
</data>
|
||||
<data name="menuAddHysteria2Server" xml:space="preserve">
|
||||
<value>Добавить [Hysteria2] сервер</value>
|
||||
<value>Добавить сервер [Hysteria2]</value>
|
||||
</data>
|
||||
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
|
||||
<value>Hysteria Max bandwidth (Up/Dw)</value>
|
||||
<value>Максимальная пропускная способность Hysteria (загрузка/отдача)</value>
|
||||
</data>
|
||||
<data name="TbSettingsUseSystemHosts" xml:space="preserve">
|
||||
<value>Использовать системные узлы</value>
|
||||
</data>
|
||||
<data name="menuAddTuicServer" xml:space="preserve">
|
||||
<value>Добавить [TUIC] сервер</value>
|
||||
<value>Добавить сервер [TUIC]</value>
|
||||
</data>
|
||||
<data name="TbHeaderType8" xml:space="preserve">
|
||||
<value>Контроль перегрузок</value>
|
||||
</data>
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<value>Previous proxy remarks</value>
|
||||
<value>Примечания к предыдущему прокси</value>
|
||||
</data>
|
||||
<data name="LvNextProfile" xml:space="preserve">
|
||||
<value>Next proxy remarks</value>
|
||||
<value>Примечания к следующему прокси</value>
|
||||
</data>
|
||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||
<value>Пожалуйста, убедитесь, что примечание существует и является уникальным.</value>
|
||||
<value>Убедитесь, что примечание существует и является уникальным</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||
<value>Enable additional Inbound</value>
|
||||
<value>Включить дополнительный входящий канал</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||
<value>Включить IPv6 адреса</value>
|
||||
</data>
|
||||
<data name="menuAddWireguardServer" xml:space="preserve">
|
||||
<value>Добавить [WireGuard] сервер</value>
|
||||
<value>Добавить сервер [WireGuard]</value>
|
||||
</data>
|
||||
<data name="TbPrivateKey" xml:space="preserve">
|
||||
<value>PrivateKey</value>
|
||||
<value>Приватный ключ</value>
|
||||
</data>
|
||||
<data name="TbReserved" xml:space="preserve">
|
||||
<value>Reserved(2,3,4)</value>
|
||||
<value>Зарезервировано (2, 3, 4)</value>
|
||||
</data>
|
||||
<data name="TbLocalAddress" xml:space="preserve">
|
||||
<value>Адрес(Ipv4,Ipv6)</value>
|
||||
<value>Адрес (Ipv4,Ipv6)</value>
|
||||
</data>
|
||||
<data name="TbPath7" xml:space="preserve">
|
||||
<value>obfs password</value>
|
||||
<value>Пароль obfs</value>
|
||||
</data>
|
||||
<data name="TbRuleMatchingTips" xml:space="preserve">
|
||||
<value>(Domain or IP or ProcName) and Port and Protocol and InboundTag => OutboundTag</value>
|
||||
<value>(Домен или IP или имя процесса) и порт, и протокол, и InboundTag => OutboundTag</value>
|
||||
</data>
|
||||
<data name="TbAutoScrollToEnd" xml:space="preserve">
|
||||
<value>Автоматическая прокрутка в конец</value>
|
||||
</data>
|
||||
<data name="TbSettingsSpeedPingTestUrl" xml:space="preserve">
|
||||
<value>URL-адрес для проверки скорости пинга</value>
|
||||
<value>URL для быстрой проверки реальной задержки</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableUpdateSubOnlyRemarksExist" xml:space="preserve">
|
||||
<value>Updating subscription, only determine remarks exists</value>
|
||||
<value>Обновляя подписку, проверять лишь наличие примечаний</value>
|
||||
</data>
|
||||
<data name="SpeedtestingStop" xml:space="preserve">
|
||||
<value>Отмена тестирования...</value>
|
||||
</data>
|
||||
<data name="TransportRequestHostTip5" xml:space="preserve">
|
||||
<value>*grpc Authority</value>
|
||||
<value>*gRPC Authority</value>
|
||||
</data>
|
||||
<data name="menuAddHttpServer" xml:space="preserve">
|
||||
<value>Добавить [HTTP] сервер</value>
|
||||
<value>Добавить сервер [HTTP]</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
||||
<value>Use Xray and enable non-Tun mode, which conflicts with the group previous proxy</value>
|
||||
<value>Используйте Xray и отключите режим TUN, так как он конфликтует с предыдущим прокси-сервером группы</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Включить фрагмент</value>
|
||||
<value>Включить фрагментацию (Fragment)</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Включить файл кэша для sing-box (файлы наборов правил)</value>
|
||||
|
@ -1138,7 +1138,7 @@
|
|||
<value>Пользовательский набор правил для sing-box</value>
|
||||
</data>
|
||||
<data name="NeedRebootTips" xml:space="preserve">
|
||||
<value>Successful operation. Click the settings menu to reboot the app.</value>
|
||||
<value>Операция успешна. Перезапустите приложение</value>
|
||||
</data>
|
||||
<data name="menuOpenTheFileLocation" xml:space="preserve">
|
||||
<value>Открыть место хранения</value>
|
||||
|
@ -1147,7 +1147,7 @@
|
|||
<value>Сортировка</value>
|
||||
</data>
|
||||
<data name="TbSortingChain" xml:space="preserve">
|
||||
<value>Chain</value>
|
||||
<value>Цепочка</value>
|
||||
</data>
|
||||
<data name="TbSortingDefault" xml:space="preserve">
|
||||
<value>По умолчанию</value>
|
||||
|
@ -1159,7 +1159,7 @@
|
|||
<value>Скорость загрузки</value>
|
||||
</data>
|
||||
<data name="TbSortingDownTraffic" xml:space="preserve">
|
||||
<value>Download Traffic</value>
|
||||
<value>Скачанный трафик</value>
|
||||
</data>
|
||||
<data name="TbSortingHost" xml:space="preserve">
|
||||
<value>Узел</value>
|
||||
|
@ -1177,10 +1177,10 @@
|
|||
<value>Тип</value>
|
||||
</data>
|
||||
<data name="TbSortingUpSpeed" xml:space="preserve">
|
||||
<value>Скорость загрузки</value>
|
||||
<value>Скорость отдачи</value>
|
||||
</data>
|
||||
<data name="TbSortingUpTraffic" xml:space="preserve">
|
||||
<value>Upload Traffic</value>
|
||||
<value>Отправленный трафик</value>
|
||||
</data>
|
||||
<data name="TbConnections" xml:space="preserve">
|
||||
<value>Соединения</value>
|
||||
|
@ -1198,13 +1198,13 @@
|
|||
<value>Режим правила</value>
|
||||
</data>
|
||||
<data name="menuModeDirect" xml:space="preserve">
|
||||
<value>Direct</value>
|
||||
<value>Прямое соединение</value>
|
||||
</data>
|
||||
<data name="menuModeGlobal" xml:space="preserve">
|
||||
<value>Global</value>
|
||||
<value>Глобальный режим</value>
|
||||
</data>
|
||||
<data name="menuModeNothing" xml:space="preserve">
|
||||
<value>Do not change</value>
|
||||
<value>Не менять</value>
|
||||
</data>
|
||||
<data name="menuModeRule" xml:space="preserve">
|
||||
<value>Правила</value>
|
||||
|
@ -1213,13 +1213,13 @@
|
|||
<value>Тест задержки</value>
|
||||
</data>
|
||||
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
||||
<value>Part Node Latency Test</value>
|
||||
<value>Тест задержки выбранных узлов</value>
|
||||
</data>
|
||||
<data name="menuProxiesReload" xml:space="preserve">
|
||||
<value>Обновить прокси</value>
|
||||
</data>
|
||||
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
||||
<value>Активировать узел (Enter)</value>
|
||||
<value>Сделать узел активным (Enter)</value>
|
||||
</data>
|
||||
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
||||
<value>Стратегия домена по умолчанию для исходящих</value>
|
||||
|
@ -1234,7 +1234,7 @@
|
|||
<value>Автоматическая регулировка ширины столбца</value>
|
||||
</data>
|
||||
<data name="menuExport2ShareUrlBase64" xml:space="preserve">
|
||||
<value>Export Base64-encoded Share Links to Clipboard</value>
|
||||
<value>Экспорт ссылок в формате Base64 в буфер обмена</value>
|
||||
</data>
|
||||
<data name="menuExport2ClientConfigClipboard" xml:space="preserve">
|
||||
<value>Экспортировать выбранный сервер для полной конфигурации в буфер обмена</value>
|
||||
|
@ -1243,7 +1243,7 @@
|
|||
<value>Показать или скрыть главное окно</value>
|
||||
</data>
|
||||
<data name="TbPreSocksPort4Sub" xml:space="preserve">
|
||||
<value>Пользовательская конфигурация порта socks</value>
|
||||
<value>Пользовательская конфигурация порта SOCKS</value>
|
||||
</data>
|
||||
<data name="menuBackupAndRestore" xml:space="preserve">
|
||||
<value>Резервное копирование и восстановление</value>
|
||||
|
@ -1255,28 +1255,28 @@
|
|||
<value>Восстановить из файла</value>
|
||||
</data>
|
||||
<data name="menuRemoteBackup" xml:space="preserve">
|
||||
<value>Backup to remote (WebDAV)</value>
|
||||
<value>Резервное копирование на удалённый сервер (WebDAV)</value>
|
||||
</data>
|
||||
<data name="menuRemoteRestore" xml:space="preserve">
|
||||
<value>Restore from remote (WebDAV)</value>
|
||||
<value>Восстановить с удалённого сервера (WebDAV)</value>
|
||||
</data>
|
||||
<data name="menuLocalBackupAndRestore" xml:space="preserve">
|
||||
<value>Local</value>
|
||||
<value>Локально</value>
|
||||
</data>
|
||||
<data name="menuRemoteBackupAndRestore" xml:space="preserve">
|
||||
<value>Remote (WebDAV)</value>
|
||||
<value>Удалённо (WebDAV)</value>
|
||||
</data>
|
||||
<data name="LvWebDavUrl" xml:space="preserve">
|
||||
<value>WebDav Url</value>
|
||||
<value>URL WebDAV</value>
|
||||
</data>
|
||||
<data name="LvWebDavUserName" xml:space="preserve">
|
||||
<value>WebDav User Name</value>
|
||||
<value>Имя пользователя WebDAV</value>
|
||||
</data>
|
||||
<data name="LvWebDavPassword" xml:space="preserve">
|
||||
<value>WebDav Password</value>
|
||||
<value>Пароль WebDAV</value>
|
||||
</data>
|
||||
<data name="LvWebDavCheck" xml:space="preserve">
|
||||
<value>WebDav Check</value>
|
||||
<value>Проверить WebDAV</value>
|
||||
</data>
|
||||
<data name="LvWebDavDirName" xml:space="preserve">
|
||||
<value>Имя удаленной папки (необязательно)</value>
|
||||
|
@ -1285,16 +1285,16 @@
|
|||
<value>Неверный файл резервной копии</value>
|
||||
</data>
|
||||
<data name="ConnectionsHostFilterTitle" xml:space="preserve">
|
||||
<value>Host filter</value>
|
||||
<value>Фильтр хостов</value>
|
||||
</data>
|
||||
<data name="TipActiveServer" xml:space="preserve">
|
||||
<value>Active</value>
|
||||
<value>Активный</value>
|
||||
</data>
|
||||
<data name="TbSettingsGeoFilesSource" xml:space="preserve">
|
||||
<value>Источник geo файлов</value>
|
||||
<value>Источник файлов Geo (необязательно)</value>
|
||||
</data>
|
||||
<data name="TbSettingsSrsFilesSource" xml:space="preserve">
|
||||
<value>Источник sing-box srs файлов</value>
|
||||
<value>Источник файлов наборов правил sing-box (необязательно)</value>
|
||||
</data>
|
||||
<data name="UpgradeAppNotExistTip" xml:space="preserve">
|
||||
<value>Программы для обновления не существует</value>
|
||||
|
@ -1315,7 +1315,7 @@
|
|||
<value>Иран</value>
|
||||
</data>
|
||||
<data name="TbSettingsChinaUserTip" xml:space="preserve">
|
||||
<value>Используйте Настройки -> Региональные пресеты вместо изменения этого поля</value>
|
||||
<value>Пользователи из Китая могут пропустить этот пункт</value>
|
||||
</data>
|
||||
<data name="menuAddServerViaImage" xml:space="preserve">
|
||||
<value>Сканировать QR-код с изображения</value>
|
||||
|
@ -1324,7 +1324,7 @@
|
|||
<value>Неверный адрес (Url)</value>
|
||||
</data>
|
||||
<data name="InsecureUrlProtocol" xml:space="preserve">
|
||||
<value>Пожалуйста, не используйте небезопасный адрес подписки по протоколу HTTP.</value>
|
||||
<value>Не используйте небезопасный адрес подписки по протоколу HTTP</value>
|
||||
</data>
|
||||
<data name="TbSettingsCurrentFontFamilyLinuxTip" xml:space="preserve">
|
||||
<value>Установите шрифт в систему и перезапустите настройки</value>
|
||||
|
@ -1333,43 +1333,43 @@
|
|||
<value>Вы уверены, что хотите выйти?</value>
|
||||
</data>
|
||||
<data name="LvMemo" xml:space="preserve">
|
||||
<value>Remarks Memo</value>
|
||||
<value>Заметка (Memo)</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPassword" xml:space="preserve">
|
||||
<value>System sudo password</value>
|
||||
<value>Пароль sudo системы</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
||||
<value>Пароль зашифрован и хранится только в локальных файлах..</value>
|
||||
<value>Введенный вами пароль не может быть подтвержден, поэтому убедитесь, что вы ввели его правильно. Если приложение не работает должным образом из-за неправильного ввода, то перезапустите его. Пароль не будет сохранен, и вам придется вводить его заново после каждого перезапуска</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
|
||||
<value>Please set the sudo password in Tun mode settings first</value>
|
||||
<value>Сначала задайте пароль sudo в настройках TUN-режима</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordNotSudoRunApp" xml:space="preserve">
|
||||
<value>Пожалуйста, не запускайте программу как суперпользователь.</value>
|
||||
<value>Не запускайте программу как суперпользователь</value>
|
||||
</data>
|
||||
<data name="TransportHeaderTypeTip5" xml:space="preserve">
|
||||
<value>*xhttp mode</value>
|
||||
<value>*XHTTP-режим</value>
|
||||
</data>
|
||||
<data name="TransportExtraTip" xml:space="preserve">
|
||||
<value>XHTTP Extra raw JSON, format: { XHTTPObject }</value>
|
||||
<value>Дополнительный XHTTP сырой JSON, формат: { XHTTPObject }</value>
|
||||
</data>
|
||||
<data name="TbSettingsHide2TrayWhenClose" xml:space="preserve">
|
||||
<value>Скрыть в трее при закрытии окна</value>
|
||||
</data>
|
||||
<data name="TbSettingsMixedConcurrencyCount" xml:space="preserve">
|
||||
<value>The number of concurrent during multi-test</value>
|
||||
<value>Количество одновременно выполняемых тестов при многоэтапном тестировании</value>
|
||||
</data>
|
||||
<data name="TbSettingsExceptionTip2" xml:space="preserve">
|
||||
<value>Исключение. Не используйте прокси-сервер для адресов с запятой (,)</value>
|
||||
</data>
|
||||
<data name="TbSettingsDestOverride" xml:space="preserve">
|
||||
<value>Sniffing type</value>
|
||||
<value>Тип сниффинга</value>
|
||||
</data>
|
||||
<data name="TbSettingsSecondLocalPortEnabled" xml:space="preserve">
|
||||
<value>Enable second mixed port</value>
|
||||
<value>Включить второй смешанный порт</value>
|
||||
</data>
|
||||
<data name="TbRoutingInboundTagTips" xml:space="preserve">
|
||||
<value>socks: local port, socks2: second local port, socks3: LAN port</value>
|
||||
<value>socks: локальный порт, socks2: второй локальный порт, socks3: LAN порт</value>
|
||||
</data>
|
||||
<data name="TbSettingsTheme" xml:space="preserve">
|
||||
<value>Темы</value>
|
||||
|
@ -1378,7 +1378,7 @@
|
|||
<value>Копировать команду прокси в буфер обмена</value>
|
||||
</data>
|
||||
<data name="SpeedtestingTestFailedPart" xml:space="preserve">
|
||||
<value>Starting retesting failed parts, {0} remaining. Press ESC to terminate...</value>
|
||||
<value>Повторное тестирование неудачных элементов, осталось {0}. Нажмите ESC для остановки…</value>
|
||||
</data>
|
||||
<data name="menuTestServerResult" xml:space="preserve">
|
||||
<value>По результату теста</value>
|
||||
|
@ -1387,33 +1387,36 @@
|
|||
<value>Удалить недействительные по результатам теста</value>
|
||||
</data>
|
||||
<data name="RemoveInvalidServerResultTip" xml:space="preserve">
|
||||
<value>Удалено {0} недействительных.</value>
|
||||
<value>Удалено {0} недействительных</value>
|
||||
</data>
|
||||
<data name="TbPorts7" xml:space="preserve">
|
||||
<value>Диапазон портов сервера</value>
|
||||
</data>
|
||||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>Will cover the port, separate with commas (,)</value>
|
||||
<value>Заменит указанный порт, перечисляйте через запятую (,)</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
|
||||
<value>Multi-server to custom configuration</value>
|
||||
<value>От мультиконфигурации к пользовательской конфигурации</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>Multi-server Random by Xray</value>
|
||||
<value>Случайный (Xray)</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>Multi-server RoundRobin by Xray</value>
|
||||
<value>Круговой (Xray)</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>Multi-server LeastPing by Xray</value>
|
||||
<value>Минимальное RTT (минимальное время туда-обратно) (Xray)</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>Multi-server LeastLoad by Xray</value>
|
||||
<value>Минимальная нагрузка (Xray)</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>Multi-server LeastPing by sing-box</value>
|
||||
<value>Минимальное RTT (минимальное время туда-обратно) (sing-box)</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>Export server</value>
|
||||
<value>Экспортировать конфигурацию</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
|
||||
<value>URL для тестирования текущего соединения</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
@ -1071,13 +1071,13 @@
|
|||
<data name="TbHeaderType8" xml:space="preserve">
|
||||
<value>拥塞控制算法</value>
|
||||
</data>
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<value>前置代理配置文件别名</value>
|
||||
</data>
|
||||
<data name="LvNextProfile" xml:space="preserve">
|
||||
<data name="LvNextProfile" xml:space="preserve">
|
||||
<value>落地代理配置文件別名</value>
|
||||
</data>
|
||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||
<value>请确保配置文件别名存在并唯一</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||
|
@ -1336,7 +1336,7 @@
|
|||
<value>系统的 sudo 密码</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
||||
<value>密码已加密且只存储在本地文件中,无密码则每次都要输入</value>
|
||||
<value>输入的密码无法校验,所以请确保输入正确。如果因为输入错误导致无法正常运行时,请重启本应用。 密码不会存储,每次重启后都需要再次输入。</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
|
||||
<value>请先在 Tun 模式设置中设置 sudo 密码</value>
|
||||
|
@ -1413,4 +1413,7 @@
|
|||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>导出配置文件</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
|
||||
<value>当前连接信息测试地址</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1071,13 +1071,13 @@
|
|||
<data name="TbHeaderType8" xml:space="preserve">
|
||||
<value>擁塞控制算法</value>
|
||||
</data>
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<value>前置代理設定檔別名</value>
|
||||
</data>
|
||||
<data name="LvNextProfile" xml:space="preserve">
|
||||
<data name="LvNextProfile" xml:space="preserve">
|
||||
<value>落地代理設定檔別名</value>
|
||||
</data>
|
||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||
<value>請確保設定檔別名存在並且唯一</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||
|
@ -1336,7 +1336,7 @@
|
|||
<value>系統的 sudo 密碼</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
||||
<value>密碼已加密且只儲存在本機檔案中,無密碼則每次都要輸入</value>
|
||||
<value>輸入的密碼無法校驗,所以請確保輸入正確。如果因為輸入錯誤導致無法正常運作時,請重新啟動本應用。 密碼不會存儲,每次重啟後都需要再次輸入。</value>
|
||||
</data>
|
||||
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
|
||||
<value>請先在 Tun 模式設定中設定 sudo 密碼</value>
|
||||
|
@ -1413,4 +1413,7 @@
|
|||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>匯出設定檔</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
|
||||
<value>目前連接資訊測試地址</value>
|
||||
</data>
|
||||
</root>
|
|
@ -6,11 +6,10 @@
|
|||
"servers": [
|
||||
{
|
||||
"address": "1.1.1.1",
|
||||
"skipFallback": true,
|
||||
"domains": [
|
||||
"geosite:geolocation-!cn"
|
||||
],
|
||||
"expectIPs": [
|
||||
"geoip:!cn"
|
||||
"domain:googleapis.cn",
|
||||
"domain:gstatic.com"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -23,6 +22,7 @@
|
|||
"geoip:cn"
|
||||
]
|
||||
},
|
||||
"1.1.1.1",
|
||||
"8.8.8.8",
|
||||
"https://dns.google/dns-query"
|
||||
]
|
||||
|
|
|
@ -95,6 +95,21 @@ detect_desktop_environment() {
|
|||
return
|
||||
fi
|
||||
|
||||
if [[ "$XDG_CURRENT_DESKTOP" == *"UKUI"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"ukui"* ]]; then
|
||||
echo "gnome"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$XDG_CURRENT_DESKTOP" == *"DDE"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"dde"* ]]; then
|
||||
echo "gnome"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$XDG_CURRENT_DESKTOP" == *"MATE"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"mate"* ]]; then
|
||||
echo "gnome"
|
||||
return
|
||||
fi
|
||||
|
||||
local KDE_ENVIRONMENTS=("KDE" "plasma")
|
||||
for ENV in "${KDE_ENVIRONMENTS[@]}"; do
|
||||
if [ "$XDG_CURRENT_DESKTOP" == "$ENV" ] || [ "$XDG_SESSION_DESKTOP" == "$ENV" ]; then
|
||||
|
|
|
@ -336,7 +336,7 @@ public class CoreConfigSingboxService
|
|||
await GenExperimental(singboxConfig);
|
||||
singboxConfig.outbounds.RemoveAt(0);
|
||||
|
||||
var tagProxy = new List<string>();
|
||||
var proxyProfiles = new List<ProfileItem>();
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (it.ConfigType == EConfigType.Custom)
|
||||
|
@ -370,42 +370,18 @@ public class CoreConfigSingboxService
|
|||
}
|
||||
|
||||
//outbound
|
||||
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(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;
|
||||
|
@ -775,7 +751,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 +918,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<Outbound4Sbox>(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<Outbound4Sbox>(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,31 +943,174 @@ public class CoreConfigSingboxService
|
|||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenOutboundsList(List<ProfileItem> 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<Outbound4Sbox>();
|
||||
var prevOutbounds = new List<Outbound4Sbox>(); // Separate list for prev outbounds
|
||||
var proxyTags = new List<string>(); // For selector and urltest outbounds
|
||||
|
||||
// Cache for chain proxies to avoid duplicate generation
|
||||
var nextProxyCache = new Dictionary<string, Outbound4Sbox?>();
|
||||
var prevProxyTags = new Dictionary<string, string?>(); // 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<Outbound4Sbox>(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<Outbound4Sbox>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="subItem">The subscription item containing proxy chain information.</param>
|
||||
/// <param name="outbound">The current outbound configuration. Its tag must be set before calling this method.</param>
|
||||
/// <param name="prevOutboundTag">The tag of the previous outbound in the chain, if any.</param>
|
||||
/// <param name="nextOutbound">The outbound for the next proxy in the chain, if already created. If null, will be created inside.</param>
|
||||
/// <returns>
|
||||
/// The outbound configuration for the next proxy in the chain, or null if no next proxy exists.
|
||||
/// </returns>
|
||||
private async Task<Outbound4Sbox?> 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<Outbound4Sbox>(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<int> GenRouting(SingboxConfig singboxConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dnsOutbound = "dns_out";
|
||||
if (!_config.Inbound.First().SniffingEnabled)
|
||||
{
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
port = [53],
|
||||
network = ["udp"],
|
||||
outbound = dnsOutbound
|
||||
});
|
||||
}
|
||||
|
||||
singboxConfig.route.rules.Insert(0, new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
clash_mode = ERuleMode.Direct.ToString()
|
||||
});
|
||||
singboxConfig.route.rules.Insert(0, new()
|
||||
{
|
||||
outbound = Global.ProxyTag,
|
||||
clash_mode = ERuleMode.Global.ToString()
|
||||
});
|
||||
|
||||
if (_config.TunModeItem.EnableTun)
|
||||
{
|
||||
|
@ -1025,6 +1137,27 @@ public class CoreConfigSingboxService
|
|||
});
|
||||
}
|
||||
|
||||
if (!_config.Inbound.First().SniffingEnabled)
|
||||
{
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
port = [53],
|
||||
network = ["udp"],
|
||||
outbound = dnsOutbound
|
||||
});
|
||||
}
|
||||
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
clash_mode = ERuleMode.Direct.ToString()
|
||||
});
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.ProxyTag,
|
||||
clash_mode = ERuleMode.Global.ToString()
|
||||
});
|
||||
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
if (routing != null)
|
||||
{
|
||||
|
@ -1047,28 +1180,30 @@ public class CoreConfigSingboxService
|
|||
|
||||
private void GenRoutingDirectExe(out List<string> lstDnsExe, out List<string> lstDirectExe)
|
||||
{
|
||||
lstDnsExe = new();
|
||||
lstDirectExe = new();
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo();
|
||||
foreach (var it in coreInfo)
|
||||
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var coreInfoResult = CoreInfoHandler.Instance.GetCoreInfo();
|
||||
|
||||
foreach (var coreConfig in coreInfoResult)
|
||||
{
|
||||
if (it.CoreType == ECoreType.v2rayN)
|
||||
if (coreConfig.CoreType == ECoreType.v2rayN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (var it2 in it.CoreExes)
|
||||
{
|
||||
if (!lstDnsExe.Contains(it2) && it.CoreType != ECoreType.sing_box)
|
||||
{
|
||||
lstDnsExe.Add($"{it2}.exe");
|
||||
}
|
||||
|
||||
if (!lstDirectExe.Contains(it2))
|
||||
foreach (var baseExeName in coreConfig.CoreExes)
|
||||
{
|
||||
if (coreConfig.CoreType != ECoreType.sing_box)
|
||||
{
|
||||
lstDirectExe.Add($"{it2}.exe");
|
||||
dnsExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
}
|
||||
directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
}
|
||||
}
|
||||
|
||||
lstDnsExe = new List<string>(dnsExeSet);
|
||||
lstDirectExe = new List<string>(directExeSet);
|
||||
}
|
||||
|
||||
private async Task<int> GenRoutingUserRule(RulesItem item, List<Rule4Sbox> rules)
|
||||
|
@ -1087,14 +1222,11 @@ public class CoreConfigSingboxService
|
|||
|
||||
if (item.Port.IsNotEmpty())
|
||||
{
|
||||
if (item.Port.Contains("-"))
|
||||
{
|
||||
rule.port_range = new List<string> { item.Port.Replace("-", ":") };
|
||||
}
|
||||
else
|
||||
{
|
||||
rule.port = new List<int> { item.Port.ToInt() };
|
||||
}
|
||||
var portRanges = item.Port.Split(',').Where(it => it.Contains('-')).Select(it => it.Replace("-", ":")).ToList();
|
||||
var ports = item.Port.Split(',').Where(it => !it.Contains('-')).Select(it => it.ToInt()).ToList();
|
||||
|
||||
rule.port_range = portRanges.Count > 0 ? portRanges : null;
|
||||
rule.port = ports.Count > 0 ? ports : null;
|
||||
}
|
||||
if (item.Network.IsNotEmpty())
|
||||
{
|
||||
|
@ -1151,7 +1283,7 @@ public class CoreConfigSingboxService
|
|||
}
|
||||
|
||||
if (!hasDomainIp
|
||||
&& (rule.port != null || rule.port_range != null || rule.protocol != null || rule.inbound != null))
|
||||
&& (rule.port != null || rule.port_range != null || rule.protocol != null || rule.inbound != null || rule.network != null))
|
||||
{
|
||||
rules.Add(rule);
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ public class CoreConfigV2rayService
|
|||
await GenStatistic(v2rayConfig);
|
||||
v2rayConfig.outbounds.RemoveAt(0);
|
||||
|
||||
var tagProxy = new List<string>();
|
||||
var proxyProfiles = new List<ProfileItem>();
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (it.ConfigType == EConfigType.Custom)
|
||||
|
@ -151,17 +151,14 @@ public class CoreConfigV2rayService
|
|||
}
|
||||
|
||||
//outbound
|
||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(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);
|
||||
|
@ -328,7 +325,7 @@ public class CoreConfigV2rayService
|
|||
{
|
||||
listen = Global.Loopback,
|
||||
port = port,
|
||||
protocol = EInboundProtocol.socks.ToString(),
|
||||
protocol = EInboundProtocol.mixed.ToString(),
|
||||
};
|
||||
inbound.tag = inbound.protocol + inbound.port.ToString();
|
||||
v2rayConfig.inbounds.Add(inbound);
|
||||
|
@ -403,7 +400,7 @@ public class CoreConfigV2rayService
|
|||
tag = $"{EInboundProtocol.socks}{port}",
|
||||
listen = Global.Loopback,
|
||||
port = port,
|
||||
protocol = EInboundProtocol.socks.ToString(),
|
||||
protocol = EInboundProtocol.mixed.ToString(),
|
||||
});
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
|
@ -507,7 +504,7 @@ public class CoreConfigV2rayService
|
|||
}
|
||||
inbound.tag = protocol.ToString();
|
||||
inbound.port = inItem.LocalPort + (int)protocol;
|
||||
inbound.protocol = EInboundProtocol.socks.ToString();
|
||||
inbound.protocol = EInboundProtocol.mixed.ToString();
|
||||
inbound.settings.udp = inItem.UdpEnabled;
|
||||
inbound.sniffing.enabled = inItem.SniffingEnabled;
|
||||
inbound.sniffing.destOverride = inItem.DestOverride;
|
||||
|
@ -614,6 +611,7 @@ public class CoreConfigV2rayService
|
|||
if (rule.port.IsNotEmpty()
|
||||
|| rule.protocol?.Count > 0
|
||||
|| rule.inboundTag?.Count > 0
|
||||
|| rule.network != null
|
||||
)
|
||||
{
|
||||
var it = JsonUtils.DeepCopy(rule);
|
||||
|
@ -633,7 +631,7 @@ public class CoreConfigV2rayService
|
|||
{
|
||||
try
|
||||
{
|
||||
var muxEnabled = _config.CoreBasicItem.MuxEnabled;
|
||||
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
|
||||
switch (node.ConfigType)
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
|
@ -1183,7 +1181,6 @@ public class CoreConfigV2rayService
|
|||
if (servers != null)
|
||||
{
|
||||
var domainList = new List<string>();
|
||||
string? wireguardEndpointDomain = null;
|
||||
if (Utils.IsDomain(node.Address))
|
||||
{
|
||||
domainList.Add(node.Address);
|
||||
|
@ -1210,14 +1207,7 @@ public class CoreConfigV2rayService
|
|||
&& nextNode.ConfigType != EConfigType.TUIC
|
||||
&& Utils.IsDomain(nextNode.Address))
|
||||
{
|
||||
if (nextNode.ConfigType == EConfigType.WireGuard)
|
||||
{
|
||||
wireguardEndpointDomain = nextNode.Address;
|
||||
}
|
||||
else
|
||||
{
|
||||
domainList.Add(nextNode.Address);
|
||||
}
|
||||
domainList.Add(nextNode.Address);
|
||||
}
|
||||
}
|
||||
if (domainList.Count > 0)
|
||||
|
@ -1230,16 +1220,6 @@ public class CoreConfigV2rayService
|
|||
};
|
||||
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
|
||||
}
|
||||
if (wireguardEndpointDomain is not null)
|
||||
{
|
||||
var dnsServer = new DnsServer4Ray()
|
||||
{
|
||||
address = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress,
|
||||
skipFallback = true,
|
||||
domains = new() { wireguardEndpointDomain }
|
||||
};
|
||||
servers.AsArray().Insert(0, JsonUtils.SerializeToNode(dnsServer));
|
||||
}
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
@ -1338,6 +1318,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
|
||||
|
@ -1345,32 +1326,15 @@ public class CoreConfigV2rayService
|
|||
{
|
||||
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(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<Outbounds4Ray>(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)
|
||||
|
@ -1381,18 +1345,179 @@ public class CoreConfigV2rayService
|
|||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get template and initialize list
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
if (txtOutbound.IsNullOrEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var resultOutbounds = new List<Outbounds4Ray>();
|
||||
var prevOutbounds = new List<Outbounds4Ray>(); // Separate list for prev outbounds and fragment
|
||||
|
||||
// Cache for chain proxies to avoid duplicate generation
|
||||
var nextProxyCache = new Dictionary<string, Outbounds4Ray?>();
|
||||
var prevProxyTags = new Dictionary<string, string?>(); // 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<Outbounds4Ray>(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<Outbounds4Ray>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="subItem">The subscription item containing proxy chain information.</param>
|
||||
/// <param name="outbound">The current outbound configuration. Its tag must be set before calling this method.</param>
|
||||
/// <param name="prevOutboundTag">The tag of the previous outbound in the chain, if any.</param>
|
||||
/// <param name="nextOutbound">The outbound for the next proxy in the chain, if already created. If null, will be created inside.</param>
|
||||
/// <returns>
|
||||
/// The outbound configuration for the next proxy in the chain, or null if no next proxy exists.
|
||||
/// </returns>
|
||||
private async Task<Outbounds4Ray?> 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<Outbounds4Ray>(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<int> 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",
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -194,11 +194,11 @@ public class UpdateService
|
|||
{
|
||||
if (Utils.IsBase64String(result2))
|
||||
{
|
||||
result += Utils.Base64Decode(result2);
|
||||
result += Environment.NewLine + Utils.Base64Decode(result2);
|
||||
}
|
||||
else
|
||||
{
|
||||
result += result2;
|
||||
result += Environment.NewLine + result2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -243,21 +243,6 @@ public class UpdateService
|
|||
_updateFunc?.Invoke(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
|
||||
}
|
||||
|
||||
public async Task<string> RunAvailabilityCheck()
|
||||
{
|
||||
var downloadHandle = new DownloadService();
|
||||
var time = await downloadHandle.RunAvailabilityCheck(null);
|
||||
var ip = Global.None;
|
||||
if (time > 0)
|
||||
{
|
||||
var result = await downloadHandle.TryDownloadString(Global.IPAPIUrl, true, Global.IPAPIUrl);
|
||||
var ipInfo = JsonUtils.Deserialize<IPAPIInfo>(result);
|
||||
ip = $"({ipInfo?.country_code}) {ipInfo?.ip}";
|
||||
}
|
||||
|
||||
return string.Format(ResUI.TestMeOutput, time, ip);
|
||||
}
|
||||
|
||||
#region CheckUpdate private
|
||||
|
||||
private async Task<RetResult> CheckUpdateAsync(DownloadService downloadHandle, ECoreType type, bool preRelease)
|
||||
|
@ -500,6 +485,12 @@ public class UpdateService
|
|||
|
||||
private async Task UpdateOtherFiles(Config config, Action<bool, string> 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)
|
||||
|
|
|
@ -174,6 +174,11 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||
|
||||
public void RefreshProxyGroups()
|
||||
{
|
||||
if (_proxies == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedName = SelectedGroup?.Name;
|
||||
_proxyGroups.Clear();
|
||||
|
||||
|
@ -229,7 +234,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||
else
|
||||
{
|
||||
SelectedGroup = _proxyGroups.First();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -297,8 +302,10 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||
|
||||
private ProxiesItem? TryGetProxy(string name)
|
||||
{
|
||||
if (_proxies is null)
|
||||
if (_proxies == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
_proxies.TryGetValue(name, out var proxy2);
|
||||
if (proxy2 != null)
|
||||
{
|
||||
|
|
|
@ -552,6 +552,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
{
|
||||
await LoadCore();
|
||||
await SysProxyHandler.UpdateSysProxy(_config, false);
|
||||
await Task.Delay(1000);
|
||||
});
|
||||
Locator.Current.GetService<StatusBarViewModel>()?.TestServerAvailability();
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||
[Reactive] public string GeoFileSourceUrl { get; set; }
|
||||
[Reactive] public string SrsFileSourceUrl { get; set; }
|
||||
[Reactive] public string RoutingRulesSourceUrl { get; set; }
|
||||
[Reactive] public string IPAPIUrl { get; set; }
|
||||
|
||||
#endregion UI
|
||||
|
||||
|
@ -88,7 +89,6 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||
[Reactive] public int TunMtu { get; set; }
|
||||
[Reactive] public bool TunEnableExInbound { get; set; }
|
||||
[Reactive] public bool TunEnableIPv6Address { get; set; }
|
||||
[Reactive] public string TunLinuxSudoPassword { get; set; }
|
||||
|
||||
#endregion Tun mode
|
||||
|
||||
|
@ -187,6 +187,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||
GeoFileSourceUrl = _config.ConstItem.GeoSourceUrl;
|
||||
SrsFileSourceUrl = _config.ConstItem.SrsSourceUrl;
|
||||
RoutingRulesSourceUrl = _config.ConstItem.RouteRulesTemplateSourceUrl;
|
||||
IPAPIUrl = _config.SpeedTestItem.IPAPIUrl;
|
||||
|
||||
#endregion UI
|
||||
|
||||
|
@ -205,7 +206,6 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||
TunMtu = _config.TunModeItem.Mtu;
|
||||
TunEnableExInbound = _config.TunModeItem.EnableExInbound;
|
||||
TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address;
|
||||
TunLinuxSudoPassword = _config.TunModeItem.LinuxSudoPwd;
|
||||
|
||||
#endregion Tun mode
|
||||
|
||||
|
@ -346,6 +346,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||
_config.ConstItem.GeoSourceUrl = GeoFileSourceUrl;
|
||||
_config.ConstItem.SrsSourceUrl = SrsFileSourceUrl;
|
||||
_config.ConstItem.RouteRulesTemplateSourceUrl = RoutingRulesSourceUrl;
|
||||
_config.SpeedTestItem.IPAPIUrl = IPAPIUrl;
|
||||
|
||||
//systemProxy
|
||||
_config.SystemProxyItem.SystemProxyExceptions = systemProxyExceptions;
|
||||
|
@ -358,10 +359,6 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||
_config.TunModeItem.Mtu = TunMtu;
|
||||
_config.TunModeItem.EnableExInbound = TunEnableExInbound;
|
||||
_config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address;
|
||||
if (TunLinuxSudoPassword != _config.TunModeItem.LinuxSudoPwd)
|
||||
{
|
||||
_config.TunModeItem.LinuxSudoPwd = DesUtils.Encrypt(TunLinuxSudoPassword);
|
||||
}
|
||||
|
||||
//coreType
|
||||
await SaveCoreType();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -320,7 +320,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
|
||||
var msg = await Task.Run(async () =>
|
||||
{
|
||||
return await (new UpdateService()).RunAvailabilityCheck();
|
||||
return await ConnectionHandler.Instance.RunAvailabilityCheck();
|
||||
});
|
||||
|
||||
NoticeHandler.Instance.SendMessageEx(msg);
|
||||
|
@ -436,11 +436,13 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
Locator.Current.GetService<MainWindowViewModel>()?.RebootAsAdmin();
|
||||
return;
|
||||
}
|
||||
else if (Utils.IsOSX())
|
||||
else
|
||||
{
|
||||
_config.TunModeItem.EnableTun = false;
|
||||
NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.TbSettingsLinuxSudoPasswordIsEmpty);
|
||||
return;
|
||||
if (await _updateView?.Invoke(EViewAction.PasswordInput, null) == false)
|
||||
{
|
||||
_config.TunModeItem.EnableTun = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
|
@ -452,15 +454,15 @@ public class StatusBarViewModel : MyReactiveObject
|
|||
{
|
||||
if (Utils.IsWindows())
|
||||
{
|
||||
return AppHandler.Instance.IsAdministrator;
|
||||
return Utils.IsAdministrator();
|
||||
}
|
||||
else if (Utils.IsLinux())
|
||||
{
|
||||
return _config.TunModeItem.LinuxSudoPwd.IsNotEmpty();
|
||||
return AppHandler.Instance.LinuxSudoPwd.IsNotEmpty();
|
||||
}
|
||||
else if (Utils.IsOSX())
|
||||
{
|
||||
return _config.TunModeItem.LinuxSudoPwd.IsNotEmpty();
|
||||
return AppHandler.Instance.LinuxSudoPwd.IsNotEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -498,8 +500,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
|
||||
{
|
||||
|
|
|
@ -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">
|
||||
<Application.Styles>
|
||||
<semi:SemiTheme />
|
||||
|
@ -32,6 +32,8 @@
|
|||
ToolTipText="{Binding RunningServerToolTipText}">
|
||||
<TrayIcon.Menu>
|
||||
<NativeMenu>
|
||||
<NativeMenuItem Command="{Binding NotifyLeftClickCmd}" Header="{x:Static resx:ResUI.menuShowOrHideMainWindow}" />
|
||||
<NativeMenuItemSeparator />
|
||||
<NativeMenuItem
|
||||
Command="{Binding SystemProxyClearCmd}"
|
||||
Header="{x:Static resx:ResUI.menuSystemProxyClear}"
|
||||
|
@ -55,13 +57,11 @@
|
|||
ToggleType="Radio" />
|
||||
<NativeMenuItemSeparator />
|
||||
<NativeMenuItem Click="MenuAddServerViaClipboardClick" Header="{x:Static resx:ResUI.menuAddServerViaClipboard}" />
|
||||
<NativeMenuItem Header="{x:Static resx:ResUI.menuAddServerViaScan}" IsVisible="False" />
|
||||
<NativeMenuItem Command="{Binding SubUpdateCmd}" Header="{x:Static resx:ResUI.menuSubUpdate}" />
|
||||
<NativeMenuItem Command="{Binding SubUpdateViaProxyCmd}" Header="{x:Static resx:ResUI.menuSubUpdateViaProxy}" />
|
||||
<NativeMenuItemSeparator />
|
||||
<NativeMenuItem Command="{Binding CopyProxyCmdToClipboardCmd}" Header="{x:Static resx:ResUI.menuCopyProxyCmdToClipboard}" />
|
||||
<NativeMenuItemSeparator />
|
||||
<NativeMenuItem Command="{Binding NotifyLeftClickCmd}" Header="{x:Static resx:ResUI.menuShowOrHideMainWindow}" />
|
||||
<NativeMenuItem Click="MenuExit_Click" Header="{x:Static resx:ResUI.menuExit}" />
|
||||
</NativeMenu>
|
||||
</TrayIcon.Menu>
|
||||
|
|
|
@ -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;
|
||||
|
@ -43,7 +38,7 @@ public partial class App : Application
|
|||
{
|
||||
if (e.ExceptionObject != null)
|
||||
{
|
||||
Logging.SaveLog("CurrentDomain_UnhandledException", (Exception)e.ExceptionObject!);
|
||||
Logging.SaveLog("CurrentDomain_UnhandledException", (Exception)e.ExceptionObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
52
v2rayN/v2rayN.Desktop/Base/WindowBase.cs
Normal file
52
v2rayN/v2rayN.Desktop/Base/WindowBase.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace v2rayN.Desktop.Base;
|
||||
|
||||
public class WindowBase<TViewModel> : ReactiveWindow<TViewModel> 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 = VisualRoot is not 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 { }
|
||||
}
|
||||
}
|
|
@ -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,30 @@ 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<App>()
|
||||
.UsePlatformDetect()
|
||||
//.WithInterFont()
|
||||
.WithFontByDefault()
|
||||
.LogToTrace()
|
||||
.UseReactiveUI()
|
||||
.With(new MacOSPlatformOptions { ShowInDock = false });
|
||||
{
|
||||
return AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
//.WithInterFont()
|
||||
.WithFontByDefault()
|
||||
.LogToTrace()
|
||||
#if OS_OSX
|
||||
.UseReactiveUI()
|
||||
.With(new MacOSPlatformOptions { ShowInDock = AppHandler.Instance.Config.UiItem.MacOSShowInDock });
|
||||
#else
|
||||
.UseReactiveUI();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<AddServer2ViewModel>
|
||||
public partial class AddServer2Window : WindowBase<AddServer2ViewModel>
|
||||
{
|
||||
public AddServer2Window()
|
||||
{
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
|
@ -152,13 +152,26 @@
|
|||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsMuxEnabled}" />
|
||||
<ToggleSwitch
|
||||
x:Name="togmuxEnabled"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridSs"
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
|
@ -185,6 +198,19 @@
|
|||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsMuxEnabled}" />
|
||||
<ToggleSwitch
|
||||
x:Name="togmuxEnabled3"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridSocks"
|
||||
|
@ -224,7 +250,7 @@
|
|||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
|
@ -271,13 +297,26 @@
|
|||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsMuxEnabled}" />
|
||||
<ToggleSwitch
|
||||
x:Name="togmuxEnabled5"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridTrojan"
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
|
@ -304,6 +343,19 @@
|
|||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsMuxEnabled}" />
|
||||
<ToggleSwitch
|
||||
x:Name="togmuxEnabled6"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridHysteria2"
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
using System.Reactive.Disposables;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
using v2rayN.Desktop.Base;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class AddServerWindow : ReactiveWindow<AddServerViewModel>
|
||||
public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
{
|
||||
public AddServerWindow()
|
||||
{
|
||||
|
@ -150,11 +150,13 @@ public partial class AddServerWindow : ReactiveWindow<AddServerViewModel>
|
|||
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 +169,13 @@ public partial class AddServerWindow : ReactiveWindow<AddServerViewModel>
|
|||
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:
|
||||
|
|
|
@ -52,9 +52,16 @@ public partial class ClashConnectionsView : ReactiveUserControl<ClashConnections
|
|||
|
||||
private void AutofitColumnWidth()
|
||||
{
|
||||
foreach (var it in lstConnections.Columns)
|
||||
try
|
||||
{
|
||||
it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
|
||||
foreach (var it in lstConnections.Columns)
|
||||
{
|
||||
it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("ClashConnectionsView", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
IsCancel="True" />
|
||||
</StackPanel>
|
||||
|
||||
<TabControl HorizontalContentAlignment="Left">
|
||||
<TabControl HorizontalContentAlignment="Stretch">
|
||||
<TabItem Header="{x:Static resx:ResUI.TbSettingsCoreDns}">
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
|
||||
|
@ -90,16 +90,18 @@
|
|||
</StackPanel>
|
||||
</WrapPanel>
|
||||
|
||||
<Grid Margin="{StaticResource Margin4}">
|
||||
<HeaderedContentControl
|
||||
Margin="{StaticResource Margin4}"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
Header="HTTP/SOCKS">
|
||||
<TextBox
|
||||
x:Name="txtnormalDNS"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Name="txtnormalDNS"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderThickness="1"
|
||||
Classes="TextArea"
|
||||
TextWrapping="Wrap"
|
||||
Watermark="HTTP/SOCKS" />
|
||||
</Grid>
|
||||
MinLines="10"
|
||||
TextWrapping="Wrap" />
|
||||
</HeaderedContentControl>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
|
||||
|
@ -144,31 +146,34 @@
|
|||
|
||||
<Grid Margin="{StaticResource Margin4}" ColumnDefinitions="*,10,*">
|
||||
|
||||
<TextBox
|
||||
x:Name="txtnormalDNS2"
|
||||
<HeaderedContentControl
|
||||
Grid.Column="0"
|
||||
Width="400"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
Classes="TextArea"
|
||||
Margin="{StaticResource Margin4}"
|
||||
TextWrapping="Wrap"
|
||||
Watermark="HTTP/SOCKS" />
|
||||
Header="HTTP/SOCKS">
|
||||
<TextBox
|
||||
Name="txtnormalDNS2"
|
||||
VerticalAlignment="Stretch"
|
||||
Classes="TextArea"
|
||||
MinLines="10"
|
||||
TextWrapping="Wrap" />
|
||||
</HeaderedContentControl>
|
||||
|
||||
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
|
||||
|
||||
<TextBox
|
||||
x:Name="txttunDNS2"
|
||||
<HeaderedContentControl
|
||||
Grid.Column="2"
|
||||
Width="400"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
Classes="TextArea"
|
||||
Margin="{StaticResource Margin4}"
|
||||
TextWrapping="Wrap"
|
||||
Watermark="{x:Static resx:ResUI.TbSettingsTunMode}" />
|
||||
Header="{x:Static resx:ResUI.TbSettingsTunMode}">
|
||||
<TextBox
|
||||
Name="txttunDNS2"
|
||||
VerticalAlignment="Stretch"
|
||||
Classes="TextArea"
|
||||
MinLines="10"
|
||||
TextWrapping="Wrap" />
|
||||
</HeaderedContentControl>
|
||||
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
|
|
|
@ -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<DNSSettingViewModel>
|
||||
public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
|
||||
{
|
||||
private static Config _config;
|
||||
|
||||
|
|
|
@ -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<GlobalHotkeySettingViewModel>
|
||||
public partial class GlobalHotkeySettingWindow : WindowBase<GlobalHotkeySettingViewModel>
|
||||
{
|
||||
private readonly List<object> _textBoxKeyEventItem = new();
|
||||
|
||||
|
|
|
@ -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<MainWindowViewModel>
|
||||
public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
{
|
||||
private static Config _config;
|
||||
private WindowNotificationManager? _manager;
|
||||
|
@ -29,7 +29,7 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
|
|||
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;
|
||||
|
@ -143,7 +143,7 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
|
|||
}
|
||||
else
|
||||
{
|
||||
if (AppHandler.Instance.IsAdministrator)
|
||||
if (Utils.IsAdministrator())
|
||||
{
|
||||
this.Title = $"{Utils.GetVersion()} - {ResUI.TbSettingsLinuxSudoPasswordNotSudoRunApp}";
|
||||
NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.TbSettingsLinuxSudoPasswordNotSudoRunApp);
|
||||
|
@ -154,7 +154,6 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
|
|||
}
|
||||
menuAddServerViaScan.IsVisible = false;
|
||||
|
||||
RestoreUI();
|
||||
AddHelpMenuItem();
|
||||
MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI);
|
||||
}
|
||||
|
@ -388,7 +387,7 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
|
|||
|
||||
private async void MenuClose_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (await UI.ShowYesNo(this, ResUI.menuExitTips) == ButtonResult.No)
|
||||
if (await UI.ShowYesNo(this, ResUI.menuExitTips) != ButtonResult.Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -436,14 +435,14 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
|
|||
_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,8 +460,7 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
|
|||
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -343,7 +343,7 @@
|
|||
<Grid
|
||||
Margin="{StaticResource Margin4}"
|
||||
ColumnDefinitions="Auto,Auto,*"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
x:Name="tbAutoRun"
|
||||
|
@ -575,9 +575,9 @@
|
|||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsSubConvert}" />
|
||||
<ctrls:AutoCompleteBox
|
||||
x:Name="cmbSubConvertUrl"
|
||||
Text="{x:Static resx:ResUI.TbSettingsIPAPIUrl}" />
|
||||
<ComboBox
|
||||
x:Name="cmbIPAPIUrl"
|
||||
Grid.Row="20"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
|
@ -588,28 +588,41 @@
|
|||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsSubConvert}" />
|
||||
<ctrls:AutoCompleteBox
|
||||
x:Name="cmbSubConvertUrl"
|
||||
Grid.Row="21"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="22"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsMainGirdOrientation}" />
|
||||
<ComboBox
|
||||
x:Name="cmbMainGirdOrientation"
|
||||
Grid.Row="21"
|
||||
Grid.Row="22"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="22"
|
||||
Grid.Row="23"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsGeoFilesSource}" />
|
||||
<ComboBox
|
||||
x:Name="cmbGetFilesSourceUrl"
|
||||
Grid.Row="22"
|
||||
Grid.Row="23"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
<TextBlock
|
||||
Grid.Row="22"
|
||||
Grid.Row="23"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
|
@ -617,19 +630,19 @@
|
|||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="23"
|
||||
Grid.Row="24"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsSrsFilesSource}" />
|
||||
<ComboBox
|
||||
x:Name="cmbSrsFilesSourceUrl"
|
||||
Grid.Row="23"
|
||||
Grid.Row="24"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
<TextBlock
|
||||
Grid.Row="23"
|
||||
Grid.Row="24"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
|
@ -637,24 +650,25 @@
|
|||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="24"
|
||||
Grid.Row="25"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsRoutingRulesSource}" />
|
||||
<ComboBox
|
||||
x:Name="cmbRoutingRulesSourceUrl"
|
||||
Grid.Row="24"
|
||||
Grid.Row="25"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
<TextBlock
|
||||
Grid.Row="24"
|
||||
Grid.Row="25"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsChinaUserTip}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
@ -784,28 +798,6 @@
|
|||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="labLinuxSudoPassword"
|
||||
Grid.Row="7"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsLinuxSudoPassword}" />
|
||||
<TextBox
|
||||
x:Name="txtLinuxSudoPassword"
|
||||
Grid.Row="7"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
<TextBlock
|
||||
x:Name="labLinuxSudoPasswordTip"
|
||||
Grid.Row="7"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsLinuxSudoPasswordTip}"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
|
|
|
@ -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<OptionSettingViewModel>
|
||||
public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
|
||||
{
|
||||
private static Config _config;
|
||||
|
||||
|
@ -92,6 +92,10 @@ public partial class OptionSettingWindow : ReactiveWindow<OptionSettingViewModel
|
|||
{
|
||||
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());
|
||||
|
@ -143,6 +147,7 @@ public partial class OptionSettingWindow : ReactiveWindow<OptionSettingViewModel
|
|||
this.Bind(ViewModel, vm => vm.GeoFileSourceUrl, v => v.cmbGetFilesSourceUrl.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.SelectedValue).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables);
|
||||
|
@ -153,7 +158,6 @@ public partial class OptionSettingWindow : ReactiveWindow<OptionSettingViewModel
|
|||
this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.TunEnableExInbound, v => v.togEnableExInbound.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.TunLinuxSudoPassword, v => v.txtLinuxSudoPassword.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.CoreType1, v => v.cmbCoreType1.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType2, v => v.cmbCoreType2.SelectedValue).DisposeWith(disposables);
|
||||
|
@ -170,10 +174,6 @@ public partial class OptionSettingWindow : ReactiveWindow<OptionSettingViewModel
|
|||
{
|
||||
txbSettingsExceptionTip2.IsVisible = false;
|
||||
|
||||
txtLinuxSudoPassword.IsVisible = false;
|
||||
labLinuxSudoPassword.IsVisible = false;
|
||||
labLinuxSudoPasswordTip.IsVisible = false;
|
||||
|
||||
labHide2TrayWhenClose.IsVisible = false;
|
||||
togHide2TrayWhenClose.IsVisible = false;
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||
break;
|
||||
|
||||
case EViewAction.ShowYesNo:
|
||||
if (await UI.ShowYesNo(_window, ResUI.RemoveServer) == ButtonResult.No)
|
||||
if (await UI.ShowYesNo(_window, ResUI.RemoveServer) != ButtonResult.Yes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||
await ViewModel.RefreshServersBiz();
|
||||
}
|
||||
|
||||
if (lstProfiles.SelectedIndex > 0)
|
||||
if (lstProfiles.SelectedIndex >= 0)
|
||||
{
|
||||
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, null);
|
||||
}
|
||||
|
@ -345,9 +345,34 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||
|
||||
private void AutofitColumnWidth()
|
||||
{
|
||||
foreach (var it in lstProfiles.Columns)
|
||||
try
|
||||
{
|
||||
it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
|
||||
//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<ProfileItemModel>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("ProfilesView", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
using System.Reactive.Disposables;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
using v2rayN.Desktop.Base;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class RoutingRuleDetailsWindow : ReactiveWindow<RoutingRuleDetailsViewModel>
|
||||
public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsViewModel>
|
||||
{
|
||||
public RoutingRuleDetailsWindow()
|
||||
{
|
||||
|
|
|
@ -2,14 +2,14 @@ using System.Reactive.Disposables;
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.ReactiveUI;
|
||||
using MsBox.Avalonia.Enums;
|
||||
using ReactiveUI;
|
||||
using v2rayN.Desktop.Base;
|
||||
using v2rayN.Desktop.Common;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class RoutingRuleSettingWindow : ReactiveWindow<RoutingRuleSettingViewModel>
|
||||
public partial class RoutingRuleSettingWindow : WindowBase<RoutingRuleSettingViewModel>
|
||||
{
|
||||
public RoutingRuleSettingWindow()
|
||||
{
|
||||
|
@ -80,14 +80,14 @@ public partial class RoutingRuleSettingWindow : ReactiveWindow<RoutingRuleSettin
|
|||
break;
|
||||
|
||||
case EViewAction.ShowYesNo:
|
||||
if (await UI.ShowYesNo(this, ResUI.RemoveServer) == ButtonResult.No)
|
||||
if (await UI.ShowYesNo(this, ResUI.RemoveServer) != ButtonResult.Yes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case EViewAction.AddBatchRoutingRulesYesNo:
|
||||
if (await UI.ShowYesNo(this, ResUI.AddBatchRoutingRulesYesNo) == ButtonResult.No)
|
||||
if (await UI.ShowYesNo(this, ResUI.AddBatchRoutingRulesYesNo) != ButtonResult.Yes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -24,28 +24,11 @@
|
|||
<MenuItem x:Name="menuRoutingAdvancedAdd2" Header="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" />
|
||||
<MenuItem x:Name="menuRoutingAdvancedImportRules2" Header="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" />
|
||||
</Menu>
|
||||
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<HyperlinkButton Classes="WithIcon" Click="linkdomainStrategy_Click">
|
||||
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy}" />
|
||||
</HyperlinkButton>
|
||||
</TextBlock>
|
||||
<ComboBox x:Name="cmbdomainStrategy" Width="110" />
|
||||
<Separator />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Static resx:ResUI.TbdomainMatcher}" />
|
||||
<ComboBox x:Name="cmbdomainMatcher" Width="60" />
|
||||
<Separator />
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<HyperlinkButton Classes="WithIcon" Click="linkdomainStrategy4Singbox_Click">
|
||||
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy4Singbox}" />
|
||||
</HyperlinkButton>
|
||||
</TextBlock>
|
||||
<ComboBox x:Name="cmbdomainStrategy4Singbox" Width="100" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Right"
|
||||
DockPanel.Dock="Bottom"
|
||||
Orientation="Horizontal">
|
||||
<StackPanel
|
||||
|
@ -69,58 +52,112 @@
|
|||
IsCancel="True" />
|
||||
</StackPanel>
|
||||
|
||||
<DockPanel>
|
||||
<TabControl x:Name="tabAdvanced">
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.TbRoutingTabRuleList}">
|
||||
<DataGrid
|
||||
x:Name="lstRoutings"
|
||||
AutoGenerateColumns="False"
|
||||
BorderThickness="1"
|
||||
CanUserResizeColumns="True"
|
||||
GridLinesVisibility="All"
|
||||
HeadersVisibility="Column"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding RoutingItems}">
|
||||
<DataGrid.KeyBindings>
|
||||
<KeyBinding Command="{Binding RoutingAdvancedSetDefaultCmd}" Gesture="Enter" />
|
||||
</DataGrid.KeyBindings>
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem x:Name="menuRoutingAdvancedAdd" Header="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" />
|
||||
<MenuItem x:Name="menuRoutingAdvancedRemove" Header="{x:Static resx:ResUI.menuRoutingAdvancedRemove}" />
|
||||
<MenuItem x:Name="menuRoutingAdvancedSelectAll" Header="{x:Static resx:ResUI.menuSelectAll}" />
|
||||
<MenuItem x:Name="menuRoutingAdvancedSetDefault" Header="{x:Static resx:ResUI.menuRoutingAdvancedSetDefault}" />
|
||||
<Separator />
|
||||
<MenuItem x:Name="menuRoutingAdvancedImportRules" Header="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<Grid
|
||||
Margin="{StaticResource Margin4}"
|
||||
ColumnDefinitions="Auto,Auto"
|
||||
DockPanel.Dock="Top"
|
||||
RowDefinitions="Auto,Auto,Auto">
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridCheckBoxColumn Width="40" Binding="{Binding IsActive}" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Remarks}"
|
||||
Header="{x:Static resx:ResUI.LvRemarks}" />
|
||||
<DataGridTextColumn
|
||||
Width="60"
|
||||
Binding="{Binding RuleNum}"
|
||||
Header="{x:Static resx:ResUI.LvCount}" />
|
||||
<DataGridTextColumn
|
||||
Width="60"
|
||||
Binding="{Binding Sort}"
|
||||
Header="{x:Static resx:ResUI.LvSort}" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Url}"
|
||||
Header="{x:Static resx:ResUI.LvUrl}" />
|
||||
<DataGridTextColumn
|
||||
Width="300"
|
||||
Binding="{Binding CustomIcon}"
|
||||
Header="{x:Static resx:ResUI.LvCustomIcon}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center">
|
||||
<HyperlinkButton Classes="WithIcon" Click="linkdomainStrategy_Click">
|
||||
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy}" />
|
||||
</HyperlinkButton>
|
||||
</TextBlock>
|
||||
<ComboBox
|
||||
x:Name="cmbdomainStrategy"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbdomainMatcher}" />
|
||||
<ComboBox
|
||||
x:Name="cmbdomainMatcher"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center">
|
||||
<HyperlinkButton Classes="WithIcon" Click="linkdomainStrategy4Singbox_Click">
|
||||
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy4Singbox}" />
|
||||
</HyperlinkButton>
|
||||
</TextBlock>
|
||||
<ComboBox
|
||||
x:Name="cmbdomainStrategy4Singbox"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<TabControl x:Name="tabAdvanced">
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.TbRoutingTabRuleList}">
|
||||
<DataGrid
|
||||
x:Name="lstRoutings"
|
||||
AutoGenerateColumns="False"
|
||||
BorderThickness="1"
|
||||
CanUserResizeColumns="True"
|
||||
GridLinesVisibility="All"
|
||||
HeadersVisibility="Column"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding RoutingItems}">
|
||||
<DataGrid.KeyBindings>
|
||||
<KeyBinding Command="{Binding RoutingAdvancedSetDefaultCmd}" Gesture="Enter" />
|
||||
</DataGrid.KeyBindings>
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem x:Name="menuRoutingAdvancedAdd" Header="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" />
|
||||
<MenuItem x:Name="menuRoutingAdvancedRemove" Header="{x:Static resx:ResUI.menuRoutingAdvancedRemove}" />
|
||||
<MenuItem x:Name="menuRoutingAdvancedSelectAll" Header="{x:Static resx:ResUI.menuSelectAll}" />
|
||||
<MenuItem x:Name="menuRoutingAdvancedSetDefault" Header="{x:Static resx:ResUI.menuRoutingAdvancedSetDefault}" />
|
||||
<Separator />
|
||||
<MenuItem x:Name="menuRoutingAdvancedImportRules" Header="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridCheckBoxColumn Width="40" Binding="{Binding IsActive}" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Remarks}"
|
||||
Header="{x:Static resx:ResUI.LvRemarks}" />
|
||||
<DataGridTextColumn
|
||||
Width="60"
|
||||
Binding="{Binding RuleNum}"
|
||||
Header="{x:Static resx:ResUI.LvCount}" />
|
||||
<DataGridTextColumn
|
||||
Width="60"
|
||||
Binding="{Binding Sort}"
|
||||
Header="{x:Static resx:ResUI.LvSort}" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Url}"
|
||||
Header="{x:Static resx:ResUI.LvUrl}" />
|
||||
<DataGridTextColumn
|
||||
Width="300"
|
||||
Binding="{Binding CustomIcon}"
|
||||
Header="{x:Static resx:ResUI.LvCustomIcon}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
|
|
|
@ -2,14 +2,14 @@ using System.Reactive.Disposables;
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.ReactiveUI;
|
||||
using MsBox.Avalonia.Enums;
|
||||
using ReactiveUI;
|
||||
using v2rayN.Desktop.Base;
|
||||
using v2rayN.Desktop.Common;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class RoutingSettingWindow : ReactiveWindow<RoutingSettingViewModel>
|
||||
public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel>
|
||||
{
|
||||
private bool _manualClose = false;
|
||||
|
||||
|
@ -68,7 +68,7 @@ public partial class RoutingSettingWindow : ReactiveWindow<RoutingSettingViewMod
|
|||
break;
|
||||
|
||||
case EViewAction.ShowYesNo:
|
||||
if (await UI.ShowYesNo(this, ResUI.RemoveRules) == ButtonResult.No)
|
||||
if (await UI.ShowYesNo(this, ResUI.RemoveRules) != ButtonResult.Yes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ using Avalonia.Controls;
|
|||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Avalonia.Threading;
|
||||
using DialogHostAvalonia;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
using v2rayN.Desktop.Common;
|
||||
|
@ -81,6 +82,10 @@ public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
|
|||
return false;
|
||||
await AvaUtils.SetClipboardData(this, (string)obj);
|
||||
break;
|
||||
|
||||
case EViewAction.PasswordInput:
|
||||
return await PasswordInputAsync();
|
||||
break;
|
||||
}
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
@ -96,6 +101,20 @@ public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<bool> PasswordInputAsync()
|
||||
{
|
||||
var dialog = new SudoPasswordInputView();
|
||||
var obj = await DialogHost.Show(dialog);
|
||||
if (obj == null)
|
||||
{
|
||||
togEnableTun.IsChecked = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
AppHandler.Instance.LinuxSudoPwd = obj.ToString() ?? string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TxtRunningServerDisplay_Tapped(object? sender, Avalonia.Input.TappedEventArgs e)
|
||||
{
|
||||
ViewModel?.TestServerAvailability();
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
using System.Reactive.Disposables;
|
||||
using Avalonia;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
using v2rayN.Desktop.Base;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class SubEditWindow : ReactiveWindow<SubEditViewModel>
|
||||
public partial class SubEditWindow : WindowBase<SubEditViewModel>
|
||||
{
|
||||
public SubEditWindow()
|
||||
{
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
using System.Reactive.Disposables;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.ReactiveUI;
|
||||
using DialogHostAvalonia;
|
||||
using DynamicData;
|
||||
using MsBox.Avalonia.Enums;
|
||||
using ReactiveUI;
|
||||
using v2rayN.Desktop.Base;
|
||||
using v2rayN.Desktop.Common;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class SubSettingWindow : ReactiveWindow<SubSettingViewModel>
|
||||
public partial class SubSettingWindow : WindowBase<SubSettingViewModel>
|
||||
{
|
||||
private bool _manualClose = false;
|
||||
|
||||
|
@ -45,7 +45,7 @@ public partial class SubSettingWindow : ReactiveWindow<SubSettingViewModel>
|
|||
break;
|
||||
|
||||
case EViewAction.ShowYesNo:
|
||||
if (await UI.ShowYesNo(this, ResUI.RemoveServer) == ButtonResult.No)
|
||||
if (await UI.ShowYesNo(this, ResUI.RemoveServer) != ButtonResult.Yes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
70
v2rayN/v2rayN.Desktop/Views/SudoPasswordInputView.axaml
Normal file
70
v2rayN/v2rayN.Desktop/Views/SudoPasswordInputView.axaml
Normal file
|
@ -0,0 +1,70 @@
|
|||
<UserControl
|
||||
x:Class="v2rayN.Desktop.Views.SudoPasswordInputView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
d:DesignHeight="200"
|
||||
d:DesignWidth="400"
|
||||
mc:Ignorable="d">
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
|
||||
<Border
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Bottom"
|
||||
Theme="{DynamicResource CardBorder}">
|
||||
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
Cursor="Hand"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
Cursor="Hand"
|
||||
IsCancel="True" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Theme="{DynamicResource CardBorder}">
|
||||
|
||||
<Grid ColumnDefinitions="Auto,400" RowDefinitions="Auto,Auto,Auto">
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsLinuxSudoPassword}" />
|
||||
|
||||
<TextBox
|
||||
x:Name="txtPassword"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Classes="revealPasswordButton"
|
||||
Focusable="True" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsLinuxSudoPasswordTip}"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</DockPanel>
|
||||
</UserControl>
|
33
v2rayN/v2rayN.Desktop/Views/SudoPasswordInputView.axaml.cs
Normal file
33
v2rayN/v2rayN.Desktop/Views/SudoPasswordInputView.axaml.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using DialogHostAvalonia;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class SudoPasswordInputView : UserControl
|
||||
{
|
||||
public SudoPasswordInputView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.Loaded += (s, e) => txtPassword.Focus();
|
||||
|
||||
btnSave.Click += (_, _) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(txtPassword.Text))
|
||||
{
|
||||
txtPassword.Focus();
|
||||
return;
|
||||
}
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
DialogHost.Close(null, txtPassword.Text);
|
||||
});
|
||||
};
|
||||
|
||||
btnCancel.Click += (_, _) =>
|
||||
{
|
||||
DialogHost.Close(null);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
<Application
|
||||
x:Class="v2rayN.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:conv="clr-namespace:v2rayN.Converters"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
ShutdownMode="OnExplicitShutdown"
|
||||
StartupUri="Views/MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
@ -215,7 +215,7 @@
|
|||
<Setter Property="Background" Value="{DynamicResource MaterialDesignPaper}" />
|
||||
<Setter Property="TextElement.FontFamily" Value="{x:Static conv:MaterialDesignFonts.MyFont}" />
|
||||
<Setter Property="FontFamily" Value="{x:Static conv:MaterialDesignFonts.MyFont}" />
|
||||
<Setter Property="ResizeMode" Value="NoResize" />
|
||||
<Setter Property="ResizeMode" Value="CanResize" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ViewGlobal"
|
||||
|
|
|
@ -56,7 +56,7 @@ public partial class App : Application
|
|||
{
|
||||
if (e.ExceptionObject != null)
|
||||
{
|
||||
Logging.SaveLog("CurrentDomain_UnhandledException", (Exception)e.ExceptionObject!);
|
||||
Logging.SaveLog("CurrentDomain_UnhandledException", (Exception)e.ExceptionObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
42
v2rayN/v2rayN/Base/WindowBase.cs
Normal file
42
v2rayN/v2rayN/Base/WindowBase.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System.Windows;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace v2rayN.Base;
|
||||
|
||||
public class WindowBase<TViewModel> : ReactiveWindow<TViewModel> where TViewModel : class
|
||||
{
|
||||
public WindowBase()
|
||||
{
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
protected virtual void OnLoaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sizeItem = ConfigHandler.GetWindowSizeItem(AppHandler.Instance.Config, GetType().Name);
|
||||
if (sizeItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Width = Math.Min(sizeItem.Width, SystemParameters.WorkArea.Width);
|
||||
Height = Math.Min(sizeItem.Height, SystemParameters.WorkArea.Height);
|
||||
|
||||
Left = SystemParameters.WorkArea.Left + ((SystemParameters.WorkArea.Width - Width) / 2);
|
||||
Top = SystemParameters.WorkArea.Top + ((SystemParameters.WorkArea.Height - Height) / 2);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
|
||||
try
|
||||
{
|
||||
ConfigHandler.SaveWindowSizeItem(AppHandler.Instance.Config, GetType().Name, Width, Height);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
<reactiveui:ReactiveWindow
|
||||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.AddServer2Window"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
@ -198,4 +199,4 @@
|
|||
</Grid>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveWindow>
|
||||
</base:WindowBase>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<reactiveui:ReactiveWindow
|
||||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.AddServerWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
@ -155,6 +156,7 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="180" />
|
||||
|
@ -214,6 +216,20 @@
|
|||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsMuxEnabled}" />
|
||||
<ToggleButton
|
||||
x:Name="togmuxEnabled"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridSs"
|
||||
|
@ -224,6 +240,7 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="180" />
|
||||
|
@ -259,6 +276,20 @@
|
|||
Width="300"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsMuxEnabled}" />
|
||||
<ToggleButton
|
||||
x:Name="togmuxEnabled3"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridSocks"
|
||||
|
@ -314,6 +345,7 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="180" />
|
||||
|
@ -373,6 +405,20 @@
|
|||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsMuxEnabled}" />
|
||||
<ToggleButton
|
||||
x:Name="togmuxEnabled5"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridTrojan"
|
||||
|
@ -383,6 +429,7 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="180" />
|
||||
|
@ -418,6 +465,20 @@
|
|||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsMuxEnabled}" />
|
||||
<ToggleButton
|
||||
x:Name="togmuxEnabled6"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridHysteria2"
|
||||
|
@ -1011,4 +1072,4 @@
|
|||
</Grid>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveWindow>
|
||||
</base:WindowBase>
|
||||
|
|
|
@ -144,11 +144,13 @@ public partial class AddServerWindow
|
|||
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.Text).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.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.SOCKS:
|
||||
|
@ -161,11 +163,13 @@ public partial class AddServerWindow
|
|||
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId5.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow5.Text).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.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Hysteria2:
|
||||
|
|
|
@ -55,9 +55,16 @@ public partial class ClashConnectionsView
|
|||
|
||||
private void AutofitColumnWidth()
|
||||
{
|
||||
foreach (var it in lstConnections.Columns)
|
||||
try
|
||||
{
|
||||
it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
|
||||
foreach (var it in lstConnections.Columns)
|
||||
{
|
||||
it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("ClashConnectionsView", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<reactiveui:ReactiveWindow
|
||||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.DNSSettingWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
@ -204,4 +205,4 @@
|
|||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveWindow>
|
||||
</base:WindowBase>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<reactiveui:ReactiveWindow
|
||||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.GlobalHotkeySettingWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
@ -169,4 +170,4 @@
|
|||
</Grid>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveWindow>
|
||||
</base:WindowBase>
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
<reactiveui:ReactiveWindow
|
||||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
xmlns:view="clr-namespace:v2rayN.Views"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
Title="v2rayN"
|
||||
Width="900"
|
||||
Height="700"
|
||||
|
@ -40,10 +41,10 @@
|
|||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ClipToBounds="True"
|
||||
KeyboardNavigation.TabNavigation="Continue"
|
||||
Style="{StaticResource MaterialDesignToolBar}">
|
||||
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
|
||||
<MenuItem Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuServers}">
|
||||
<MenuItem Padding="8,0" AutomationProperties.Name="{x:Static resx:ResUI.menuServers}">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
|
@ -111,8 +112,7 @@
|
|||
</Menu>
|
||||
<Separator />
|
||||
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
|
||||
<MenuItem Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSubscription}">
|
||||
<MenuItem Padding="8,0" AutomationProperties.Name="{x:Static resx:ResUI.menuSubscription}">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
|
@ -147,8 +147,7 @@
|
|||
</Menu>
|
||||
<Separator />
|
||||
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
|
||||
<MenuItem Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSetting}">
|
||||
<MenuItem Padding="8,0" AutomationProperties.Name="{x:Static resx:ResUI.menuSetting}">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
|
@ -214,8 +213,10 @@
|
|||
</Menu>
|
||||
<Separator />
|
||||
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
|
||||
<MenuItem x:Name="menuReload" Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuReload}">
|
||||
<MenuItem
|
||||
x:Name="menuReload"
|
||||
Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuReload}">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
|
@ -229,8 +230,10 @@
|
|||
</Menu>
|
||||
<Separator />
|
||||
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
|
||||
<MenuItem Name="menuCheckUpdate" Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuCheckUpdate}">
|
||||
<MenuItem
|
||||
Name="menuCheckUpdate"
|
||||
Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuCheckUpdate}">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
|
@ -244,8 +247,10 @@
|
|||
</Menu>
|
||||
<Separator />
|
||||
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
|
||||
<MenuItem x:Name="menuHelp" Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuHelp}">
|
||||
<MenuItem
|
||||
x:Name="menuHelp"
|
||||
Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuHelp}">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
|
@ -259,8 +264,10 @@
|
|||
</Menu>
|
||||
<Separator />
|
||||
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
|
||||
<MenuItem x:Name="menuPromotion" Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuPromotion}">
|
||||
<MenuItem
|
||||
x:Name="menuPromotion"
|
||||
Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuPromotion}">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
|
@ -274,8 +281,10 @@
|
|||
</Menu>
|
||||
<Separator />
|
||||
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
|
||||
<MenuItem x:Name="menuClose" Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuClose}">
|
||||
<MenuItem
|
||||
x:Name="menuClose"
|
||||
Padding="8,0"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuClose}">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
|
@ -424,4 +433,4 @@
|
|||
</DockPanel>
|
||||
</Grid>
|
||||
</materialDesign:DialogHost>
|
||||
</reactiveui:ReactiveWindow>
|
||||
</base:WindowBase>
|
||||
|
|
|
@ -132,14 +132,13 @@ public partial class MainWindow
|
|||
}
|
||||
});
|
||||
|
||||
this.Title = $"{Utils.GetVersion()} - {(AppHandler.Instance.IsAdministrator ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
|
||||
this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
|
||||
|
||||
if (!_config.GuiItem.EnableHWA)
|
||||
{
|
||||
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
|
||||
}
|
||||
|
||||
RestoreUI();
|
||||
AddHelpMenuItem();
|
||||
WindowsHandler.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, null);
|
||||
MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI);
|
||||
|
@ -395,20 +394,14 @@ public partial class MainWindow
|
|||
_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;
|
||||
}
|
||||
|
||||
var maxWidth = SystemParameters.WorkArea.Width;
|
||||
var maxHeight = SystemParameters.WorkArea.Height;
|
||||
if (Width > maxWidth)
|
||||
Width = maxWidth;
|
||||
if (Height > maxHeight)
|
||||
Height = maxHeight;
|
||||
if (_config.UiItem.MainGirdHeight1 > 0 && _config.UiItem.MainGirdHeight2 > 0)
|
||||
{
|
||||
if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal)
|
||||
|
@ -426,8 +419,7 @@ public partial class MainWindow
|
|||
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<reactiveui:ReactiveWindow
|
||||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.OptionSettingWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
@ -552,6 +553,7 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
@ -845,10 +847,26 @@
|
|||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsIPAPIUrl}" />
|
||||
<ComboBox
|
||||
x:Name="cmbIPAPIUrl"
|
||||
Grid.Row="20"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin8}"
|
||||
IsEditable="True"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="21"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsSubConvert}" />
|
||||
<ComboBox
|
||||
x:Name="cmbSubConvertUrl"
|
||||
Grid.Row="20"
|
||||
Grid.Row="21"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin8}"
|
||||
|
@ -857,7 +875,7 @@
|
|||
Style="{StaticResource DefComboBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="21"
|
||||
Grid.Row="22"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
|
@ -865,14 +883,14 @@
|
|||
Text="{x:Static resx:ResUI.TbSettingsMainGirdOrientation}" />
|
||||
<ComboBox
|
||||
x:Name="cmbMainGirdOrientation"
|
||||
Grid.Row="21"
|
||||
Grid.Row="22"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin8}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="22"
|
||||
Grid.Row="23"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
|
@ -880,14 +898,14 @@
|
|||
Text="{x:Static resx:ResUI.TbSettingsGeoFilesSource}" />
|
||||
<ComboBox
|
||||
x:Name="cmbGetFilesSourceUrl"
|
||||
Grid.Row="22"
|
||||
Grid.Row="23"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin8}"
|
||||
IsEditable="True"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
<TextBlock
|
||||
Grid.Row="22"
|
||||
Grid.Row="23"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
|
@ -896,7 +914,7 @@
|
|||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="23"
|
||||
Grid.Row="24"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
|
@ -904,14 +922,14 @@
|
|||
Text="{x:Static resx:ResUI.TbSettingsSrsFilesSource}" />
|
||||
<ComboBox
|
||||
x:Name="cmbSrsFilesSourceUrl"
|
||||
Grid.Row="23"
|
||||
Grid.Row="24"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin8}"
|
||||
IsEditable="True"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
<TextBlock
|
||||
Grid.Row="23"
|
||||
Grid.Row="24"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
|
@ -920,7 +938,7 @@
|
|||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="24"
|
||||
Grid.Row="25"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
|
@ -928,14 +946,14 @@
|
|||
Text="{x:Static resx:ResUI.TbSettingsRoutingRulesSource}" />
|
||||
<ComboBox
|
||||
x:Name="cmbRoutingRulesSourceUrl"
|
||||
Grid.Row="24"
|
||||
Grid.Row="25"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin8}"
|
||||
IsEditable="True"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
<TextBlock
|
||||
Grid.Row="24"
|
||||
Grid.Row="25"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
|
@ -1212,4 +1230,4 @@
|
|||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveWindow>
|
||||
</base:WindowBase>
|
||||
|
|
|
@ -101,6 +101,10 @@ public partial class OptionSettingWindow
|
|||
{
|
||||
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());
|
||||
|
@ -162,6 +166,7 @@ public partial class OptionSettingWindow
|
|||
this.Bind(ViewModel, vm => vm.GeoFileSourceUrl, v => v.cmbGetFilesSourceUrl.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.Text).DisposeWith(disposables);
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
<ListBox
|
||||
x:Name="lstGroup"
|
||||
MaxHeight="200"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSubscription}"
|
||||
FontSize="{DynamicResource StdFontSize}"
|
||||
ItemContainerStyle="{StaticResource MyChipListBoxItem}"
|
||||
Style="{StaticResource MaterialDesignChoiceChipPrimaryOutlineListBox}">
|
||||
|
@ -40,9 +41,9 @@
|
|||
Width="30"
|
||||
Height="30"
|
||||
Margin="{StaticResource MarginLeftRight4}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSubEdit}"
|
||||
Style="{StaticResource MaterialDesignFloatingActionMiniLightButton}"
|
||||
ToolTip="{x:Static resx:ResUI.menuSubEdit}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSubEdit}">
|
||||
ToolTip="{x:Static resx:ResUI.menuSubEdit}">
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Kind="Edit" />
|
||||
</Button>
|
||||
<Button
|
||||
|
@ -50,9 +51,9 @@
|
|||
Width="30"
|
||||
Height="30"
|
||||
Margin="{StaticResource MarginLeftRight4}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSubAdd}"
|
||||
Style="{StaticResource MaterialDesignFloatingActionMiniLightButton}"
|
||||
ToolTip="{x:Static resx:ResUI.menuSubAdd}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSubAdd}">
|
||||
ToolTip="{x:Static resx:ResUI.menuSubAdd}">
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Kind="Plus" />
|
||||
</Button>
|
||||
|
||||
|
@ -61,9 +62,9 @@
|
|||
Width="30"
|
||||
Height="30"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}"
|
||||
Style="{StaticResource MaterialDesignFloatingActionMiniLightButton}"
|
||||
ToolTip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
|
||||
ToolTip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Kind="ArrowSplitVertical" />
|
||||
</Button>
|
||||
<TextBox
|
||||
|
@ -73,8 +74,8 @@
|
|||
VerticalContentAlignment="Center"
|
||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.MsgServerTitle}"
|
||||
materialDesign:TextFieldAssist.HasClearButton="True"
|
||||
Style="{StaticResource DefTextBox}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.MsgServerTitle}" />
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.MsgServerTitle}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
</WrapPanel>
|
||||
<DataGrid
|
||||
x:Name="lstProfiles"
|
||||
|
|
|
@ -323,9 +323,16 @@ public partial class ProfilesView
|
|||
|
||||
private void AutofitColumnWidth()
|
||||
{
|
||||
foreach (var it in lstProfiles.Columns)
|
||||
try
|
||||
{
|
||||
it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
|
||||
foreach (var it in lstProfiles.Columns)
|
||||
{
|
||||
it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("ProfilesView", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<reactiveui:ReactiveWindow
|
||||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.RoutingRuleDetailsWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
Title="{x:Static resx:ResUI.menuRoutingRuleDetailsSetting}"
|
||||
|
@ -242,4 +243,4 @@
|
|||
</GroupBox>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveWindow>
|
||||
</base:WindowBase>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<reactiveui:ReactiveWindow
|
||||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.RoutingRuleSettingWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
Title="{x:Static resx:ResUI.menuRoutingRuleSetting}"
|
||||
|
@ -334,4 +335,4 @@
|
|||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveWindow>
|
||||
</base:WindowBase>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<reactiveui:ReactiveWindow
|
||||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.RoutingSettingWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
@ -29,68 +30,25 @@
|
|||
VerticalAlignment="Center"
|
||||
ClipToBounds="True"
|
||||
Style="{StaticResource MaterialDesignToolBar}">
|
||||
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
|
||||
<MenuItem x:Name="menuRoutingAdvanced" Padding="8,0">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
Margin="{StaticResource MarginRight8}"
|
||||
VerticalAlignment="Center"
|
||||
Kind="Routes" />
|
||||
<TextBlock Text="{x:Static resx:ResUI.menuRoutingAdvanced}" />
|
||||
</StackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem
|
||||
x:Name="menuRoutingAdvancedAdd2"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" />
|
||||
<MenuItem
|
||||
x:Name="menuRoutingAdvancedImportRules2"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Button x:Name="menuRoutingAdvancedAdd2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
Margin="{StaticResource MarginRight8}"
|
||||
VerticalAlignment="Center"
|
||||
Kind="Plus" />
|
||||
<TextBlock Style="{StaticResource ToolbarTextBlock}" Text="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Separator />
|
||||
<TextBlock
|
||||
Margin="{StaticResource MarginLeft8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}">
|
||||
<Hyperlink Click="linkdomainStrategy_Click">
|
||||
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy}" />
|
||||
<materialDesign:PackIcon Kind="Link" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<ComboBox
|
||||
x:Name="cmbdomainStrategy"
|
||||
Width="110"
|
||||
Margin="{StaticResource MarginLeft8}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
<Separator />
|
||||
<TextBlock
|
||||
Margin="{StaticResource MarginLeft8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbdomainMatcher}" />
|
||||
<ComboBox
|
||||
x:Name="cmbdomainMatcher"
|
||||
Width="60"
|
||||
Margin="{StaticResource MarginLeft8}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
<Separator />
|
||||
<TextBlock
|
||||
Margin="{StaticResource MarginLeft8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}">
|
||||
<Hyperlink Click="linkdomainStrategy4Singbox_Click">
|
||||
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy4Singbox}" />
|
||||
<materialDesign:PackIcon Kind="Link" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<ComboBox
|
||||
x:Name="cmbdomainStrategy4Singbox"
|
||||
Width="100"
|
||||
Margin="{StaticResource MarginLeft8}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
<Button x:Name="menuRoutingAdvancedImportRules2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
Margin="{StaticResource MarginRight8}"
|
||||
VerticalAlignment="Center"
|
||||
Kind="Import" />
|
||||
<TextBlock Style="{StaticResource ToolbarTextBlock}" Text="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</ToolBar>
|
||||
</ToolBarTray>
|
||||
|
||||
|
@ -122,82 +80,145 @@
|
|||
Style="{StaticResource DefButton}" />
|
||||
</StackPanel>
|
||||
|
||||
<DockPanel>
|
||||
<TabControl x:Name="tabAdvanced">
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.TbRoutingTabRuleList}">
|
||||
<DataGrid
|
||||
x:Name="lstRoutings"
|
||||
AutoGenerateColumns="False"
|
||||
BorderThickness="1"
|
||||
CanUserAddRows="False"
|
||||
CanUserResizeRows="False"
|
||||
CanUserSortColumns="False"
|
||||
EnableRowVirtualization="True"
|
||||
GridLinesVisibility="All"
|
||||
HeadersVisibility="Column"
|
||||
IsReadOnly="True"
|
||||
Style="{StaticResource DefDataGrid}">
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu Style="{StaticResource DefContextMenu}">
|
||||
<MenuItem
|
||||
x:Name="menuRoutingAdvancedAdd"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" />
|
||||
<MenuItem
|
||||
x:Name="menuRoutingAdvancedRemove"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuRoutingAdvancedRemove}" />
|
||||
<MenuItem
|
||||
x:Name="menuRoutingAdvancedSelectAll"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuSelectAll}" />
|
||||
<MenuItem
|
||||
x:Name="menuRoutingAdvancedSetDefault"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuRoutingAdvancedSetDefault}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
x:Name="menuRoutingAdvancedImportRules"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<DataGrid.Resources>
|
||||
<Style BasedOn="{StaticResource MaterialDesignDataGridCell}" TargetType="DataGridCell">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsActive}" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource MaterialDesign.Brush.Primary.Light}" />
|
||||
<Setter Property="Foreground" Value="Black" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource MaterialDesign.Brush.Primary.Light}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Remarks}"
|
||||
Header="{x:Static resx:ResUI.LvRemarks}" />
|
||||
<DataGridTextColumn
|
||||
Width="60"
|
||||
Binding="{Binding RuleNum}"
|
||||
Header="{x:Static resx:ResUI.LvCount}" />
|
||||
<DataGridTextColumn
|
||||
Width="60"
|
||||
Binding="{Binding Sort}"
|
||||
Header="{x:Static resx:ResUI.LvSort}" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Url}"
|
||||
Header="{x:Static resx:ResUI.LvUrl}" />
|
||||
<DataGridTextColumn
|
||||
Width="300"
|
||||
Binding="{Binding CustomIcon}"
|
||||
Header="{x:Static resx:ResUI.LvCustomIcon}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
<Grid Margin="{StaticResource Margin8}" DockPanel.Dock="Top">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}">
|
||||
<Hyperlink Click="linkdomainStrategy_Click">
|
||||
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy}" />
|
||||
<materialDesign:PackIcon Kind="Link" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<ComboBox
|
||||
x:Name="cmbdomainStrategy"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbdomainMatcher}" />
|
||||
<ComboBox
|
||||
x:Name="cmbdomainMatcher"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}">
|
||||
<Hyperlink Click="linkdomainStrategy4Singbox_Click">
|
||||
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy4Singbox}" />
|
||||
<materialDesign:PackIcon Kind="Link" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<ComboBox
|
||||
x:Name="cmbdomainStrategy4Singbox"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
</Grid>
|
||||
|
||||
<TabControl x:Name="tabAdvanced">
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.TbRoutingTabRuleList}">
|
||||
<DataGrid
|
||||
x:Name="lstRoutings"
|
||||
AutoGenerateColumns="False"
|
||||
BorderThickness="1"
|
||||
CanUserAddRows="False"
|
||||
CanUserResizeRows="False"
|
||||
CanUserSortColumns="False"
|
||||
EnableRowVirtualization="True"
|
||||
GridLinesVisibility="All"
|
||||
HeadersVisibility="Column"
|
||||
IsReadOnly="True"
|
||||
Style="{StaticResource DefDataGrid}">
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu Style="{StaticResource DefContextMenu}">
|
||||
<MenuItem
|
||||
x:Name="menuRoutingAdvancedAdd"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" />
|
||||
<MenuItem
|
||||
x:Name="menuRoutingAdvancedRemove"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuRoutingAdvancedRemove}" />
|
||||
<MenuItem
|
||||
x:Name="menuRoutingAdvancedSelectAll"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuSelectAll}" />
|
||||
<MenuItem
|
||||
x:Name="menuRoutingAdvancedSetDefault"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuRoutingAdvancedSetDefault}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
x:Name="menuRoutingAdvancedImportRules"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<DataGrid.Resources>
|
||||
<Style BasedOn="{StaticResource MaterialDesignDataGridCell}" TargetType="DataGridCell">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsActive}" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource MaterialDesign.Brush.Primary.Light}" />
|
||||
<Setter Property="Foreground" Value="Black" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource MaterialDesign.Brush.Primary.Light}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Remarks}"
|
||||
Header="{x:Static resx:ResUI.LvRemarks}" />
|
||||
<DataGridTextColumn
|
||||
Width="60"
|
||||
Binding="{Binding RuleNum}"
|
||||
Header="{x:Static resx:ResUI.LvCount}" />
|
||||
<DataGridTextColumn
|
||||
Width="60"
|
||||
Binding="{Binding Sort}"
|
||||
Header="{x:Static resx:ResUI.LvSort}" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Url}"
|
||||
Header="{x:Static resx:ResUI.LvUrl}" />
|
||||
<DataGridTextColumn
|
||||
Width="300"
|
||||
Binding="{Binding CustomIcon}"
|
||||
Header="{x:Static resx:ResUI.LvCustomIcon}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveWindow>
|
||||
</base:WindowBase>
|
||||
|
|
|
@ -61,7 +61,8 @@
|
|||
x:Name="togEnableTun"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" AutomationProperties.Name="{x:Static resx:ResUI.TbEnableTunAs}" />
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.TbEnableTunAs}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
|
@ -74,9 +75,9 @@
|
|||
Width="160"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.menuSystemproxy}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemproxy}"
|
||||
FontSize="{DynamicResource StdFontSize}"
|
||||
Style="{StaticResource MaterialDesignFloatingHintComboBox}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemproxy}">
|
||||
Style="{StaticResource MaterialDesignFloatingHintComboBox}">
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuSystemProxyClear}" />
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuSystemProxySet}" />
|
||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuSystemProxyNothing}" />
|
||||
|
@ -88,10 +89,11 @@
|
|||
Width="160"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.menuRouting}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}"
|
||||
DisplayMemberPath="Remarks"
|
||||
FontSize="{DynamicResource StdFontSize}"
|
||||
Style="{StaticResource MaterialDesignFloatingHintComboBox}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}"/>
|
||||
Style="{StaticResource MaterialDesignFloatingHintComboBox}">
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="{StaticResource MarginLeftRight8}" VerticalAlignment="Center">
|
||||
|
@ -109,8 +111,10 @@
|
|||
ToolTipText="v2rayN">
|
||||
<tb:TaskbarIcon.ContextMenu>
|
||||
<ContextMenu Style="{StaticResource DefContextMenu}">
|
||||
<MenuItem x:Name="menuSystemProxyClear" Height="{StaticResource MenuItemHeight}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxyClear}">
|
||||
<MenuItem
|
||||
x:Name="menuSystemProxyClear"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxyClear}">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
|
@ -122,8 +126,10 @@
|
|||
</StackPanel>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="menuSystemProxySet" Height="{StaticResource MenuItemHeight}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxySet}">
|
||||
<MenuItem
|
||||
x:Name="menuSystemProxySet"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxySet}">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
|
@ -135,8 +141,10 @@
|
|||
</StackPanel>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="menuSystemProxyNothing" Height="{StaticResource MenuItemHeight}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxyNothing}">
|
||||
<MenuItem
|
||||
x:Name="menuSystemProxyNothing"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxyNothing}">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
|
@ -148,8 +156,10 @@
|
|||
</StackPanel>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="menuSystemProxyPac" Height="{StaticResource MenuItemHeight}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxyPac}">
|
||||
<MenuItem
|
||||
x:Name="menuSystemProxyPac"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxyPac}">
|
||||
<MenuItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon
|
||||
|
@ -162,18 +172,21 @@
|
|||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<Separator x:Name="sepRoutings" />
|
||||
<MenuItem x:Name="menuRoutings" Height="Auto"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}">
|
||||
<MenuItem
|
||||
x:Name="menuRoutings"
|
||||
Height="Auto"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}">
|
||||
<MenuItem.Header>
|
||||
<DockPanel>
|
||||
<ComboBox
|
||||
x:Name="cmbRoutings"
|
||||
MaxWidth="300"
|
||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.menuRouting}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}"
|
||||
DisplayMemberPath="Remarks"
|
||||
FontSize="{DynamicResource StdFontSize}"
|
||||
Style="{StaticResource MaterialDesignFilledComboBox}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}"/>
|
||||
Style="{StaticResource MaterialDesignFilledComboBox}">
|
||||
</ComboBox>
|
||||
</DockPanel>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
|
@ -184,10 +197,11 @@
|
|||
x:Name="cmbServers"
|
||||
MaxWidth="300"
|
||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.menuServers}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuServers}"
|
||||
DisplayMemberPath="Text"
|
||||
FontSize="{DynamicResource StdFontSize}"
|
||||
Style="{StaticResource MaterialDesignFilledComboBox}"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.menuServers}"/>
|
||||
Style="{StaticResource MaterialDesignFilledComboBox}">
|
||||
</ComboBox>
|
||||
</DockPanel>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<reactiveui:ReactiveWindow
|
||||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.SubEditWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
Title="{x:Static resx:ResUI.menuSubSetting}"
|
||||
|
@ -315,4 +316,4 @@
|
|||
</Grid>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveWindow>
|
||||
</base:WindowBase>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<reactiveui:ReactiveWindow
|
||||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.SubSettingWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
Title="{x:Static resx:ResUI.menuSubSetting}"
|
||||
|
@ -120,4 +121,4 @@
|
|||
</DataGrid>
|
||||
</DockPanel>
|
||||
</materialDesign:DialogHost>
|
||||
</reactiveui:ReactiveWindow>
|
||||
</base:WindowBase>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<reactiveui:ReactiveUserControl
|
||||
x:Class="v2rayN.Views.ThemeSettingView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:v2rayN.ViewModels"
|
||||
d:DesignHeight="450"
|
||||
|
|
Loading…
Reference in a new issue