Compare commits

...

42 Commits

Author SHA1 Message Date
2dust
8d1853e991 up 7.10.2 2025-03-04 15:11:05 +08:00
DHR60
859299c712 Add kcp DNS masquerade support (#6852)
* Add kcp DNS masquerade support

* Update V2rayConfig.cs

* Update CoreConfigV2rayService.cs

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2025-03-04 10:32:07 +08:00
2dust
7fbb0013b0 Optimizing Task Code 2025-03-03 16:57:55 +08:00
2dust
837cfbd03b Optimize UI prompts 2025-03-03 14:36:30 +08:00
2dust
cdc5d72cfa Update CoreConfigSingboxService.cs 2025-03-03 14:36:01 +08:00
DHR60
8dcf5c5b90 Add Hy2 Port hopping URI support (#6848) 2025-03-03 14:11:53 +08:00
2dust
67fe6ac3d8 Optimizing Task Code, add unified processing of scheduled tasks 2025-03-03 12:20:58 +08:00
2dust
438eaba4d5 up 7.10.1 2025-03-02 10:30:08 +08:00
2dust
3c8baa99d5 Update Directory.Packages.props 2025-03-02 10:29:43 +08:00
2dust
e70658f311 Add Hy2 Port hopping for sing-box 1.11+
https://github.com/2dust/v2rayN/issues/6772
2025-03-01 21:13:37 +08:00
2dust
2dd10cf5a1 Optimize QrcodeView 2025-03-01 19:56:52 +08:00
2dust
96781a784b git submodule update --remote 2025-03-01 15:29:54 +08:00
2dust
9748fbb076 If the update fails during the upgrade, the update will be retried. 2025-03-01 14:23:43 +08:00
2dust
aa5e4378ab Update AutoStartupHandler.cs 2025-03-01 14:14:07 +08:00
2dust
a7de149fd7 up 7.10.0 2025-02-28 09:46:43 +08:00
2dust
ae38be36f5 Checkout submodules 2025-02-27 20:53:29 +08:00
2dust
a20e989211 Update build-windows-desktop.yml 2025-02-27 20:50:52 +08:00
2dust
579f47ba0d Create GlobalHotKeys 2025-02-27 20:25:20 +08:00
2dust
24cad87954 Bug fix
https://github.com/2dust/v2rayN/issues/6812
2025-02-27 20:12:53 +08:00
2dust
84d72cd110 Use project to implement Windows global hotkey
https://github.com/2dust/GlobalHotKeys
2025-02-27 20:12:07 +08:00
2dust
c0cd46a5aa Optimize HotkeyHandler 2025-02-27 17:50:54 +08:00
2dust
98613c43ca Fix tun mtu setting 2025-02-26 20:46:31 +08:00
2dust
555960e210 Optimize and improve code 2025-02-26 17:01:57 +08:00
2dust
a18ae5582b Jump to the selected item when refreshing the server list
https://github.com/2dust/v2rayN/issues/6800
2025-02-26 16:36:36 +08:00
2dust
6d6894591c Update Global.cs 2025-02-26 15:51:47 +08:00
2dust
984b97fc14 Optimize latency and IP address information testing
If the first test fails, it will be tested again after 500ms.
2025-02-26 14:30:10 +08:00
2dust
a7f35d4495 Windows desktop version add global hotkey function 2025-02-26 10:52:36 +08:00
2dust
add92cfa7c Update desktop global hotkey setting 2025-02-25 16:14:50 +08:00
2dust
6079e76be5 Improved global hotkey setting 2025-02-25 14:26:12 +08:00
2dust
166c7cb2f5 Fix window title 2025-02-25 14:15:54 +08:00
nayeko
dbd3ca44c2 Fix Package AppImage script (#6794)
Co-authored-by: nayeko <nayeko@users.noreply.github.com>
2025-02-25 10:06:33 +08:00
dependabot[bot]
ae495dde54 Bump actions/upload-artifact from 4.6.0 to 4.6.1 (#6797)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.6.0...v4.6.1)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-25 09:46:08 +08:00
2dust
4ae25b2f34 Improved global hotkey setting 2025-02-24 19:53:38 +08:00
2dust
cdfb621c59 Update MsgView.axaml 2025-02-24 19:07:05 +08:00
2dust
29e8df7d2e When set the linux system proxy, use shell scripts instead of command lines 2025-02-24 11:02:51 +08:00
2dust
72ff947d95 Bug fix for ProxySettingOSX 2025-02-23 19:59:01 +08:00
2dust
3edaac5739 When set the macOS system proxy, use shell scripts instead of command lines 2025-02-23 19:52:13 +08:00
2dust
5777a97119 Create proxy_set_osx_sh 2025-02-23 19:41:34 +08:00
2dust
aeddbc1dcc CreateLinuxShellFile in the binConfigs folder 2025-02-23 17:19:45 +08:00
2dust
2f3e409487 Add linux bash param 2025-02-23 16:59:22 +08:00
2dust
aa133bb50b Update SpeedtestService.cs 2025-02-23 16:34:40 +08:00
2dust
3bc79a4ba1 Update 01_bug_report.yml 2025-02-23 12:05:51 +08:00
80 changed files with 1323 additions and 929 deletions

View File

@@ -3,6 +3,13 @@ description: 在提出问题前请先自行排除服务器端问题和升级到
title: "[Bug]: "
labels: ["bug"]
body:
- type: input
id: "os-version"
attributes:
label: "操作系统和版本"
description: "操作系统和版本"
validations:
required: true
- type: input
id: "expectation"
attributes:

View File

@@ -27,6 +27,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v4.3.0
@@ -42,7 +45,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 --self-contained=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.6.1
with:
name: v2rayN-linux
path: |
@@ -68,9 +71,8 @@ jobs:
- name: Package AppImage
if: github.event.inputs.release_tag != ''
run: |
chmod 755 package-appimage.sh
./package-appimage.sh $OutputArch $OutputPath64 ${{ github.event.inputs.release_tag }}
./package-appimage.sh $OutputArchArm $OutputPathArm64 ${{ github.event.inputs.release_tag }}
chmod a+x package-appimage.sh
./package-appimage.sh
- name: Upload AppImage to release
uses: svenstaro/upload-release-action@v2

View File

@@ -27,6 +27,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v4.3.0
@@ -42,7 +45,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 --self-contained=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.6.1
with:
name: v2rayN-macos
path: |

View File

@@ -27,6 +27,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v4.3.0
@@ -42,7 +45,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.6.1
with:
name: v2rayN-windows-desktop
path: |

View File

@@ -46,7 +46,7 @@ jobs:
- name: Upload build artifacts
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.6.1
with:
name: v2rayN-windows
path: |

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "v2rayN/GlobalHotKeys"]
path = v2rayN/GlobalHotKeys
url = https://github.com/2dust/GlobalHotKeys

View File

@@ -1,71 +1,14 @@
#!/bin/bash
Arch="$1"
OutputPath="$2"
Version="$3"
FileName="v2rayN-${Arch}.zip"
wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/$FileName"
7z x $FileName -aoa
cp -rf v2rayN-${Arch}/* $OutputPath
PackagePath="v2rayN-Package-${Arch}"
mkdir -p "${PackagePath}/AppDir/opt"
cp -rf $OutputPath "${PackagePath}/AppDir/opt/v2rayN"
echo "When this file exists, app will not store configs under this folder" >"${PackagePath}/AppDir/opt/v2rayN/NotStoreConfigHere.txt"
if [ $Arch = "linux-64" ]; then
Arch2="x86_64"
Arch3="amd64"
else
Arch2="aarch64"
Arch3="arm64"
fi
echo $Arch2
# basic
cat >"${PackagePath}/AppDir/AppRun" <<-EOF
#!/bin/sh
HERE="\$(dirname "\$(readlink -f "\${0}")")"
export PATH="\${HERE}"/opt/v2rayN/:"\${PATH}"
export LD_LIBRARY_PATH="\${HERE}"/opt/v2rayN/:"\${LD_LIBRARY_PATH}"
exec "\${HERE}/opt/v2rayN/v2rayN" \$@
EOF
cat >"${PackagePath}/AppDir/v2rayN.desktop" <<-EOF
[Desktop Entry]
Name=v2rayN
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
Exec=v2rayN
Icon=v2rayN
Terminal=false
Type=Application
Categories=Network;
EOF
sudo cp "${PackagePath}/AppDir/opt/v2rayN/v2rayN.png" "${PackagePath}/AppDir/v2rayN.png"
sudo dpkg --add-architecture ${Arch3}
mkdir deb_folder
cd deb_folder
apt download libicu74:${Arch3}
apt download libfontconfig1:${Arch3} || true
apt download libfontconfig:${Arch3} || true
mkdir ../output_folder
for deb in *.deb; do
dpkg-deb -x "$deb" ../output_folder/
done
cd ..
find output_folder -type f -name "*.so*" -exec cp {} ${PackagePath}/AppDir/opt/v2rayN/ \;
find output_folder -type l -name "*.so*" -exec cp {} ${PackagePath}/AppDir/opt/v2rayN/ \;
rm -rf deb_folder output_folder
sudo chmod 0755 "${PackagePath}/AppDir/opt/v2rayN/v2rayN"
sudo chmod 0755 "${PackagePath}/AppDir/AppRun"
# desktop && PATH
wget "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
chmod a+x appimagetool-x86_64.AppImage
sudo apt update -y
sudo apt install -y libfuse2
sudo ./appimagetool-x86_64.AppImage "${PackagePath}/AppDir"
sudo mv "v2rayN-${Arch2}.AppImage" "v2rayN-${Arch}.AppImage"
wget -O pkg2appimage https://github.com/AppImageCommunity/pkg2appimage/releases/download/continuous/pkg2appimage-1eceb30-x86_64.AppImage
chmod a+x pkg2appimage
export AppImageOutputArch=$OutputArch
export OutputPath=$OutputPath64
./pkg2appimage ./pkg2appimage.yml
mv out/*.AppImage v2rayN-${AppImageOutputArch}.AppImage
export AppImageOutputArch=$OutputArchArm
export OutputPath=$OutputPathArm64
./pkg2appimage ./pkg2appimage.yml
mv out/*.AppImage v2rayN-${AppImageOutputArch}.AppImage

37
pkg2appimage.yml Normal file
View File

@@ -0,0 +1,37 @@
app: v2rayN
binpatch: true
ingredients:
script:
- export FileName="v2rayN-${AppImageOutputArch}.zip"
- wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/${FileName}"
- 7z x $FileName -aoa
- cp -rf v2rayN-${AppImageOutputArch}/* $OutputPath
script:
- mkdir -p usr/bin usr/lib
- cp -rf $OutputPath usr/lib/v2rayN
- echo "When this file exists, app will not store configs under this folder" > usr/lib/v2rayN/NotStoreConfigHere.txt
- ln -sf usr/lib/v2rayN/v2rayN usr/bin/v2rayN
- chmod a+x usr/lib/v2rayN/v2rayN
- find usr -type f -exec sh -c 'file "{}" | grep -qi "executable" && chmod +x "{}"' \;
- install -Dm644 usr/lib/v2rayN/v2rayN.png v2rayN.png
- install -Dm644 usr/lib/v2rayN/v2rayN.png usr/share/pixmaps/v2rayN.png
- cat > v2rayN.desktop <<EOF
- [Desktop Entry]
- Name=v2rayN
- Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
- Exec=v2rayN
- Icon=v2rayN
- Terminal=false
- Type=Application
- Categories=Network;
- EOF
- install -Dm644 v2rayN.desktop usr/share/applications/v2rayN.desktop
- cat > AppRun <<\EOF
- #!/bin/sh
- HERE="$(dirname "$(readlink -f "${0}")")"
- cd ${HERE}/usr/lib/v2rayN
- exec ${HERE}/usr/lib/v2rayN/v2rayN $@
- EOF
- chmod a+x AppRun

View File

@@ -1,29 +1,39 @@
namespace AmazTool
namespace AmazTool
{
internal static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
private static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine(Resx.Resource.Guidelines);
Thread.Sleep(5000);
Utils.WriteLine(Resx.Resource.Guidelines);
Utils.Waiting(5);
return;
}
var argData = Uri.UnescapeDataString(string.Join(" ", args));
if (argData.Equals("rebootas"))
{
Thread.Sleep(1000);
Utils.Waiting(1);
Utils.StartV2RayN();
return;
}
var tryTimes = 0;
UpgradeApp.Init();
while (tryTimes++ < 3)
{
if (!UpgradeApp.Upgrade(argData))
{
continue;
}
UpgradeApp.Upgrade(argData);
Utils.WriteLine(Resx.Resource.Restartv2rayN);
Utils.Waiting(3);
Utils.StartV2RayN();
break;
}
}
}
}
}

View File

@@ -6,19 +6,99 @@ namespace AmazTool
{
internal class UpgradeApp
{
public static void Upgrade(string fileName)
public static bool Upgrade(string fileName)
{
Console.WriteLine($"{Resx.Resource.StartUnzipping}\n{fileName}");
Utils.Waiting(5);
Utils.WriteLine($"{Resx.Resource.StartUnzipping}\n{fileName}");
if (!File.Exists(fileName))
{
Console.WriteLine(Resx.Resource.UpgradeFileNotFound);
return;
Utils.WriteLine(Resx.Resource.UpgradeFileNotFound);
return false;
}
Console.WriteLine(Resx.Resource.TryTerminateProcess);
Utils.Waiting(5);
KillV2rayN();
Utils.WriteLine(Resx.Resource.StartUnzipping);
StringBuilder sb = new();
try
{
var splitKey = "/";
using var archive = ZipFile.OpenRead(fileName);
foreach (var entry in archive.Entries)
{
try
{
if (entry.Length == 0)
{
continue;
}
Utils.WriteLine(entry.FullName);
var lst = entry.FullName.Split(splitKey);
if (lst.Length == 1)
{
continue;
}
var fullName = string.Join(splitKey, lst[1..lst.Length]);
var entryOutputPath = Utils.GetPath(fullName);
Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!);
//In the bin folder, if the file already exists, it will be skipped
if (fullName.StartsWith("bin") && File.Exists(entryOutputPath))
{
continue;
}
entry.ExtractToFile(entryOutputPath, true);
Utils.WriteLine(entryOutputPath);
}
catch (Exception ex)
{
sb.Append(ex.Message);
sb.Append(ex.StackTrace);
}
}
}
catch (Exception ex)
{
sb.Append(Resx.Resource.FailedUpgrade + ex.StackTrace);
}
if (sb.Length <= 0)
{
return true;
}
Utils.WriteLine(sb.ToString());
Utils.WriteLine(Resx.Resource.FailedUpgrade);
return false;
}
public static bool Init()
{
//Process temporary files generated by the last update
var files = Directory.GetFiles(Utils.GetPath(""), "*.tmp");
foreach (var file in files)
{
if (file.Contains(Utils.AmazTool))
{
File.Delete(file);
}
}
var destFileName = $"{Utils.GetExePath()}{Guid.NewGuid().ToString("N")[..8]}.tmp";
File.Move(Utils.GetExePath(), destFileName);
return true;
}
private static bool KillV2rayN()
{
Utils.WriteLine(Resx.Resource.TryTerminateProcess);
try
{
var existing = Process.GetProcessesByName(Utils.V2rayN);
@@ -35,71 +115,10 @@ namespace AmazTool
catch (Exception ex)
{
// Access may be denied without admin right. The user may not be an administrator.
Console.WriteLine(Resx.Resource.FailedTerminateProcess + ex.StackTrace);
Utils.WriteLine(Resx.Resource.FailedTerminateProcess + ex.StackTrace);
}
Console.WriteLine(Resx.Resource.StartUnzipping);
StringBuilder sb = new();
try
{
var thisAppOldFile = $"{Utils.GetExePath()}.tmp";
File.Delete(thisAppOldFile);
var splitKey = "/";
using var archive = ZipFile.OpenRead(fileName);
foreach (var entry in archive.Entries)
{
try
{
if (entry.Length == 0)
{
continue;
}
Console.WriteLine(entry.FullName);
var lst = entry.FullName.Split(splitKey);
if (lst.Length == 1)
continue;
var fullName = string.Join(splitKey, lst[1..lst.Length]);
if (string.Equals(Utils.GetExePath(), Utils.GetPath(fullName), StringComparison.OrdinalIgnoreCase))
{
File.Move(Utils.GetExePath(), thisAppOldFile);
}
var entryOutputPath = Utils.GetPath(fullName);
Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!);
//In the bin folder, if the file already exists, it will be skipped
if (fullName.StartsWith("bin") && File.Exists(entryOutputPath))
{
continue;
}
entry.ExtractToFile(entryOutputPath, true);
Console.WriteLine(entryOutputPath);
}
catch (Exception ex)
{
sb.Append(ex.StackTrace);
}
}
}
catch (Exception ex)
{
Console.WriteLine(Resx.Resource.FailedUpgrade + ex.StackTrace);
//return;
}
if (sb.Length > 0)
{
Console.WriteLine(Resx.Resource.FailedUpgrade + sb.ToString());
//return;
}
Console.WriteLine(Resx.Resource.Restartv2rayN);
Utils.Waiting(2);
Utils.StartV2RayN();
return true;
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
namespace AmazTool
{
@@ -14,9 +14,9 @@ namespace AmazTool
return AppDomain.CurrentDomain.BaseDirectory;
}
public static string GetPath(string fileName)
public static string GetPath(string? fileName)
{
string startupPath = StartupPath();
var startupPath = StartupPath();
if (string.IsNullOrEmpty(fileName))
{
return startupPath;
@@ -25,6 +25,7 @@ namespace AmazTool
}
public static string V2rayN => "v2rayN";
public static string AmazTool => "AmazTool";
public static void StartV2RayN()
{
@@ -44,9 +45,14 @@ namespace AmazTool
{
for (var i = second; i > 0; i--)
{
Console.WriteLine(i);
Utils.WriteLine(i);
Thread.Sleep(1000);
}
}
public static void WriteLine(object obj)
{
Console.WriteLine(obj);
}
}
}
}

View File

@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.9.3</Version>
<Version>7.10.2</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -5,24 +5,24 @@
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.2.4" />
<PackageVersion Include="Avalonia.Desktop" Version="11.2.4" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.2.4" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.2.4" />
<PackageVersion Include="CliWrap" Version="3.8.0" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.2.5" />
<PackageVersion Include="Avalonia.Desktop" Version="11.2.5" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.2.5" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.2.5" />
<PackageVersion Include="CliWrap" Version="3.8.1" />
<PackageVersion Include="Downloader" Version="3.3.3" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.2.0" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
<PackageVersion Include="MaterialDesignThemes" Version="5.2.1" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" />
<PackageVersion Include="QRCoder" Version="1.6.0" />
<PackageVersion Include="ReactiveUI" Version="20.1.63" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="20.1.63" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.4" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.4" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.5" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.5" />
<PackageVersion Include="Splat.NLog" Version="15.3.1" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.11.0" />
<PackageVersion Include="TaskScheduler" Version="2.12.0" />
<PackageVersion Include="WebDav.Client" Version="2.8.0" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />

1
v2rayN/GlobalHotKeys Submodule

Submodule v2rayN/GlobalHotKeys added at b3b635ef46

View File

@@ -20,9 +20,13 @@ public static class ProcUtils
try
{
if (fileName.Contains(' '))
{
fileName = fileName.AppendQuotes();
}
if (arguments.Contains(' '))
{
arguments = arguments.AppendQuotes();
}
Process proc = new()
{

View File

@@ -857,7 +857,7 @@ namespace ServiceLib.Common
private static async Task<string?> GetLinuxUserId()
{
var arg = new List<string>() { "-c", "id -u" };
return await GetCliWrapOutput("/bin/bash", arg);
return await GetCliWrapOutput(Global.LinuxBash, arg);
}
public static async Task<string?> SetLinuxChmod(string? fileName)
@@ -868,14 +868,14 @@ namespace ServiceLib.Common
fileName = fileName.AppendQuotes();
//File.SetUnixFileMode(fileName, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
var arg = new List<string>() { "-c", $"chmod +x {fileName}" };
return await GetCliWrapOutput("/bin/bash", arg);
return await GetCliWrapOutput(Global.LinuxBash, arg);
}
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 : family" };
return await GetCliWrapOutput("/bin/bash", arg);
return await GetCliWrapOutput(Global.LinuxBash, arg);
}
public static string? GetHomePath()
@@ -885,12 +885,6 @@ namespace ServiceLib.Common
: Environment.GetEnvironmentVariable("HOME");
}
public static async Task<string?> GetListNetworkServices()
{
var arg = new List<string>() { "-c", $"networksetup -listallnetworkservices" };
return await GetCliWrapOutput("/bin/bash", arg);
}
#endregion Platform
}
}

View File

@@ -38,6 +38,8 @@ namespace ServiceLib
public const string ClashTunYaml = NamespaceSample + "clash_tun_yaml";
public const string LinuxAutostartConfig = NamespaceSample + "linux_autostart_config";
public const string PacFileName = NamespaceSample + "pac";
public const string ProxySetOSXShellFileName = NamespaceSample + "proxy_set_osx_sh";
public const string ProxySetLinuxShellFileName = NamespaceSample + "proxy_set_linux_sh";
public const string DefaultSecurity = "auto";
public const string DefaultNetwork = "tcp";
@@ -67,10 +69,11 @@ namespace ServiceLib
public const int MinFontSize = 8;
public const string RebootAs = "rebootas";
public const string AvaAssets = "avares://v2rayN/Assets/";
public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA";
public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA_V2";
public const string V2RayLocalAsset = "V2RAY_LOCATION_ASSET";
public const string XrayLocalAsset = "XRAY_LOCATION_ASSET";
public const int SpeedTestPageSize = 1000;
public const string LinuxBash = "/bin/bash";
public static readonly List<string> IEProxyProtocols =
[
@@ -262,7 +265,8 @@ namespace ServiceLib
"utp",
"wechat-video",
"dtls",
"wireguard"
"wireguard",
"dns"
];
public static readonly List<string> CoreTypes =
@@ -426,12 +430,12 @@ namespace ServiceLib
"fakedns+others"
];
public static readonly List<string> TunMtus =
public static readonly List<int> TunMtus =
[
"1280",
"1408",
"1500",
"9000"
1280,
1408,
1500,
9000
];
public static readonly List<string> TunStacks =

View File

@@ -80,8 +80,6 @@ namespace ServiceLib.Handler
Logging.SaveLog($"v2rayN start up | {Utils.GetRuntimeInfo()}");
Logging.LoggingEnabled(_config.GuiItem.EnableLog);
ClearExpiredFiles();
return true;
}
@@ -92,16 +90,6 @@ namespace ServiceLib.Handler
return true;
}
private void ClearExpiredFiles()
{
Task.Run(() =>
{
FileManager.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1));
FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
FileManager.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1));
});
}
#endregion Init
#region Config

View File

@@ -109,7 +109,7 @@ namespace ServiceLib.Handler
task.Settings.RunOnlyIfIdle = false;
task.Settings.IdleSettings.StopOnIdleEnd = false;
task.Settings.ExecutionTimeLimit = TimeSpan.Zero;
task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(10) });
task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(30) });
task.Principal.RunLevel = Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest;
task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName)));
@@ -177,7 +177,7 @@ namespace ServiceLib.Handler
if (File.Exists(launchAgentPath))
{
var args = new[] { "-c", $"launchctl unload -w \"{launchAgentPath}\"" };
await Utils.GetCliWrapOutput("/bin/bash", args);
await Utils.GetCliWrapOutput(Global.LinuxBash, args);
File.Delete(launchAgentPath);
}
@@ -197,7 +197,7 @@ namespace ServiceLib.Handler
await File.WriteAllTextAsync(launchAgentPath, plistContent);
var args = new[] { "-c", $"launchctl load -w \"{launchAgentPath}\"" };
await Utils.GetCliWrapOutput("/bin/bash", args);
await Utils.GetCliWrapOutput(Global.LinuxBash, args);
}
catch (Exception ex)
{

View File

@@ -7,9 +7,9 @@ namespace ServiceLib.Handler
private static readonly Lazy<ClashApiHandler> instance = new(() => new());
public static ClashApiHandler Instance => instance.Value;
private static readonly string _tag = "ClashApiHandler";
private Dictionary<string, ProxiesItem>? _proxies;
public Dictionary<string, object> ProfileContent { get; set; }
private static readonly string _tag = "ClashApiHandler";
public async Task<Tuple<ClashProxies, ClashProviders>?> GetClashProxiesAsync(Config config)
{
@@ -54,7 +54,7 @@ namespace ServiceLib.Handler
return;
}
lstProxy = new List<ClashProxyModel>();
foreach (KeyValuePair<string, ProxiesItem> kv in _proxies)
foreach (var kv in _proxies)
{
if (Global.notAllowTestType.Contains(kv.Value.type.ToLower()))
{

View File

@@ -158,6 +158,7 @@ namespace ServiceLib.Handler
Length = "100-200",
Interval = "10-20"
};
config.GlobalHotkeys ??= new();
if (config.SystemProxyItem.SystemProxyExceptions.IsNullOrEmpty())
{
@@ -216,6 +217,7 @@ namespace ServiceLib.Handler
item.Remarks = profileItem.Remarks;
item.Address = profileItem.Address;
item.Port = profileItem.Port;
item.Ports = profileItem.Ports;
item.Id = profileItem.Id;
item.AlterId = profileItem.AlterId;
@@ -724,7 +726,7 @@ namespace ServiceLib.Handler
profileItem.Network = string.Empty;
if (profileItem.ShortId.IsNullOrEmpty())
{
profileItem.ShortId = Global.TunMtus.FirstOrDefault();
profileItem.ShortId = Global.TunMtus.First().ToString();
}
if (profileItem.Id.IsNullOrEmpty())

View File

@@ -253,7 +253,7 @@ namespace ServiceLib.Handler
StartInfo = new()
{
FileName = fileName,
Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath) : configPath),
Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath),
WorkingDirectory = Utils.GetBinConfigPath(),
UseShellExecute = false,
RedirectStandardOutput = displayLog,
@@ -380,7 +380,7 @@ namespace ServiceLib.Handler
private async Task<string> CreateLinuxShellFile(string cmdLine, string fileName)
{
//Shell scripts
var shFilePath = Utils.GetBinPath(AppHandler.Instance.IsAdministrator ? "root_" + fileName : fileName);
var shFilePath = Utils.GetBinConfigPath(AppHandler.Instance.IsAdministrator ? "root_" + fileName : fileName);
File.Delete(shFilePath);
var sb = new StringBuilder();
sb.AppendLine("#!/bin/sh");

View File

@@ -24,6 +24,8 @@ namespace ServiceLib.Handler.Fmt
item.Path = Utils.UrlDecode(query["obfs-password"] ?? "");
item.AllowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false";
item.Ports = Utils.UrlDecode(query["mport"] ?? "").Replace('-', ':');
return item;
}
@@ -53,6 +55,10 @@ namespace ServiceLib.Handler.Fmt
dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path));
}
dicQuery.Add("insecure", item.AllowInsecure.ToLower() == "true" ? "1" : "0");
if (Utils.IsNotEmpty(item.Ports))
{
dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-')));
}
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, dicQuery, remark);
}

View File

@@ -20,14 +20,6 @@ namespace ServiceLib.Handler
public async Task Init()
{
await InitData();
_ = Task.Run(async () =>
{
while (true)
{
await Task.Delay(1000 * 600);
await SaveQueueIndexIds();
}
});
}
public async Task<ConcurrentBag<ProfileExItem>> GetProfileExs()

View File

@@ -1,202 +1,33 @@
namespace ServiceLib.Handler.SysProxy
namespace ServiceLib.Handler.SysProxy
{
public class ProxySettingLinux
{
private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh";
public static async Task SetProxy(string host, int port, string exceptions)
{
var lstCmd = GetSetCmds(host, port, exceptions);
await ExecCmd(lstCmd);
List<string> args = ["manual", host, port.ToString(), exceptions];
await ExecCmd(args);
}
public static async Task UnsetProxy()
{
var lstCmd = GetUnsetCmds();
await ExecCmd(lstCmd);
List<string> args = ["none"];
await ExecCmd(args);
}
private static async Task ExecCmd(List<CmdItem> lstCmd)
private static async Task ExecCmd(List<string> args)
{
foreach (var cmd in lstCmd)
var fileName = Utils.GetBinConfigPath(_proxySetFileName);
if (!File.Exists(fileName))
{
if (cmd is null || cmd.Cmd.IsNullOrEmpty() || cmd.Arguments is null)
{
continue;
}
await Task.Delay(10);
await Utils.GetCliWrapOutput(cmd.Cmd, cmd.Arguments);
}
}
var contents = EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName);
await File.AppendAllTextAsync(fileName, contents);
private static List<CmdItem> GetSetCmds(string host, int port, string exceptions)
{
var isKde = IsKde(out var configDir);
List<string> lstType = ["", "http", "https", "socks", "ftp"];
List<CmdItem> lstCmd = [];
//GNOME
foreach (var type in lstType)
{
lstCmd.AddRange(GetSetCmd4Gnome(type, host, port));
}
if (exceptions.IsNotEmpty())
{
lstCmd.AddRange(GetSetCmd4Gnome("exceptions", exceptions, 0));
await Utils.SetLinuxChmod(fileName);
}
if (isKde)
{
foreach (var type in lstType)
{
lstCmd.AddRange(GetSetCmd4Kde(type, host, port, configDir));
}
if (exceptions.IsNotEmpty())
{
lstCmd.AddRange(GetSetCmd4Kde("exceptions", exceptions, 0, configDir));
}
// Notify system to reload
lstCmd.Add(new CmdItem()
{
Cmd = "dbus-send",
Arguments = ["--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''"]
});
}
return lstCmd;
}
private static List<CmdItem> GetUnsetCmds()
{
var isKde = IsKde(out var configDir);
List<CmdItem> lstCmd = [];
//GNOME
lstCmd.Add(new CmdItem()
{
Cmd = "gsettings",
Arguments = ["set", "org.gnome.system.proxy", "mode", "none"]
});
if (isKde)
{
lstCmd.Add(new CmdItem()
{
Cmd = GetKdeVersion(),
Arguments = ["--file", $"{configDir}/kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0"]
});
// Notify system to reload
lstCmd.Add(new CmdItem()
{
Cmd = "dbus-send",
Arguments = ["--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''"]
});
}
return lstCmd;
}
private static List<CmdItem> GetSetCmd4Kde(string type, string host, int port, string configDir)
{
List<CmdItem> lstCmd = [];
var cmd = GetKdeVersion();
if (type.IsNullOrEmpty())
{
lstCmd.Add(new()
{
Cmd = cmd,
Arguments = ["--file", $"{configDir}/kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1"]
});
}
else if (type == "exceptions")
{
lstCmd.Add(new()
{
Cmd = cmd,
Arguments = ["--file", $"{configDir}/kioslaverc", "--group", "Proxy Settings", "--key", "NoProxyFor", host]
});
}
else
{
var type2 = type.Equals("https") ? "http" : type;
lstCmd.Add(new CmdItem()
{
Cmd = cmd,
Arguments = ["--file", $"{configDir}/kioslaverc", "--group", "Proxy Settings", "--key", $"{type}Proxy", $"{type2}://{host}:{port}"]
});
}
return lstCmd;
}
private static List<CmdItem> GetSetCmd4Gnome(string type, string host, int port)
{
List<CmdItem> lstCmd = [];
if (type.IsNullOrEmpty())
{
lstCmd.Add(new()
{
Cmd = "gsettings",
Arguments = ["set", "org.gnome.system.proxy", "mode", "manual"]
});
}
else if (type == "exceptions")
{
lstCmd.Add(new()
{
Cmd = "gsettings",
Arguments = ["set", $"org.gnome.system.proxy", "ignore-hosts", JsonUtils.Serialize(host.Split(','), false)]
});
}
else
{
lstCmd.Add(new()
{
Cmd = "gsettings",
Arguments = ["set", $"org.gnome.system.proxy.{type}", "host", host]
});
lstCmd.Add(new()
{
Cmd = "gsettings",
Arguments = ["set", $"org.gnome.system.proxy.{type}", "port", $"{port}"]
});
}
return lstCmd;
}
private static bool IsKde(out string configDir)
{
configDir = "/home";
var desktop = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP");
var desktop2 = Environment.GetEnvironmentVariable("XDG_SESSION_DESKTOP");
var isKde = string.Equals(desktop, "KDE", StringComparison.OrdinalIgnoreCase)
|| string.Equals(desktop, "plasma", StringComparison.OrdinalIgnoreCase)
|| string.Equals(desktop2, "KDE", StringComparison.OrdinalIgnoreCase)
|| string.Equals(desktop2, "plasma", StringComparison.OrdinalIgnoreCase);
if (isKde)
{
var homeDir = Environment.GetEnvironmentVariable("HOME");
if (homeDir != null)
{
configDir = Path.Combine(homeDir, ".config");
}
}
return isKde;
}
private static string GetKdeVersion()
{
var ver = Environment.GetEnvironmentVariable("KDE_SESSION_VERSION") ?? "0";
return ver switch
{
"6" => "kwriteconfig6",
_ => "kwriteconfig5"
};
await Utils.GetCliWrapOutput(fileName, args);
}
}
}
}

View File

@@ -1,101 +1,38 @@
namespace ServiceLib.Handler.SysProxy
namespace ServiceLib.Handler.SysProxy
{
public class ProxySettingOSX
{
/// <summary>
/// 应用接口类型
/// </summary>
private static readonly List<string> LstInterface = ["Ethernet", "Wi-Fi", "Thunderbolt Bridge", "USB 10/100/1000 LAN"];
/// <summary>
/// 代理类型,对应 http,https,socks
/// </summary>
private static readonly List<string> LstTypes = ["setwebproxy", "setsecurewebproxy", "setsocksfirewallproxy"];
private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh";
public static async Task SetProxy(string host, int port, string exceptions)
{
var lstInterface = await GetListNetworkServices();
var lstCmd = GetSetCmds(lstInterface, host, port, exceptions);
await ExecCmd(lstCmd);
List<string> args = ["set", host, port.ToString()];
if (exceptions.IsNotEmpty())
{
args.AddRange(exceptions.Split(','));
}
await ExecCmd(args);
}
public static async Task UnsetProxy()
{
var lstInterface = await GetListNetworkServices();
var lstCmd = GetUnsetCmds(lstInterface);
await ExecCmd(lstCmd);
List<string> args = ["clear"];
await ExecCmd(args);
}
private static async Task ExecCmd(List<CmdItem> lstCmd)
private static async Task ExecCmd(List<string> args)
{
foreach (var cmd in lstCmd)
var fileName = Utils.GetBinConfigPath(_proxySetFileName);
if (!File.Exists(fileName))
{
if (cmd is null || cmd.Cmd.IsNullOrEmpty() || cmd.Arguments is null)
{
continue;
}
var contents = EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName);
await File.AppendAllTextAsync(fileName, contents);
await Task.Delay(10);
await Utils.GetCliWrapOutput(cmd.Cmd, cmd.Arguments);
}
}
private static List<CmdItem> GetSetCmds(List<string> lstInterface, string host, int port, string exceptions)
{
List<CmdItem> lstCmd = [];
foreach (var interf in lstInterface)
{
foreach (var type in LstTypes)
{
lstCmd.Add(new CmdItem()
{
Cmd = "networksetup",
Arguments = [$"-{type}", interf, host, port.ToString()]
});
}
if (exceptions.IsNotEmpty())
{
List<string> args = [$"-setproxybypassdomains", interf];
args.AddRange(exceptions.Split(','));
lstCmd.Add(new CmdItem()
{
Cmd = "networksetup",
Arguments = args
});
}
await Utils.SetLinuxChmod(fileName);
}
return lstCmd;
}
private static List<CmdItem> GetUnsetCmds(List<string> lstInterface)
{
List<CmdItem> lstCmd = [];
foreach (var interf in lstInterface)
{
foreach (var type in LstTypes)
{
lstCmd.Add(new CmdItem()
{
Cmd = "networksetup",
Arguments = [$"-{type}state", interf, "off"]
});
}
}
return lstCmd;
}
public static async Task<List<string>> GetListNetworkServices()
{
var services = await Utils.GetListNetworkServices();
if (services.IsNullOrEmpty())
{
return LstInterface;
}
var lst = services.Split(Environment.NewLine).Where(t => t.Length > 0 && t.Contains('*') == false);
return lst.ToList();
await Utils.GetCliWrapOutput(fileName, args);
}
}
}
}

View File

@@ -1,4 +1,4 @@
namespace ServiceLib.Handler
namespace ServiceLib.Handler
{
public class TaskHandler
{
@@ -7,66 +7,92 @@
public void RegUpdateTask(Config config, Action<bool, string> updateFunc)
{
Task.Run(() => UpdateTaskRunSubscription(config, updateFunc));
Task.Run(() => UpdateTaskRunGeo(config, updateFunc));
Task.Run(() => ScheduledTasks(config, updateFunc));
}
private async Task ScheduledTasks(Config config, Action<bool, string> updateFunc)
{
Logging.SaveLog("Setup Scheduled Tasks");
var numOfExecuted = 1;
while (true)
{
//1 minute
await Task.Delay(1000 * 60);
//Execute once 1 minute
await UpdateTaskRunSubscription(config, updateFunc);
//Execute once 20 minute
if (numOfExecuted % 20 == 0)
{
//Logging.SaveLog("Execute save config");
await ConfigHandler.SaveConfig(config);
await ProfileExHandler.Instance.SaveTo();
}
//Execute once 1 hour
if (numOfExecuted % 60 == 0)
{
//Logging.SaveLog("Execute delete expired files");
FileManager.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1));
FileManager.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1));
FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
//Check once 1 hour
await UpdateTaskRunGeo(config, numOfExecuted / 60, updateFunc);
}
numOfExecuted++;
}
}
private async Task UpdateTaskRunSubscription(Config config, Action<bool, string> updateFunc)
{
await Task.Delay(60000);
Logging.SaveLog("UpdateTaskRunSubscription");
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
var lstSubs = (await AppHandler.Instance.SubItems())?
.Where(t => t.AutoUpdateInterval > 0)
.Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60)
.ToList();
var updateHandle = new UpdateService();
while (true)
if (lstSubs is not { Count: > 0 })
{
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
var lstSubs = (await AppHandler.Instance.SubItems())
.Where(t => t.AutoUpdateInterval > 0)
.Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60)
.ToList();
return;
}
foreach (var item in lstSubs)
Logging.SaveLog("Execute update subscription");
var updateHandle = new UpdateService();
foreach (var item in lstSubs)
{
await updateHandle.UpdateSubscriptionProcess(config, item.Id, true, (bool success, string msg) =>
{
await updateHandle.UpdateSubscriptionProcess(config, item.Id, true, (bool success, string msg) =>
{
updateFunc?.Invoke(success, msg);
if (success)
Logging.SaveLog("subscription" + msg);
});
item.UpdateTime = updateTime;
await ConfigHandler.AddSubItem(config, item);
await Task.Delay(5000);
}
await Task.Delay(60000);
updateFunc?.Invoke(success, msg);
if (success)
{
Logging.SaveLog($"Update subscription end. {msg}");
}
});
item.UpdateTime = updateTime;
await ConfigHandler.AddSubItem(config, item);
await Task.Delay(1000);
}
}
private async Task UpdateTaskRunGeo(Config config, Action<bool, string> updateFunc)
private async Task UpdateTaskRunGeo(Config config, int hours, Action<bool, string> updateFunc)
{
var autoUpdateGeoTime = DateTime.Now;
//await Task.Delay(1000 * 120);
Logging.SaveLog("UpdateTaskRunGeo");
var updateHandle = new UpdateService();
while (true)
if (config.GuiItem.AutoUpdateInterval > 0 && hours > 0 && hours % config.GuiItem.AutoUpdateInterval == 0)
{
await Task.Delay(1000 * 3600);
Logging.SaveLog("Execute update geo files");
var dtNow = DateTime.Now;
if (config.GuiItem.AutoUpdateInterval > 0)
var updateHandle = new UpdateService();
await updateHandle.UpdateGeoFileAll(config, (bool success, string msg) =>
{
if ((dtNow - autoUpdateGeoTime).Hours % config.GuiItem.AutoUpdateInterval == 0)
{
await updateHandle.UpdateGeoFileAll(config, (bool success, string msg) =>
{
updateFunc?.Invoke(false, msg);
});
autoUpdateGeoTime = dtNow;
}
}
updateFunc?.Invoke(false, msg);
});
}
}
}
}
}

View File

@@ -197,6 +197,7 @@ namespace ServiceLib.Models
{
public int UpMbps { get; set; }
public int DownMbps { get; set; }
public int HopInterval { get; set; } = 30;
}
[Serializable]

View File

@@ -1,4 +1,4 @@
using SQLite;
using SQLite;
namespace ServiceLib.Models
{
@@ -64,11 +64,11 @@ namespace ServiceLib.Models
[PrimaryKey]
public string IndexId { get; set; }
public EConfigType ConfigType { get; set; }
public int ConfigVersion { get; set; }
public string Address { get; set; }
public int Port { get; set; }
public string Ports { get; set; }
public string Id { get; set; }
public int AlterId { get; set; }
public string Security { get; set; }
@@ -93,4 +93,4 @@ namespace ServiceLib.Models
public string SpiderX { get; set; }
public string Extra { get; set; }
}
}
}

View File

@@ -1,4 +1,4 @@
namespace ServiceLib.Models
namespace ServiceLib.Models
{
public class SingboxConfig
{
@@ -101,21 +101,23 @@
public string tag { get; set; }
public string? server { get; set; }
public int? server_port { get; set; }
public string uuid { get; set; }
public string security { get; set; }
public List<string>? server_ports { get; set; }
public string? uuid { get; set; }
public string? security { get; set; }
public int? alter_id { get; set; }
public string flow { get; set; }
public string? flow { get; set; }
public string? hop_interval { get; set; }
public int? up_mbps { get; set; }
public int? down_mbps { get; set; }
public string auth_str { get; set; }
public string? auth_str { get; set; }
public int? recv_window_conn { get; set; }
public int? recv_window { get; set; }
public bool? disable_mtu_discovery { get; set; }
public string? detour { get; set; }
public string method { get; set; }
public string username { get; set; }
public string password { get; set; }
public string congestion_control { get; set; }
public string? method { get; set; }
public string? username { get; set; }
public string? password { get; set; }
public string? congestion_control { get; set; }
public string? version { get; set; }
public string? network { get; set; }
public string? packet_encoding { get; set; }
@@ -252,4 +254,4 @@
public string? download_detour { get; set; }
public string? update_interval { get; set; }
}
}
}

View File

@@ -291,6 +291,8 @@ namespace ServiceLib.Models
public object request { get; set; }
public object response { get; set; }
public string? domain { get; set; }
}
public class KcpSettings4Ray

View File

@@ -2483,7 +2483,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Address(Ip,Ipv6) 的本地化字符串。
/// 查找类似 Address(Ipv4,Ipv6) 的本地化字符串。
/// </summary>
public static string TbLocalAddress {
get {
@@ -2536,6 +2536,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Server port range 的本地化字符串。
/// </summary>
public static string TbPorts7 {
get {
return ResourceManager.GetString("TbPorts7", resourceCulture);
}
}
/// <summary>
/// 查找类似 Will cover the port, separate with commas (,) 的本地化字符串。
/// </summary>
public static string TbPorts7Tips {
get {
return ResourceManager.GetString("TbPorts7Tips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Socks port 的本地化字符串。
/// </summary>

View File

@@ -1319,7 +1319,7 @@
<value>Previous proxy remarks</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>آدرس (IP, IPv6)</value>
<value>آدرس (IPv4, IPv6)</value>
</data>
<data name="TbReserved" xml:space="preserve">
<value>Reserved(2,3,4)</value>
@@ -1396,4 +1396,10 @@
<data name="RemoveInvalidServerResultTip" xml:space="preserve">
<value>Removed {0} invalid test results.</value>
</data>
<data name="TbPorts7" xml:space="preserve">
<value>Server port range</value>
</data>
<data name="TbPorts7Tips" xml:space="preserve">
<value>Will cover the port, separate with commas (,)</value>
</data>
</root>

View File

@@ -1100,7 +1100,7 @@
<value>Fenntartva (2,3,4)</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>Cím (Ip,Ipv6)</value>
<value>Cím (Ipv4,Ipv6)</value>
</data>
<data name="TbPath7" xml:space="preserve">
<value>obfs jelszó</value>
@@ -1396,4 +1396,10 @@
<data name="RemoveInvalidServerResultTip" xml:space="preserve">
<value>Removed {0} invalid test results.</value>
</data>
<data name="TbPorts7" xml:space="preserve">
<value>Server port range</value>
</data>
<data name="TbPorts7Tips" xml:space="preserve">
<value>Will cover the port, separate with commas (,)</value>
</data>
</root>

View File

@@ -1100,7 +1100,7 @@
<value>Reserved(2,3,4)</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>Address(Ip,Ipv6)</value>
<value>Address(Ipv4,Ipv6)</value>
</data>
<data name="TbPath7" xml:space="preserve">
<value>obfs password</value>
@@ -1396,4 +1396,10 @@
<data name="RemoveInvalidServerResultTip" xml:space="preserve">
<value>Removed {0} invalid test results.</value>
</data>
</root>
<data name="TbPorts7" xml:space="preserve">
<value>Server port range</value>
</data>
<data name="TbPorts7Tips" xml:space="preserve">
<value>Will cover the port, separate with commas (,)</value>
</data>
</root>

View File

@@ -1052,7 +1052,7 @@
<value>obfs password</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>Address(Ip,Ipv6)</value>
<value>Address(Ipv4,Ipv6)</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Default domain strategy for outbound</value>
@@ -1396,4 +1396,10 @@
<data name="RemoveInvalidServerResultTip" xml:space="preserve">
<value>Removed {0} invalid test results.</value>
</data>
<data name="TbPorts7" xml:space="preserve">
<value>Server port range</value>
</data>
<data name="TbPorts7Tips" xml:space="preserve">
<value>Will cover the port, separate with commas (,)</value>
</data>
</root>

View File

@@ -1097,7 +1097,7 @@
<value>Reserved(2,3,4)</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>Address(Ip,Ipv6)</value>
<value>Address(Ipv4,Ipv6)</value>
</data>
<data name="TbPath7" xml:space="preserve">
<value>混淆密码(obfs password)</value>
@@ -1393,4 +1393,10 @@
<data name="RemoveInvalidServerResultTip" xml:space="preserve">
<value>移除无效测试结果 {0} 个。</value>
</data>
<data name="TbPorts7" xml:space="preserve">
<value>跳跃端口范围</value>
</data>
<data name="TbPorts7Tips" xml:space="preserve">
<value>会覆盖端口,多组时用逗号(,)隔开</value>
</data>
</root>

View File

@@ -1326,7 +1326,7 @@
<value>Reserved(2,3,4)</value>
</data>
<data name="TbLocalAddress" xml:space="preserve">
<value>Address(Ip,Ipv6)</value>
<value>Address(Ipv4,Ipv6)</value>
</data>
<data name="TbSettingsUseSystemHosts" xml:space="preserve">
<value>使用系統hosts</value>
@@ -1394,4 +1394,10 @@
<data name="RemoveInvalidServerResultTip" xml:space="preserve">
<value>移除無效測試結果 {0} 個。</value>
</data>
<data name="TbPorts7" xml:space="preserve">
<value>跳躍端口範圍</value>
</data>
<data name="TbPorts7Tips" xml:space="preserve">
<value>會覆蓋端口,多組時用逗號(,)隔開</value>
</data>
</root>

View File

@@ -0,0 +1,118 @@
#!/bin/bash
# Function to set proxy for GNOME
set_gnome_proxy() {
local MODE=$1
local PROXY_IP=$2
local PROXY_PORT=$3
local IGNORE_HOSTS=$4
# Set the proxy mode
gsettings set org.gnome.system.proxy mode "$MODE"
if [ "$MODE" == "manual" ]; then
# List of protocols
local PROTOCOLS=("http" "https" "ftp" "socks")
# Loop through protocols to set the proxy
for PROTOCOL in "${PROTOCOLS[@]}"; do
gsettings set org.gnome.system.proxy.$PROTOCOL host "$PROXY_IP"
gsettings set org.gnome.system.proxy.$PROTOCOL port "$PROXY_PORT"
done
# Set ignored hosts
gsettings set org.gnome.system.proxy ignore-hosts "['$IGNORE_HOSTS']"
echo "GNOME: Manual proxy settings applied."
echo "Proxy IP: $PROXY_IP"
echo "Proxy Port: $PROXY_PORT"
echo "Ignored Hosts: $IGNORE_HOSTS"
elif [ "$MODE" == "none" ]; then
echo "GNOME: Proxy disabled."
else
echo "GNOME: Invalid mode. Use 'none' or 'manual'."
exit 1
fi
}
# Function to set proxy for KDE
set_kde_proxy() {
local MODE=$1
local PROXY_IP=$2
local PROXY_PORT=$3
local IGNORE_HOSTS=$4
# Determine the correct kwriteconfig command based on KDE_SESSION_VERSION
if [ "$KDE_SESSION_VERSION" == "6" ]; then
KWRITECONFIG="kwriteconfig6"
else
KWRITECONFIG="kwriteconfig5"
fi
# KDE uses kwriteconfig to modify proxy settings
if [ "$MODE" == "manual" ]; then
# Set proxy for all protocols
$KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key ProxyType 1
$KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key httpProxy "http://$PROXY_IP:$PROXY_PORT"
$KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key httpsProxy "http://$PROXY_IP:$PROXY_PORT"
$KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key ftpProxy "http://$PROXY_IP:$PROXY_PORT"
$KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key socksProxy "http://$PROXY_IP:$PROXY_PORT"
# Set ignored hosts
$KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key NoProxyFor "$IGNORE_HOSTS"
echo "KDE: Manual proxy settings applied."
echo "Proxy IP: $PROXY_IP"
echo "Proxy Port: $PROXY_PORT"
echo "Ignored Hosts: $IGNORE_HOSTS"
elif [ "$MODE" == "none" ]; then
# Disable proxy
$KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key ProxyType 0
echo "KDE: Proxy disabled."
else
echo "KDE: Invalid mode. Use 'none' or 'manual'."
exit 1
fi
# Apply changes by restarting KDE's network settings
dbus-send --type=signal /KIO/Scheduler org.kde.KIO.Scheduler.reparseSlaveConfiguration string:""
}
# Detect the current desktop environment
detect_desktop_environment() {
if [ "$XDG_CURRENT_DESKTOP" == "GNOME" ] || [ "$XDG_CURRENT_DESKTOP" == "ubuntu:GNOME" ] || [ "$XDG_SESSION_DESKTOP" == "GNOME" ] || [ "$XDG_SESSION_DESKTOP" == "ubuntu:GNOME" ]; then
echo "gnome"
elif [ "$XDG_CURRENT_DESKTOP" == "KDE" ] || [ "$XDG_CURRENT_DESKTOP" == "plasma" ] || [ "$XDG_SESSION_DESKTOP" == "KDE" ] || [ "$XDG_SESSION_DESKTOP" == "plasma" ]; then
echo "kde"
else
echo "unsupported"
fi
}
# Main script logic
if [ "$#" -lt 1 ]; then
echo "Usage: $0 <mode> [proxy_ip proxy_port ignore_hosts]"
echo " mode: 'none' or 'manual'"
echo " If mode is 'manual', provide proxy IP, port, and ignore hosts."
exit 1
fi
# Get the mode
MODE=$1
PROXY_IP=$2
PROXY_PORT=$3
IGNORE_HOSTS=$4
# Detect desktop environment
DE=$(detect_desktop_environment)
# Apply settings based on the desktop environment
if [ "$DE" == "gnome" ]; then
set_gnome_proxy "$MODE" "$PROXY_IP" "$PROXY_PORT" "$IGNORE_HOSTS"
elif [ "$DE" == "kde" ]; then
set_gnome_proxy "$MODE" "$PROXY_IP" "$PROXY_PORT" "$IGNORE_HOSTS"
set_kde_proxy "$MODE" "$PROXY_IP" "$PROXY_PORT" "$IGNORE_HOSTS"
else
echo "Unsupported desktop environment: $DE"
exit 1
fi

View File

@@ -0,0 +1,74 @@
#!/bin/bash
# Function to set proxy
set_proxy() {
PROXY_IP=$1
PROXY_PORT=$2
shift 2
BYPASS_DOMAINS=("$@")
# If no bypass domains are provided, set it to empty by default
if [ ${#BYPASS_DOMAINS[@]} -eq 0 ]; then
BYPASS_DOMAINS=("")
fi
# Get all network service names
SERVICES=$(networksetup -listallnetworkservices | grep -v '*')
# Loop through each network service
echo "$SERVICES" | while read -r SERVICE; do
echo "Setting proxy for network service '$SERVICE'..."
# Set HTTP proxy
networksetup -setwebproxy "$SERVICE" "$PROXY_IP" "$PROXY_PORT"
# Set HTTPS proxy
networksetup -setsecurewebproxy "$SERVICE" "$PROXY_IP" "$PROXY_PORT"
# Set SOCKS proxy
networksetup -setsocksfirewallproxy "$SERVICE" "$PROXY_IP" "$PROXY_PORT"
# Set bypass domains
networksetup -setproxybypassdomains "$SERVICE" "${BYPASS_DOMAINS[@]}"
echo "Proxy for network service '$SERVICE' has been set to $PROXY_IP:$PROXY_PORT"
done
echo "Proxy settings for all network services are complete!"
}
# Function to disable proxy
clear_proxy() {
# Get all network service names
SERVICES=$(networksetup -listallnetworkservices | grep -v '*')
# Loop through each network service
echo "$SERVICES" | while read -r SERVICE; do
echo "Disabling proxy and clearing bypass domains for network service '$SERVICE'..."
# Disable HTTP proxy
networksetup -setwebproxystate "$SERVICE" off
# Disable HTTPS proxy
networksetup -setsecurewebproxystate "$SERVICE" off
# Disable SOCKS proxy
networksetup -setsocksfirewallproxystate "$SERVICE" off
echo "Proxy for network service '$SERVICE' has been disabled"
done
echo "Proxy for all network services has been disabled!"
}
# Main script logic
if [ "$1" == "set" ]; then
# Check if enough parameters are passed for setting proxy
if [ "$#" -lt 3 ]; then
echo "Usage: $0 set <IP Address> <Port> [Bypass Domain 1 Bypass Domain 2 ...]"
exit 1
fi
set_proxy "$2" "$3" "${@:4}"
elif [ "$1" == "clear" ]; then
clear_proxy
else
echo "Usage:"
echo " To set proxy: $0 set <IP Address> <Port> [Bypass Domain 1 Bypass Domain 2 ...]"
echo " To clear proxy: $0 clear"
exit 1
fi

View File

@@ -29,6 +29,8 @@
<EmbeddedResource Include="Sample\dns_singbox_normal" />
<EmbeddedResource Include="Sample\dns_v2ray_normal" />
<EmbeddedResource Include="Sample\pac" />
<EmbeddedResource Include="Sample\proxy_set_linux_sh" />
<EmbeddedResource Include="Sample\proxy_set_osx_sh" />
<EmbeddedResource Include="Sample\SampleClientConfig" />
<EmbeddedResource Include="Sample\SampleHttpRequest" />
<EmbeddedResource Include="Sample\SampleHttpResponse" />

View File

@@ -116,7 +116,7 @@ namespace ServiceLib.Services.CoreConfig
//enable tun mode
if (_config.TunModeItem.EnableTun)
{
string tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml);
var tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml);
if (Utils.IsNotEmpty(tun))
{
var tunContent = YamlUtils.FromYaml<Dictionary<string, object>>(tun);

View File

@@ -599,7 +599,7 @@ namespace ServiceLib.Services.CoreConfig
{
if (_config.TunModeItem.Mtu <= 0)
{
_config.TunModeItem.Mtu = Utils.ToInt(Global.TunMtus.First());
_config.TunModeItem.Mtu = Global.TunMtus.First();
}
if (Utils.IsNullOrEmpty(_config.TunModeItem.Stack))
{
@@ -730,6 +730,16 @@ namespace ServiceLib.Services.CoreConfig
outbound.up_mbps = _config.HysteriaItem.UpMbps > 0 ? _config.HysteriaItem.UpMbps : null;
outbound.down_mbps = _config.HysteriaItem.DownMbps > 0 ? _config.HysteriaItem.DownMbps : null;
if (node.Ports.IsNotEmpty())
{
outbound.server_port = null;
outbound.server_ports = node.Ports.Split(',')
.Where(p => p.Trim().IsNotEmpty())
.Select(p => p.Replace('-', ':'))
.ToList();
outbound.hop_interval = _config.HysteriaItem.HopInterval > 0 ? $"{_config.HysteriaItem.HopInterval}s" : null;
}
break;
}
case EConfigType.TUIC:
@@ -745,7 +755,7 @@ namespace ServiceLib.Services.CoreConfig
outbound.peer_public_key = node.PublicKey;
outbound.reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList();
outbound.local_address = Utils.String2List(node.RequestHost);
outbound.mtu = Utils.ToInt(node.ShortId.IsNullOrEmpty() ? Global.TunMtus.FirstOrDefault() : node.ShortId);
outbound.mtu = Utils.ToInt(node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId);
break;
}
}

View File

@@ -915,7 +915,8 @@ namespace ServiceLib.Services.CoreConfig
kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize;
kcpSettings.header = new Header4Ray
{
type = node.HeaderType
type = node.HeaderType,
domain = host.IsNullOrEmpty() ? null : host
};
if (Utils.IsNotEmpty(path))
{

View File

@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Sockets;
@@ -222,20 +222,20 @@ namespace ServiceLib.Services
public async Task<int> RunAvailabilityCheck(IWebProxy? webProxy)
{
var responseTime = -1;
try
{
webProxy ??= await GetWebProxy(true);
var config = AppHandler.Instance.Config;
try
for (var i = 0; i < 2; i++)
{
var config = AppHandler.Instance.Config;
var responseTime = await GetRealPingTime(config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
return responseTime;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
return -1;
responseTime = await GetRealPingTime(config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
if (responseTime > 0)
{
break;
}
await Task.Delay(500);
}
}
catch (Exception ex)
@@ -243,6 +243,7 @@ namespace ServiceLib.Services
Logging.SaveLog(_tag, ex);
return -1;
}
return responseTime;
}
public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
@@ -319,4 +320,4 @@ namespace ServiceLib.Services
ServicePointManager.DefaultConnectionLimit = 256;
}
}
}
}

View File

@@ -25,8 +25,6 @@ namespace ServiceLib.Services
await RunAsync(actionType, selecteds);
await ProfileExHandler.Instance.SaveTo();
UpdateFunc("", ResUI.SpeedtestingCompleted);
FileManager.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1));
});
}
@@ -336,21 +334,19 @@ namespace ServiceLib.Services
ipAddress = ipHostInfo.AddressList.First();
}
var timer = Stopwatch.StartNew();
IPEndPoint endPoint = new(ipAddress, port);
using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
var timer = Stopwatch.StartNew();
var result = clientSocket.BeginConnect(endPoint, null, null);
if (!result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5)))
{
throw new TimeoutException("connect timeout (5s): " + url);
}
clientSocket.EndConnect(result);
timer.Stop();
responseTime = (int)timer.Elapsed.TotalMilliseconds;
clientSocket.EndConnect(result);
}
catch (Exception ex)
{

View File

@@ -53,26 +53,31 @@ namespace ServiceLib.ViewModels
private async Task Init()
{
var lastTime = DateTime.Now;
Task.Run(async () =>
{
var numOfExecuted = 1;
while (true)
{
await Task.Delay(1000 * 5);
numOfExecuted++;
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
{
continue;
}
if (_config.ClashUIItem.ConnectionsRefreshInterval <= 0)
{
continue;
}
if (numOfExecuted % _config.ClashUIItem.ConnectionsRefreshInterval != 0)
{
continue;
}
await GetClashConnections();
}
});
Observable.Interval(TimeSpan.FromSeconds(5))
.Subscribe(async x =>
{
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
{
return;
}
var dtNow = DateTime.Now;
if (_config.ClashUIItem.ConnectionsRefreshInterval > 0)
{
if ((dtNow - lastTime).Minutes % _config.ClashUIItem.ConnectionsRefreshInterval == 0)
{
await GetClashConnections();
lastTime = dtNow;
}
Task.Delay(1000).Wait();
}
});
await Task.CompletedTask;
}

View File

@@ -95,8 +95,8 @@ namespace ServiceLib.ViewModels
private async Task Init()
{
await ProxiesReload();
_ = DelayTestTask();
await ProxiesReload();
}
private async Task DoRulemodeSelected(bool c)
@@ -383,8 +383,6 @@ namespace ServiceLib.ViewModels
private async Task ProxiesDelayTest(bool blAll)
{
//UpdateHandler(false, "Clash Proxies Latency Test");
ClashApiHandler.Instance.ClashProxiesDelayTest(blAll, _proxyDetails.ToList(), async (item, result) =>
{
if (item == null)
@@ -434,13 +432,13 @@ namespace ServiceLib.ViewModels
public async Task DelayTestTask()
{
var lastTime = DateTime.Now;
_ = Task.Run(async () =>
Task.Run(async () =>
{
var numOfExecuted = 1;
while (true)
{
await Task.Delay(1000 * 60);
numOfExecuted++;
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
{
continue;
@@ -449,13 +447,11 @@ namespace ServiceLib.ViewModels
{
continue;
}
var dtNow = DateTime.Now;
if ((dtNow - lastTime).Minutes % _config.ClashUIItem.ProxiesAutoDelayTestInterval != 0)
if (numOfExecuted % _config.ClashUIItem.ProxiesAutoDelayTestInterval != 0)
{
continue;
}
await ProxiesDelayTest();
lastTime = dtNow;
}
});
await Task.CompletedTask;

View File

@@ -0,0 +1,65 @@
using System.Reactive;
using ReactiveUI;
namespace ServiceLib.ViewModels
{
public class GlobalHotkeySettingViewModel : MyReactiveObject
{
private readonly List<KeyEventItem> _globalHotkeys;
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
public GlobalHotkeySettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{
_config = AppHandler.Instance.Config;
_updateView = updateView;
_globalHotkeys = JsonUtils.DeepCopy(_config.GlobalHotkeys);
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
{
await SaveSettingAsync();
});
}
public KeyEventItem GetKeyEventItem(EGlobalHotkey eg)
{
var item = _globalHotkeys.FirstOrDefault((it) => it.EGlobalHotkey == eg);
if (item != null)
{
return item;
}
item = new()
{
EGlobalHotkey = eg,
Control = false,
Alt = false,
Shift = false,
KeyCode = null
};
_globalHotkeys.Add(item);
return item;
}
public void ResetKeyEventItem()
{
_globalHotkeys.Clear();
}
private async Task SaveSettingAsync()
{
_config.GlobalHotkeys = _globalHotkeys;
if (await ConfigHandler.SaveConfig(_config) == 0)
{
_updateView?.Invoke(EViewAction.CloseWindow, null);
}
else
{
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
}
}
}
}

View File

@@ -316,6 +316,8 @@ namespace ServiceLib.ViewModels
return;
}
_updateView?.Invoke(EViewAction.DispatcherServerAvailability, ResUI.Speedtesting);
var msg = await (new UpdateService()).RunAvailabilityCheck();
NoticeHandler.Instance.SendMessageEx(msg);

View File

@@ -0,0 +1,91 @@
using System.Reactive.Linq;
using Avalonia.Input;
using Avalonia.ReactiveUI;
using Avalonia.Win32.Input;
using GlobalHotKeys;
namespace v2rayN.Desktop.Handler
{
public sealed class HotkeyHandler
{
private static readonly Lazy<HotkeyHandler> _instance = new(() => new());
public static HotkeyHandler Instance = _instance.Value;
private readonly Dictionary<int, EGlobalHotkey> _hotkeyTriggerDic = new();
private HotKeyManager? _hotKeyManager;
private Config? _config;
private event Action<EGlobalHotkey>? _updateFunc;
public bool IsPause { get; set; } = false;
public void Init(Config config, Action<EGlobalHotkey> updateFunc)
{
_config = config;
_updateFunc = updateFunc;
Register();
}
public void Dispose()
{
_hotKeyManager?.Dispose();
}
private void Register()
{
if (_config.GlobalHotkeys.Any(t => t.KeyCode > 0) == false)
{
return;
}
_hotKeyManager ??= new GlobalHotKeys.HotKeyManager();
_hotkeyTriggerDic.Clear();
foreach (var item in _config.GlobalHotkeys)
{
if (item.KeyCode is null or 0)
{
continue;
}
var vKey = KeyInterop.VirtualKeyFromKey((Key)item.KeyCode);
var modifiers = Modifiers.None;
if (item.Control)
{
modifiers |= Modifiers.Control;
}
if (item.Shift)
{
modifiers |= Modifiers.Shift;
}
if (item.Alt)
{
modifiers |= Modifiers.Alt;
}
var result = _hotKeyManager?.Register((VirtualKeyCode)vKey, modifiers);
if (result?.IsSuccessful == true)
{
_hotkeyTriggerDic.Add(result.Id, item.EGlobalHotkey);
}
}
_hotKeyManager?.HotKeyPressed
.ObserveOn(AvaloniaScheduler.Instance)
.Subscribe(OnNext);
}
private void OnNext(HotKey key)
{
if (_updateFunc == null || IsPause)
{
return;
}
if (_hotkeyTriggerDic.TryGetValue(key.Id, out var value))
{
_updateFunc?.Invoke(value);
}
}
}
}

View File

@@ -6,7 +6,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="v2rayN"
Title="{x:Static resx:ResUI.menuAddCustomServer}"
Width="700"
Height="500"
x:DataType="vms:AddServer2ViewModel"
@@ -15,8 +15,8 @@
mc:Ignorable="d">
<DockPanel Margin="{StaticResource Margin8}">
<StackPanel
HorizontalAlignment="Center"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Center"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
@@ -45,8 +45,8 @@
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRemarks}" />
<TextBox
@@ -54,24 +54,24 @@
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}" />
VerticalAlignment="Center" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAddress}" />
<TextBox
x:Name="txtAddress"
Grid.Row="2"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
IsReadOnly="True" />
<StackPanel
Grid.Row="2"
@@ -91,23 +91,23 @@
<TextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCoreType}" />
<ComboBox
x:Name="cmbCoreType"
Grid.Row="3"
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
MaxDropDownHeight="1000" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbDisplayLog}" />
<StackPanel
Grid.Row="4"
@@ -116,27 +116,27 @@
Orientation="Horizontal">
<ToggleSwitch
x:Name="togDisplayLog"
HorizontalAlignment="Left"
Margin="{StaticResource Margin4}" />
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
VerticalAlignment="Center"
Margin="{StaticResource MarginLr8}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TipDisplayLog}" />
</StackPanel>
<TextBlock
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPreSocksPort}" />
<TextBox
x:Name="txtPreSocksPort"
Grid.Row="5"
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
Margin="{StaticResource Margin4}" />
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<StackPanel
Grid.Row="6"
Grid.Column="1"
@@ -149,8 +149,8 @@
TextWrapping="Wrap" />
<TextBlock
Width="500"
VerticalAlignment="Center"
Margin="{StaticResource MarginLr8}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.CustomServerTips}"
TextWrapping="Wrap" />
</StackPanel>

View File

@@ -308,7 +308,7 @@
<Grid
x:Name="gridHysteria2"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="180,Auto,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto">
@@ -337,6 +337,26 @@
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPorts7}" />
<TextBox
x:Name="txtPorts7"
Grid.Row="3"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Watermark="1000:2000,3000:4000" />
<TextBlock
Grid.Row="3"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPorts7Tips}" />
</Grid>
<Grid
x:Name="gridTuic"
@@ -429,7 +449,8 @@
Grid.Row="3"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
Margin="{StaticResource Margin4}"
Watermark="2,3,4" />
<TextBlock
Grid.Row="4"
@@ -442,7 +463,8 @@
Grid.Row="4"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
Margin="{StaticResource Margin4}"
Watermark="Ipv4,Ipv6" />
<TextBlock
Grid.Row="5"
@@ -456,7 +478,8 @@
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
HorizontalAlignment="Left"
Watermark="1500" />
</Grid>
<Separator

View File

@@ -178,6 +178,7 @@ namespace v2rayN.Desktop.Views
case EConfigType.Hysteria2:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
break;
case EConfigType.TUIC:

View File

@@ -6,17 +6,17 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuSetting}"
Title="{x:Static resx:ResUI.menuGlobalHotkeySetting}"
Width="700"
Height="500"
x:DataType="vms:SubEditViewModel"
x:DataType="vms:GlobalHotkeySettingViewModel"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<DockPanel Margin="{StaticResource Margin8}">
<StackPanel
HorizontalAlignment="Center"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Center"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
@@ -55,77 +55,77 @@
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbDisplayGUI}" />
<TextBox
x:Name="txtGlobalHotkey0"
Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
IsReadOnly="True" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbClearSystemProxy}" />
<TextBox
x:Name="txtGlobalHotkey1"
Grid.Row="2"
Grid.Column="1"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
IsReadOnly="True" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSetSystemProxy}" />
<TextBox
x:Name="txtGlobalHotkey2"
Grid.Row="3"
Grid.Column="1"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
IsReadOnly="True" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbNotChangeSystemProxy}" />
<TextBox
x:Name="txtGlobalHotkey3"
Grid.Row="4"
Grid.Column="1"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
IsReadOnly="True" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSystemProxyPac}" />
<TextBox
x:Name="txtGlobalHotkey4"
Grid.Row="5"
Grid.Column="1"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
IsReadOnly="True" />
</Grid>
<TextBlock
Grid.Row="1"
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbGlobalHotkeySettingTip}" />
</Grid>
</ScrollViewer>

View File

@@ -1,129 +1,138 @@
using Avalonia.Controls;
using System.Reactive.Disposables;
using System.Text;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using ReactiveUI;
using v2rayN.Desktop.Handler;
namespace v2rayN.Desktop.Views
{
public partial class GlobalHotkeySettingWindow : Window
public partial class GlobalHotkeySettingWindow : ReactiveWindow<GlobalHotkeySettingViewModel>
{
private static Config _config = default!;
private Dictionary<object, KeyEventItem> _TextBoxKeyEventItem = default!;
private readonly List<object> _textBoxKeyEventItem = new();
public GlobalHotkeySettingWindow()
{
InitializeComponent();
ViewModel = new GlobalHotkeySettingViewModel(UpdateViewHandler);
btnReset.Click += btnReset_Click;
HotkeyHandler.Instance.IsPause = true;
this.Closing += (s, e) => HotkeyHandler.Instance.IsPause = false;
btnCancel.Click += (s, e) => this.Close();
_config = AppHandler.Instance.Config;
//_config.globalHotkeys ??= new List<KeyEventItem>();
//txtGlobalHotkey0.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
//txtGlobalHotkey1.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
//txtGlobalHotkey2.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
//txtGlobalHotkey3.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
//txtGlobalHotkey4.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
this.WhenActivated(disposables =>
{
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});
//HotkeyHandler.Instance.IsPause = true;
//this.Closing += (s, e) => HotkeyHandler.Instance.IsPause = false;
//InitData();
Init();
BindingData();
}
//private void InitData()
//{
// _TextBoxKeyEventItem = new()
// {
// { txtGlobalHotkey0,GetKeyEventItemByEGlobalHotkey(_config.globalHotkeys,EGlobalHotkey.ShowForm) },
// { txtGlobalHotkey1,GetKeyEventItemByEGlobalHotkey(_config.globalHotkeys,EGlobalHotkey.SystemProxyClear) },
// { txtGlobalHotkey2,GetKeyEventItemByEGlobalHotkey(_config.globalHotkeys,EGlobalHotkey.SystemProxySet) },
// { txtGlobalHotkey3,GetKeyEventItemByEGlobalHotkey(_config.globalHotkeys,EGlobalHotkey.SystemProxyUnchanged)},
// { txtGlobalHotkey4,GetKeyEventItemByEGlobalHotkey(_config.globalHotkeys,EGlobalHotkey.SystemProxyPac)}
// };
// BindingData();
//}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.CloseWindow:
this.Close(true);
break;
}
return await Task.FromResult(true);
}
//private void TxtGlobalHotkey_PreviewKeyDown(object? sender, KeyEventArgs e)
//{
// e.Handled = true;
// var _ModifierKeys = new Key[] { Key.LeftCtrl, Key.RightCtrl, Key.LeftShift,
// Key.RightShift, Key.LeftAlt, Key.RightAlt, Key.LWin, Key.RWin};
// _TextBoxKeyEventItem[sender].KeyCode = (int)(e.Key == Key.System ? (_ModifierKeys.Contains(e.SystemKey) ? Key.None : e.SystemKey) : (_ModifierKeys.Contains(e.Key) ? Key.None : e.Key));
// _TextBoxKeyEventItem[sender].Alt = (Keyboard.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt;
// _TextBoxKeyEventItem[sender].Control = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;
// _TextBoxKeyEventItem[sender].Shift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
// (sender as TextBox)!.Text = KeyEventItemToString(_TextBoxKeyEventItem[sender]);
//}
private void Init()
{
_textBoxKeyEventItem.Add(txtGlobalHotkey0);
_textBoxKeyEventItem.Add(txtGlobalHotkey1);
_textBoxKeyEventItem.Add(txtGlobalHotkey2);
_textBoxKeyEventItem.Add(txtGlobalHotkey3);
_textBoxKeyEventItem.Add(txtGlobalHotkey4);
//private KeyEventItem GetKeyEventItemByEGlobalHotkey(List<KeyEventItem> KEList, EGlobalHotkey eg)
//{
// return JsonUtils.DeepCopy(KEList.Find((it) => it.eGlobalHotkey == eg) ?? new()
// {
// eGlobalHotkey = eg,
// Control = false,
// Alt = false,
// Shift = false,
// KeyCode = null
// });
//}
for (var index = 0; index < _textBoxKeyEventItem.Count; index++)
{
var sender = _textBoxKeyEventItem[index];
if (sender is not TextBox txtBox)
{
continue;
}
txtBox.Tag = (EGlobalHotkey)index;
txtBox.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
}
}
//private string KeyEventItemToString(KeyEventItem item)
//{
// var res = new StringBuilder();
private void TxtGlobalHotkey_PreviewKeyDown(object? sender, KeyEventArgs e)
{
e.Handled = true;
if (sender is not TextBox txtBox)
{
return;
}
// if (item.Control) res.Append($"{ModifierKeys.Control}+");
// if (item.Shift) res.Append($"{ModifierKeys.Shift}+");
// if (item.Alt) res.Append($"{ModifierKeys.Alt}+");
// if (item.KeyCode != null && (Key)item.KeyCode != Key.None)
// res.Append($"{(Key)item.KeyCode}");
var item = ViewModel?.GetKeyEventItem((EGlobalHotkey)txtBox.Tag);
var modifierKeys = new Key[] { Key.LeftCtrl, Key.RightCtrl, Key.LeftShift, Key.RightShift, Key.LeftAlt, Key.RightAlt, Key.LWin, Key.RWin };
// return res.ToString();
//}
item.KeyCode = (int)(e.Key == Key.System ? modifierKeys.Contains(Key.System) ? Key.None : Key.System : modifierKeys.Contains(e.Key) ? Key.None : e.Key);
item.Alt = (e.KeyModifiers & KeyModifiers.Alt) == KeyModifiers.Alt;
item.Control = (e.KeyModifiers & KeyModifiers.Control) == KeyModifiers.Control;
item.Shift = (e.KeyModifiers & KeyModifiers.Shift) == KeyModifiers.Shift;
//private void BindingData()
//{
// foreach (var item in _TextBoxKeyEventItem)
// {
// if (item.Value.KeyCode != null && (Key)item.Value.KeyCode != Key.None)
// {
// (item.Key as TextBox)!.Text = KeyEventItemToString(item.Value);
// }
// else
// {
// (item.Key as TextBox)!.Text = string.Empty;
// }
// }
//}
txtBox.Text = KeyEventItemToString(item);
}
//private void btnSave_Click(object? sender, RoutedEventArgs e)
//{
// _config.globalHotkeys = _TextBoxKeyEventItem.Values.ToList();
private void BindingData()
{
foreach (var sender in _textBoxKeyEventItem)
{
if (sender is not TextBox txtBox)
{
continue;
}
// if (ConfigHandler.SaveConfig(_config, false) == 0)
// {
// HotkeyHandler.Instance.ReLoad();
// this.Close();
// }
// else
// {
// UI.Show(ResUI.OperationFailed);
// }
//}
var item = ViewModel?.GetKeyEventItem((EGlobalHotkey)txtBox.Tag);
txtBox.Text = KeyEventItemToString(item);
}
}
//private void btnReset_Click(object? sender, RoutedEventArgs e)
//{
// foreach (var k in _TextBoxKeyEventItem.Keys)
// {
// _TextBoxKeyEventItem[k].Alt = false;
// _TextBoxKeyEventItem[k].Control = false;
// _TextBoxKeyEventItem[k].Shift = false;
// _TextBoxKeyEventItem[k].KeyCode = (int)Key.None;
// }
// BindingData();
//}
private void btnReset_Click(object sender, RoutedEventArgs e)
{
ViewModel?.ResetKeyEventItem();
BindingData();
}
//private void GlobalHotkeySettingWindow_KeyDown(object? sender, KeyEventArgs e)
//{
// if (e.Key == Key.Escape)
// {
// this.Close();
// }
//}
private string KeyEventItemToString(KeyEventItem? item)
{
if (item == null)
{
return string.Empty;
}
var res = new StringBuilder();
if (item.Control)
{
res.Append($"{KeyModifiers.Control} +");
}
if (item.Shift)
{
res.Append($"{KeyModifiers.Shift} +");
}
if (item.Alt)
{
res.Append($"{KeyModifiers.Alt} +");
}
if (item.KeyCode != null && (Key)item.KeyCode != Key.None)
{
res.Append($"{(Key)item.KeyCode}");
}
return res.ToString();
}
}
}
}

View File

@@ -12,6 +12,7 @@ using MsBox.Avalonia.Enums;
using ReactiveUI;
using Splat;
using v2rayN.Desktop.Common;
using v2rayN.Desktop.Handler;
namespace v2rayN.Desktop.Views
{
@@ -138,8 +139,7 @@ namespace v2rayN.Desktop.Views
if (Utils.IsWindows())
{
ThreadPool.RegisterWaitForSingleObject(Program.ProgramStarted, OnProgramStarted, null, -1, false);
menuGlobalHotkeySetting.IsVisible = false;
HotkeyHandler.Instance.Init(_config, OnHotkeyHandler);
}
else
{
@@ -156,7 +156,6 @@ namespace v2rayN.Desktop.Views
RestoreUI();
AddHelpMenuItem();
//WindowsHandler.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, null);
MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI);
}
@@ -233,6 +232,7 @@ namespace v2rayN.Desktop.Views
StorageUI();
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
HotkeyHandler.Instance.Dispose();
desktop.Shutdown();
}
break;
@@ -247,7 +247,10 @@ namespace v2rayN.Desktop.Views
case EViewAction.AddServerViaClipboard:
var clipboardData = await AvaUtils.GetClipboardData(this);
ViewModel?.AddServerViaClipboardAsync(clipboardData);
if (ViewModel != null)
{
await ViewModel.AddServerViaClipboardAsync(clipboardData);
}
break;
case EViewAction.AdjustMainLvColWidth:
@@ -268,21 +271,12 @@ namespace v2rayN.Desktop.Views
ShowHideWindow(null);
break;
//case EGlobalHotkey.SystemProxyClear:
// ViewModel?.SetListenerType(ESysProxyType.ForcedClear);
// break;
//case EGlobalHotkey.SystemProxySet:
// ViewModel?.SetListenerType(ESysProxyType.ForcedChange);
// break;
//case EGlobalHotkey.SystemProxyUnchanged:
// ViewModel?.SetListenerType(ESysProxyType.Unchanged);
// break;
//case EGlobalHotkey.SystemProxyPac:
// ViewModel?.SetListenerType(ESysProxyType.Pac);
// break;
case EGlobalHotkey.SystemProxyClear:
case EGlobalHotkey.SystemProxySet:
case EGlobalHotkey.SystemProxyUnchanged:
case EGlobalHotkey.SystemProxyPac:
Locator.Current.GetService<StatusBarViewModel>()?.SetListenerType((ESysProxyType)((int)e - 1));
break;
}
}
@@ -303,7 +297,10 @@ namespace v2rayN.Desktop.Views
break;
case WindowCloseReason.ApplicationShutdown or WindowCloseReason.OSShutdown:
await ViewModel?.MyAppExitAsync(true);
if (ViewModel != null)
{
await ViewModel.MyAppExitAsync(true);
}
break;
}
@@ -318,7 +315,10 @@ namespace v2rayN.Desktop.Views
{
case Key.V:
var clipboardData = await AvaUtils.GetClipboardData(this);
ViewModel?.AddServerViaClipboardAsync(clipboardData);
if (ViewModel != null)
{
await ViewModel.AddServerViaClipboardAsync(clipboardData);
}
break;
case Key.S:
@@ -367,7 +367,11 @@ namespace v2rayN.Desktop.Views
{
return;
}
await ViewModel?.ScanImageResult(fileName);
if (ViewModel != null)
{
await ViewModel.ScanImageResult(fileName);
}
}
private void MenuCheckUpdate_Click(object? sender, RoutedEventArgs e)
@@ -391,8 +395,10 @@ namespace v2rayN.Desktop.Views
_blCloseByUser = true;
StorageUI();
await ViewModel?.MyAppExitAsync(false);
if (ViewModel != null)
{
await ViewModel.MyAppExitAsync(false);
}
}
#endregion Event

View File

@@ -10,8 +10,8 @@
mc:Ignorable="d">
<DockPanel Margin="2">
<WrapPanel
VerticalAlignment="Center"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
DockPanel.Dock="Top"
Orientation="Horizontal">
@@ -37,6 +37,8 @@
</Button>
<Button
x:Name="btnClear"
Width="{StaticResource IconButtonWidth}"
Height="{StaticResource IconButtonHeight}"
Margin="{StaticResource MarginLr8}"
Classes="Success"
Click="menuMsgViewClear_Click"

View File

@@ -248,7 +248,10 @@ namespace v2rayN.Desktop.Views
private void ClbdestOverride_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
ViewModel.destOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
if (ViewModel != null)
{
ViewModel.destOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
}
}
}
}

View File

@@ -86,7 +86,7 @@ namespace v2rayN.Desktop.Views
this.BindCommand(ViewModel, vm => vm.SpeedServerCmd, v => v.menuSpeedServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SortServerResultCmd, v => v.menuSortServerResult).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RemoveInvalidServerResultCmd, v => v.menuRemoveInvalidServerResult).DisposeWith(disposables);
//servers export
this.BindCommand(ViewModel, vm => vm.Export2ClientConfigCmd, v => v.menuExport2ClientConfig).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2ClientConfigClipboardCmd, v => v.menuExport2ClientConfigClipboard).DisposeWith(disposables);
@@ -102,11 +102,16 @@ namespace v2rayN.Desktop.Views
private async void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e)
{
e.Handled = true;
await ViewModel?.SortServer(e.Column.Tag.ToString());
if (ViewModel != null && e.Column?.Tag?.ToString() != null)
{
await ViewModel.SortServer(e.Column.Tag.ToString());
}
e.Handled = false;
}
//#region Event
#region Event
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
@@ -179,7 +184,9 @@ namespace v2rayN.Desktop.Views
case EViewAction.DispatcherRefreshServersBiz:
Dispatcher.UIThread.Post(() =>
ViewModel?.RefreshServersBiz(),
{
_ = RefreshServersBiz();
},
DispatcherPriority.Default);
break;
}
@@ -198,9 +205,25 @@ namespace v2rayN.Desktop.Views
await DialogHost.Show(dialog);
}
public async Task RefreshServersBiz()
{
if (ViewModel != null)
{
await ViewModel.RefreshServersBiz();
}
if (lstProfiles.SelectedIndex > 0)
{
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, null);
}
}
private void lstProfiles_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
ViewModel.SelectedProfiles = lstProfiles.SelectedItems.Cast<ProfileItemModel>().ToList();
if (ViewModel != null)
{
ViewModel.SelectedProfiles = lstProfiles.SelectedItems.Cast<ProfileItemModel>().ToList();
}
}
private void LstProfiles_DoubleTapped(object? sender, Avalonia.Input.TappedEventArgs e)
@@ -333,9 +356,9 @@ namespace v2rayN.Desktop.Views
}
}
//#endregion Event
#endregion Event
//#region UI
#region UI
private void RestoreUI()
{
@@ -388,9 +411,9 @@ namespace v2rayN.Desktop.Views
_config.UiItem.MainColumnItem = lvColumnItem;
}
//#endregion UI
#endregion UI
//#region Drag and Drop
#region Drag and Drop
//private Point startPoint = new();
//private int startIndex = -1;
@@ -480,6 +503,6 @@ namespace v2rayN.Desktop.Views
// }
//}
//#endregion Drag and Drop
#endregion Drag and Drop
}
}

View File

@@ -17,10 +17,11 @@
x:Name="txtContent"
Grid.Row="1"
Width="300"
MaxHeight="100"
Margin="{StaticResource MarginTb8}"
VerticalAlignment="Center"
IsReadOnly="True"
MaxLines="1" />
TextWrapping="WrapWithOverflow" />
</Grid>
</UserControl>

View File

@@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
namespace v2rayN.Desktop.Views
{
@@ -17,7 +18,7 @@ namespace v2rayN.Desktop.Views
txtContent.Text = url;
imgQrcode.Source = GetQRCode(url);
// btnCancel.Click += (s, e) => this.Close();
txtContent.GotFocus += (_, _) => Dispatcher.UIThread.Post(() => { txtContent.SelectAll(); });
}
private Bitmap? GetQRCode(string? url)
@@ -29,7 +30,9 @@ namespace v2rayN.Desktop.Views
private Bitmap? ByteToBitmap(byte[]? bytes)
{
if (bytes is null)
{
return null;
}
using var ms = new MemoryStream(bytes);
return new Bitmap(ms);

View File

@@ -85,12 +85,18 @@ namespace v2rayN.Desktop.Views
private void ClbProtocol_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
ViewModel.ProtocolItems = clbProtocol.SelectedItems.Cast<string>().ToList();
if (ViewModel != null)
{
ViewModel.ProtocolItems = clbProtocol.SelectedItems.Cast<string>().ToList();
}
}
private void ClbInboundTag_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
ViewModel.InboundTagItems = clbInboundTag.SelectedItems.Cast<string>().ToList();
if (ViewModel != null)
{
ViewModel.InboundTagItems = clbInboundTag.SelectedItems.Cast<string>().ToList();
}
}
private void linkRuleobjectDoc_Click(object? sender, RoutedEventArgs e)

View File

@@ -167,7 +167,10 @@ namespace v2rayN.Desktop.Views
private void lstRules_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
ViewModel.SelectedSources = lstRules.SelectedItems.Cast<RulesItemModel>().ToList();
if (ViewModel != null)
{
ViewModel.SelectedSources = lstRules.SelectedItems.Cast<RulesItemModel>().ToList();
}
}
private void LstRules_DoubleTapped(object? sender, Avalonia.Input.TappedEventArgs e)

View File

@@ -108,7 +108,10 @@ namespace v2rayN.Desktop.Views
private void lstRoutings_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
ViewModel.SelectedSources = lstRoutings.SelectedItems.Cast<RoutingItemModel>().ToList();
if (ViewModel != null)
{
ViewModel.SelectedSources = lstRoutings.SelectedItems.Cast<RoutingItemModel>().ToList();
}
}
private void LstRoutings_DoubleTapped(object? sender, TappedEventArgs e)

View File

@@ -84,7 +84,10 @@ namespace v2rayN.Desktop.Views
private void LstSubscription_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
ViewModel.SelectedSources = lstSubscription.SelectedItems.Cast<SubItem>().ToList();
if (ViewModel != null)
{
ViewModel.SelectedSources = lstSubscription.SelectedItems.Cast<SubItem>().ToList();
}
}
private void menuClose_Click(object? sender, RoutedEventArgs e)

View File

@@ -31,6 +31,7 @@
<ItemGroup>
<ProjectCapability Include="Avalonia" />
<AvaloniaResource Include="Assets\**" />
<ProjectReference Include="..\GlobalHotKeys\src\GlobalHotKeys\GlobalHotKeys.csproj" />
<ProjectReference Include="..\ServiceLib\ServiceLib.csproj" />
</ItemGroup>

View File

@@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "v2rayN.Desktop", "v2rayN.De
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AmazTool", "AmazTool\AmazTool.csproj", "{47D68B1C-601C-4C69-873B-FFF0DC13EC97}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GlobalHotKeys", "GlobalHotKeys\src\GlobalHotKeys\GlobalHotKeys.csproj", "{CB3DE54F-3A26-AE02-1299-311132C32156}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -33,6 +35,10 @@ Global
{47D68B1C-601C-4C69-873B-FFF0DC13EC97}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47D68B1C-601C-4C69-873B-FFF0DC13EC97}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47D68B1C-601C-4C69-873B-FFF0DC13EC97}.Release|Any CPU.Build.0 = Release|Any CPU
{CB3DE54F-3A26-AE02-1299-311132C32156}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB3DE54F-3A26-AE02-1299-311132C32156}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB3DE54F-3A26-AE02-1299-311132C32156}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB3DE54F-3A26-AE02-1299-311132C32156}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -11,15 +11,8 @@ namespace v2rayN.Handler
{
private static readonly Lazy<HotkeyHandler> _instance = new(() => new());
public static HotkeyHandler Instance = _instance.Value;
private const int WmHotkey = 0x0312;
private Config _config
{
get => AppHandler.Instance.Config;
}
private Dictionary<int, List<EGlobalHotkey>> _hotkeyTriggerDic;
private readonly Dictionary<int, List<EGlobalHotkey>> _hotkeyTriggerDic = new();
public bool IsPause { get; set; } = false;
@@ -29,7 +22,6 @@ namespace v2rayN.Handler
public HotkeyHandler()
{
_hotkeyTriggerDic = new();
ComponentDispatcher.ThreadPreprocessMessage += OnThreadPreProcessMessage;
Init();
}
@@ -37,20 +29,27 @@ namespace v2rayN.Handler
private void Init()
{
_hotkeyTriggerDic.Clear();
if (_config.GlobalHotkeys == null)
return;
foreach (var item in _config.GlobalHotkeys)
foreach (var item in AppHandler.Instance.Config.GlobalHotkeys)
{
if (item.KeyCode != null && (Key)item.KeyCode != Key.None)
{
int key = KeyInterop.VirtualKeyFromKey((Key)item.KeyCode);
KeyModifiers modifiers = KeyModifiers.None;
var key = KeyInterop.VirtualKeyFromKey((Key)item.KeyCode);
var modifiers = KeyModifiers.None;
if (item.Control)
{
modifiers |= KeyModifiers.Ctrl;
}
if (item.Shift)
{
modifiers |= KeyModifiers.Shift;
}
if (item.Alt)
{
modifiers |= KeyModifiers.Alt;
}
key = (key << 16) | (int)modifiers;
if (!_hotkeyTriggerDic.ContainsKey(key))
{
@@ -59,7 +58,9 @@ namespace v2rayN.Handler
else
{
if (!_hotkeyTriggerDic[key].Contains(item.EGlobalHotkey))
{
_hotkeyTriggerDic[key].Add(item.EGlobalHotkey);
}
}
}
}
@@ -70,8 +71,8 @@ namespace v2rayN.Handler
foreach (var _hotkeyCode in _hotkeyTriggerDic.Keys)
{
var hotkeyInfo = GetHotkeyInfo(_hotkeyCode);
bool isSuccess = false;
string msg;
var isSuccess = false;
var msg = string.Empty;
Application.Current?.Dispatcher.Invoke(() =>
{
@@ -106,29 +107,38 @@ namespace v2rayN.Handler
Load();
}
private (int fsModifiers, int vKey, string hotkeyStr, List<string> Names) GetHotkeyInfo(int hotkeycode)
private (int fsModifiers, int vKey, string hotkeyStr, List<string> Names) GetHotkeyInfo(int hotkeyCode)
{
var _fsModifiers = hotkeycode & 0xffff;
var _vkey = (hotkeycode >> 16) & 0xffff;
var _hotkeyStr = new StringBuilder();
var _names = new List<string>();
var fsModifiers = hotkeyCode & 0xffff;
var vKey = (hotkeyCode >> 16) & 0xffff;
var hotkeyStr = new StringBuilder();
var names = new List<string>();
var mdif = (KeyModifiers)_fsModifiers;
var key = KeyInterop.KeyFromVirtualKey(_vkey);
if ((mdif & KeyModifiers.Ctrl) == KeyModifiers.Ctrl)
_hotkeyStr.Append($"{KeyModifiers.Ctrl}+");
if ((mdif & KeyModifiers.Alt) == KeyModifiers.Alt)
_hotkeyStr.Append($"{KeyModifiers.Alt}+");
if ((mdif & KeyModifiers.Shift) == KeyModifiers.Shift)
_hotkeyStr.Append($"{KeyModifiers.Shift}+");
_hotkeyStr.Append(key.ToString());
foreach (var name in _hotkeyTriggerDic[hotkeycode])
var modify = (KeyModifiers)fsModifiers;
var key = KeyInterop.KeyFromVirtualKey(vKey);
if ((modify & KeyModifiers.Ctrl) == KeyModifiers.Ctrl)
{
_names.Add(name.ToString());
hotkeyStr.Append($"{KeyModifiers.Ctrl}+");
}
return (_fsModifiers, _vkey, _hotkeyStr.ToString(), _names);
if ((modify & KeyModifiers.Alt) == KeyModifiers.Alt)
{
hotkeyStr.Append($"{KeyModifiers.Alt}+");
}
if ((modify & KeyModifiers.Shift) == KeyModifiers.Shift)
{
hotkeyStr.Append($"{KeyModifiers.Shift}+");
}
hotkeyStr.Append(key.ToString());
foreach (var name in _hotkeyTriggerDic[hotkeyCode])
{
names.Add(name.ToString());
}
return (fsModifiers, vKey, hotkeyStr.ToString(), names);
}
private void OnThreadPreProcessMessage(ref MSG msg, ref bool handled)
@@ -138,21 +148,18 @@ namespace v2rayN.Handler
return;
}
handled = true;
var _hotKeyCode = (int)msg.lParam;
var hotKeyCode = (int)msg.lParam;
if (IsPause)
{
Application.Current?.Dispatcher.Invoke(() =>
{
UIElement? element = Keyboard.FocusedElement as UIElement;
if (element != null)
if (Keyboard.FocusedElement is UIElement element)
{
var _keyEventArgs = new KeyEventArgs(Keyboard.PrimaryDevice,
PresentationSource.FromVisual(element), 0,
KeyInterop.KeyFromVirtualKey(GetHotkeyInfo(_hotKeyCode).vKey))
var keyEventArgs = new KeyEventArgs(Keyboard.PrimaryDevice, PresentationSource.FromVisual(element), 0, KeyInterop.KeyFromVirtualKey(GetHotkeyInfo(hotKeyCode).vKey))
{
RoutedEvent = UIElement.KeyDownEvent
};
element.RaiseEvent(_keyEventArgs);
element.RaiseEvent(keyEventArgs);
}
});
}

View File

@@ -1,4 +1,4 @@
<reactiveui:ReactiveWindow
<reactiveui:ReactiveWindow
x:Class="v2rayN.Views.AddServer2Window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -8,7 +8,7 @@
xmlns:reactiveui="http://reactiveui.net"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="v2rayN"
Title="{x:Static resx:ResUI.menuAddCustomServer}"
Width="700"
Height="500"
x:TypeArguments="vms:AddServer2ViewModel"
@@ -198,4 +198,4 @@
</Grid>
</ScrollViewer>
</DockPanel>
</reactiveui:ReactiveWindow>
</reactiveui:ReactiveWindow>

View File

@@ -1,4 +1,4 @@
<reactiveui:ReactiveWindow
<reactiveui:ReactiveWindow
x:Class="v2rayN.Views.AddServerWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -432,6 +432,7 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
@@ -463,6 +464,30 @@
Width="400"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbPorts7}" />
<TextBox
x:Name="txtPorts7"
Grid.Row="3"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
materialDesign:HintAssist.Hint="1000:2000,3000:4000"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="3"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbPorts7Tips}" />
</Grid>
<Grid
x:Name="gridTuic"
@@ -585,6 +610,7 @@
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
materialDesign:HintAssist.Hint="2,3,4"
Style="{StaticResource DefTextBox}" />
<TextBlock
@@ -600,6 +626,7 @@
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
materialDesign:HintAssist.Hint="Ipv4,Ipv6"
Style="{StaticResource DefTextBox}" />
<TextBlock
@@ -616,6 +643,7 @@
Width="200"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
materialDesign:HintAssist.Hint="1500"
Style="{StaticResource DefTextBox}" />
</Grid>
@@ -983,4 +1011,4 @@
</Grid>
</ScrollViewer>
</DockPanel>
</reactiveui:ReactiveWindow>
</reactiveui:ReactiveWindow>

View File

@@ -172,6 +172,7 @@ namespace v2rayN.Views
case EConfigType.Hysteria2:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
break;
case EConfigType.TUIC:

View File

@@ -1,18 +1,17 @@
<reactiveui:ReactiveWindow
x:Class="v2rayN.Views.GlobalHotkeySettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuSetting}"
Title="{x:Static resx:ResUI.menuGlobalHotkeySetting}"
Width="700"
Height="500"
x:TypeArguments="vms:SubEditViewModel"
KeyDown="GlobalHotkeySettingWindow_KeyDown"
x:TypeArguments="vms:GlobalHotkeySettingViewModel"
ShowInTaskbar="False"
Style="{StaticResource WindowGlobal}"
WindowStartupLocation="CenterScreen"
@@ -92,7 +91,6 @@
VerticalAlignment="Center"
AcceptsReturn="True"
IsReadOnly="True"
PreviewKeyDown="TxtGlobalHotkey_PreviewKeyDown"
Style="{StaticResource MyOutlinedTextBox}" />
<TextBlock
@@ -110,7 +108,6 @@
VerticalAlignment="Center"
AcceptsReturn="True"
IsReadOnly="True"
PreviewKeyDown="TxtGlobalHotkey_PreviewKeyDown"
Style="{StaticResource MyOutlinedTextBox}" />
<TextBlock
@@ -128,7 +125,6 @@
VerticalAlignment="Center"
AcceptsReturn="True"
IsReadOnly="True"
PreviewKeyDown="TxtGlobalHotkey_PreviewKeyDown"
Style="{StaticResource MyOutlinedTextBox}" />
<TextBlock
Grid.Row="4"
@@ -145,7 +141,6 @@
VerticalAlignment="Center"
AcceptsReturn="True"
IsReadOnly="True"
PreviewKeyDown="TxtGlobalHotkey_PreviewKeyDown"
Style="{StaticResource MyOutlinedTextBox}" />
<TextBlock
Grid.Row="5"
@@ -162,7 +157,6 @@
VerticalAlignment="Center"
AcceptsReturn="True"
IsReadOnly="True"
PreviewKeyDown="TxtGlobalHotkey_PreviewKeyDown"
Style="{StaticResource MyOutlinedTextBox}" />
</Grid>

View File

@@ -1,140 +1,139 @@
using System.Reactive.Disposables;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using ReactiveUI;
using v2rayN.Handler;
namespace v2rayN.Views
{
public partial class GlobalHotkeySettingWindow
{
private static Config _config = default!;
private Dictionary<object, KeyEventItem> _TextBoxKeyEventItem = default!;
private readonly List<object> _textBoxKeyEventItem = new();
public GlobalHotkeySettingWindow()
{
InitializeComponent();
this.Owner = Application.Current.MainWindow;
_config = AppHandler.Instance.Config;
_config.GlobalHotkeys ??= new List<KeyEventItem>();
ViewModel = new GlobalHotkeySettingViewModel(UpdateViewHandler);
btnReset.Click += btnReset_Click;
btnSave.Click += btnSave_ClickAsync;
txtGlobalHotkey0.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
txtGlobalHotkey1.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
txtGlobalHotkey2.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
txtGlobalHotkey3.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
txtGlobalHotkey4.KeyDown += TxtGlobalHotkey_PreviewKeyDown;
HotkeyHandler.Instance.IsPause = true;
this.Closing += (s, e) => HotkeyHandler.Instance.IsPause = false;
WindowsUtils.SetDarkBorder(this, _config.UiItem.CurrentTheme);
InitData();
}
private void InitData()
{
_TextBoxKeyEventItem = new()
this.WhenActivated(disposables =>
{
{ txtGlobalHotkey0,GetKeyEventItemByEGlobalHotkey(_config.GlobalHotkeys,EGlobalHotkey.ShowForm) },
{ txtGlobalHotkey1,GetKeyEventItemByEGlobalHotkey(_config.GlobalHotkeys,EGlobalHotkey.SystemProxyClear) },
{ txtGlobalHotkey2,GetKeyEventItemByEGlobalHotkey(_config.GlobalHotkeys,EGlobalHotkey.SystemProxySet) },
{ txtGlobalHotkey3,GetKeyEventItemByEGlobalHotkey(_config.GlobalHotkeys,EGlobalHotkey.SystemProxyUnchanged)},
{ txtGlobalHotkey4,GetKeyEventItemByEGlobalHotkey(_config.GlobalHotkeys,EGlobalHotkey.SystemProxyPac)}
};
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});
WindowsUtils.SetDarkBorder(this, AppHandler.Instance.Config.UiItem.CurrentTheme);
Init();
BindingData();
}
private void TxtGlobalHotkey_PreviewKeyDown(object sender, KeyEventArgs e)
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.CloseWindow:
this.DialogResult = true;
break;
}
return await Task.FromResult(true);
}
private void Init()
{
_textBoxKeyEventItem.Add(txtGlobalHotkey0);
_textBoxKeyEventItem.Add(txtGlobalHotkey1);
_textBoxKeyEventItem.Add(txtGlobalHotkey2);
_textBoxKeyEventItem.Add(txtGlobalHotkey3);
_textBoxKeyEventItem.Add(txtGlobalHotkey4);
for (var index = 0; index < _textBoxKeyEventItem.Count; index++)
{
var sender = _textBoxKeyEventItem[index];
if (sender is not TextBox txtBox)
{
continue;
}
txtBox.Tag = (EGlobalHotkey)index;
txtBox.PreviewKeyDown += TxtGlobalHotkey_PreviewKeyDown;
}
}
private void TxtGlobalHotkey_PreviewKeyDown(object? sender, KeyEventArgs e)
{
e.Handled = true;
var _ModifierKeys = new Key[] { Key.LeftCtrl, Key.RightCtrl, Key.LeftShift,
Key.RightShift, Key.LeftAlt, Key.RightAlt, Key.LWin, Key.RWin};
_TextBoxKeyEventItem[sender].KeyCode = (int)(e.Key == Key.System ? (_ModifierKeys.Contains(e.SystemKey) ? Key.None : e.SystemKey) : (_ModifierKeys.Contains(e.Key) ? Key.None : e.Key));
_TextBoxKeyEventItem[sender].Alt = (Keyboard.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt;
_TextBoxKeyEventItem[sender].Control = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;
_TextBoxKeyEventItem[sender].Shift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
(sender as TextBox)!.Text = KeyEventItemToString(_TextBoxKeyEventItem[sender]);
}
private KeyEventItem GetKeyEventItemByEGlobalHotkey(List<KeyEventItem> KEList, EGlobalHotkey eg)
{
return JsonUtils.DeepCopy(KEList.Find((it) => it.EGlobalHotkey == eg) ?? new()
if (sender is not TextBox txtBox)
{
EGlobalHotkey = eg,
Control = false,
Alt = false,
Shift = false,
KeyCode = null
});
}
return;
}
private string KeyEventItemToString(KeyEventItem item)
{
var res = new StringBuilder();
var item = ViewModel?.GetKeyEventItem((EGlobalHotkey)txtBox.Tag);
var modifierKeys = new Key[] { Key.LeftCtrl, Key.RightCtrl, Key.LeftShift, Key.RightShift, Key.LeftAlt, Key.RightAlt, Key.LWin, Key.RWin };
if (item.Control)
res.Append($"{ModifierKeys.Control}+");
if (item.Shift)
res.Append($"{ModifierKeys.Shift}+");
if (item.Alt)
res.Append($"{ModifierKeys.Alt}+");
if (item.KeyCode != null && (Key)item.KeyCode != Key.None)
res.Append($"{(Key)item.KeyCode}");
item.KeyCode = (int)(e.Key == Key.System ? (modifierKeys.Contains(e.SystemKey) ? Key.None : e.SystemKey) : (modifierKeys.Contains(e.Key) ? Key.None : e.Key));
item.Alt = (Keyboard.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt;
item.Control = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;
item.Shift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
return res.ToString();
txtBox.Text = KeyEventItemToString(item);
}
private void BindingData()
{
foreach (var item in _TextBoxKeyEventItem)
foreach (var sender in _textBoxKeyEventItem)
{
if (item.Value.KeyCode != null && (Key)item.Value.KeyCode != Key.None)
if (sender is not TextBox txtBox)
{
(item.Key as TextBox)!.Text = KeyEventItemToString(item.Value);
continue;
}
else
{
(item.Key as TextBox)!.Text = string.Empty;
}
}
}
private async void btnSave_ClickAsync(object sender, RoutedEventArgs e)
{
_config.GlobalHotkeys = _TextBoxKeyEventItem.Values.ToList();
if (await ConfigHandler.SaveConfig(_config) == 0)
{
HotkeyHandler.Instance.ReLoad();
this.DialogResult = true;
}
else
{
UI.Show(ResUI.OperationFailed);
var item = ViewModel?.GetKeyEventItem((EGlobalHotkey)txtBox.Tag);
txtBox.Text = KeyEventItemToString(item);
}
}
private void btnReset_Click(object sender, RoutedEventArgs e)
{
foreach (var k in _TextBoxKeyEventItem.Keys)
{
_TextBoxKeyEventItem[k].Alt = false;
_TextBoxKeyEventItem[k].Control = false;
_TextBoxKeyEventItem[k].Shift = false;
_TextBoxKeyEventItem[k].KeyCode = (int)Key.None;
}
ViewModel?.ResetKeyEventItem();
BindingData();
}
private void GlobalHotkeySettingWindow_KeyDown(object sender, KeyEventArgs e)
private string KeyEventItemToString(KeyEventItem? item)
{
if (e.Key == Key.Escape)
if (item == null)
{
this.Close();
return string.Empty;
}
var res = new StringBuilder();
if (item.Control)
{
res.Append($"{ModifierKeys.Control} +");
}
if (item.Shift)
{
res.Append($"{ModifierKeys.Shift} +");
}
if (item.Alt)
{
res.Append($"{ModifierKeys.Alt} +");
}
if (item.KeyCode != null && (Key)item.KeyCode != Key.None)
{
res.Append($"{(Key)item.KeyCode}");
}
return res.ToString();
}
}
}

View File

@@ -259,7 +259,10 @@ namespace v2rayN.Views
private void ClbdestOverride_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ViewModel.destOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
if (ViewModel != null)
{
ViewModel.destOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
}
}
}
}

View File

@@ -166,7 +166,7 @@ namespace v2rayN.Views
case EViewAction.DispatcherRefreshServersBiz:
Application.Current?.Dispatcher.Invoke((() =>
{
ViewModel?.RefreshServersBiz();
_ = RefreshServersBiz();
}), DispatcherPriority.Normal);
break;
}
@@ -186,9 +186,25 @@ namespace v2rayN.Views
await DialogHost.Show(dialog, "RootDialog");
}
public async Task RefreshServersBiz()
{
if (ViewModel != null)
{
await ViewModel.RefreshServersBiz();
}
if (lstProfiles.SelectedIndex > 0)
{
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, null);
}
}
private void lstProfiles_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ViewModel.SelectedProfiles = lstProfiles.SelectedItems.Cast<ProfileItemModel>().ToList();
if (ViewModel != null)
{
ViewModel.SelectedProfiles = lstProfiles.SelectedItems.Cast<ProfileItemModel>().ToList();
}
}
private void LstProfiles_LoadingRow(object? sender, DataGridRowEventArgs e)

View File

@@ -79,12 +79,18 @@ namespace v2rayN.Views
private void ClbProtocol_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ViewModel.ProtocolItems = clbProtocol.SelectedItems.Cast<string>().ToList();
if (ViewModel != null)
{
ViewModel.ProtocolItems = clbProtocol.SelectedItems.Cast<string>().ToList();
}
}
private void ClbInboundTag_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ViewModel.InboundTagItems = clbInboundTag.SelectedItems.Cast<string>().ToList();
if (ViewModel != null)
{
ViewModel.InboundTagItems = clbInboundTag.SelectedItems.Cast<string>().ToList();
}
}
private void linkRuleobjectDoc_Click(object sender, RoutedEventArgs e)

View File

@@ -162,7 +162,10 @@ namespace v2rayN.Views
private void lstRules_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ViewModel.SelectedSources = lstRules.SelectedItems.Cast<RulesItemModel>().ToList();
if (ViewModel != null)
{
ViewModel.SelectedSources = lstRules.SelectedItems.Cast<RulesItemModel>().ToList();
}
}
private void LstRules_MouseDoubleClick(object sender, MouseButtonEventArgs e)

View File

@@ -113,7 +113,10 @@ namespace v2rayN.Views
private void lstRoutings_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ViewModel.SelectedSources = lstRoutings.SelectedItems.Cast<RoutingItemModel>().ToList();
if (ViewModel != null)
{
ViewModel.SelectedSources = lstRoutings.SelectedItems.Cast<RoutingItemModel>().ToList();
}
}
private void LstRoutings_MouseDoubleClick(object sender, MouseButtonEventArgs e)

View File

@@ -94,7 +94,10 @@ namespace v2rayN.Views
private void LstSubscription_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ViewModel.SelectedSources = lstSubscription.SelectedItems.Cast<SubItem>().ToList();
if (ViewModel != null)
{
ViewModel.SelectedSources = lstSubscription.SelectedItems.Cast<SubItem>().ToList();
}
}
private void menuClose_Click(object sender, System.Windows.RoutedEventArgs e)