Compare commits

...

32 commits

Author SHA1 Message Date
2dust
7a9ee6e9e2 Each window can remember its size
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-07-01 19:39:27 +08:00
2dust
cb28c31519 Remove unused 2025-07-01 19:19:15 +08:00
DHR60
84f93f2ae6
Optimize proxy chain handling (#7515)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
* Optimize proxy chain handling

* Avoids duplicate proxy chain generation
2025-06-30 20:26:10 +08:00
2dust
30c09a7b54 Add mux settings for per-server, VMess/Shadowsocks/VLESS/Trojan
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
If you want to use global settings, do not set per-server
2025-06-29 15:06:02 +08:00
2dust
b3874a78b9 Improved order of items in settings
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-06-29 11:16:21 +08:00
2dust
3e71965cd4 Optimization and Improvement,DataGrid list only updates some attribute values
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
https://github.com/2dust/v2rayN/issues/7489
2025-06-26 14:23:30 +08:00
2dust
3df57f74ba Update winget-publish.yml
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-06-22 10:36:05 +08:00
2dust
7972cb8e1f Update winget-publish.yml 2025-06-22 10:31:41 +08:00
2dust
0d74452c6c Added parameter MacOSShowInDock to control whether MacOS platform app is displayed in the Dock
https://github.com/2dust/v2rayN/issues/7465
2025-06-21 17:00:49 +08:00
2dust
f947f63e6d Display or hide the main window menu in the tray and move it to the top
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-06-21 16:54:36 +08:00
DHR60
fefa7ded5a
Optimize proxy chain handling for multiple nodes (#7468)
* Improves outbound proxy chain handling.

* Improves sing-box outbound proxy chain handling.

* AI-optimized code
2025-06-21 16:45:17 +08:00
2dust
a46a4ad7c1 up 7.12.7
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-06-19 11:59:20 +08:00
2dust
e46f680651 Optimize the UI for dns settings 2025-06-19 11:24:33 +08:00
2dust
93a20852f5 Optimize the UI for routing settings 2025-06-19 10:48:47 +08:00
2dust
298bb64e66 Routing rules do not determine whether it is version V3 2025-06-19 10:04:48 +08:00
2dust
0e5ac65f55 Improved network speed display when using clash api without proxy and direct
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-06-16 15:06:18 +08:00
2dust
cb6122f872 First scroll horizontally to the initial position to avoid the control crash bug
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
https://github.com/2dust/v2rayN/issues/7387
2025-06-16 10:48:35 +08:00
2dust
06500e0218 When testing, start the core and then delay 1s before starting the test
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
https://github.com/2dust/v2rayN/issues/7391
2025-06-15 17:33:11 +08:00
DHR60
9ddf0b42e7
Fix Observatory (#7437)
* Enables concurrency for observatory

* Adds burst observatory for least load balancer
2025-06-15 14:41:47 +08:00
2dust
2056377f55 up 7.12.6 2025-06-15 14:39:02 +08:00
2dust
7065dabc94 If it is not in China area, no update is required
https://github.com/2dust/v2rayN/issues/7417
2025-06-15 14:35:59 +08:00
2dust
9e2336a71e The notification pop-up position is changed to the top right
https://github.com/2dust/v2rayN/issues/7421
2025-06-15 14:16:30 +08:00
2dust
d239c627f3 Use File.SetUnixFileMode to set the execute permission first. If that fails, use chmod to set the execute permission.
https://github.com/2dust/v2rayN/issues/7416
2025-06-15 12:33:21 +08:00
2dust
984b36d34e Update Directory.Packages.props 2025-06-15 12:33:02 +08:00
DHR60
c81bc2f536
Fix (#7405)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
#7404
2025-06-08 09:30:08 +08:00
Miheichev Aleksandr Sergeevich
87f7e65076
docs: improve README.md formatting and readability (#7376)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
- Split long line for better readability
- Add consistent spacing between sections
- Remove extra blank lines
- Update sing-box link to point to main repo instead of releases
2025-06-02 09:52:49 +08:00
duolaameng
9985b68b6b
Update LICENSE (#7327)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
完善版权信息:项目名称、时间、作者
2025-05-28 20:04:12 +08:00
Miheichev Aleksandr Sergeevich
fb4b8b2789
Revision of the Russian translation (#7342)
* Revision of the Russian translation

* Fix: remove duplicate TbSettingsSocksPortTip and refine three Russian strings

* refactor: improve Russian translations and fix punctuation

- Standardized punctuation in tooltips and messages (replaced semicolons with commas where appropriate)
- Improved consistency in server type labels (moved square brackets in "[TUIC] server" and similar)
- Enhanced clarity in "Speed Ping Test URL" to "URL for real ping test"
- Simplified "NeedRebootTips" message
- Fixed minor grammatical and formatting issues in various UI strings
- Removed duplicate "TbSettingsStartBootTip" entry
- Improved tooltip for port settings
2025-05-28 20:03:50 +08:00
Yuri Tukhachevsky
3812ccc780
Add support for DDE and MATE (#7353)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
* add support for UKUI

* Add support for DDE and MATE
2025-05-28 09:05:55 +08:00
Yuri Tukhachevsky
608a6c387a
add support for UKUI (#7348)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-05-26 13:48:31 +08:00
2dust
4875b37f70 up 7.12.5
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-05-25 19:12:06 +08:00
2dust
2f3fba73de Bug fix
https://github.com/2dust/v2rayN/issues/7333
2025-05-25 19:11:42 +08:00
58 changed files with 1412 additions and 875 deletions

View file

@ -22,10 +22,18 @@ jobs:
$github = Invoke-RestMethod -uri "https://api.github.com/repos/2dust/v2rayN/releases" $github = Invoke-RestMethod -uri "https://api.github.com/repos/2dust/v2rayN/releases"
$targetRelease = $github | Where-Object -Property prerelease -match 'False' | Select -First 1 $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 $ver = $targetRelease.tag_name
# getting latest wingetcreate file # getting latest wingetcreate file
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe 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

View file

@ -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. 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.> <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 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 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 If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode: 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 program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details. under certain conditions; type `show c' for details.

View file

@ -1,16 +1,18 @@
# v2rayN # 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)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayN)](https://github.com/2dust/v2rayN/commits/master) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayN)](https://github.com/2dust/v2rayN/commits/master)
[![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayn/badge)](https://www.codefactor.io/repository/github/2dust/v2rayn) [![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayn/badge)](https://www.codefactor.io/repository/github/2dust/v2rayn)
[![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayN/latest/total?logo=github)](https://github.com/2dust/v2rayN/releases) [![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayN/latest/total?logo=github)](https://github.com/2dust/v2rayN/releases)
[![Chat on Telegram](https://img.shields.io/badge/Chat%20on-Telegram-brightgreen.svg)](https://t.me/v2rayn) [![Chat on Telegram](https://img.shields.io/badge/Chat%20on-Telegram-brightgreen.svg)](https://t.me/v2rayn)
## How to use ## How to use
Read the [Wiki](https://github.com/2dust/v2rayN/wiki) for details. Read the [Wiki](https://github.com/2dust/v2rayN/wiki) for details.
## Telegram Channel ## Telegram Channel
[github_2dust](https://t.me/github_2dust) [github_2dust](https://t.me/github_2dust)

View file

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.12.4</Version> <Version>7.12.7</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -5,21 +5,21 @@
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled> <CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.0" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.1" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.0" /> <PackageVersion Include="Avalonia.Desktop" Version="11.3.1" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.0" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.1" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.0" /> <PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.1" />
<PackageVersion Include="CliWrap" Version="3.8.2" /> <PackageVersion Include="CliWrap" Version="3.9.0" />
<PackageVersion Include="Downloader" Version="3.3.4" /> <PackageVersion Include="Downloader" Version="3.3.4" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" /> <PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
<PackageVersion Include="MaterialDesignThemes" Version="5.2.1" /> <PackageVersion Include="MaterialDesignThemes" Version="5.2.1" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" /> <PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" />
<PackageVersion Include="QRCoder" Version="1.6.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.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="20.2.45" /> <PackageVersion Include="ReactiveUI.WPF" Version="20.3.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.7" /> <PackageVersion Include="Semi.Avalonia" Version="11.2.1.8" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.7" /> <PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.8" />
<PackageVersion Include="Splat.NLog" Version="15.3.1" /> <PackageVersion Include="Splat.NLog" Version="15.3.1" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" /> <PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.1" /> <PackageVersion Include="TaskScheduler" Version="2.12.1" />

View file

@ -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;
}
}

View file

@ -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");
}
}

View file

@ -582,7 +582,7 @@ public class Utils
var result = await cmd.ExecuteBufferedAsync(); var result = await cmd.ExecuteBufferedAsync();
if (result.IsSuccess) if (result.IsSuccess)
{ {
return result.StandardOutput.ToString(); return result.StandardOutput ?? "";
} }
Logging.SaveLog(result.ToString() ?? ""); Logging.SaveLog(result.ToString() ?? "");
@ -839,14 +839,46 @@ public class Utils
public static async Task<string?> SetLinuxChmod(string? fileName) public static async Task<string?> SetLinuxChmod(string? fileName)
{ {
if (fileName.IsNullOrEmpty()) if (fileName.IsNullOrEmpty())
{
return null; return null;
}
if (SetUnixFileMode(fileName))
{
Logging.SaveLog($"Successfully set the file execution permission, {fileName}");
return "";
}
if (fileName.Contains(' ')) if (fileName.Contains(' '))
{
fileName = fileName.AppendQuotes(); fileName = fileName.AppendQuotes();
//File.SetUnixFileMode(fileName, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); }
var arg = new List<string>() { "-c", $"chmod +x {fileName}" }; var arg = new List<string>() { "-c", $"chmod +x {fileName}" };
return await GetCliWrapOutput(Global.LinuxBash, arg); 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) public static async Task<string?> GetLinuxFontFamily(string lang)
{ {
// var arg = new List<string>() { "-c", $"fc-list :lang={lang} family" }; // var arg = new List<string>() { "-c", $"fc-list :lang={lang} family" };

View file

@ -101,6 +101,7 @@ public class ConfigHandler
EnableAutoAdjustMainLvColWidth = true EnableAutoAdjustMainLvColWidth = true
}; };
config.UiItem.MainColumnItem ??= new(); config.UiItem.MainColumnItem ??= new();
config.UiItem.WindowSizeItem ??= new();
if (config.UiItem.CurrentLanguage.IsNullOrEmpty()) if (config.UiItem.CurrentLanguage.IsNullOrEmpty())
{ {
@ -246,6 +247,7 @@ public class ConfigHandler
item.ShortId = profileItem.ShortId; item.ShortId = profileItem.ShortId;
item.SpiderX = profileItem.SpiderX; item.SpiderX = profileItem.SpiderX;
item.Extra = profileItem.Extra; item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled;
} }
var ret = item.ConfigType switch var ret = item.ConfigType switch
@ -1979,7 +1981,7 @@ public class ConfigHandler
items = await AppHandler.Instance.RoutingItems(); items = await AppHandler.Instance.RoutingItems();
} }
if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(ver)).ToList().Count > 0) if (!blImportAdvancedRules && items.Count > 0)
{ {
return 0; return 0;
} }
@ -2173,4 +2175,34 @@ public class ConfigHandler
} }
#endregion Regional Presets #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
} }

View file

@ -89,8 +89,6 @@ public class UIItem
{ {
public bool EnableAutoAdjustMainLvColWidth { get; set; } public bool EnableAutoAdjustMainLvColWidth { get; set; }
public bool EnableUpdateSubOnlyRemarksExist { get; set; } public bool EnableUpdateSubOnlyRemarksExist { get; set; }
public double MainWidth { get; set; }
public double MainHeight { get; set; }
public double MainGirdHeight1 { get; set; } public double MainGirdHeight1 { get; set; }
public double MainGirdHeight2 { get; set; } public double MainGirdHeight2 { get; set; }
public EGirdOrientation MainGirdOrientation { get; set; } = EGirdOrientation.Vertical; public EGirdOrientation MainGirdOrientation { get; set; } = EGirdOrientation.Vertical;
@ -103,8 +101,10 @@ public class UIItem
public bool DoubleClick2Activate { get; set; } public bool DoubleClick2Activate { get; set; }
public bool AutoHideStartup { get; set; } public bool AutoHideStartup { get; set; }
public bool Hide2TrayWhenClose { get; set; } public bool Hide2TrayWhenClose { get; set; }
public List<ColumnItem> MainColumnItem { get; set; }
public bool ShowInTaskbar { 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] [Serializable]
@ -245,3 +245,11 @@ public class Fragment4RayItem
public string? Length { get; set; } public string? Length { get; set; }
public string? Interval { 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; }
}

View file

@ -4,7 +4,7 @@ internal class IPAPIInfo
{ {
public string? ip { get; set; } public string? ip { get; set; }
public string? clientIp { get; set; } public string? clientIp { get; set; }
public string? ip_addr { get; set; } public string? ip_addr { get; set; }
public string? query { get; set; } public string? query { get; set; }
public string? country { get; set; } public string? country { get; set; }
public string? country_name { get; set; } public string? country_name { get; set; }

View file

@ -1,9 +1,10 @@
using ReactiveUI;
using SQLite; using SQLite;
namespace ServiceLib.Models; namespace ServiceLib.Models;
[Serializable] [Serializable]
public class ProfileItem public class ProfileItem : ReactiveObject
{ {
public ProfileItem() public ProfileItem()
{ {
@ -93,4 +94,5 @@ public class ProfileItem
public string ShortId { get; set; } public string ShortId { get; set; }
public string SpiderX { get; set; } public string SpiderX { get; set; }
public string Extra { get; set; } public string Extra { get; set; }
public bool? MuxEnabled { get; set; }
} }

View file

@ -1,3 +1,5 @@
using ReactiveUI.Fody.Helpers;
namespace ServiceLib.Models; namespace ServiceLib.Models;
[Serializable] [Serializable]
@ -5,13 +7,28 @@ public class ProfileItemModel : ProfileItem
{ {
public bool IsActive { get; set; } public bool IsActive { get; set; }
public string SubRemarks { get; set; } public string SubRemarks { get; set; }
[Reactive]
public int Delay { get; set; } public int Delay { get; set; }
public decimal Speed { get; set; } public decimal Speed { get; set; }
public int Sort { get; set; } public int Sort { get; set; }
[Reactive]
public string DelayVal { get; set; } public string DelayVal { get; set; }
[Reactive]
public string SpeedVal { get; set; } public string SpeedVal { get; set; }
[Reactive]
public string TodayUp { get; set; } public string TodayUp { get; set; }
[Reactive]
public string TodayDown { get; set; } public string TodayDown { get; set; }
[Reactive]
public string TotalUp { get; set; } public string TotalUp { get; set; }
[Reactive]
public string TotalDown { get; set; } public string TotalDown { get; set; }
} }

View file

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
Version 2.0 Version 2.0
The primary goals of this format is to allow a simple XML format The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes various data types are done through the TypeConverter classes
associated with the data types. associated with the data types.
Example: Example:
... ado.net/XML headers & schema ... ... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader> <resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</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> <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment> <comment>This is a comment</comment>
</data> </data>
There are any number of "resheader" rows that contain simple There are any number of "resheader" rows that contain simple
name/value pairs. name/value pairs.
Each data row contains a name, and value. The row also contains a Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture. text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the Classes that don't support this are serialized and stored with the
mimetype set. mimetype set.
The mimetype is used for serialized objects, and tells the The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly: extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below. read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64 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 : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64 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 : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64 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 : using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding. : 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> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="BatchExportURLSuccessfully" xml:space="preserve"> <data name="BatchExportURLSuccessfully" xml:space="preserve">
<value>Экспортирование URL в буфер обмена успешно завершено</value> <value>Ссылка успешно скопирована в буфер обмена</value>
</data> </data>
<data name="CheckServerSettings" xml:space="preserve"> <data name="CheckServerSettings" xml:space="preserve">
<value>Пожалуйста, сначала проверьте настройки сервера</value> <value>Сначала проверьте настройки сервера</value>
</data> </data>
<data name="ConfigurationFormatIncorrect" xml:space="preserve"> <data name="ConfigurationFormatIncorrect" xml:space="preserve">
<value>Недопустимый формат конфигурации</value> <value>Недопустимый формат конфигурации</value>
</data> </data>
<data name="CustomServerTips" xml:space="preserve"> <data name="CustomServerTips" xml:space="preserve">
<value>Обратите внимание, что пользовательская конфигурация полностью зависит от вашей собственной конфигурации и работает не со всеми настройками. Если вы хотите использовать системный прокси, пожалуйста, измените порт прослушивания вручную.</value> <value>Обратите внимание, что пользовательская конфигурация полностью зависит от вашей собственной конфигурации и работает не со всеми настройками. Если вы хотите использовать системный прокси, то измените порт прослушивания вручную</value>
</data> </data>
<data name="Downloading" xml:space="preserve"> <data name="Downloading" xml:space="preserve">
<value>Загрузка...</value> <value>Загрузка...</value>
@ -136,13 +136,13 @@
<value>Скорость загрузки</value> <value>Скорость загрузки</value>
</data> </data>
<data name="DownloadYesNo" xml:space="preserve"> <data name="DownloadYesNo" xml:space="preserve">
<value>Хотите загрузить? {0}</value> <value>Хотите загрузить {0}?</value>
</data> </data>
<data name="FailedConversionConfiguration" xml:space="preserve"> <data name="FailedConversionConfiguration" xml:space="preserve">
<value>Не удалось преобразовать файл конфигурации</value> <value>Не удалось преобразовать файл конфигурации</value>
</data> </data>
<data name="FailedGenDefaultConfiguration" xml:space="preserve"> <data name="FailedGenDefaultConfiguration" xml:space="preserve">
<value>Не удалось создать файл конфигурации по умолчаниюe</value> <value>Не удалось создать файл конфигурации по умолчанию</value>
</data> </data>
<data name="FailedGetDefaultConfiguration" xml:space="preserve"> <data name="FailedGetDefaultConfiguration" xml:space="preserve">
<value>Не удалось получить конфигурацию по умолчанию</value> <value>Не удалось получить конфигурацию по умолчанию</value>
@ -154,37 +154,37 @@
<value>Не удалось прочитать файл конфигурации</value> <value>Не удалось прочитать файл конфигурации</value>
</data> </data>
<data name="FillCorrectServerPort" xml:space="preserve"> <data name="FillCorrectServerPort" xml:space="preserve">
<value>Пожалуйста, укажите порт сервера в правильном формате</value> <value>Укажите порт сервера в правильном формате</value>
</data> </data>
<data name="FillLocalListeningPort" xml:space="preserve"> <data name="FillLocalListeningPort" xml:space="preserve">
<value>Пожалуйста, укажите локальный порт прослушивания</value> <value>Введите локальный порт для прослушивания</value>
</data> </data>
<data name="FillPassword" xml:space="preserve"> <data name="FillPassword" xml:space="preserve">
<value>Пожалуйста, введите пароль</value> <value>Введите пароль</value>
</data> </data>
<data name="FillServerAddress" xml:space="preserve"> <data name="FillServerAddress" xml:space="preserve">
<value>Пожалуйста, заполните адрес сервера</value> <value>Введите адрес сервера</value>
</data> </data>
<data name="FillUUID" xml:space="preserve"> <data name="FillUUID" xml:space="preserve">
<value>Пожалуйста, заполните идентификатор пользователя</value> <value>Введите идентификатор пользователя</value>
</data> </data>
<data name="Incorrectconfiguration" xml:space="preserve"> <data name="Incorrectconfiguration" xml:space="preserve">
<value>Некорректная конфигурация, пожалуйста, проверьте</value> <value>Некорректная конфигурация. Проверьте</value>
</data> </data>
<data name="InitialConfiguration" xml:space="preserve"> <data name="InitialConfiguration" xml:space="preserve">
<value>Исходная конфигурация</value> <value>Исходная конфигурация</value>
</data> </data>
<data name="IsLatestCore" xml:space="preserve"> <data name="IsLatestCore" xml:space="preserve">
<value>{0} {1} является последней версией.</value> <value>{0} {1} является последней версией</value>
</data> </data>
<data name="IsLatestN" xml:space="preserve"> <data name="IsLatestN" xml:space="preserve">
<value>{0} {1} является последней версией.</value> <value>{0} {1} является последней версией</value>
</data> </data>
<data name="LvAddress" xml:space="preserve"> <data name="LvAddress" xml:space="preserve">
<value>Адрес</value> <value>Адрес</value>
</data> </data>
<data name="LvEncryptionMethod" xml:space="preserve"> <data name="LvEncryptionMethod" xml:space="preserve">
<value>Безопасность</value> <value>Метод шифрования</value>
</data> </data>
<data name="LvPort" xml:space="preserve"> <data name="LvPort" xml:space="preserve">
<value>Порт</value> <value>Порт</value>
@ -199,7 +199,7 @@
<value>Загружено трафика сегодня</value> <value>Загружено трафика сегодня</value>
</data> </data>
<data name="LvTodayUploadDataAmount" xml:space="preserve"> <data name="LvTodayUploadDataAmount" xml:space="preserve">
<value>Отдано трафика сегодня</value> <value>Отправлено сегодня</value>
</data> </data>
<data name="LvTotalDownloadDataAmount" xml:space="preserve"> <data name="LvTotalDownloadDataAmount" xml:space="preserve">
<value>Всего загружено</value> <value>Всего загружено</value>
@ -214,13 +214,13 @@
<value>Ядро успешно загружено</value> <value>Ядро успешно загружено</value>
</data> </data>
<data name="MsgFailedImportSubscription" xml:space="preserve"> <data name="MsgFailedImportSubscription" xml:space="preserve">
<value>Не удалось импортировать содержимое подписки</value> <value>Не удалось импортировать подписку</value>
</data> </data>
<data name="MsgGetSubscriptionSuccessfully" xml:space="preserve"> <data name="MsgGetSubscriptionSuccessfully" xml:space="preserve">
<value>Содержимое подписки успешно импортировано</value> <value>Содержимое подписки успешно импортировано</value>
</data> </data>
<data name="MsgNoValidSubscription" xml:space="preserve"> <data name="MsgNoValidSubscription" xml:space="preserve">
<value>Нет установлены подписки</value> <value>Нет установленных подписок</value>
</data> </data>
<data name="MsgParsingSuccessfully" xml:space="preserve"> <data name="MsgParsingSuccessfully" xml:space="preserve">
<value>Парсинг {0} прошел успешно</value> <value>Парсинг {0} прошел успешно</value>
@ -235,7 +235,7 @@
<value>Некорректное содержимое подписки</value> <value>Некорректное содержимое подписки</value>
</data> </data>
<data name="MsgUnpacking" xml:space="preserve"> <data name="MsgUnpacking" xml:space="preserve">
<value>распаковывается...</value> <value>Распаковка…</value>
</data> </data>
<data name="MsgUpdateSubscriptionEnd" xml:space="preserve"> <data name="MsgUpdateSubscriptionEnd" xml:space="preserve">
<value>Обновление подписки закончено</value> <value>Обновление подписки закончено</value>
@ -244,37 +244,37 @@
<value>Обновление подписки начинается</value> <value>Обновление подписки начинается</value>
</data> </data>
<data name="MsgUpdateV2rayCoreSuccessfully" xml:space="preserve"> <data name="MsgUpdateV2rayCoreSuccessfully" xml:space="preserve">
<value>Успешное обновление ядра</value> <value>Ядро успешно обновлено</value>
</data> </data>
<data name="MsgUpdateV2rayCoreSuccessfullyMore" xml:space="preserve"> <data name="MsgUpdateV2rayCoreSuccessfullyMore" xml:space="preserve">
<value>Успешное обновление ядра! Перезапуск службы...</value> <value>Успешное обновление ядра! Перезапуск службы...</value>
</data> </data>
<data name="NonvmessOrssProtocol" xml:space="preserve"> <data name="NonvmessOrssProtocol" xml:space="preserve">
<value>Не является протоколом Vmess или SS</value> <value>Не является протоколом VMess или Shadowsocks</value>
</data> </data>
<data name="NotFoundCore" xml:space="preserve"> <data name="NotFoundCore" xml:space="preserve">
<value>Файл Core (имя файла: {1}) не найден в папке ({0}), загрузите и поместите его в папку, адрес загрузки: {2}</value> <value>Файл ядра ({1}) не найден в папке {0}. Скачайте по адресу {2} и поместите его туда</value>
</data> </data>
<data name="NoValidQRcodeFound" xml:space="preserve"> <data name="NoValidQRcodeFound" xml:space="preserve">
<value>Сканирование завершено, не найден корректный QR код</value> <value>Сканирование завершено, не найден корректный QR код</value>
</data> </data>
<data name="OperationFailed" xml:space="preserve"> <data name="OperationFailed" xml:space="preserve">
<value> операция безуспешна, проверьте и попробуйте ещё раз</value> <value>Операция безуспешна, проверьте и попробуйте ещё раз</value>
</data> </data>
<data name="PleaseFillRemarks" xml:space="preserve"> <data name="PleaseFillRemarks" xml:space="preserve">
<value>Пожалуйста, заполните примечания</value> <value>Введите примечания</value>
</data> </data>
<data name="PleaseSelectEncryption" xml:space="preserve"> <data name="PleaseSelectEncryption" xml:space="preserve">
<value>Пожалуйста, выберите метод шифрования</value> <value>Выберите метод шифрования</value>
</data> </data>
<data name="PleaseSelectProtocol" xml:space="preserve"> <data name="PleaseSelectProtocol" xml:space="preserve">
<value>Пожалуйста, выберите протокол</value> <value>Выберите протокол</value>
</data> </data>
<data name="PleaseSelectServer" xml:space="preserve"> <data name="PleaseSelectServer" xml:space="preserve">
<value>Сначала выберите сервер</value> <value>Сначала выберите сервер</value>
</data> </data>
<data name="RemoveDuplicateServerResult" xml:space="preserve"> <data name="RemoveDuplicateServerResult" xml:space="preserve">
<value>Удаление дублей завершено. Старая: {0}, Новая: {1}.</value> <value>Удаление дублей завершено. Старая: {0}, Новая: {1}</value>
</data> </data>
<data name="RemoveServer" xml:space="preserve"> <data name="RemoveServer" xml:space="preserve">
<value>Вы уверены, что хотите удалить сервер?</value> <value>Вы уверены, что хотите удалить сервер?</value>
@ -286,16 +286,16 @@
<value>Запуск сервиса ({0})...</value> <value>Запуск сервиса ({0})...</value>
</data> </data>
<data name="SuccessfulConfiguration" xml:space="preserve"> <data name="SuccessfulConfiguration" xml:space="preserve">
<value>Конфигурация успешна {0}</value> <value>Конфигурация выполнена успешно {0}</value>
</data> </data>
<data name="SuccessfullyImportedCustomServer" xml:space="preserve"> <data name="SuccessfullyImportedCustomServer" xml:space="preserve">
<value>Пользовательский сервер конфигурации успешно импортирован.</value> <value>Пользовательская конфигурация сервера успешно импортирована</value>
</data> </data>
<data name="SuccessfullyImportedServerViaClipboard" xml:space="preserve"> <data name="SuccessfullyImportedServerViaClipboard" xml:space="preserve">
<value>{0} серверов импортировано из буфера обмена.</value> <value>{0} серверов импортировано из буфера обмена</value>
</data> </data>
<data name="SuccessfullyImportedServerViaScan" xml:space="preserve"> <data name="SuccessfullyImportedServerViaScan" xml:space="preserve">
<value>Сканирование URL-адреса импорта успешна.</value> <value>Сканирование URL-адреса импорта прошло успешно</value>
</data> </data>
<data name="TestMeOutput" xml:space="preserve"> <data name="TestMeOutput" xml:space="preserve">
<value>Задержка текущего сервера: {0} мс, {1}</value> <value>Задержка текущего сервера: {0} мс, {1}</value>
@ -310,7 +310,7 @@
<value>Вы уверены, что хотите удалить правила?</value> <value>Вы уверены, что хотите удалить правила?</value>
</data> </data>
<data name="RoutingRuleDetailRequiredTips" xml:space="preserve"> <data name="RoutingRuleDetailRequiredTips" xml:space="preserve">
<value>{0}, Один из обязательных..</value> <value>{0}: одно из обязательных полей</value>
</data> </data>
<data name="LvRemarks" xml:space="preserve"> <data name="LvRemarks" xml:space="preserve">
<value>Примечания</value> <value>Примечания</value>
@ -322,13 +322,13 @@
<value>Количество</value> <value>Количество</value>
</data> </data>
<data name="MsgNeedUrl" xml:space="preserve"> <data name="MsgNeedUrl" xml:space="preserve">
<value>Пожалуйста, заполните адрес (Url)</value> <value>Введите URL-адрес</value>
</data> </data>
<data name="AddBatchRoutingRulesYesNo" xml:space="preserve"> <data name="AddBatchRoutingRulesYesNo" xml:space="preserve">
<value>Вы хотите добавить правила? Выберите «Да», чтобы добавить, выберите иное, чтобы заменить</value> <value>Хотите добавить правила? Выберите «Да» для добавления или «Нет» для замены</value>
</data> </data>
<data name="MsgDownloadGeoFileSuccessfully" xml:space="preserve"> <data name="MsgDownloadGeoFileSuccessfully" xml:space="preserve">
<value>Загрузка GeoFile: {0} успешна</value> <value>Загрузка GeoFile {0} прошла успешно</value>
</data> </data>
<data name="MsgInformationTitle" xml:space="preserve"> <data name="MsgInformationTitle" xml:space="preserve">
<value>Информация</value> <value>Информация</value>
@ -337,49 +337,49 @@
<value>Пользовательская иконка</value> <value>Пользовательская иконка</value>
</data> </data>
<data name="FillCorrectDNSText" xml:space="preserve"> <data name="FillCorrectDNSText" xml:space="preserve">
<value>Пожалуйста, заполните правильный пользовательский DNS</value> <value>Введите корректный пользовательский DNS</value>
</data> </data>
<data name="TransportPathTip1" xml:space="preserve"> <data name="TransportPathTip1" xml:space="preserve">
<value>*ws путь</value> <value>*WebSocket-путь</value>
</data> </data>
<data name="TransportPathTip2" xml:space="preserve"> <data name="TransportPathTip2" xml:space="preserve">
<value>*h2 путь</value> <value>*HTTP2-путь</value>
</data> </data>
<data name="TransportPathTip3" xml:space="preserve"> <data name="TransportPathTip3" xml:space="preserve">
<value>*QUIC ключ/Kcp seed</value> <value>*QUIC-ключ / KCP-seed</value>
</data> </data>
<data name="TransportPathTip4" xml:space="preserve"> <data name="TransportPathTip4" xml:space="preserve">
<value>*grpc serviceName</value> <value>Имя сервиса *gRPC</value>
</data> </data>
<data name="TransportRequestHostTip1" xml:space="preserve"> <data name="TransportRequestHostTip1" xml:space="preserve">
<value>*http хосты разделенные запятыми (,)</value> <value>*http-хосты, разделённые запятыми (,)</value>
</data> </data>
<data name="TransportRequestHostTip2" xml:space="preserve"> <data name="TransportRequestHostTip2" xml:space="preserve">
<value>*ws хост</value> <value>*WebSocket-хост</value>
</data> </data>
<data name="TransportRequestHostTip3" xml:space="preserve"> <data name="TransportRequestHostTip3" xml:space="preserve">
<value>*h2 хосты разделенные запятыми (,)</value> <value>*HTTP2-хосты, разделённые запятыми (,)</value>
</data> </data>
<data name="TransportRequestHostTip4" xml:space="preserve"> <data name="TransportRequestHostTip4" xml:space="preserve">
<value>* безопасность QUIC</value> <value>Безопасность *QUIC</value>
</data> </data>
<data name="TransportHeaderTypeTip1" xml:space="preserve"> <data name="TransportHeaderTypeTip1" xml:space="preserve">
<value>* тип TCP камуфляжа</value> <value>Тип *TCP-камуфляжа</value>
</data> </data>
<data name="TransportHeaderTypeTip2" xml:space="preserve"> <data name="TransportHeaderTypeTip2" xml:space="preserve">
<value>* тип KCP камуфляжа</value> <value>Тип *KCP-камуфляжа</value>
</data> </data>
<data name="TransportHeaderTypeTip3" xml:space="preserve"> <data name="TransportHeaderTypeTip3" xml:space="preserve">
<value>* тип QUIC камуфляжа</value> <value>Тип *QUIC-камуфляжа</value>
</data> </data>
<data name="TransportHeaderTypeTip4" xml:space="preserve"> <data name="TransportHeaderTypeTip4" xml:space="preserve">
<value>* режим grpc</value> <value>Режим *gRPC</value>
</data> </data>
<data name="LvTLS" xml:space="preserve"> <data name="LvTLS" xml:space="preserve">
<value>TLS</value> <value>TLS</value>
</data> </data>
<data name="TransportPathTip5" xml:space="preserve"> <data name="TransportPathTip5" xml:space="preserve">
<value>*Kcp seed</value> <value>*KCP-seed</value>
</data> </data>
<data name="RegisterGlobalHotkeyFailed" xml:space="preserve"> <data name="RegisterGlobalHotkeyFailed" xml:space="preserve">
<value>Не удалось зарегистрировать глобальную горячую клавишу {0}, причина: {1}</value> <value>Не удалось зарегистрировать глобальную горячую клавишу {0}, причина: {1}</value>
@ -391,10 +391,10 @@
<value>Разгруппировано</value> <value>Разгруппировано</value>
</data> </data>
<data name="AllGroupServers" xml:space="preserve"> <data name="AllGroupServers" xml:space="preserve">
<value>Все сервера</value> <value>Все серверы</value>
</data> </data>
<data name="FillServerAddressCustom" xml:space="preserve"> <data name="FillServerAddressCustom" xml:space="preserve">
<value>Пожалуйста, просмотрите, чтобы импортировать конфигурацию сервера</value> <value>Выберите файл конфигурации сервера для импорта</value>
</data> </data>
<data name="Speedtesting" xml:space="preserve"> <data name="Speedtesting" xml:space="preserve">
<value>Тестирование...</value> <value>Тестирование...</value>
@ -436,7 +436,7 @@
<value>Настройки маршрутизации</value> <value>Настройки маршрутизации</value>
</data> </data>
<data name="menuServers" xml:space="preserve"> <data name="menuServers" xml:space="preserve">
<value>Сервера</value> <value>Серверы</value>
</data> </data>
<data name="menuSetting" xml:space="preserve"> <data name="menuSetting" xml:space="preserve">
<value>Настройки</value> <value>Настройки</value>
@ -445,7 +445,7 @@
<value>Обновить текущую подписку без прокси</value> <value>Обновить текущую подписку без прокси</value>
</data> </data>
<data name="menuSubGroupUpdateViaProxy" xml:space="preserve"> <data name="menuSubGroupUpdateViaProxy" xml:space="preserve">
<value>Обновить текущую подписку с прокси</value> <value>Обновить подписку через прокси</value>
</data> </data>
<data name="menuSubscription" xml:space="preserve"> <data name="menuSubscription" xml:space="preserve">
<value>Группа подписки</value> <value>Группа подписки</value>
@ -571,7 +571,7 @@
<value>Сортировка</value> <value>Сортировка</value>
</data> </data>
<data name="LvUserAgent" xml:space="preserve"> <data name="LvUserAgent" xml:space="preserve">
<value>User Agent</value> <value>Заголовок User-Agent</value>
</data> </data>
<data name="TbCancel" xml:space="preserve"> <data name="TbCancel" xml:space="preserve">
<value>Отмена</value> <value>Отмена</value>
@ -628,7 +628,7 @@
<value>TLS</value> <value>TLS</value>
</data> </data>
<data name="TipNetwork" xml:space="preserve"> <data name="TipNetwork" xml:space="preserve">
<value>* По-умолчанию TCP</value> <value>*По-умолчанию TCP</value>
</data> </data>
<data name="TbCoreType" xml:space="preserve"> <data name="TbCoreType" xml:space="preserve">
<value>Ядро</value> <value>Ядро</value>
@ -658,10 +658,10 @@
<value>Шифрования</value> <value>Шифрования</value>
</data> </data>
<data name="TbPreSocksPort" xml:space="preserve"> <data name="TbPreSocksPort" xml:space="preserve">
<value>txtPreSocksPort</value> <value>Порт SOCKS</value>
</data> </data>
<data name="TipPreSocksPort" xml:space="preserve"> <data name="TipPreSocksPort" xml:space="preserve">
<value>* После установки этого значения служба socks будет запущена с использованием Xray/sing-box(Tun) для обеспечения таких функций, как отображение скорости</value> <value>* После установки этого значения служба SOCKS будет запущена с использованием Xray/sing-box(TUN) для обеспечения таких функций, как отображение скорости</value>
</data> </data>
<data name="TbBrowse" xml:space="preserve"> <data name="TbBrowse" xml:space="preserve">
<value>Просмотр</value> <value>Просмотр</value>
@ -697,7 +697,7 @@
<value>Разрешить небезопасные</value> <value>Разрешить небезопасные</value>
</data> </data>
<data name="TbSettingsDomainStrategy4Freedom" xml:space="preserve"> <data name="TbSettingsDomainStrategy4Freedom" xml:space="preserve">
<value>Outbound Freedom domainStrategy</value> <value>«Freedom»: стратегия обработки доменов исходящего трафика</value>
</data> </data>
<data name="TbSettingsEnableAutoAdjustMainLvColWidth" xml:space="preserve"> <data name="TbSettingsEnableAutoAdjustMainLvColWidth" xml:space="preserve">
<value>Автоматически регулировать ширину столбца после обновления подписки</value> <value>Автоматически регулировать ширину столбца после обновления подписки</value>
@ -712,7 +712,7 @@
<value>Исключение. Не используйте прокси-сервер для адресов, начинающихся с (,), используйте точку с запятой (;)</value> <value>Исключение. Не используйте прокси-сервер для адресов, начинающихся с (,), используйте точку с запятой (;)</value>
</data> </data>
<data name="TbSettingsDisplayRealTimeSpeed" xml:space="preserve"> <data name="TbSettingsDisplayRealTimeSpeed" xml:space="preserve">
<value>Display real-time speed</value> <value>Показывать скорость в реальном времени</value>
</data> </data>
<data name="TbSettingsKeepOlderDedupl" xml:space="preserve"> <data name="TbSettingsKeepOlderDedupl" xml:space="preserve">
<value>Сохранить старые при удалении дублей</value> <value>Сохранить старые при удалении дублей</value>
@ -721,10 +721,10 @@
<value>Записывать логи</value> <value>Записывать логи</value>
</data> </data>
<data name="TbSettingsLogLevel" xml:space="preserve"> <data name="TbSettingsLogLevel" xml:space="preserve">
<value>Уровень логгирования</value> <value>Уровень записи логов</value>
</data> </data>
<data name="TbSettingsMuxEnabled" xml:space="preserve"> <data name="TbSettingsMuxEnabled" xml:space="preserve">
<value>Включите мультиплексирование Mux</value> <value>Включить мультиплексирование Mux</value>
</data> </data>
<data name="TbSettingsN" xml:space="preserve"> <data name="TbSettingsN" xml:space="preserve">
<value>Настройки v2rayN</value> <value>Настройки v2rayN</value>
@ -733,16 +733,16 @@
<value>Пароль аутентификации</value> <value>Пароль аутентификации</value>
</data> </data>
<data name="TbSettingsRemoteDNS" xml:space="preserve"> <data name="TbSettingsRemoteDNS" xml:space="preserve">
<value>Пользовательский DNS (если несколько делите с запятыми (,))</value> <value>Пользовательский DNS (если несколько, то делите запятыми (,))</value>
</data> </data>
<data name="TbSettingsSetUWP" xml:space="preserve"> <data name="TbSettingsSetUWP" xml:space="preserve">
<value>Set Win10 UWP Loopback</value> <value>Разрешить loopback для приложений UWP (Win10)</value>
</data> </data>
<data name="TbSettingsSniffingEnabled" xml:space="preserve"> <data name="TbSettingsSniffingEnabled" xml:space="preserve">
<value>Включить сниффинг</value> <value>Включить сниффинг</value>
</data> </data>
<data name="TbSettingsSocksPort" xml:space="preserve"> <data name="TbSettingsSocksPort" xml:space="preserve">
<value>Mixed Port</value> <value>Смешанный порт</value>
</data> </data>
<data name="TbSettingsStartBoot" xml:space="preserve"> <data name="TbSettingsStartBoot" xml:space="preserve">
<value>Автозапуск</value> <value>Автозапуск</value>
@ -751,7 +751,7 @@
<value>Включить статистику (требуется перезагрузка)</value> <value>Включить статистику (требуется перезагрузка)</value>
</data> </data>
<data name="TbSettingsSubConvert" xml:space="preserve"> <data name="TbSettingsSubConvert" xml:space="preserve">
<value>URL-адрес конверсии подписки</value> <value>URL конвертации подписок</value>
</data> </data>
<data name="TbSettingsSystemproxy" xml:space="preserve"> <data name="TbSettingsSystemproxy" xml:space="preserve">
<value>Настройки системного прокси</value> <value>Настройки системного прокси</value>
@ -766,7 +766,7 @@
<value>Включить UDP</value> <value>Включить UDP</value>
</data> </data>
<data name="TbSettingsUser" xml:space="preserve"> <data name="TbSettingsUser" xml:space="preserve">
<value>Auth user</value> <value>Имя пользователя (логин)</value>
</data> </data>
<data name="TbClearSystemProxy" xml:space="preserve"> <data name="TbClearSystemProxy" xml:space="preserve">
<value>Очистить системный прокси</value> <value>Очистить системный прокси</value>
@ -816,6 +816,9 @@
<data name="menuMoveUp" xml:space="preserve"> <data name="menuMoveUp" xml:space="preserve">
<value>Вверх (U)</value> <value>Вверх (U)</value>
</data> </data>
<data name="menuMoveTo" xml:space="preserve">
<value>Переместить вверх/вниз</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve"> <data name="MsgFilterTitle" xml:space="preserve">
<value>Фильтр, поддерживает regex</value> <value>Фильтр, поддерживает regex</value>
</data> </data>
@ -850,13 +853,13 @@
<value>2.Прямой домен или IP</value> <value>2.Прямой домен или IP</value>
</data> </data>
<data name="TbRoutingTabProxy" xml:space="preserve"> <data name="TbRoutingTabProxy" xml:space="preserve">
<value>1.Прокси-домен или IP</value> <value>1.Прокси домен или IP</value>
</data> </data>
<data name="TbRoutingTabRuleList" xml:space="preserve"> <data name="TbRoutingTabRuleList" xml:space="preserve">
<value>Предустановленный список наборов правил</value> <value>Предустановленный список наборов правил</value>
</data> </data>
<data name="TbRoutingTips" xml:space="preserve"> <data name="TbRoutingTips" xml:space="preserve">
<value>* Установите правила, разделенные запятыми (,); Запятая в регулярке заменена на &lt;COMMA&gt;</value> <value>*Разделяйте правила запятыми (,). Литерал «,» — &lt;COMMA&gt;. Префикс # — отключить правило</value>
</data> </data>
<data name="menuImportRulesFromClipboard" xml:space="preserve"> <data name="menuImportRulesFromClipboard" xml:space="preserve">
<value>Импорт правил из буфера обмена</value> <value>Импорт правил из буфера обмена</value>
@ -883,16 +886,16 @@
<value>Удалить правила (Delete)</value> <value>Удалить правила (Delete)</value>
</data> </data>
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve"> <data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
<value>RoutingRuleDetailsSetting</value> <value>Детальные настройки правил маршрутизации</value>
</data> </data>
<data name="TbAutoSort" xml:space="preserve"> <data name="TbAutoSort" xml:space="preserve">
<value>Домен и IP автоматически сортируются при сохранении</value> <value>Домен и IP автоматически сортируются при сохранении</value>
</data> </data>
<data name="TbRuleobjectDoc" xml:space="preserve"> <data name="TbRuleobjectDoc" xml:space="preserve">
<value>Ruleobject Doc</value> <value>Документация RuleObject</value>
</data> </data>
<data name="TbDnsObjectDoc" xml:space="preserve"> <data name="TbDnsObjectDoc" xml:space="preserve">
<value>Поддержка DnsObject</value> <value>Поддерживаются DNS-объекты, нажмите для просмотра документации</value>
</data> </data>
<data name="SubUrlTips" xml:space="preserve"> <data name="SubUrlTips" xml:space="preserve">
<value>Необязательное поле</value> <value>Необязательное поле</value>
@ -913,16 +916,16 @@
<value>Тест задержки и скорости всех серверов (Ctrl+E)</value> <value>Тест задержки и скорости всех серверов (Ctrl+E)</value>
</data> </data>
<data name="LvTestDelay" xml:space="preserve"> <data name="LvTestDelay" xml:space="preserve">
<value>Задержка (ms)</value> <value>Задержка (мс)</value>
</data> </data>
<data name="LvTestSpeed" xml:space="preserve"> <data name="LvTestSpeed" xml:space="preserve">
<value>Скорость (M/s)</value> <value>Скорость (МБ/с)</value>
</data> </data>
<data name="FailedToRunCore" xml:space="preserve"> <data name="FailedToRunCore" xml:space="preserve">
<value>Не удалось запустить ядро, посмотрите логи</value> <value>Не удалось запустить ядро, посмотрите логи</value>
</data> </data>
<data name="LvFilter" xml:space="preserve"> <data name="LvFilter" xml:space="preserve">
<value>Remarks regular filter</value> <value>Фильтр примечаний (Regex)</value>
</data> </data>
<data name="TbDisplayLog" xml:space="preserve"> <data name="TbDisplayLog" xml:space="preserve">
<value>Показать логи</value> <value>Показать логи</value>
@ -934,7 +937,7 @@
<value>Новый порт для локальной сети</value> <value>Новый порт для локальной сети</value>
</data> </data>
<data name="TbSettingsTunMode" xml:space="preserve"> <data name="TbSettingsTunMode" xml:space="preserve">
<value>Настройки TunMode</value> <value>Настройки режима TUN</value>
</data> </data>
<data name="menuMoveToGroup" xml:space="preserve"> <data name="menuMoveToGroup" xml:space="preserve">
<value>Перейти в группу</value> <value>Перейти в группу</value>
@ -958,22 +961,22 @@
<value>Тест завершен</value> <value>Тест завершен</value>
</data> </data>
<data name="TbSettingsDefFingerprint" xml:space="preserve"> <data name="TbSettingsDefFingerprint" xml:space="preserve">
<value>TLS отпечаток по умлочанию</value> <value>TLS отпечаток по умолчанию</value>
</data> </data>
<data name="TbSettingsDefUserAgent" xml:space="preserve"> <data name="TbSettingsDefUserAgent" xml:space="preserve">
<value>User-Agent</value> <value>User-Agent</value>
</data> </data>
<data name="TbSettingsDefUserAgentTips" xml:space="preserve"> <data name="TbSettingsDefUserAgentTips" xml:space="preserve">
<value>Этот параметр действителен только для TCP/HTTP и WS</value> <value>Параметр действует только для TCP/HTTP и WebSocket (WS)</value>
</data> </data>
<data name="TbSettingsCurrentFontFamily" xml:space="preserve"> <data name="TbSettingsCurrentFontFamily" xml:space="preserve">
<value>Шрифт (требуется перезагрузка)</value> <value>Шрифт (требуется перезагрузка)</value>
</data> </data>
<data name="TbSettingsCurrentFontFamilyTip" xml:space="preserve"> <data name="TbSettingsCurrentFontFamilyTip" xml:space="preserve">
<value>Скопируйте файл шрифта TTF/TTC в каталог guiFonts, перезапустите настройки</value> <value>Скопируйте файл шрифта TTF/TTC в каталог guiFonts и заново откройте окно настроек</value>
</data> </data>
<data name="TbSettingsSocksPortTip" xml:space="preserve"> <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>
<data name="TbSettingsStartBootTip" xml:space="preserve"> <data name="TbSettingsStartBootTip" xml:space="preserve">
<value>Установите это с правами администратора</value> <value>Установите это с правами администратора</value>
@ -982,13 +985,10 @@
<value>Размер шрифта</value> <value>Размер шрифта</value>
</data> </data>
<data name="TbSettingsSpeedTestTimeout" xml:space="preserve"> <data name="TbSettingsSpeedTestTimeout" xml:space="preserve">
<value>Таймаут одиночного спидтеста</value> <value>Тайм-аут одиночного тестирования скорости</value>
</data> </data>
<data name="TbSettingsSpeedTestUrl" xml:space="preserve"> <data name="TbSettingsSpeedTestUrl" xml:space="preserve">
<value>URL спидтеста</value> <value>URL для тестирования скорости</value>
</data>
<data name="menuMoveTo" xml:space="preserve">
<value>Move up and down</value>
</data> </data>
<data name="TbPublicKey" xml:space="preserve"> <data name="TbPublicKey" xml:space="preserve">
<value>PublicKey</value> <value>PublicKey</value>
@ -1003,19 +1003,19 @@
<value>Включить аппаратное ускорение (требуется перезагрузка)</value> <value>Включить аппаратное ускорение (требуется перезагрузка)</value>
</data> </data>
<data name="SpeedtestingWait" xml:space="preserve"> <data name="SpeedtestingWait" xml:space="preserve">
<value>Ожидание тестирования (нажмите ESC для отмены)...</value> <value>Ожидание тестирования (нажмите ESC для отмены)</value>
</data> </data>
<data name="TipDisplayLog" xml:space="preserve"> <data name="TipDisplayLog" xml:space="preserve">
<value>Please turn off when there is an abnormal disconnection</value> <value>Отключите при аномальном разрыве соединения</value>
</data> </data>
<data name="MsgSkipSubscriptionUpdate" xml:space="preserve"> <data name="MsgSkipSubscriptionUpdate" xml:space="preserve">
<value>Updates are not enabled, skip this subscription</value> <value>Обновления не включены — подписка пропущена</value>
</data> </data>
<data name="menuRebootAsAdmin" xml:space="preserve"> <data name="menuRebootAsAdmin" xml:space="preserve">
<value>Перезагрузить как администратор</value> <value>Перезагрузить как администратор</value>
</data> </data>
<data name="LvMoreUrl" xml:space="preserve"> <data name="LvMoreUrl" xml:space="preserve">
<value>More URLs, separated by commas; Subscription conversion will be invalid</value> <value>Дополнительные URL через запятую, конвертация подписки недоступна</value>
</data> </data>
<data name="SpeedDisplayText" xml:space="preserve"> <data name="SpeedDisplayText" xml:space="preserve">
<value>{0} : {1}/s↑ | {2}/s↓</value> <value>{0} : {1}/s↑ | {2}/s↓</value>
@ -1024,13 +1024,13 @@
<value>Интервал автоматического обновления в минутах</value> <value>Интервал автоматического обновления в минутах</value>
</data> </data>
<data name="TbSettingsLogEnabledToFile" xml:space="preserve"> <data name="TbSettingsLogEnabledToFile" xml:space="preserve">
<value>Включить логгирование в файл</value> <value>Включить запись логов в файл</value>
</data> </data>
<data name="LvConvertTarget" xml:space="preserve"> <data name="LvConvertTarget" xml:space="preserve">
<value>Преобразовать тип цели</value> <value>Преобразовать тип цели</value>
</data> </data>
<data name="LvConvertTargetTip" xml:space="preserve"> <data name="LvConvertTargetTip" xml:space="preserve">
<value>Если преобразование не требуется, оставьте поле пустым.</value> <value>Если преобразование не требуется, оставьте поле пустым</value>
</data> </data>
<data name="menuDNSSetting" xml:space="preserve"> <data name="menuDNSSetting" xml:space="preserve">
<value>Настройки DNS</value> <value>Настройки DNS</value>
@ -1039,7 +1039,7 @@
<value>Настройки DNS sing-box</value> <value>Настройки DNS sing-box</value>
</data> </data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve"> <data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>Пожалуйста, заполните структуру DNS. Нажмите, чтобы просмотреть документ.</value> <value>Заполните структуру DNS, нажмите, чтобы открыть документ</value>
</data> </data>
<data name="TbSettingDnsImportDefConfig" xml:space="preserve"> <data name="TbSettingDnsImportDefConfig" xml:space="preserve">
<value>Нажмите, чтобы импортировать конфигурацию DNS по умолчанию</value> <value>Нажмите, чтобы импортировать конфигурацию DNS по умолчанию</value>
@ -1048,88 +1048,88 @@
<value>Стратегия домена для sing-box</value> <value>Стратегия домена для sing-box</value>
</data> </data>
<data name="TbSettingsMux4SboxProtocol" xml:space="preserve"> <data name="TbSettingsMux4SboxProtocol" xml:space="preserve">
<value>sing-box Mux Protocol</value> <value>Протокол Mux для sing-box</value>
</data> </data>
<data name="TbRoutingRuleProcess" xml:space="preserve"> <data name="TbRoutingRuleProcess" xml:space="preserve">
<value>Full process name (Tun mode)</value> <value>Полное имя процесса (режим TUN)</value>
</data> </data>
<data name="TbRoutingRuleIP" xml:space="preserve"> <data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP or IP CIDR</value> <value>IP-адрес или сеть CIDR</value>
</data> </data>
<data name="TbRoutingRuleDomain" xml:space="preserve"> <data name="TbRoutingRuleDomain" xml:space="preserve">
<value>Домен</value> <value>Домен</value>
</data> </data>
<data name="menuAddHysteria2Server" xml:space="preserve"> <data name="menuAddHysteria2Server" xml:space="preserve">
<value>Добавить [Hysteria2] сервер</value> <value>Добавить сервер [Hysteria2]</value>
</data> </data>
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve"> <data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
<value>Hysteria Max bandwidth (Up/Dw)</value> <value>Максимальная пропускная способность Hysteria (загрузка/отдача)</value>
</data> </data>
<data name="TbSettingsUseSystemHosts" xml:space="preserve"> <data name="TbSettingsUseSystemHosts" xml:space="preserve">
<value>Использовать системные узлы</value> <value>Использовать системные узлы</value>
</data> </data>
<data name="menuAddTuicServer" xml:space="preserve"> <data name="menuAddTuicServer" xml:space="preserve">
<value>Добавить [TUIC] сервер</value> <value>Добавить сервер [TUIC]</value>
</data> </data>
<data name="TbHeaderType8" xml:space="preserve"> <data name="TbHeaderType8" xml:space="preserve">
<value>Контроль перегрузок</value> <value>Контроль перегрузок</value>
</data> </data>
<data name="LvPrevProfile" xml:space="preserve"> <data name="LvPrevProfile" xml:space="preserve">
<value>Previous proxy remarks</value> <value>Примечания к предыдущему прокси</value>
</data> </data>
<data name="LvNextProfile" xml:space="preserve"> <data name="LvNextProfile" xml:space="preserve">
<value>Next proxy remarks</value> <value>Примечания к следующему прокси</value>
</data> </data>
<data name="LvPrevProfileTip" xml:space="preserve"> <data name="LvPrevProfileTip" xml:space="preserve">
<value>Пожалуйста, убедитесь, что примечание существует и является уникальным.</value> <value>Убедитесь, что примечание существует и является уникальным</value>
</data> </data>
<data name="TbSettingsEnableExInbound" xml:space="preserve"> <data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>Enable additional Inbound</value> <value>Включить дополнительный входящий канал</value>
</data> </data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve"> <data name="TbSettingsEnableIPv6Address" xml:space="preserve">
<value>Включить IPv6 адреса</value> <value>Включить IPv6 адреса</value>
</data> </data>
<data name="menuAddWireguardServer" xml:space="preserve"> <data name="menuAddWireguardServer" xml:space="preserve">
<value>Добавить [WireGuard] сервер</value> <value>Добавить сервер [WireGuard]</value>
</data> </data>
<data name="TbPrivateKey" xml:space="preserve"> <data name="TbPrivateKey" xml:space="preserve">
<value>PrivateKey</value> <value>Приватный ключ</value>
</data> </data>
<data name="TbReserved" xml:space="preserve"> <data name="TbReserved" xml:space="preserve">
<value>Reserved(2,3,4)</value> <value>Зарезервировано (2, 3, 4)</value>
</data> </data>
<data name="TbLocalAddress" xml:space="preserve"> <data name="TbLocalAddress" xml:space="preserve">
<value>Адрес(Ipv4,Ipv6)</value> <value>Адрес (Ipv4,Ipv6)</value>
</data> </data>
<data name="TbPath7" xml:space="preserve"> <data name="TbPath7" xml:space="preserve">
<value>obfs password</value> <value>Пароль obfs</value>
</data> </data>
<data name="TbRuleMatchingTips" xml:space="preserve"> <data name="TbRuleMatchingTips" xml:space="preserve">
<value>(Domain or IP or ProcName) and Port and Protocol and InboundTag =&gt; OutboundTag</value> <value>(Домен или IP или имя процесса) и порт, и протокол, и InboundTag =&gt; OutboundTag</value>
</data> </data>
<data name="TbAutoScrollToEnd" xml:space="preserve"> <data name="TbAutoScrollToEnd" xml:space="preserve">
<value>Автоматическая прокрутка в конец</value> <value>Автоматическая прокрутка в конец</value>
</data> </data>
<data name="TbSettingsSpeedPingTestUrl" xml:space="preserve"> <data name="TbSettingsSpeedPingTestUrl" xml:space="preserve">
<value>URL-адрес для проверки скорости пинга</value> <value>URL для быстрой проверки реальной задержки</value>
</data> </data>
<data name="TbSettingsEnableUpdateSubOnlyRemarksExist" xml:space="preserve"> <data name="TbSettingsEnableUpdateSubOnlyRemarksExist" xml:space="preserve">
<value>Updating subscription, only determine remarks exists</value> <value>Обновляя подписку, проверять лишь наличие примечаний</value>
</data> </data>
<data name="SpeedtestingStop" xml:space="preserve"> <data name="SpeedtestingStop" xml:space="preserve">
<value>Отмена тестирования...</value> <value>Отмена тестирования...</value>
</data> </data>
<data name="TransportRequestHostTip5" xml:space="preserve"> <data name="TransportRequestHostTip5" xml:space="preserve">
<value>*grpc Authority</value> <value>*gRPC Authority</value>
</data> </data>
<data name="menuAddHttpServer" xml:space="preserve"> <data name="menuAddHttpServer" xml:space="preserve">
<value>Добавить [HTTP] сервер</value> <value>Добавить сервер [HTTP]</value>
</data> </data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve"> <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>
<data name="TbSettingsEnableFragment" xml:space="preserve"> <data name="TbSettingsEnableFragment" xml:space="preserve">
<value>Включить фрагмент</value> <value>Включить фрагментацию (Fragment)</value>
</data> </data>
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve"> <data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
<value>Включить файл кэша для sing-box (файлы наборов правил)</value> <value>Включить файл кэша для sing-box (файлы наборов правил)</value>
@ -1138,7 +1138,7 @@
<value>Пользовательский набор правил для sing-box</value> <value>Пользовательский набор правил для sing-box</value>
</data> </data>
<data name="NeedRebootTips" xml:space="preserve"> <data name="NeedRebootTips" xml:space="preserve">
<value>Successful operation. Click the settings menu to reboot the app.</value> <value>Операция успешна. Перезапустите приложение</value>
</data> </data>
<data name="menuOpenTheFileLocation" xml:space="preserve"> <data name="menuOpenTheFileLocation" xml:space="preserve">
<value>Открыть место хранения</value> <value>Открыть место хранения</value>
@ -1147,7 +1147,7 @@
<value>Сортировка</value> <value>Сортировка</value>
</data> </data>
<data name="TbSortingChain" xml:space="preserve"> <data name="TbSortingChain" xml:space="preserve">
<value>Chain</value> <value>Цепочка</value>
</data> </data>
<data name="TbSortingDefault" xml:space="preserve"> <data name="TbSortingDefault" xml:space="preserve">
<value>По умолчанию</value> <value>По умолчанию</value>
@ -1159,7 +1159,7 @@
<value>Скорость загрузки</value> <value>Скорость загрузки</value>
</data> </data>
<data name="TbSortingDownTraffic" xml:space="preserve"> <data name="TbSortingDownTraffic" xml:space="preserve">
<value>Download Traffic</value> <value>Скачанный трафик</value>
</data> </data>
<data name="TbSortingHost" xml:space="preserve"> <data name="TbSortingHost" xml:space="preserve">
<value>Узел</value> <value>Узел</value>
@ -1177,10 +1177,10 @@
<value>Тип</value> <value>Тип</value>
</data> </data>
<data name="TbSortingUpSpeed" xml:space="preserve"> <data name="TbSortingUpSpeed" xml:space="preserve">
<value>Скорость загрузки</value> <value>Скорость отдачи</value>
</data> </data>
<data name="TbSortingUpTraffic" xml:space="preserve"> <data name="TbSortingUpTraffic" xml:space="preserve">
<value>Upload Traffic</value> <value>Отправленный трафик</value>
</data> </data>
<data name="TbConnections" xml:space="preserve"> <data name="TbConnections" xml:space="preserve">
<value>Соединения</value> <value>Соединения</value>
@ -1198,13 +1198,13 @@
<value>Режим правила</value> <value>Режим правила</value>
</data> </data>
<data name="menuModeDirect" xml:space="preserve"> <data name="menuModeDirect" xml:space="preserve">
<value>Direct</value> <value>Прямое соединение</value>
</data> </data>
<data name="menuModeGlobal" xml:space="preserve"> <data name="menuModeGlobal" xml:space="preserve">
<value>Global</value> <value>Глобальный режим</value>
</data> </data>
<data name="menuModeNothing" xml:space="preserve"> <data name="menuModeNothing" xml:space="preserve">
<value>Do not change</value> <value>Не менять</value>
</data> </data>
<data name="menuModeRule" xml:space="preserve"> <data name="menuModeRule" xml:space="preserve">
<value>Правила</value> <value>Правила</value>
@ -1213,13 +1213,13 @@
<value>Тест задержки</value> <value>Тест задержки</value>
</data> </data>
<data name="menuProxiesDelaytestPart" xml:space="preserve"> <data name="menuProxiesDelaytestPart" xml:space="preserve">
<value>Part Node Latency Test</value> <value>Тест задержки выбранных узлов</value>
</data> </data>
<data name="menuProxiesReload" xml:space="preserve"> <data name="menuProxiesReload" xml:space="preserve">
<value>Обновить прокси</value> <value>Обновить прокси</value>
</data> </data>
<data name="menuProxiesSelectActivity" xml:space="preserve"> <data name="menuProxiesSelectActivity" xml:space="preserve">
<value>Активировать узел (Enter)</value> <value>Сделать узел активным (Enter)</value>
</data> </data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve"> <data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Стратегия домена по умолчанию для исходящих</value> <value>Стратегия домена по умолчанию для исходящих</value>
@ -1234,7 +1234,7 @@
<value>Автоматическая регулировка ширины столбца</value> <value>Автоматическая регулировка ширины столбца</value>
</data> </data>
<data name="menuExport2ShareUrlBase64" xml:space="preserve"> <data name="menuExport2ShareUrlBase64" xml:space="preserve">
<value>Export Base64-encoded Share Links to Clipboard</value> <value>Экспорт ссылок в формате Base64 в буфер обмена</value>
</data> </data>
<data name="menuExport2ClientConfigClipboard" xml:space="preserve"> <data name="menuExport2ClientConfigClipboard" xml:space="preserve">
<value>Экспортировать выбранный сервер для полной конфигурации в буфер обмена</value> <value>Экспортировать выбранный сервер для полной конфигурации в буфер обмена</value>
@ -1243,7 +1243,7 @@
<value>Показать или скрыть главное окно</value> <value>Показать или скрыть главное окно</value>
</data> </data>
<data name="TbPreSocksPort4Sub" xml:space="preserve"> <data name="TbPreSocksPort4Sub" xml:space="preserve">
<value>Пользовательская конфигурация порта socks</value> <value>Пользовательская конфигурация порта SOCKS</value>
</data> </data>
<data name="menuBackupAndRestore" xml:space="preserve"> <data name="menuBackupAndRestore" xml:space="preserve">
<value>Резервное копирование и восстановление</value> <value>Резервное копирование и восстановление</value>
@ -1255,28 +1255,28 @@
<value>Восстановить из файла</value> <value>Восстановить из файла</value>
</data> </data>
<data name="menuRemoteBackup" xml:space="preserve"> <data name="menuRemoteBackup" xml:space="preserve">
<value>Backup to remote (WebDAV)</value> <value>Резервное копирование на удалённый сервер (WebDAV)</value>
</data> </data>
<data name="menuRemoteRestore" xml:space="preserve"> <data name="menuRemoteRestore" xml:space="preserve">
<value>Restore from remote (WebDAV)</value> <value>Восстановить с удалённого сервера (WebDAV)</value>
</data> </data>
<data name="menuLocalBackupAndRestore" xml:space="preserve"> <data name="menuLocalBackupAndRestore" xml:space="preserve">
<value>Local</value> <value>Локально</value>
</data> </data>
<data name="menuRemoteBackupAndRestore" xml:space="preserve"> <data name="menuRemoteBackupAndRestore" xml:space="preserve">
<value>Remote (WebDAV)</value> <value>Удалённо (WebDAV)</value>
</data> </data>
<data name="LvWebDavUrl" xml:space="preserve"> <data name="LvWebDavUrl" xml:space="preserve">
<value>WebDav Url</value> <value>URL WebDAV</value>
</data> </data>
<data name="LvWebDavUserName" xml:space="preserve"> <data name="LvWebDavUserName" xml:space="preserve">
<value>WebDav User Name</value> <value>Имя пользователя WebDAV</value>
</data> </data>
<data name="LvWebDavPassword" xml:space="preserve"> <data name="LvWebDavPassword" xml:space="preserve">
<value>WebDav Password</value> <value>Пароль WebDAV</value>
</data> </data>
<data name="LvWebDavCheck" xml:space="preserve"> <data name="LvWebDavCheck" xml:space="preserve">
<value>WebDav Check</value> <value>Проверить WebDAV</value>
</data> </data>
<data name="LvWebDavDirName" xml:space="preserve"> <data name="LvWebDavDirName" xml:space="preserve">
<value>Имя удаленной папки (необязательно)</value> <value>Имя удаленной папки (необязательно)</value>
@ -1285,16 +1285,16 @@
<value>Неверный файл резервной копии</value> <value>Неверный файл резервной копии</value>
</data> </data>
<data name="ConnectionsHostFilterTitle" xml:space="preserve"> <data name="ConnectionsHostFilterTitle" xml:space="preserve">
<value>Host filter</value> <value>Фильтр хостов</value>
</data> </data>
<data name="TipActiveServer" xml:space="preserve"> <data name="TipActiveServer" xml:space="preserve">
<value>Active</value> <value>Активный</value>
</data> </data>
<data name="TbSettingsGeoFilesSource" xml:space="preserve"> <data name="TbSettingsGeoFilesSource" xml:space="preserve">
<value>Источник geo файлов</value> <value>Источник файлов Geo (необязательно)</value>
</data> </data>
<data name="TbSettingsSrsFilesSource" xml:space="preserve"> <data name="TbSettingsSrsFilesSource" xml:space="preserve">
<value>Источник sing-box srs файлов</value> <value>Источник файлов наборов правил sing-box (необязательно)</value>
</data> </data>
<data name="UpgradeAppNotExistTip" xml:space="preserve"> <data name="UpgradeAppNotExistTip" xml:space="preserve">
<value>Программы для обновления не существует</value> <value>Программы для обновления не существует</value>
@ -1315,7 +1315,7 @@
<value>Иран</value> <value>Иран</value>
</data> </data>
<data name="TbSettingsChinaUserTip" xml:space="preserve"> <data name="TbSettingsChinaUserTip" xml:space="preserve">
<value>Используйте Настройки -&gt; Региональные пресеты вместо изменения этого поля</value> <value>Пользователи из Китая могут пропустить этот пункт</value>
</data> </data>
<data name="menuAddServerViaImage" xml:space="preserve"> <data name="menuAddServerViaImage" xml:space="preserve">
<value>Сканировать QR-код с изображения</value> <value>Сканировать QR-код с изображения</value>
@ -1324,7 +1324,7 @@
<value>Неверный адрес (Url)</value> <value>Неверный адрес (Url)</value>
</data> </data>
<data name="InsecureUrlProtocol" xml:space="preserve"> <data name="InsecureUrlProtocol" xml:space="preserve">
<value>Пожалуйста, не используйте небезопасный адрес подписки по протоколу HTTP.</value> <value>Не используйте небезопасный адрес подписки по протоколу HTTP</value>
</data> </data>
<data name="TbSettingsCurrentFontFamilyLinuxTip" xml:space="preserve"> <data name="TbSettingsCurrentFontFamilyLinuxTip" xml:space="preserve">
<value>Установите шрифт в систему и перезапустите настройки</value> <value>Установите шрифт в систему и перезапустите настройки</value>
@ -1333,43 +1333,43 @@
<value>Вы уверены, что хотите выйти?</value> <value>Вы уверены, что хотите выйти?</value>
</data> </data>
<data name="LvMemo" xml:space="preserve"> <data name="LvMemo" xml:space="preserve">
<value>Remarks Memo</value> <value>Заметка (Memo)</value>
</data> </data>
<data name="TbSettingsLinuxSudoPassword" xml:space="preserve"> <data name="TbSettingsLinuxSudoPassword" xml:space="preserve">
<value>System sudo password</value> <value>Пароль sudo системы</value>
</data> </data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve"> <data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<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> <value>Введенный вами пароль не может быть подтвержден, поэтому убедитесь, что вы ввели его правильно. Если приложение не работает должным образом из-за неправильного ввода, то перезапустите его. Пароль не будет сохранен, и вам придется вводить его заново после каждого перезапуска</value>
</data> </data>
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve"> <data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
<value>Please set the sudo password in Tun mode settings first</value> <value>Сначала задайте пароль sudo в настройках TUN-режима</value>
</data> </data>
<data name="TbSettingsLinuxSudoPasswordNotSudoRunApp" xml:space="preserve"> <data name="TbSettingsLinuxSudoPasswordNotSudoRunApp" xml:space="preserve">
<value>Пожалуйста, не запускайте программу как суперпользователь.</value> <value>Не запускайте программу как суперпользователь</value>
</data> </data>
<data name="TransportHeaderTypeTip5" xml:space="preserve"> <data name="TransportHeaderTypeTip5" xml:space="preserve">
<value>*xhttp mode</value> <value>*XHTTP-режим</value>
</data> </data>
<data name="TransportExtraTip" xml:space="preserve"> <data name="TransportExtraTip" xml:space="preserve">
<value>XHTTP Extra raw JSON, format: { XHTTPObject }</value> <value>Дополнительный XHTTP сырой JSON, формат: { XHTTPObject }</value>
</data> </data>
<data name="TbSettingsHide2TrayWhenClose" xml:space="preserve"> <data name="TbSettingsHide2TrayWhenClose" xml:space="preserve">
<value>Скрыть в трее при закрытии окна</value> <value>Скрыть в трее при закрытии окна</value>
</data> </data>
<data name="TbSettingsMixedConcurrencyCount" xml:space="preserve"> <data name="TbSettingsMixedConcurrencyCount" xml:space="preserve">
<value>The number of concurrent during multi-test</value> <value>Количество одновременно выполняемых тестов при многоэтапном тестировании</value>
</data> </data>
<data name="TbSettingsExceptionTip2" xml:space="preserve"> <data name="TbSettingsExceptionTip2" xml:space="preserve">
<value>Исключение. Не используйте прокси-сервер для адресов с запятой (,)</value> <value>Исключение. Не используйте прокси-сервер для адресов с запятой (,)</value>
</data> </data>
<data name="TbSettingsDestOverride" xml:space="preserve"> <data name="TbSettingsDestOverride" xml:space="preserve">
<value>Sniffing type</value> <value>Тип сниффинга</value>
</data> </data>
<data name="TbSettingsSecondLocalPortEnabled" xml:space="preserve"> <data name="TbSettingsSecondLocalPortEnabled" xml:space="preserve">
<value>Enable second mixed port</value> <value>Включить второй смешанный порт</value>
</data> </data>
<data name="TbRoutingInboundTagTips" xml:space="preserve"> <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>
<data name="TbSettingsTheme" xml:space="preserve"> <data name="TbSettingsTheme" xml:space="preserve">
<value>Темы</value> <value>Темы</value>
@ -1378,7 +1378,7 @@
<value>Копировать команду прокси в буфер обмена</value> <value>Копировать команду прокси в буфер обмена</value>
</data> </data>
<data name="SpeedtestingTestFailedPart" xml:space="preserve"> <data name="SpeedtestingTestFailedPart" xml:space="preserve">
<value>Starting retesting failed parts, {0} remaining. Press ESC to terminate...</value> <value>Повторное тестирование неудачных элементов, осталось {0}. Нажмите ESC для остановки…</value>
</data> </data>
<data name="menuTestServerResult" xml:space="preserve"> <data name="menuTestServerResult" xml:space="preserve">
<value>По результату теста</value> <value>По результату теста</value>
@ -1387,36 +1387,36 @@
<value>Удалить недействительные по результатам теста</value> <value>Удалить недействительные по результатам теста</value>
</data> </data>
<data name="RemoveInvalidServerResultTip" xml:space="preserve"> <data name="RemoveInvalidServerResultTip" xml:space="preserve">
<value>Удалено {0} недействительных.</value> <value>Удалено {0} недействительных</value>
</data> </data>
<data name="TbPorts7" xml:space="preserve"> <data name="TbPorts7" xml:space="preserve">
<value>Диапазон портов сервера</value> <value>Диапазон портов сервера</value>
</data> </data>
<data name="TbPorts7Tips" xml:space="preserve"> <data name="TbPorts7Tips" xml:space="preserve">
<value>Will cover the port, separate with commas (,)</value> <value>Заменит указанный порт, перечисляйте через запятую (,)</value>
</data> </data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve"> <data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>Multi-server to custom configuration</value> <value>От мультиконфигурации к пользовательской конфигурации</value>
</data> </data>
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve"> <data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<value>Multi-server Random by Xray</value> <value>Случайный (Xray)</value>
</data> </data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve"> <data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Multi-server RoundRobin by Xray</value> <value>Круговой (Xray)</value>
</data> </data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve"> <data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<value>Multi-server LeastPing by Xray</value> <value>Минимальное RTT (минимальное время туда-обратно) (Xray)</value>
</data> </data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve"> <data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Multi-server LeastLoad by Xray</value> <value>Минимальная нагрузка (Xray)</value>
</data> </data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve"> <data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Multi-server LeastPing by sing-box</value> <value>Минимальное RTT (минимальное время туда-обратно) (sing-box)</value>
</data> </data>
<data name="menuExportConfig" xml:space="preserve"> <data name="menuExportConfig" xml:space="preserve">
<value>Export server</value> <value>Экспортировать конфигурацию</value>
</data> </data>
<data name="TbSettingsIPAPIUrl" xml:space="preserve"> <data name="TbSettingsIPAPIUrl" xml:space="preserve">
<value>Current connection info test URL</value> <value>URL для тестирования текущего соединения</value>
</data> </data>
</root> </root>

View file

@ -95,6 +95,21 @@ detect_desktop_environment() {
return return
fi 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") local KDE_ENVIRONMENTS=("KDE" "plasma")
for ENV in "${KDE_ENVIRONMENTS[@]}"; do for ENV in "${KDE_ENVIRONMENTS[@]}"; do
if [ "$XDG_CURRENT_DESKTOP" == "$ENV" ] || [ "$XDG_SESSION_DESKTOP" == "$ENV" ]; then if [ "$XDG_CURRENT_DESKTOP" == "$ENV" ] || [ "$XDG_SESSION_DESKTOP" == "$ENV" ]; then

View file

@ -336,7 +336,7 @@ public class CoreConfigSingboxService
await GenExperimental(singboxConfig); await GenExperimental(singboxConfig);
singboxConfig.outbounds.RemoveAt(0); singboxConfig.outbounds.RemoveAt(0);
var tagProxy = new List<string>(); var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (it.ConfigType == EConfigType.Custom) if (it.ConfigType == EConfigType.Custom)
@ -370,42 +370,18 @@ public class CoreConfigSingboxService
} }
//outbound //outbound
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); proxyProfiles.Add(item);
await GenOutbound(item, outbound);
outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}";
singboxConfig.outbounds.Insert(0, outbound);
tagProxy.Add(outbound.tag);
} }
if (tagProxy.Count <= 0) if (proxyProfiles.Count <= 0)
{ {
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
} }
await GenOutboundsList(proxyProfiles, singboxConfig);
await GenDns(null, singboxConfig); await GenDns(null, singboxConfig);
await ConvertGeo2Ruleset(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.Success = true;
ret.Data = JsonUtils.Serialize(singboxConfig); ret.Data = JsonUtils.Serialize(singboxConfig);
return ret; return ret;
@ -775,7 +751,8 @@ public class CoreConfigSingboxService
{ {
try 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() var mux = new Multiplex4Sbox()
{ {
@ -941,29 +918,21 @@ public class CoreConfigSingboxService
//Previous proxy //Previous proxy
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
string? prevOutboundTag = null;
if (prevNode is not null if (prevNode is not null
&& prevNode.ConfigType != EConfigType.Custom) && prevNode.ConfigType != EConfigType.Custom)
{ {
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(prevNode, prevOutbound); await GenOutbound(prevNode, prevOutbound);
prevOutbound.tag = $"{Global.ProxyTag}2"; prevOutboundTag = $"prev-{Global.ProxyTag}";
prevOutbound.tag = prevOutboundTag;
singboxConfig.outbounds.Add(prevOutbound); singboxConfig.outbounds.Add(prevOutbound);
outbound.detour = prevOutbound.tag;
} }
var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
//Next proxy if (nextOutbound is not null)
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (nextNode is not null
&& nextNode.ConfigType != EConfigType.Custom)
{ {
var nextOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(nextNode, nextOutbound);
nextOutbound.tag = Global.ProxyTag;
singboxConfig.outbounds.Insert(0, nextOutbound); singboxConfig.outbounds.Insert(0, nextOutbound);
outbound.tag = $"{Global.ProxyTag}1";
nextOutbound.detour = outbound.tag;
} }
} }
catch (Exception ex) catch (Exception ex)
@ -974,6 +943,169 @@ public class CoreConfigSingboxService
return 0; 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) private async Task<int> GenRouting(SingboxConfig singboxConfig)
{ {
try try
@ -1151,7 +1283,7 @@ public class CoreConfigSingboxService
} }
if (!hasDomainIp 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); rules.Add(rule);
} }

View file

@ -113,7 +113,7 @@ public class CoreConfigV2rayService
await GenStatistic(v2rayConfig); await GenStatistic(v2rayConfig);
v2rayConfig.outbounds.RemoveAt(0); v2rayConfig.outbounds.RemoveAt(0);
var tagProxy = new List<string>(); var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (it.ConfigType == EConfigType.Custom) if (it.ConfigType == EConfigType.Custom)
@ -151,17 +151,14 @@ public class CoreConfigV2rayService
} }
//outbound //outbound
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound); proxyProfiles.Add(item);
await GenOutbound(item, outbound);
outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}";
v2rayConfig.outbounds.Insert(0, outbound);
tagProxy.Add(outbound.tag);
} }
if (tagProxy.Count <= 0) if (proxyProfiles.Count <= 0)
{ {
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
} }
await GenOutboundsList(proxyProfiles, v2rayConfig);
//add balancers //add balancers
await GenBalancer(v2rayConfig, multipleLoad); await GenBalancer(v2rayConfig, multipleLoad);
@ -614,6 +611,7 @@ public class CoreConfigV2rayService
if (rule.port.IsNotEmpty() if (rule.port.IsNotEmpty()
|| rule.protocol?.Count > 0 || rule.protocol?.Count > 0
|| rule.inboundTag?.Count > 0 || rule.inboundTag?.Count > 0
|| rule.network != null
) )
{ {
var it = JsonUtils.DeepCopy(rule); var it = JsonUtils.DeepCopy(rule);
@ -633,7 +631,7 @@ public class CoreConfigV2rayService
{ {
try try
{ {
var muxEnabled = _config.CoreBasicItem.MuxEnabled; var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
switch (node.ConfigType) switch (node.ConfigType)
{ {
case EConfigType.VMess: case EConfigType.VMess:
@ -1320,6 +1318,7 @@ public class CoreConfigV2rayService
//Previous proxy //Previous proxy
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
string? prevOutboundTag = null;
if (prevNode is not null if (prevNode is not null
&& prevNode.ConfigType != EConfigType.Custom && prevNode.ConfigType != EConfigType.Custom
&& prevNode.ConfigType != EConfigType.Hysteria2 && prevNode.ConfigType != EConfigType.Hysteria2
@ -1327,32 +1326,15 @@ public class CoreConfigV2rayService
{ {
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound); var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(prevNode, prevOutbound); await GenOutbound(prevNode, prevOutbound);
prevOutbound.tag = $"{Global.ProxyTag}2"; prevOutboundTag = $"prev-{Global.ProxyTag}";
prevOutbound.tag = prevOutboundTag;
v2rayConfig.outbounds.Add(prevOutbound); v2rayConfig.outbounds.Add(prevOutbound);
outbound.streamSettings.sockopt = new()
{
dialerProxy = prevOutbound.tag
};
} }
var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
//Next proxy if (nextOutbound is not null)
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)
{ {
var nextOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(nextNode, nextOutbound);
nextOutbound.tag = Global.ProxyTag;
v2rayConfig.outbounds.Insert(0, nextOutbound); v2rayConfig.outbounds.Insert(0, nextOutbound);
outbound.tag = $"{Global.ProxyTag}1";
nextOutbound.streamSettings.sockopt = new()
{
dialerProxy = outbound.tag
};
} }
} }
catch (Exception ex) catch (Exception ex)
@ -1363,18 +1345,179 @@ public class CoreConfigV2rayService
return 0; 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) 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 var observatory = new Observatory4Ray
{ {
subjectSelector = [Global.ProxyTag], subjectSelector = [Global.ProxyTag],
probeUrl = AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl, probeUrl = AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
probeInterval = "3m" probeInterval = "3m",
enableConcurrency = true,
}; };
v2rayConfig.observatory = observatory; 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 var strategyType = multipleLoad switch
{ {
EMultipleLoad.Random => "random", EMultipleLoad.Random => "random",

View file

@ -196,6 +196,7 @@ public class SpeedtestService
{ {
return false; return false;
} }
await Task.Delay(1000);
var downloadHandle = new DownloadService(); var downloadHandle = new DownloadService();
@ -255,9 +256,13 @@ public class SpeedtestService
try try
{ {
pid = await CoreHandler.Instance.LoadCoreConfigSpeedtest(it); 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); var delay = await DoRealPing(downloadHandle, it);
if (blSpeedTest) if (blSpeedTest)
{ {
@ -271,10 +276,6 @@ public class SpeedtestService
} }
} }
} }
else
{
UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore);
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -485,6 +485,12 @@ public class UpdateService
private async Task UpdateOtherFiles(Config config, Action<bool, string> updateFunc) 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; _updateFunc = updateFunc;
foreach (var url in Global.OtherGeoUrls) foreach (var url in Global.OtherGeoUrls)

View file

@ -552,6 +552,7 @@ public class MainWindowViewModel : MyReactiveObject
{ {
await LoadCore(); await LoadCore();
await SysProxyHandler.UpdateSysProxy(_config, false); await SysProxyHandler.UpdateSysProxy(_config, false);
await Task.Delay(1000);
}); });
Locator.Current.GetService<StatusBarViewModel>()?.TestServerAvailability(); Locator.Current.GetService<StatusBarViewModel>()?.TestServerAvailability();

View file

@ -304,7 +304,7 @@ public class ProfilesViewModel : MyReactiveObject
{ {
item.SpeedVal = result.Speed ?? string.Empty; item.SpeedVal = result.Speed ?? string.Empty;
} }
_profileItems.Replace(item, JsonUtils.DeepCopy(item)); //_profileItems.Replace(item, JsonUtils.DeepCopy(item));
} }
public void UpdateStatistics(ServerSpeedItem update) public void UpdateStatistics(ServerSpeedItem update)
@ -319,16 +319,16 @@ public class ProfilesViewModel : MyReactiveObject
item.TotalDown = Utils.HumanFy(update.TotalDown); item.TotalDown = Utils.HumanFy(update.TotalDown);
item.TotalUp = Utils.HumanFy(update.TotalUp); item.TotalUp = Utils.HumanFy(update.TotalUp);
if (SelectedProfile?.IndexId == item.IndexId) //if (SelectedProfile?.IndexId == item.IndexId)
{ //{
var temp = JsonUtils.DeepCopy(item); // var temp = JsonUtils.DeepCopy(item);
_profileItems.Replace(item, temp); // _profileItems.Replace(item, temp);
SelectedProfile = temp; // SelectedProfile = temp;
} //}
else //else
{ //{
_profileItems.Replace(item, JsonUtils.DeepCopy(item)); // _profileItems.Replace(item, JsonUtils.DeepCopy(item));
} //}
} }
} }
catch catch

View file

@ -500,8 +500,16 @@ public class StatusBarViewModel : MyReactiveObject
{ {
try try
{ {
SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, Global.ProxyTag, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown)); if (_config.IsRunningCore(ECoreType.sing_box))
SpeedDirectDisplay = string.Format(ResUI.SpeedDisplayText, Global.DirectTag, Utils.HumanFy(update.DirectUp), Utils.HumanFy(update.DirectDown)); {
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 catch
{ {

View file

@ -6,8 +6,8 @@
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:semi="https://irihi.tech/semi" xmlns:semi="https://irihi.tech/semi"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib" xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
x:DataType="vms:StatusBarViewModel"
Name="v2rayN" Name="v2rayN"
x:DataType="vms:StatusBarViewModel"
RequestedThemeVariant="Default"> RequestedThemeVariant="Default">
<Application.Styles> <Application.Styles>
<semi:SemiTheme /> <semi:SemiTheme />
@ -32,6 +32,8 @@
ToolTipText="{Binding RunningServerToolTipText}"> ToolTipText="{Binding RunningServerToolTipText}">
<TrayIcon.Menu> <TrayIcon.Menu>
<NativeMenu> <NativeMenu>
<NativeMenuItem Command="{Binding NotifyLeftClickCmd}" Header="{x:Static resx:ResUI.menuShowOrHideMainWindow}" />
<NativeMenuItemSeparator />
<NativeMenuItem <NativeMenuItem
Command="{Binding SystemProxyClearCmd}" Command="{Binding SystemProxyClearCmd}"
Header="{x:Static resx:ResUI.menuSystemProxyClear}" Header="{x:Static resx:ResUI.menuSystemProxyClear}"
@ -55,13 +57,11 @@
ToggleType="Radio" /> ToggleType="Radio" />
<NativeMenuItemSeparator /> <NativeMenuItemSeparator />
<NativeMenuItem Click="MenuAddServerViaClipboardClick" Header="{x:Static resx:ResUI.menuAddServerViaClipboard}" /> <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 SubUpdateCmd}" Header="{x:Static resx:ResUI.menuSubUpdate}" />
<NativeMenuItem Command="{Binding SubUpdateViaProxyCmd}" Header="{x:Static resx:ResUI.menuSubUpdateViaProxy}" /> <NativeMenuItem Command="{Binding SubUpdateViaProxyCmd}" Header="{x:Static resx:ResUI.menuSubUpdateViaProxy}" />
<NativeMenuItemSeparator /> <NativeMenuItemSeparator />
<NativeMenuItem Command="{Binding CopyProxyCmdToClipboardCmd}" Header="{x:Static resx:ResUI.menuCopyProxyCmdToClipboard}" /> <NativeMenuItem Command="{Binding CopyProxyCmdToClipboardCmd}" Header="{x:Static resx:ResUI.menuCopyProxyCmdToClipboard}" />
<NativeMenuItemSeparator /> <NativeMenuItemSeparator />
<NativeMenuItem Command="{Binding NotifyLeftClickCmd}" Header="{x:Static resx:ResUI.menuShowOrHideMainWindow}" />
<NativeMenuItem Click="MenuExit_Click" Header="{x:Static resx:ResUI.menuExit}" /> <NativeMenuItem Click="MenuExit_Click" Header="{x:Static resx:ResUI.menuExit}" />
</NativeMenu> </NativeMenu>
</TrayIcon.Menu> </TrayIcon.Menu>

View file

@ -11,11 +11,6 @@ public partial class App : Application
{ {
public override void Initialize() public override void Initialize()
{ {
if (!AppHandler.Instance.InitApp())
{
Environment.Exit(0);
return;
}
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

View 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 { }
}
}

View file

@ -14,13 +14,17 @@ internal class Program
[STAThread] [STAThread]
public static void Main(string[] args) public static void Main(string[] args)
{ {
OnStartup(args); if (OnStartup(args) == false)
{
Environment.Exit(0);
return;
}
BuildAvaloniaApp() BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args); .StartWithClassicDesktopLifetime(args);
} }
private static void OnStartup(string[]? Args) private static bool OnStartup(string[]? Args)
{ {
if (Utils.IsWindows()) if (Utils.IsWindows())
{ {
@ -30,8 +34,7 @@ internal class Program
if (!rebootas && !bCreatedNew) if (!rebootas && !bCreatedNew)
{ {
ProgramStarted.Set(); ProgramStarted.Set();
Environment.Exit(0); return false;
return;
} }
} }
else else
@ -39,19 +42,30 @@ internal class Program
_ = new Mutex(true, "v2rayN", out var bOnlyOneInstance); _ = new Mutex(true, "v2rayN", out var bOnlyOneInstance);
if (!bOnlyOneInstance) if (!bOnlyOneInstance)
{ {
Environment.Exit(0); return false;
return;
} }
} }
if (!AppHandler.Instance.InitApp())
{
return false;
}
return true;
} }
// Avalonia configuration, don't remove; also used by visual designer. // Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp() public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>() {
.UsePlatformDetect() return AppBuilder.Configure<App>()
//.WithInterFont() .UsePlatformDetect()
.WithFontByDefault() //.WithInterFont()
.LogToTrace() .WithFontByDefault()
.UseReactiveUI() .LogToTrace()
.With(new MacOSPlatformOptions { ShowInDock = false }); #if OS_OSX
.UseReactiveUI()
.With(new MacOSPlatformOptions { ShowInDock = AppHandler.Instance.Config.UiItem.MacOSShowInDock });
#else
.UseReactiveUI();
#endif
}
} }

View file

@ -1,12 +1,12 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI; using ReactiveUI;
using v2rayN.Desktop.Base;
using v2rayN.Desktop.Common; using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
public partial class AddServer2Window : ReactiveWindow<AddServer2ViewModel> public partial class AddServer2Window : WindowBase<AddServer2ViewModel>
{ {
public AddServer2Window() public AddServer2Window()
{ {

View file

@ -105,7 +105,7 @@
Grid.Row="2" Grid.Row="2"
ColumnDefinitions="180,Auto,Auto" ColumnDefinitions="180,Auto,Auto"
IsVisible="False" IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto"> RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@ -152,13 +152,26 @@
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> 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>
<Grid <Grid
x:Name="gridSs" x:Name="gridSs"
Grid.Row="2" Grid.Row="2"
ColumnDefinitions="180,Auto" ColumnDefinitions="180,Auto"
IsVisible="False" IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto"> RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@ -185,6 +198,19 @@
Grid.Column="1" Grid.Column="1"
Width="300" Width="300"
Margin="{StaticResource Margin4}" /> 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>
<Grid <Grid
x:Name="gridSocks" x:Name="gridSocks"
@ -224,7 +250,7 @@
Grid.Row="2" Grid.Row="2"
ColumnDefinitions="180,Auto,Auto" ColumnDefinitions="180,Auto,Auto"
IsVisible="False" IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto"> RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@ -271,13 +297,26 @@
Width="200" Width="200"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" /> 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>
<Grid <Grid
x:Name="gridTrojan" x:Name="gridTrojan"
Grid.Row="2" Grid.Row="2"
ColumnDefinitions="180,Auto" ColumnDefinitions="180,Auto"
IsVisible="False" IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto"> RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@ -304,6 +343,19 @@
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> 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>
<Grid <Grid
x:Name="gridHysteria2" x:Name="gridHysteria2"

View file

@ -1,12 +1,12 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI; using ReactiveUI;
using v2rayN.Desktop.Base;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
public partial class AddServerWindow : ReactiveWindow<AddServerViewModel> public partial class AddServerWindow : WindowBase<AddServerViewModel>
{ {
public AddServerWindow() 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.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.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.Security, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId3.Text).DisposeWith(disposables); 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.Security, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.SOCKS: 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.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.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.Security, v => v.txtSecurity5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Trojan: case EConfigType.Trojan:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId6.Text).DisposeWith(disposables); 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.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Hysteria2: case EConfigType.Hysteria2:

View file

@ -34,7 +34,7 @@
IsCancel="True" /> IsCancel="True" />
</StackPanel> </StackPanel>
<TabControl HorizontalContentAlignment="Left"> <TabControl HorizontalContentAlignment="Stretch">
<TabItem Header="{x:Static resx:ResUI.TbSettingsCoreDns}"> <TabItem Header="{x:Static resx:ResUI.TbSettingsCoreDns}">
<DockPanel Margin="{StaticResource Margin8}"> <DockPanel Margin="{StaticResource Margin8}">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal"> <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
@ -90,16 +90,18 @@
</StackPanel> </StackPanel>
</WrapPanel> </WrapPanel>
<Grid Margin="{StaticResource Margin4}"> <HeaderedContentControl
Margin="{StaticResource Margin4}"
BorderBrush="Gray"
BorderThickness="1"
Header="HTTP/SOCKS">
<TextBox <TextBox
x:Name="txtnormalDNS" Name="txtnormalDNS"
Margin="{StaticResource Margin4}"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
BorderThickness="1"
Classes="TextArea" Classes="TextArea"
TextWrapping="Wrap" MinLines="10"
Watermark="HTTP/SOCKS" /> TextWrapping="Wrap" />
</Grid> </HeaderedContentControl>
</DockPanel> </DockPanel>
</TabItem> </TabItem>
@ -144,31 +146,34 @@
<Grid Margin="{StaticResource Margin4}" ColumnDefinitions="*,10,*"> <Grid Margin="{StaticResource Margin4}" ColumnDefinitions="*,10,*">
<TextBox <HeaderedContentControl
x:Name="txtnormalDNS2"
Grid.Column="0" Grid.Column="0"
Width="400" BorderBrush="Gray"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderThickness="1" BorderThickness="1"
Classes="TextArea" Header="HTTP/SOCKS">
Margin="{StaticResource Margin4}" <TextBox
TextWrapping="Wrap" Name="txtnormalDNS2"
Watermark="HTTP/SOCKS" /> VerticalAlignment="Stretch"
Classes="TextArea"
MinLines="10"
TextWrapping="Wrap" />
</HeaderedContentControl>
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" /> <GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
<TextBox <HeaderedContentControl
x:Name="txttunDNS2"
Grid.Column="2" Grid.Column="2"
Width="400" BorderBrush="Gray"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderThickness="1" BorderThickness="1"
Classes="TextArea" Header="{x:Static resx:ResUI.TbSettingsTunMode}">
Margin="{StaticResource Margin4}" <TextBox
TextWrapping="Wrap" Name="txttunDNS2"
Watermark="{x:Static resx:ResUI.TbSettingsTunMode}" /> VerticalAlignment="Stretch"
Classes="TextArea"
MinLines="10"
TextWrapping="Wrap" />
</HeaderedContentControl>
</Grid> </Grid>
</DockPanel> </DockPanel>
</TabItem> </TabItem>

View file

@ -1,11 +1,11 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI; using ReactiveUI;
using v2rayN.Desktop.Base;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
public partial class DNSSettingWindow : ReactiveWindow<DNSSettingViewModel> public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
{ {
private static Config _config; private static Config _config;

View file

@ -3,13 +3,13 @@ using System.Text;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI; using ReactiveUI;
using v2rayN.Desktop.Base;
using v2rayN.Desktop.Handler; using v2rayN.Desktop.Handler;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
public partial class GlobalHotkeySettingWindow : ReactiveWindow<GlobalHotkeySettingViewModel> public partial class GlobalHotkeySettingWindow : WindowBase<GlobalHotkeySettingViewModel>
{ {
private readonly List<object> _textBoxKeyEventItem = new(); private readonly List<object> _textBoxKeyEventItem = new();

View file

@ -5,18 +5,18 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Notifications; using Avalonia.Controls.Notifications;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using Avalonia.Threading; using Avalonia.Threading;
using DialogHostAvalonia; using DialogHostAvalonia;
using MsBox.Avalonia.Enums; using MsBox.Avalonia.Enums;
using ReactiveUI; using ReactiveUI;
using Splat; using Splat;
using v2rayN.Desktop.Base;
using v2rayN.Desktop.Common; using v2rayN.Desktop.Common;
using v2rayN.Desktop.Handler; using v2rayN.Desktop.Handler;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
public partial class MainWindow : ReactiveWindow<MainWindowViewModel> public partial class MainWindow : WindowBase<MainWindowViewModel>
{ {
private static Config _config; private static Config _config;
private WindowNotificationManager? _manager; private WindowNotificationManager? _manager;
@ -29,7 +29,7 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
InitializeComponent(); InitializeComponent();
_config = AppHandler.Instance.Config; _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; this.KeyDown += MainWindow_KeyDown;
menuSettingsSetUWP.Click += menuSettingsSetUWP_Click; menuSettingsSetUWP.Click += menuSettingsSetUWP_Click;
@ -154,7 +154,6 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
} }
menuAddServerViaScan.IsVisible = false; menuAddServerViaScan.IsVisible = false;
RestoreUI();
AddHelpMenuItem(); AddHelpMenuItem();
MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI); 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) 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; return;
} }
@ -436,14 +435,14 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
_config.UiItem.ShowInTaskbar = bl; _config.UiItem.ShowInTaskbar = bl;
} }
protected override void OnLoaded(object? sender, RoutedEventArgs e)
{
base.OnLoaded(sender, e);
RestoreUI();
}
private void 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.MainGirdHeight1 > 0 && _config.UiItem.MainGirdHeight2 > 0)
{ {
if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal) if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal)
@ -461,8 +460,7 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
private void StorageUI(string? n = null) private void StorageUI(string? n = null)
{ {
_config.UiItem.MainWidth = this.Width; ConfigHandler.SaveWindowSizeItem(_config, GetType().Name, Width, Height);
_config.UiItem.MainHeight = this.Height;
if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal) if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal)
{ {

View file

@ -575,9 +575,9 @@
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsSubConvert}" /> Text="{x:Static resx:ResUI.TbSettingsIPAPIUrl}" />
<ctrls:AutoCompleteBox <ComboBox
x:Name="cmbSubConvertUrl" x:Name="cmbIPAPIUrl"
Grid.Row="20" Grid.Row="20"
Grid.Column="1" Grid.Column="1"
Width="300" Width="300"
@ -588,28 +588,41 @@
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" 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}" /> Text="{x:Static resx:ResUI.TbSettingsMainGirdOrientation}" />
<ComboBox <ComboBox
x:Name="cmbMainGirdOrientation" x:Name="cmbMainGirdOrientation"
Grid.Row="21" Grid.Row="22"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock <TextBlock
Grid.Row="22" Grid.Row="23"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsGeoFilesSource}" /> Text="{x:Static resx:ResUI.TbSettingsGeoFilesSource}" />
<ComboBox <ComboBox
x:Name="cmbGetFilesSourceUrl" x:Name="cmbGetFilesSourceUrl"
Grid.Row="22" Grid.Row="23"
Grid.Column="1" Grid.Column="1"
Width="300" Width="300"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock <TextBlock
Grid.Row="22" Grid.Row="23"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -617,19 +630,19 @@
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
Grid.Row="23" Grid.Row="24"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsSrsFilesSource}" /> Text="{x:Static resx:ResUI.TbSettingsSrsFilesSource}" />
<ComboBox <ComboBox
x:Name="cmbSrsFilesSourceUrl" x:Name="cmbSrsFilesSourceUrl"
Grid.Row="23" Grid.Row="24"
Grid.Column="1" Grid.Column="1"
Width="300" Width="300"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock <TextBlock
Grid.Row="23" Grid.Row="24"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -637,38 +650,25 @@
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
Grid.Row="24" Grid.Row="25"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsRoutingRulesSource}" /> Text="{x:Static resx:ResUI.TbSettingsRoutingRulesSource}" />
<ComboBox <ComboBox
x:Name="cmbRoutingRulesSourceUrl" x:Name="cmbRoutingRulesSourceUrl"
Grid.Row="24" Grid.Row="25"
Grid.Column="1" Grid.Column="1"
Width="300" Width="300"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock <TextBlock
Grid.Row="24" Grid.Row="25"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsChinaUserTip}" Text="{x:Static resx:ResUI.TbSettingsChinaUserTip}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock
Grid.Row="25"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsIPAPIUrl}" />
<ComboBox
x:Name="cmbIPAPIUrl"
Grid.Row="25"
Grid.Column="1"
Width="300"
Margin="{StaticResource Margin4}" />
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</TabItem> </TabItem>

View file

@ -1,11 +1,11 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.ReactiveUI;
using ReactiveUI; using ReactiveUI;
using v2rayN.Desktop.Base;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
public partial class OptionSettingWindow : ReactiveWindow<OptionSettingViewModel> public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
{ {
private static Config _config; private static Config _config;

View file

@ -138,7 +138,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
break; break;
case EViewAction.ShowYesNo: case EViewAction.ShowYesNo:
if (await UI.ShowYesNo(_window, ResUI.RemoveServer) == ButtonResult.No) if (await UI.ShowYesNo(_window, ResUI.RemoveServer) != ButtonResult.Yes)
{ {
return false; return false;
} }
@ -215,7 +215,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
await ViewModel.RefreshServersBiz(); await ViewModel.RefreshServersBiz();
} }
if (lstProfiles.SelectedIndex > 0) if (lstProfiles.SelectedIndex >= 0)
{ {
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, null); lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, null);
} }
@ -347,6 +347,24 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
{ {
try try
{ {
//First scroll horizontally to the initial position to avoid the control crash bug
if (lstProfiles.SelectedIndex >= 0)
{
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, lstProfiles.Columns[0]);
}
else
{
var model = lstProfiles.ItemsSource.Cast<ProfileItemModel>();
if (model.Any())
{
lstProfiles.ScrollIntoView(model.First(), lstProfiles.Columns[0]);
}
else
{
return;
}
}
foreach (var it in lstProfiles.Columns) foreach (var it in lstProfiles.Columns)
{ {
it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto); it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);

View file

@ -1,12 +1,12 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI; using ReactiveUI;
using v2rayN.Desktop.Base;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
public partial class RoutingRuleDetailsWindow : ReactiveWindow<RoutingRuleDetailsViewModel> public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsViewModel>
{ {
public RoutingRuleDetailsWindow() public RoutingRuleDetailsWindow()
{ {

View file

@ -2,14 +2,14 @@ using System.Reactive.Disposables;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using MsBox.Avalonia.Enums; using MsBox.Avalonia.Enums;
using ReactiveUI; using ReactiveUI;
using v2rayN.Desktop.Base;
using v2rayN.Desktop.Common; using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
public partial class RoutingRuleSettingWindow : ReactiveWindow<RoutingRuleSettingViewModel> public partial class RoutingRuleSettingWindow : WindowBase<RoutingRuleSettingViewModel>
{ {
public RoutingRuleSettingWindow() public RoutingRuleSettingWindow()
{ {
@ -80,14 +80,14 @@ public partial class RoutingRuleSettingWindow : ReactiveWindow<RoutingRuleSettin
break; break;
case EViewAction.ShowYesNo: case EViewAction.ShowYesNo:
if (await UI.ShowYesNo(this, ResUI.RemoveServer) == ButtonResult.No) if (await UI.ShowYesNo(this, ResUI.RemoveServer) != ButtonResult.Yes)
{ {
return false; return false;
} }
break; break;
case EViewAction.AddBatchRoutingRulesYesNo: case EViewAction.AddBatchRoutingRulesYesNo:
if (await UI.ShowYesNo(this, ResUI.AddBatchRoutingRulesYesNo) == ButtonResult.No) if (await UI.ShowYesNo(this, ResUI.AddBatchRoutingRulesYesNo) != ButtonResult.Yes)
{ {
return false; return false;
} }

View file

@ -24,28 +24,11 @@
<MenuItem x:Name="menuRoutingAdvancedAdd2" Header="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" /> <MenuItem x:Name="menuRoutingAdvancedAdd2" Header="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" />
<MenuItem x:Name="menuRoutingAdvancedImportRules2" Header="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" /> <MenuItem x:Name="menuRoutingAdvancedImportRules2" Header="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" />
</Menu> </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>
<StackPanel <StackPanel
HorizontalAlignment="Right"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Right"
DockPanel.Dock="Bottom" DockPanel.Dock="Bottom"
Orientation="Horizontal"> Orientation="Horizontal">
<StackPanel <StackPanel
@ -69,58 +52,112 @@
IsCancel="True" /> IsCancel="True" />
</StackPanel> </StackPanel>
<DockPanel> <Grid
<TabControl x:Name="tabAdvanced"> Margin="{StaticResource Margin4}"
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.TbRoutingTabRuleList}"> ColumnDefinitions="Auto,Auto"
<DataGrid DockPanel.Dock="Top"
x:Name="lstRoutings" RowDefinitions="Auto,Auto,Auto">
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> <TextBlock
<DataGridCheckBoxColumn Width="40" Binding="{Binding IsActive}" /> Grid.Row="0"
<DataGridTextColumn Grid.Column="0"
Width="*" Margin="{StaticResource Margin4}"
Binding="{Binding Remarks}" VerticalAlignment="Center">
Header="{x:Static resx:ResUI.LvRemarks}" /> <HyperlinkButton Classes="WithIcon" Click="linkdomainStrategy_Click">
<DataGridTextColumn <TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy}" />
Width="60" </HyperlinkButton>
Binding="{Binding RuleNum}" </TextBlock>
Header="{x:Static resx:ResUI.LvCount}" /> <ComboBox
<DataGridTextColumn x:Name="cmbdomainStrategy"
Width="60" Grid.Row="0"
Binding="{Binding Sort}" Grid.Column="1"
Header="{x:Static resx:ResUI.LvSort}" /> Width="300"
<DataGridTextColumn Margin="{StaticResource Margin4}"
Width="*" HorizontalAlignment="Left"
Binding="{Binding Url}" VerticalAlignment="Center" />
Header="{x:Static resx:ResUI.LvUrl}" />
<DataGridTextColumn <TextBlock
Width="300" Grid.Row="1"
Binding="{Binding CustomIcon}" Grid.Column="0"
Header="{x:Static resx:ResUI.LvCustomIcon}" /> Margin="{StaticResource Margin4}"
</DataGrid.Columns> VerticalAlignment="Center"
</DataGrid> Text="{x:Static resx:ResUI.TbdomainMatcher}" />
</TabItem> <ComboBox
</TabControl> x:Name="cmbdomainMatcher"
</DockPanel> 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> </DockPanel>
</Window> </Window>

View file

@ -2,14 +2,14 @@ using System.Reactive.Disposables;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using MsBox.Avalonia.Enums; using MsBox.Avalonia.Enums;
using ReactiveUI; using ReactiveUI;
using v2rayN.Desktop.Base;
using v2rayN.Desktop.Common; using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
public partial class RoutingSettingWindow : ReactiveWindow<RoutingSettingViewModel> public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel>
{ {
private bool _manualClose = false; private bool _manualClose = false;
@ -68,7 +68,7 @@ public partial class RoutingSettingWindow : ReactiveWindow<RoutingSettingViewMod
break; break;
case EViewAction.ShowYesNo: case EViewAction.ShowYesNo:
if (await UI.ShowYesNo(this, ResUI.RemoveRules) == ButtonResult.No) if (await UI.ShowYesNo(this, ResUI.RemoveRules) != ButtonResult.Yes)
{ {
return false; return false;
} }

View file

@ -1,12 +1,12 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia; using Avalonia;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI; using ReactiveUI;
using v2rayN.Desktop.Base;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
public partial class SubEditWindow : ReactiveWindow<SubEditViewModel> public partial class SubEditWindow : WindowBase<SubEditViewModel>
{ {
public SubEditWindow() public SubEditWindow()
{ {

View file

@ -1,16 +1,16 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using DialogHostAvalonia; using DialogHostAvalonia;
using DynamicData; using DynamicData;
using MsBox.Avalonia.Enums; using MsBox.Avalonia.Enums;
using ReactiveUI; using ReactiveUI;
using v2rayN.Desktop.Base;
using v2rayN.Desktop.Common; using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
public partial class SubSettingWindow : ReactiveWindow<SubSettingViewModel> public partial class SubSettingWindow : WindowBase<SubSettingViewModel>
{ {
private bool _manualClose = false; private bool _manualClose = false;
@ -45,7 +45,7 @@ public partial class SubSettingWindow : ReactiveWindow<SubSettingViewModel>
break; break;
case EViewAction.ShowYesNo: case EViewAction.ShowYesNo:
if (await UI.ShowYesNo(this, ResUI.RemoveServer) == ButtonResult.No) if (await UI.ShowYesNo(this, ResUI.RemoveServer) != ButtonResult.Yes)
{ {
return false; return false;
} }

View file

@ -215,7 +215,7 @@
<Setter Property="Background" Value="{DynamicResource MaterialDesignPaper}" /> <Setter Property="Background" Value="{DynamicResource MaterialDesignPaper}" />
<Setter Property="TextElement.FontFamily" Value="{x:Static conv:MaterialDesignFonts.MyFont}" /> <Setter Property="TextElement.FontFamily" Value="{x:Static conv:MaterialDesignFonts.MyFont}" />
<Setter Property="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>
<Style <Style
x:Key="ViewGlobal" x:Key="ViewGlobal"

View 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 { }
}
}

View file

@ -1,7 +1,8 @@
<reactiveui:ReactiveWindow <base:WindowBase
x:Class="v2rayN.Views.AddServer2Window" x:Class="v2rayN.Views.AddServer2Window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -198,4 +199,4 @@
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</DockPanel> </DockPanel>
</reactiveui:ReactiveWindow> </base:WindowBase>

View file

@ -1,7 +1,8 @@
<reactiveui:ReactiveWindow <base:WindowBase
x:Class="v2rayN.Views.AddServerWindow" x:Class="v2rayN.Views.AddServerWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 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" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /> <ColumnDefinition Width="180" />
@ -214,6 +216,20 @@
Width="200" Width="200"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Style="{StaticResource DefComboBox}" /> 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>
<Grid <Grid
x:Name="gridSs" x:Name="gridSs"
@ -224,6 +240,7 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /> <ColumnDefinition Width="180" />
@ -259,6 +276,20 @@
Width="300" Width="300"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Style="{StaticResource DefComboBox}" /> 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>
<Grid <Grid
x:Name="gridSocks" x:Name="gridSocks"
@ -314,6 +345,7 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /> <ColumnDefinition Width="180" />
@ -373,6 +405,20 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Style="{StaticResource DefTextBox}" /> 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>
<Grid <Grid
x:Name="gridTrojan" x:Name="gridTrojan"
@ -383,6 +429,7 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /> <ColumnDefinition Width="180" />
@ -418,6 +465,20 @@
Width="200" Width="200"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Style="{StaticResource DefComboBox}" /> 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>
<Grid <Grid
x:Name="gridHysteria2" x:Name="gridHysteria2"
@ -1011,4 +1072,4 @@
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</DockPanel> </DockPanel>
</reactiveui:ReactiveWindow> </base:WindowBase>

View file

@ -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.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.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.Security, v => v.cmbSecurity.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId3.Text).DisposeWith(disposables); 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.Security, v => v.cmbSecurity3.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.SOCKS: 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.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.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.Security, v => v.txtSecurity5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Trojan: case EConfigType.Trojan:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId6.Text).DisposeWith(disposables); 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.Flow, v => v.cmbFlow6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Hysteria2: case EConfigType.Hysteria2:

View file

@ -1,7 +1,8 @@
<reactiveui:ReactiveWindow <base:WindowBase
x:Class="v2rayN.Views.DNSSettingWindow" x:Class="v2rayN.Views.DNSSettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -204,4 +205,4 @@
</TabItem> </TabItem>
</TabControl> </TabControl>
</DockPanel> </DockPanel>
</reactiveui:ReactiveWindow> </base:WindowBase>

View file

@ -1,7 +1,8 @@
<reactiveui:ReactiveWindow <base:WindowBase
x:Class="v2rayN.Views.GlobalHotkeySettingWindow" x:Class="v2rayN.Views.GlobalHotkeySettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -169,4 +170,4 @@
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</DockPanel> </DockPanel>
</reactiveui:ReactiveWindow> </base:WindowBase>

View file

@ -1,7 +1,8 @@
<reactiveui:ReactiveWindow <base:WindowBase
x:Class="v2rayN.Views.MainWindow" x:Class="v2rayN.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -40,8 +41,8 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
ClipToBounds="True" ClipToBounds="True"
Style="{StaticResource MaterialDesignToolBar}" KeyboardNavigation.TabNavigation="Continue"
KeyboardNavigation.TabNavigation="Continue"> Style="{StaticResource MaterialDesignToolBar}">
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}"> <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> <MenuItem.Header>
@ -432,4 +433,4 @@
</DockPanel> </DockPanel>
</Grid> </Grid>
</materialDesign:DialogHost> </materialDesign:DialogHost>
</reactiveui:ReactiveWindow> </base:WindowBase>

View file

@ -139,7 +139,6 @@ public partial class MainWindow
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly; RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
} }
RestoreUI();
AddHelpMenuItem(); AddHelpMenuItem();
WindowsHandler.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, null); WindowsHandler.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, null);
MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI); MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI);
@ -395,20 +394,14 @@ public partial class MainWindow
_config.UiItem.ShowInTaskbar = bl; _config.UiItem.ShowInTaskbar = bl;
} }
protected override void OnLoaded(object? sender, RoutedEventArgs e)
{
base.OnLoaded(sender, e);
RestoreUI();
}
private void 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.MainGirdHeight1 > 0 && _config.UiItem.MainGirdHeight2 > 0)
{ {
if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal) if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal)
@ -426,8 +419,7 @@ public partial class MainWindow
private void StorageUI(string? n = null) private void StorageUI(string? n = null)
{ {
_config.UiItem.MainWidth = this.Width; ConfigHandler.SaveWindowSizeItem(_config, GetType().Name, Width, Height);
_config.UiItem.MainHeight = this.Height;
if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal) if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal)
{ {

View file

@ -1,7 +1,8 @@
<reactiveui:ReactiveWindow <base:WindowBase
x:Class="v2rayN.Views.OptionSettingWindow" x:Class="v2rayN.Views.OptionSettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -846,10 +847,26 @@
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" 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}" /> Text="{x:Static resx:ResUI.TbSettingsSubConvert}" />
<ComboBox <ComboBox
x:Name="cmbSubConvertUrl" x:Name="cmbSubConvertUrl"
Grid.Row="20" Grid.Row="21"
Grid.Column="1" Grid.Column="1"
Width="300" Width="300"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
@ -858,7 +875,7 @@
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="21" Grid.Row="22"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -866,14 +883,14 @@
Text="{x:Static resx:ResUI.TbSettingsMainGirdOrientation}" /> Text="{x:Static resx:ResUI.TbSettingsMainGirdOrientation}" />
<ComboBox <ComboBox
x:Name="cmbMainGirdOrientation" x:Name="cmbMainGirdOrientation"
Grid.Row="21" Grid.Row="22"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="22" Grid.Row="23"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -881,14 +898,14 @@
Text="{x:Static resx:ResUI.TbSettingsGeoFilesSource}" /> Text="{x:Static resx:ResUI.TbSettingsGeoFilesSource}" />
<ComboBox <ComboBox
x:Name="cmbGetFilesSourceUrl" x:Name="cmbGetFilesSourceUrl"
Grid.Row="22" Grid.Row="23"
Grid.Column="1" Grid.Column="1"
Width="300" Width="300"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
IsEditable="True" IsEditable="True"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="22" Grid.Row="23"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -897,7 +914,7 @@
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
Grid.Row="23" Grid.Row="24"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -905,14 +922,14 @@
Text="{x:Static resx:ResUI.TbSettingsSrsFilesSource}" /> Text="{x:Static resx:ResUI.TbSettingsSrsFilesSource}" />
<ComboBox <ComboBox
x:Name="cmbSrsFilesSourceUrl" x:Name="cmbSrsFilesSourceUrl"
Grid.Row="23" Grid.Row="24"
Grid.Column="1" Grid.Column="1"
Width="300" Width="300"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
IsEditable="True" IsEditable="True"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="23" Grid.Row="24"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -921,7 +938,7 @@
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
Grid.Row="24" Grid.Row="25"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -929,36 +946,20 @@
Text="{x:Static resx:ResUI.TbSettingsRoutingRulesSource}" /> Text="{x:Static resx:ResUI.TbSettingsRoutingRulesSource}" />
<ComboBox <ComboBox
x:Name="cmbRoutingRulesSourceUrl" x:Name="cmbRoutingRulesSourceUrl"
Grid.Row="24" Grid.Row="25"
Grid.Column="1" Grid.Column="1"
Width="300" Width="300"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
IsEditable="True" IsEditable="True"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="24" Grid.Row="25"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsChinaUserTip}" Text="{x:Static resx:ResUI.TbSettingsChinaUserTip}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock
Grid.Row="25"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsIPAPIUrl}" />
<ComboBox
x:Name="cmbIPAPIUrl"
Grid.Row="25"
Grid.Column="1"
Width="300"
Margin="{StaticResource Margin8}"
IsEditable="True"
Style="{StaticResource DefComboBox}" />
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</TabItem> </TabItem>
@ -1229,4 +1230,4 @@
</TabItem> </TabItem>
</TabControl> </TabControl>
</DockPanel> </DockPanel>
</reactiveui:ReactiveWindow> </base:WindowBase>

View file

@ -1,7 +1,8 @@
<reactiveui:ReactiveWindow <base:WindowBase
x:Class="v2rayN.Views.RoutingRuleDetailsWindow" x:Class="v2rayN.Views.RoutingRuleDetailsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -242,4 +243,4 @@
</GroupBox> </GroupBox>
</Grid> </Grid>
</DockPanel> </DockPanel>
</reactiveui:ReactiveWindow> </base:WindowBase>

View file

@ -1,7 +1,8 @@
<reactiveui:ReactiveWindow <base:WindowBase
x:Class="v2rayN.Views.RoutingRuleSettingWindow" x:Class="v2rayN.Views.RoutingRuleSettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -334,4 +335,4 @@
</TabItem> </TabItem>
</TabControl> </TabControl>
</DockPanel> </DockPanel>
</reactiveui:ReactiveWindow> </base:WindowBase>

View file

@ -1,7 +1,8 @@
<reactiveui:ReactiveWindow <base:WindowBase
x:Class="v2rayN.Views.RoutingSettingWindow" x:Class="v2rayN.Views.RoutingSettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -29,68 +30,25 @@
VerticalAlignment="Center" VerticalAlignment="Center"
ClipToBounds="True" ClipToBounds="True"
Style="{StaticResource MaterialDesignToolBar}"> Style="{StaticResource MaterialDesignToolBar}">
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}"> <Button x:Name="menuRoutingAdvancedAdd2">
<MenuItem x:Name="menuRoutingAdvanced" Padding="8,0"> <StackPanel Orientation="Horizontal">
<MenuItem.Header> <materialDesign:PackIcon
<StackPanel Orientation="Horizontal"> Margin="{StaticResource MarginRight8}"
<materialDesign:PackIcon VerticalAlignment="Center"
Margin="{StaticResource MarginRight8}" Kind="Plus" />
VerticalAlignment="Center" <TextBlock Style="{StaticResource ToolbarTextBlock}" Text="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" />
Kind="Routes" /> </StackPanel>
<TextBlock Text="{x:Static resx:ResUI.menuRoutingAdvanced}" /> </Button>
</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>
<Separator /> <Separator />
<TextBlock <Button x:Name="menuRoutingAdvancedImportRules2">
Margin="{StaticResource MarginLeft8}" <StackPanel Orientation="Horizontal">
VerticalAlignment="Center" <materialDesign:PackIcon
Style="{StaticResource ToolbarTextBlock}"> Margin="{StaticResource MarginRight8}"
<Hyperlink Click="linkdomainStrategy_Click"> VerticalAlignment="Center"
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy}" /> Kind="Import" />
<materialDesign:PackIcon Kind="Link" /> <TextBlock Style="{StaticResource ToolbarTextBlock}" Text="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" />
</Hyperlink> </StackPanel>
</TextBlock> </Button>
<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}" />
</ToolBar> </ToolBar>
</ToolBarTray> </ToolBarTray>
@ -122,82 +80,145 @@
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
</StackPanel> </StackPanel>
<DockPanel> <Grid Margin="{StaticResource Margin8}" DockPanel.Dock="Top">
<TabControl x:Name="tabAdvanced"> <Grid.RowDefinitions>
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.TbRoutingTabRuleList}"> <RowDefinition Height="Auto" />
<DataGrid <RowDefinition Height="Auto" />
x:Name="lstRoutings" <RowDefinition Height="Auto" />
AutoGenerateColumns="False" </Grid.RowDefinitions>
BorderThickness="1" <Grid.ColumnDefinitions>
CanUserAddRows="False" <ColumnDefinition Width="Auto" />
CanUserResizeRows="False" <ColumnDefinition Width="Auto" />
CanUserSortColumns="False" </Grid.ColumnDefinitions>
EnableRowVirtualization="True"
GridLinesVisibility="All" <TextBlock
HeadersVisibility="Column" Grid.Row="0"
IsReadOnly="True" Grid.Column="0"
Style="{StaticResource DefDataGrid}"> Margin="{StaticResource Margin4}"
<DataGrid.ContextMenu> VerticalAlignment="Center"
<ContextMenu Style="{StaticResource DefContextMenu}"> Style="{StaticResource ToolbarTextBlock}">
<MenuItem <Hyperlink Click="linkdomainStrategy_Click">
x:Name="menuRoutingAdvancedAdd" <TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy}" />
Height="{StaticResource MenuItemHeight}" <materialDesign:PackIcon Kind="Link" />
Header="{x:Static resx:ResUI.menuRoutingAdvancedAdd}" /> </Hyperlink>
<MenuItem </TextBlock>
x:Name="menuRoutingAdvancedRemove" <ComboBox
Height="{StaticResource MenuItemHeight}" x:Name="cmbdomainStrategy"
Header="{x:Static resx:ResUI.menuRoutingAdvancedRemove}" /> Grid.Row="0"
<MenuItem Grid.Column="1"
x:Name="menuRoutingAdvancedSelectAll" Width="300"
Height="{StaticResource MenuItemHeight}" Margin="{StaticResource Margin4}"
Header="{x:Static resx:ResUI.menuSelectAll}" /> Style="{StaticResource DefComboBox}" />
<MenuItem
x:Name="menuRoutingAdvancedSetDefault" <TextBlock
Height="{StaticResource MenuItemHeight}" Grid.Row="1"
Header="{x:Static resx:ResUI.menuRoutingAdvancedSetDefault}" /> Grid.Column="0"
<Separator /> Margin="{StaticResource Margin4}"
<MenuItem VerticalAlignment="Center"
x:Name="menuRoutingAdvancedImportRules" Style="{StaticResource ToolbarTextBlock}"
Height="{StaticResource MenuItemHeight}" Text="{x:Static resx:ResUI.TbdomainMatcher}" />
Header="{x:Static resx:ResUI.menuRoutingAdvancedImportRules}" /> <ComboBox
</ContextMenu> x:Name="cmbdomainMatcher"
</DataGrid.ContextMenu> Grid.Row="1"
<DataGrid.Resources> Grid.Column="1"
<Style BasedOn="{StaticResource MaterialDesignDataGridCell}" TargetType="DataGridCell"> Width="300"
<Style.Triggers> Margin="{StaticResource Margin4}"
<DataTrigger Binding="{Binding IsActive}" Value="True"> Style="{StaticResource DefComboBox}" />
<Setter Property="Background" Value="{DynamicResource MaterialDesign.Brush.Primary.Light}" />
<Setter Property="Foreground" Value="Black" /> <TextBlock
<Setter Property="BorderBrush" Value="{DynamicResource MaterialDesign.Brush.Primary.Light}" /> Grid.Row="2"
</DataTrigger> Grid.Column="0"
</Style.Triggers> Margin="{StaticResource Margin4}"
</Style> VerticalAlignment="Center"
</DataGrid.Resources> Style="{StaticResource ToolbarTextBlock}">
<DataGrid.Columns> <Hyperlink Click="linkdomainStrategy4Singbox_Click">
<DataGridTextColumn <TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy4Singbox}" />
Width="*" <materialDesign:PackIcon Kind="Link" />
Binding="{Binding Remarks}" </Hyperlink>
Header="{x:Static resx:ResUI.LvRemarks}" /> </TextBlock>
<DataGridTextColumn <ComboBox
Width="60" x:Name="cmbdomainStrategy4Singbox"
Binding="{Binding RuleNum}" Grid.Row="2"
Header="{x:Static resx:ResUI.LvCount}" /> Grid.Column="1"
<DataGridTextColumn Width="300"
Width="60" Margin="{StaticResource Margin4}"
Binding="{Binding Sort}" Style="{StaticResource DefComboBox}" />
Header="{x:Static resx:ResUI.LvSort}" /> </Grid>
<DataGridTextColumn
Width="*" <TabControl x:Name="tabAdvanced">
Binding="{Binding Url}" <TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.TbRoutingTabRuleList}">
Header="{x:Static resx:ResUI.LvUrl}" /> <DataGrid
<DataGridTextColumn x:Name="lstRoutings"
Width="300" AutoGenerateColumns="False"
Binding="{Binding CustomIcon}" BorderThickness="1"
Header="{x:Static resx:ResUI.LvCustomIcon}" /> CanUserAddRows="False"
</DataGrid.Columns> CanUserResizeRows="False"
</DataGrid> CanUserSortColumns="False"
</TabItem> EnableRowVirtualization="True"
</TabControl> GridLinesVisibility="All"
</DockPanel> 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> </DockPanel>
</reactiveui:ReactiveWindow> </base:WindowBase>

View file

@ -92,7 +92,7 @@
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}" AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}"
DisplayMemberPath="Remarks" DisplayMemberPath="Remarks"
FontSize="{DynamicResource StdFontSize}" FontSize="{DynamicResource StdFontSize}"
Style="{StaticResource MaterialDesignFloatingHintComboBox}"> Style="{StaticResource MaterialDesignFloatingHintComboBox}">
</ComboBox> </ComboBox>
</StackPanel> </StackPanel>
@ -185,7 +185,7 @@
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}" AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}"
DisplayMemberPath="Remarks" DisplayMemberPath="Remarks"
FontSize="{DynamicResource StdFontSize}" FontSize="{DynamicResource StdFontSize}"
Style="{StaticResource MaterialDesignFilledComboBox}"> Style="{StaticResource MaterialDesignFilledComboBox}">
</ComboBox> </ComboBox>
</DockPanel> </DockPanel>
</MenuItem.Header> </MenuItem.Header>
@ -200,7 +200,7 @@
AutomationProperties.Name="{x:Static resx:ResUI.menuServers}" AutomationProperties.Name="{x:Static resx:ResUI.menuServers}"
DisplayMemberPath="Text" DisplayMemberPath="Text"
FontSize="{DynamicResource StdFontSize}" FontSize="{DynamicResource StdFontSize}"
Style="{StaticResource MaterialDesignFilledComboBox}"> Style="{StaticResource MaterialDesignFilledComboBox}">
</ComboBox> </ComboBox>
</DockPanel> </DockPanel>
</MenuItem.Header> </MenuItem.Header>

View file

@ -1,7 +1,8 @@
<reactiveui:ReactiveWindow <base:WindowBase
x:Class="v2rayN.Views.SubEditWindow" x:Class="v2rayN.Views.SubEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -315,4 +316,4 @@
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</DockPanel> </DockPanel>
</reactiveui:ReactiveWindow> </base:WindowBase>

View file

@ -1,7 +1,8 @@
<reactiveui:ReactiveWindow <base:WindowBase
x:Class="v2rayN.Views.SubSettingWindow" x:Class="v2rayN.Views.SubSettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -120,4 +121,4 @@
</DataGrid> </DataGrid>
</DockPanel> </DockPanel>
</materialDesign:DialogHost> </materialDesign:DialogHost>
</reactiveui:ReactiveWindow> </base:WindowBase>