Compare commits

..

30 Commits
7.0.0 ... 7.0.5

Author SHA1 Message Date
2dust
0bb6e6bd86 up 7.0.5 2024-11-01 16:54:16 +08:00
2dust
693e8ab157 Add startup auto-start for desktop 2024-11-01 14:54:23 +08:00
2dust
033c16c992 Refactor to select active nodes when updating subscriptions 2024-11-01 10:19:31 +08:00
2dust
6ec61433fb Only update the stable version of the core
https://github.com/2dust/v2rayN/issues/5950
2024-11-01 09:26:15 +08:00
2dust
072f773245 up 7.0.4 2024-10-31 09:59:59 +08:00
2dust
0086f65a96 Bug fix
b7f4fd7469
2024-10-30 08:57:10 +08:00
2dust
281f14f47e Adjustment of preset rule sets 2024-10-28 13:45:39 +08:00
2dust
9fd20ff001 De-duplication after subscription update 2024-10-28 09:38:20 +08:00
2dust
7df90a6034 Bug fix
https://github.com/2dust/v2rayN/issues/5928
2024-10-28 09:16:24 +08:00
2dust
019869ec28 up 7.0.3 2024-10-27 15:01:14 +08:00
2dust
b7f4fd7469 Add font settings for Desktop 2024-10-27 14:59:06 +08:00
2dust
1273d2aee1 Improved font settings 2024-10-27 14:57:45 +08:00
2dust
88990b4828 Bug fix
0efb0b5e3e
2024-10-26 18:00:22 +08:00
2dust
2f02c2970c UseShellExecute = true 2024-10-26 17:55:36 +08:00
2dust
ade789c6d4 Update UpgradeApp.cs 2024-10-26 17:55:08 +08:00
2dust
9738f90970 up 7.0.2 2024-10-26 15:00:57 +08:00
2dust
6ed0741339 Bug fix
https://github.com/2dust/v2rayN/issues/5923
2024-10-26 14:57:28 +08:00
2dust
e6b1e22245 Update UpgradeApp.cs 2024-10-26 14:42:58 +08:00
2dust
6b922be0c6 Set AssemblyName to v2rayN 2024-10-26 10:42:29 +08:00
2dust
6e35a260e8 Bug fix
https://github.com/2dust/v2rayN/issues/5915
2024-10-26 09:55:22 +08:00
2dust
1106fd8cf1 Give upgrade app execute permission at runtime 2024-10-25 20:32:47 +08:00
2dust
fb92b90d5c Bug fix
https://github.com/2dust/v2rayN/issues/5909
2024-10-25 17:58:54 +08:00
2dust
5effbee50b Fix
https://github.com/2dust/v2rayN/issues/5905
2024-10-25 17:41:01 +08:00
2dust
9bc50a9f34 up 7.0.1 2024-10-25 13:50:17 +08:00
2dust
2a5a339c27 Update UpgradeApp.cs 2024-10-25 11:56:15 +08:00
2dust
78d182fff3 Add a warning about the use of insecure HTTP protocol subscription address 2024-10-25 11:06:04 +08:00
2dust
0efb0b5e3e Give core execute permission at runtime 2024-10-25 10:10:55 +08:00
2dust
a2e8755730 Xray Asset Location use XRAY_LOCATION_ASSET 2024-10-25 09:31:16 +08:00
2dust
06ddedbc4c The core folder is all lowercase letters 2024-10-25 09:27:11 +08:00
2dust
fa148cdf42 Bug fix 2024-10-25 09:01:59 +08:00
40 changed files with 717 additions and 555 deletions

View File

@@ -1,6 +1,5 @@
using System.Diagnostics;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text;
namespace AmazTool
@@ -12,7 +11,7 @@ namespace AmazTool
Console.WriteLine(fileName);
Console.WriteLine("In progress, please wait...(正在进行中,请等待)");
Thread.Sleep(5000);
Thread.Sleep(9000);
if (!File.Exists(fileName))
{
@@ -20,18 +19,15 @@ namespace AmazTool
return;
}
Console.WriteLine("Try to end the process(尝试结束进程).");
try
{
Process[] existing = Process.GetProcessesByName(V2rayN());
foreach (Process p in existing)
{
var path = p.MainModule?.FileName ?? "";
if (path.StartsWith(GetPath(V2rayN())))
{
p.Kill();
p.WaitForExit(100);
}
}
var path = GetPath(V2rayN);
Console.WriteLine(path);
var existing = Process.GetProcessesByName(V2rayN);
var pp = existing.FirstOrDefault(p => p.MainModule?.FileName != null && p.MainModule?.FileName.Contains(path) == true);
pp?.Kill();
pp?.WaitForExit(1000);
}
catch (Exception ex)
{
@@ -40,6 +36,7 @@ namespace AmazTool
"Close it manually, or the upgrade may fail.(请手动关闭正在运行的v2rayN否则可能升级失败。\n\n" + ex.StackTrace);
}
Console.WriteLine("Start extracting files(开始解压文件).");
StringBuilder sb = new();
try
{
@@ -57,6 +54,8 @@ namespace AmazTool
continue;
}
Console.WriteLine(entry.FullName);
var lst = entry.FullName.Split(splitKey);
if (lst.Length == 1) continue;
string fullName = string.Join(splitKey, lst[1..lst.Length]);
@@ -81,22 +80,22 @@ namespace AmazTool
catch (Exception ex)
{
Console.WriteLine("Upgrade Failed(升级失败)." + ex.StackTrace);
return;
//return;
}
if (sb.Length > 0)
{
Console.WriteLine("Upgrade Failed.\n" +
"(升级失败)." + sb.ToString());
return;
Console.WriteLine("Upgrade Failed(升级失败)." + sb.ToString());
//return;
}
Console.WriteLine("Start v2rayN, please wait...(正在重启,请等待)");
Thread.Sleep(3000);
Thread.Sleep(9000);
Process process = new()
{
StartInfo = new()
{
FileName = V2rayN(),
UseShellExecute = true,
FileName = V2rayN,
WorkingDirectory = StartupPath()
}
};
@@ -123,19 +122,6 @@ namespace AmazTool
return Path.Combine(startupPath, fileName);
}
private static string V2rayN()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (File.Exists(GetPath("v2rayN.exe")))
return "v2rayN";
else
return "v2rayN.Desktop";
}
else
{
return "v2rayN.Desktop";
}
}
private static string V2rayN => "v2rayN";
}
}

View File

@@ -37,40 +37,41 @@ namespace ServiceLib.Common
foreach (var filePath in files)
{
var file = new FileInfo(filePath);
if (file.CreationTime < now)
if (file.CreationTime >= now) continue;
try
{
try
{
file.Delete();
}
catch { }
file.Delete();
}
catch
{
// ignored
}
}
}
catch { }
catch
{
// ignored
}
});
}
public static void SaveLog(string strContent)
{
if (LogManager.IsLoggingEnabled())
{
var logger = LogManager.GetLogger("Log1");
logger.Info(strContent);
}
if (!LogManager.IsLoggingEnabled()) return;
LogManager.GetLogger("Log1").Info(strContent);
}
public static void SaveLog(string strTitle, Exception ex)
{
if (LogManager.IsLoggingEnabled())
if (!LogManager.IsLoggingEnabled()) return;
var logger = LogManager.GetLogger("Log2");
logger.Debug($"{strTitle},{ex.Message}");
logger.Debug(ex.StackTrace);
if (ex?.InnerException != null)
{
var logger = LogManager.GetLogger("Log2");
logger.Debug($"{strTitle},{ex.Message}");
logger.Debug(ex.StackTrace);
if (ex?.InnerException != null)
{
logger.Error(ex.InnerException);
}
logger.Error(ex.InnerException);
}
}
}

View File

@@ -38,6 +38,7 @@ namespace ServiceLib.Common
{
Logging.SaveLog(ex.Message, ex);
}
return result;
}
@@ -58,6 +59,7 @@ namespace ServiceLib.Common
{
Logging.SaveLog(ex.Message, ex);
}
return null;
}
@@ -92,6 +94,7 @@ namespace ServiceLib.Common
{
Logging.SaveLog(ex.Message, ex);
}
return string.Empty;
}
@@ -116,6 +119,7 @@ namespace ServiceLib.Common
{
Logging.SaveLog(ex.Message, ex);
}
return null;
}
@@ -137,6 +141,7 @@ namespace ServiceLib.Common
{
Logging.SaveLog(ex.Message, ex);
}
return null;
}
@@ -156,6 +161,7 @@ namespace ServiceLib.Common
{
Logging.SaveLog("Base64Encode", ex);
}
return string.Empty;
}
@@ -170,12 +176,12 @@ namespace ServiceLib.Common
{
if (plainText.IsNullOrEmpty()) return "";
plainText = plainText.Trim()
.Replace(Environment.NewLine, "")
.Replace("\n", "")
.Replace("\r", "")
.Replace('_', '/')
.Replace('-', '+')
.Replace(" ", "");
.Replace(Environment.NewLine, "")
.Replace("\n", "")
.Replace("\r", "")
.Replace('_', '/')
.Replace('-', '+')
.Replace(" ", "");
if (plainText.Length % 4 > 0)
{
@@ -189,6 +195,7 @@ namespace ServiceLib.Common
{
Logging.SaveLog("Base64Decode", ex);
}
return string.Empty;
}
@@ -251,14 +258,17 @@ namespace ServiceLib.Common
unit = "TB";
return;
}
result = GBs + ((MBs % factor) / (factor + 0.0));
unit = "GB";
return;
}
result = MBs + ((KBs % factor) / (factor + 0.0));
unit = "MB";
return;
}
result = KBs + ((amount % factor) / (factor + 0.0));
unit = "KB";
return;
@@ -302,6 +312,7 @@ namespace ServiceLib.Common
{
continue;
}
var key = Uri.UnescapeDataString(keyValue[0]);
var val = Uri.UnescapeDataString(keyValue[1]);
@@ -323,6 +334,7 @@ namespace ServiceLib.Common
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
@@ -337,6 +349,7 @@ namespace ServiceLib.Common
{
return url;
}
try
{
Uri uri = new(url);
@@ -368,6 +381,7 @@ namespace ServiceLib.Common
{
return text;
}
return text.Replace("", ",").Replace(Environment.NewLine, ",");
}
@@ -391,6 +405,7 @@ namespace ServiceLib.Common
{
return true;
}
return text == "null";
}
@@ -424,6 +439,7 @@ namespace ServiceLib.Common
_ => false,
};
}
return false;
}
@@ -448,6 +464,7 @@ namespace ServiceLib.Common
if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31) return true;
if (ipBytes[0] == 192 && ipBytes[1] == 168) return true;
}
return false;
}
@@ -468,6 +485,7 @@ namespace ServiceLib.Common
{
Logging.SaveLog(ex.Message, ex);
}
return false;
}
@@ -489,6 +507,7 @@ namespace ServiceLib.Common
catch
{
}
return 59090;
}
@@ -512,7 +531,8 @@ namespace ServiceLib.Common
{
if (blFull)
{
return $"{Global.AppName} - V{GetVersionInfo()} - {RuntimeInformation.ProcessArchitecture} - {File.GetLastWriteTime(GetExePath()):yyyy/MM/dd}";
return
$"{Global.AppName} - V{GetVersionInfo()} - {RuntimeInformation.ProcessArchitecture} - {File.GetLastWriteTime(GetExePath()):yyyy/MM/dd}";
}
else
{
@@ -523,6 +543,7 @@ namespace ServiceLib.Common
{
Logging.SaveLog(ex.Message, ex);
}
return Global.AppName;
}
@@ -560,6 +581,7 @@ namespace ServiceLib.Common
{
Logging.SaveLog(ex.Message, ex);
}
return string.Empty;
}
@@ -572,7 +594,11 @@ namespace ServiceLib.Common
{
try
{
if (fileName.IsNullOrEmpty()) { return; }
if (fileName.IsNullOrEmpty())
{
return;
}
Process.Start(new ProcessStartInfo(fileName, arguments) { UseShellExecute = true });
}
catch (Exception ex)
@@ -605,6 +631,7 @@ namespace ServiceLib.Common
{
Logging.SaveLog(ex.Message, ex);
}
return systemHosts;
}
@@ -629,17 +656,20 @@ namespace ServiceLib.Common
cmd = cmd.WithArguments(args);
}
}
var result = await cmd.ExecuteBufferedAsync();
if (result.IsSuccess)
{
return result.StandardOutput.ToString();
}
Logging.SaveLog(result.ToString() ?? "");
}
catch (Exception ex)
{
Logging.SaveLog("GetCliWrapOutput", ex);
}
return null;
}
@@ -654,6 +684,7 @@ namespace ServiceLib.Common
{
return startupPath;
}
return Path.Combine(startupPath, fileName);
}
@@ -674,6 +705,7 @@ namespace ServiceLib.Common
{
Directory.CreateDirectory(tempPath);
}
if (IsNullOrEmpty(filename))
{
return tempPath;
@@ -691,6 +723,7 @@ namespace ServiceLib.Common
{
Directory.CreateDirectory(tempPath);
}
return Path.Combine(tempPath, filename);
}
@@ -701,6 +734,7 @@ namespace ServiceLib.Common
{
Directory.CreateDirectory(tempPath);
}
if (Utils.IsNullOrEmpty(filename))
{
return tempPath;
@@ -718,14 +752,16 @@ namespace ServiceLib.Common
{
Directory.CreateDirectory(tempPath);
}
if (coreType != null)
{
tempPath = Path.Combine(tempPath, coreType.ToString());
tempPath = Path.Combine(tempPath, coreType.ToLower().ToString());
if (!Directory.Exists(tempPath))
{
Directory.CreateDirectory(tempPath);
}
}
if (IsNullOrEmpty(filename))
{
return tempPath;
@@ -743,6 +779,7 @@ namespace ServiceLib.Common
{
Directory.CreateDirectory(tempPath);
}
if (Utils.IsNullOrEmpty(filename))
{
return tempPath;
@@ -760,6 +797,7 @@ namespace ServiceLib.Common
{
Directory.CreateDirectory(tempPath);
}
if (Utils.IsNullOrEmpty(filename))
{
return tempPath;
@@ -818,6 +856,20 @@ namespace ServiceLib.Common
return await GetCliWrapOutput("/bin/bash", 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);
}
public static string? GetHomePath()
{
return IsWindows()
? Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%")
: Environment.GetEnvironmentVariable("HOME");
}
#endregion Platform
}
}

View File

@@ -0,0 +1,52 @@
using Microsoft.Win32;
namespace ServiceLib.Common
{
internal static class WindowsUtils
{
public static string? RegReadValue(string path, string name, string def)
{
RegistryKey? regKey = null;
try
{
regKey = Registry.CurrentUser.OpenSubKey(path, false);
var value = regKey?.GetValue(name) as string;
return Utils.IsNullOrEmpty(value) ? def : value;
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
finally
{
regKey?.Close();
}
return def;
}
public static void RegWriteValue(string path, string name, object value)
{
RegistryKey? regKey = null;
try
{
regKey = Registry.CurrentUser.CreateSubKey(path);
if (Utils.IsNullOrEmpty(value.ToString()))
{
regKey?.DeleteValue(name, false);
}
else
{
regKey?.SetValue(name, value);
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
finally
{
regKey?.Close();
}
}
}
}

View File

@@ -19,6 +19,7 @@
Shutdown,
BrowseServer,
ImportRulesFromFile,
InitSettingFont,
SubEditWindow,
RoutingRuleSettingWindow,
RoutingRuleDetailsWindow,

View File

@@ -28,22 +28,25 @@
public const string CoreSpeedtestConfigFileName = "configSpeedtest.json";
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
public const string ClashMixinConfigFileName = "Mixin.yaml";
public const string V2raySampleClient = "ServiceLib.Sample.SampleClientConfig";
public const string SingboxSampleClient = "ServiceLib.Sample.SingboxSampleClientConfig";
public const string V2raySampleHttpRequestFileName = "ServiceLib.Sample.SampleHttpRequest";
public const string V2raySampleHttpResponseFileName = "ServiceLib.Sample.SampleHttpResponse";
public const string V2raySampleInbound = "ServiceLib.Sample.SampleInbound";
public const string V2raySampleOutbound = "ServiceLib.Sample.SampleOutbound";
public const string SingboxSampleOutbound = "ServiceLib.Sample.SingboxSampleOutbound";
public const string CustomRoutingFileName = "ServiceLib.Sample.custom_routing_";
public const string TunSingboxDNSFileName = "ServiceLib.Sample.tun_singbox_dns";
public const string TunSingboxInboundFileName = "ServiceLib.Sample.tun_singbox_inbound";
public const string TunSingboxRulesFileName = "ServiceLib.Sample.tun_singbox_rules";
public const string DNSV2rayNormalFileName = "ServiceLib.Sample.dns_v2ray_normal";
public const string DNSSingboxNormalFileName = "ServiceLib.Sample.dns_singbox_normal";
public const string ClashMixinYaml = "ServiceLib.Sample.clash_mixin_yaml";
public const string ClashTunYaml = "ServiceLib.Sample.clash_tun_yaml";
public const string NamespaceSample = "ServiceLib.Sample.";
public const string V2raySampleClient = NamespaceSample + "SampleClientConfig";
public const string SingboxSampleClient = NamespaceSample + "SingboxSampleClientConfig";
public const string V2raySampleHttpRequestFileName = NamespaceSample + "SampleHttpRequest";
public const string V2raySampleHttpResponseFileName = NamespaceSample + "SampleHttpResponse";
public const string V2raySampleInbound = NamespaceSample + "SampleInbound";
public const string V2raySampleOutbound = NamespaceSample + "SampleOutbound";
public const string SingboxSampleOutbound = NamespaceSample + "SingboxSampleOutbound";
public const string CustomRoutingFileName = NamespaceSample + "custom_routing_";
public const string TunSingboxDNSFileName = NamespaceSample + "tun_singbox_dns";
public const string TunSingboxInboundFileName = NamespaceSample + "tun_singbox_inbound";
public const string TunSingboxRulesFileName = NamespaceSample + "tun_singbox_rules";
public const string DNSV2rayNormalFileName = NamespaceSample + "dns_v2ray_normal";
public const string DNSSingboxNormalFileName = NamespaceSample + "dns_singbox_normal";
public const string ClashMixinYaml = NamespaceSample + "clash_mixin_yaml";
public const string ClashTunYaml = NamespaceSample + "clash_tun_yaml";
public const string LinuxAutostartConfig = NamespaceSample + "linux_autostart_config";
public const string DefaultSecurity = "auto";
public const string DefaultNetwork = "tcp";
public const string TcpHeaderHttp = "http";

View File

@@ -71,7 +71,7 @@
public bool InitComponents()
{
Logging.Setup();
Logging.LoggingEnabled(true);
Logging.LoggingEnabled(_config.GuiItem.EnableLog);
Logging.SaveLog($"v2rayN start up | {Utils.GetVersion()} | {Utils.GetExePath()}");
Logging.SaveLog($"{Environment.OSVersion} - {(Environment.Is64BitOperatingSystem ? 64 : 32)}");
Logging.ClearLogs();
@@ -159,7 +159,7 @@
await ConfigHandler.SetDefaultServer(_config, lstModel);
var lstServerStat = (_config.GuiItem.EnableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? [];
var lstProfileExs = ProfileExHandler.Instance.ProfileExs;
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs();
lstModel = (from t in lstModel
join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b
from t22 in t2b.DefaultIfEmpty()

View File

@@ -0,0 +1,148 @@
using System.Security.Principal;
using System.Text.RegularExpressions;
namespace ServiceLib.Handler
{
public static class AutoStartupHandler
{
public static async Task<bool> UpdateTask(Config config)
{
if (Utils.IsWindows())
{
await ClearTaskWindows();
if (config.GuiItem.AutoRun)
{
await SetTaskWindows();
}
}
else if (Utils.IsLinux())
{
await ClearTaskLinux();
if (config.GuiItem.AutoRun)
{
await SetTaskLinux();
}
}
return true;
}
#region Windows
private static async Task ClearTaskWindows()
{
var autoRunName = GetAutoRunNameWindows();
WindowsUtils.RegWriteValue(Global.AutoRunRegPath, autoRunName, "");
if (Utils.IsAdministrator())
{
AutoStartTaskService(autoRunName, "", "");
}
}
private static async Task SetTaskWindows()
{
try
{
var autoRunName = GetAutoRunNameWindows();
var exePath = Utils.GetExePath();
if (Utils.IsAdministrator())
{
AutoStartTaskService(autoRunName, exePath, "");
}
else
{
WindowsUtils.RegWriteValue(Global.AutoRunRegPath, autoRunName, exePath.AppendQuotes());
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
/// <summary>
/// Auto Start via TaskService
/// </summary>
/// <param name="taskName"></param>
/// <param name="fileName"></param>
/// <param name="description"></param>
/// <exception cref="ArgumentNullException"></exception>
public static void AutoStartTaskService(string taskName, string fileName, string description)
{
if (Utils.IsNullOrEmpty(taskName))
{
return;
}
var logonUser = WindowsIdentity.GetCurrent().Name;
using var taskService = new Microsoft.Win32.TaskScheduler.TaskService();
var tasks = taskService.RootFolder.GetTasks(new Regex(taskName));
if (Utils.IsNullOrEmpty(fileName))
{
foreach (var t in tasks)
{
taskService.RootFolder.DeleteTask(t.Name);
}
return;
}
var task = taskService.NewTask();
task.RegistrationInfo.Description = description;
task.Settings.DisallowStartIfOnBatteries = false;
task.Settings.StopIfGoingOnBatteries = false;
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.Principal.RunLevel = Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest;
task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName)));
taskService.RootFolder.RegisterTaskDefinition(taskName, task);
}
private static string GetAutoRunNameWindows()
{
return $"{Global.AutoRunName}_{Utils.GetMd5(Utils.StartupPath())}";
}
#endregion Windows
#region Linux
private static async Task ClearTaskLinux()
{
File.Delete(GetHomePathLinux());
}
private static async Task SetTaskLinux()
{
try
{
var linuxConfig = Utils.GetEmbedText(Global.LinuxAutostartConfig);
if (linuxConfig.IsNotEmpty())
{
linuxConfig = linuxConfig.Replace("$ExecPath$", Utils.GetExePath());
Logging.SaveLog(linuxConfig);
var homePath = GetHomePathLinux();
Directory.CreateDirectory(Path.GetDirectoryName(homePath));
await File.WriteAllTextAsync(homePath, linuxConfig);
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
private static string GetHomePathLinux()
{
return Path.Combine(Utils.GetHomePath(), ".config", "autostart", $"{Global.AppName}.desktop");
}
#endregion Linux
}
}

View File

@@ -28,7 +28,7 @@ namespace ServiceLib.Handler
return new Tuple<ClashProxies, ClashProviders>(clashProxies, clashProviders);
}
await Task.Delay(5000);
await Task.Delay(2000);
}
return null;

View File

@@ -746,7 +746,7 @@ namespace ServiceLib.Handler
{
return -1;
}
var lstProfileExs = ProfileExHandler.Instance.ProfileExs;
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs();
var lstProfile = (from t in lstModel
join t3 in lstProfileExs on t.IndexId equals t3.IndexId into t3b
from t33 in t3b.DefaultIfEmpty()
@@ -1034,14 +1034,14 @@ namespace ServiceLib.Handler
/// <param name="strData"></param>
/// <param name="subid"></param>
/// <returns>成功导入的数量</returns>
private static async Task<int> AddBatchServers(Config config, string strData, string subid, bool isSub, List<ProfileItem> lstOriSub)
private static async Task<int> AddBatchServersCommon(Config config, string strData, string subid, bool isSub)
{
if (Utils.IsNullOrEmpty(strData))
{
return -1;
}
string subFilter = string.Empty;
var subFilter = string.Empty;
//remove sub items
if (isSub && Utils.IsNotEmpty(subid))
{
@@ -1049,16 +1049,14 @@ namespace ServiceLib.Handler
subFilter = (await AppHandler.Instance.GetSubItem(subid))?.Filter ?? "";
}
int countServers = 0;
//Check for duplicate indexId
List<string>? lstDbIndexId = null;
var countServers = 0;
List<ProfileItem> lstAdd = new();
var arrData = strData.Split(Environment.NewLine.ToCharArray()).Where(t => !t.IsNullOrEmpty());
if (isSub)
{
arrData = arrData.Distinct();
}
foreach (string str in arrData)
foreach (var str in arrData)
{
//maybe sub
if (!isSub && (str.StartsWith(Global.HttpsProtocol) || str.StartsWith(Global.HttpProtocol)))
@@ -1075,35 +1073,12 @@ namespace ServiceLib.Handler
continue;
}
//exist sub items
if (isSub && Utils.IsNotEmpty(subid))
//exist sub items //filter
if (isSub && Utils.IsNotEmpty(subid) && Utils.IsNotEmpty(subFilter))
{
var existItem = lstOriSub?.FirstOrDefault(t => t.IsSub == isSub
&& config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == profileItem.Remarks : CompareProfileItem(t, profileItem, true));
if (existItem != null)
if (!Regex.IsMatch(profileItem.Remarks, subFilter))
{
//Check for duplicate indexId
if (lstDbIndexId is null)
{
lstDbIndexId = await AppHandler.Instance.ProfileItemIndexes("");
}
if (lstAdd.Any(t => t.IndexId == existItem.IndexId)
|| lstDbIndexId.Any(t => t == existItem.IndexId))
{
profileItem.IndexId = string.Empty;
}
else
{
profileItem.IndexId = existItem.IndexId;
}
}
//filter
if (Utils.IsNotEmpty(subFilter))
{
if (!Regex.IsMatch(profileItem.Remarks, subFilter))
{
continue;
}
continue;
}
}
profileItem.Subid = subid;
@@ -1138,7 +1113,7 @@ namespace ServiceLib.Handler
return countServers;
}
private static async Task<int> AddBatchServers4Custom(Config config, string strData, string subid, bool isSub, List<ProfileItem> lstOriSub)
private static async Task<int> AddBatchServers4Custom(Config config, string strData, string subid, bool isSub)
{
if (Utils.IsNullOrEmpty(strData))
{
@@ -1222,10 +1197,7 @@ namespace ServiceLib.Handler
{
await RemoveServerViaSubid(config, subid, isSub);
}
if (isSub && lstOriSub?.Count == 1)
{
profileItem.IndexId = lstOriSub[0].IndexId;
}
profileItem.Subid = subid;
profileItem.IsSub = isSub;
profileItem.PreSocksPort = preSocksPort;
@@ -1239,7 +1211,7 @@ namespace ServiceLib.Handler
}
}
private static async Task<int> AddBatchServers4SsSIP008(Config config, string strData, string subid, bool isSub, List<ProfileItem> lstOriSub)
private static async Task<int> AddBatchServers4SsSIP008(Config config, string strData, string subid, bool isSub)
{
if (Utils.IsNullOrEmpty(strData))
{
@@ -1278,34 +1250,47 @@ namespace ServiceLib.Handler
return -1;
}
List<ProfileItem>? lstOriSub = null;
ProfileItem? activeProfile = null;
if (isSub && Utils.IsNotEmpty(subid))
{
lstOriSub = await AppHandler.Instance.ProfileItems(subid);
activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId);
}
var counter = 0;
if (Utils.IsBase64String(strData))
{
counter = await AddBatchServers(config, Utils.Base64Decode(strData), subid, isSub, lstOriSub);
counter = await AddBatchServersCommon(config, Utils.Base64Decode(strData), subid, isSub);
}
if (counter < 1)
{
counter = await AddBatchServers(config, strData, subid, isSub, lstOriSub);
counter = await AddBatchServersCommon(config, strData, subid, isSub);
}
if (counter < 1)
{
counter = await AddBatchServers(config, Utils.Base64Decode(strData), subid, isSub, lstOriSub);
counter = await AddBatchServersCommon(config, Utils.Base64Decode(strData), subid, isSub);
}
if (counter < 1)
{
counter = await AddBatchServers4SsSIP008(config, strData, subid, isSub, lstOriSub);
counter = await AddBatchServers4SsSIP008(config, strData, subid, isSub);
}
//maybe other sub
if (counter < 1)
{
counter = await AddBatchServers4Custom(config, strData, subid, isSub, lstOriSub);
counter = await AddBatchServers4Custom(config, strData, subid, isSub);
}
//Select active node
if (activeProfile != null)
{
var lstSub = await AppHandler.Instance.ProfileItems(subid);
var existItem = lstSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == activeProfile.Remarks : CompareProfileItem(t, activeProfile, true));
if (existItem != null)
{
await ConfigHandler.SetDefaultServerIndex(config, existItem.IndexId);
}
}
return counter;
@@ -1340,7 +1325,9 @@ namespace ServiceLib.Handler
//Do not allow http protocol
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
{
return -1;
//TODO Temporary reminder to be removed later
NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol);
//return -1;
}
var queryVars = Utils.ParseQueryString(uri.Query);

View File

@@ -12,18 +12,12 @@
if (node.ConfigType == EConfigType.Custom)
{
if (node.CoreType is ECoreType.mihomo)
result = node.CoreType switch
{
result = await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName);
}
if (node.CoreType is ECoreType.sing_box)
{
result = await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName);
}
else
{
result = await GenerateClientCustomConfig(node, fileName);
}
ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName),
ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName),
_ => await GenerateClientCustomConfig(node, fileName)
};
}
else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
{

View File

@@ -15,13 +15,38 @@ namespace ServiceLib.Handler
private Process? _processPre;
private Action<bool, string>? _updateFunc;
public void Init(Config config, Action<bool, string> updateFunc)
public async Task Init(Config config, Action<bool, string> updateFunc)
{
_config = config;
_updateFunc = updateFunc;
Environment.SetEnvironmentVariable("v2ray.location.asset", Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("xray.location.asset", Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("V2RAY_LOCATION_ASSET", Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("XRAY_LOCATION_ASSET", Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
if (Utils.IsLinux())
{
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo();
foreach (var it in coreInfo)
{
if (it.CoreType == ECoreType.v2rayN)
{
if (Utils.UpgradeAppExists(out var fileName))
{
await Utils.SetLinuxChmod(fileName);
}
continue;
}
foreach (var name in it.CoreExes)
{
var exe = Utils.GetBinPath(Utils.GetExeName(name), it.CoreType.ToString());
if (File.Exists(exe))
{
await Utils.SetLinuxChmod(exe);
}
}
}
}
}
public async Task LoadCore(ProfileItem? node)
@@ -109,17 +134,12 @@ namespace ServiceLib.Handler
{
continue;
}
foreach (string vName in it.CoreExes)
foreach (var name in it.CoreExes)
{
var existing = Process.GetProcessesByName(vName);
foreach (Process p in existing)
{
string? path = p.MainModule?.FileName;
if (path == Utils.GetExeName(Utils.GetBinPath(vName, it.CoreType.ToString())))
{
await KillProcess(p);
}
}
var path = Utils.GetBinPath(Utils.GetExeName(name), it.CoreType.ToString());
var existing = Process.GetProcessesByName(name);
var pp = existing.FirstOrDefault(p => p.MainModule?.FileName != null && p.MainModule?.FileName.Contains(path) == true);
await KillProcess(pp);
}
}
}
@@ -148,10 +168,9 @@ namespace ServiceLib.Handler
private string CoreFindExe(CoreInfo coreInfo)
{
string fileName = string.Empty;
foreach (string name in coreInfo.CoreExes)
foreach (var name in coreInfo.CoreExes)
{
string vName = Utils.GetExeName(name);
vName = Utils.GetBinPath(vName, coreInfo.CoreType.ToString());
var vName = Utils.GetBinPath(Utils.GetExeName(name), coreInfo.CoreType.ToString());
if (File.Exists(vName))
{
fileName = vName;

View File

@@ -9,27 +9,31 @@ namespace ServiceLib.Handler
private static readonly Lazy<ProfileExHandler> _instance = new(() => new());
private ConcurrentBag<ProfileExItem> _lstProfileEx = [];
private Queue<string> _queIndexIds = new();
public ConcurrentBag<ProfileExItem> ProfileExs => _lstProfileEx;
public static ProfileExHandler Instance => _instance.Value;
public ProfileExHandler()
{
Init();
//Init();
}
private async Task Init()
public async Task Init()
{
await InitData();
await Task.Run(async () =>
Task.Run(async () =>
{
while (true)
{
await SaveQueueIndexIds();
await Task.Delay(1000 * 600);
await SaveQueueIndexIds();
}
});
}
public async Task<ConcurrentBag<ProfileExItem>> GetProfileExs()
{
return _lstProfileEx;
}
private async Task InitData()
{
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileExItem where indexId not in ( select indexId from ProfileItem )");

View File

@@ -11,24 +11,24 @@ namespace ServiceLib.Handler.SysProxy
{
if (type == 1)
{
RegWriteValue(_regPath, "ProxyEnable", 0);
RegWriteValue(_regPath, "ProxyServer", string.Empty);
RegWriteValue(_regPath, "ProxyOverride", string.Empty);
RegWriteValue(_regPath, "AutoConfigURL", string.Empty);
WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 0);
WindowsUtils.RegWriteValue(_regPath, "ProxyServer", string.Empty);
WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", string.Empty);
WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", string.Empty);
}
if (type == 2)
{
RegWriteValue(_regPath, "ProxyEnable", 1);
RegWriteValue(_regPath, "ProxyServer", strProxy ?? string.Empty);
RegWriteValue(_regPath, "ProxyOverride", exceptions ?? string.Empty);
RegWriteValue(_regPath, "AutoConfigURL", string.Empty);
WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 1);
WindowsUtils.RegWriteValue(_regPath, "ProxyServer", strProxy ?? string.Empty);
WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", exceptions ?? string.Empty);
WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", string.Empty);
}
else if (type == 4)
{
RegWriteValue(_regPath, "ProxyEnable", 0);
RegWriteValue(_regPath, "ProxyServer", string.Empty);
RegWriteValue(_regPath, "ProxyOverride", string.Empty);
RegWriteValue(_regPath, "AutoConfigURL", strProxy ?? string.Empty);
WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 0);
WindowsUtils.RegWriteValue(_regPath, "ProxyServer", string.Empty);
WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", string.Empty);
WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", strProxy ?? string.Empty);
}
return true;
}
@@ -356,30 +356,5 @@ namespace ServiceLib.Handler.SysProxy
ref int lpcEntries // Number of entries written to the buffer
);
}
private static void RegWriteValue(string path, string name, object value)
{
Microsoft.Win32.RegistryKey? regKey = null;
try
{
regKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(path);
if (string.IsNullOrEmpty(value.ToString()))
{
regKey?.DeleteValue(name, false);
}
else
{
regKey?.SetValue(name, value);
}
}
catch (Exception ex)
{
//Logging.SaveLog(ex.Message, ex);
}
finally
{
regKey?.Close();
}
}
}
}

View File

@@ -88,6 +88,8 @@
public int TrayMenuServersLimit { get; set; } = 20;
public bool EnableHWA { get; set; } = false;
public bool EnableLog { get; set; } = true;
}
[Serializable]

View File

@@ -294,6 +294,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Please do not use the insecure HTTP protocol subscription address 的本地化字符串。
/// </summary>
public static string InsecureUrlProtocol {
get {
return ResourceManager.GetString("InsecureUrlProtocol", resourceCulture);
}
}
/// <summary>
/// 查找类似 Invalid address (Url) 的本地化字符串。
/// </summary>
public static string InvalidUrlTip {
get {
return ResourceManager.GetString("InvalidUrlTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 {0} {1} already up to date. 的本地化字符串。
/// </summary>
@@ -2842,6 +2860,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Install the font to the system and restart the settings 的本地化字符串。
/// </summary>
public static string TbSettingsCurrentFontFamilyLinuxTip {
get {
return ResourceManager.GetString("TbSettingsCurrentFontFamilyLinuxTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 Copy the font TTF/TTC file to the directory guiFonts, restart the settings 的本地化字符串。
/// </summary>

View File

@@ -1354,4 +1354,13 @@
<data name="menuAddServerViaImage" xml:space="preserve">
<value>Scan QR code in the image</value>
</data>
<data name="InvalidUrlTip" xml:space="preserve">
<value>Invalid address (Url)</value>
</data>
<data name="InsecureUrlProtocol" xml:space="preserve">
<value>Please do not use the insecure HTTP protocol subscription address</value>
</data>
<data name="TbSettingsCurrentFontFamilyLinuxTip" xml:space="preserve">
<value>Install the font to the system and restart the settings</value>
</data>
</root>

View File

@@ -1351,4 +1351,13 @@
<data name="menuAddServerViaImage" xml:space="preserve">
<value>扫描图片中的二维码</value>
</data>
<data name="InvalidUrlTip" xml:space="preserve">
<value>地址(Url)无效</value>
</data>
<data name="InsecureUrlProtocol" xml:space="preserve">
<value>请不要使用不安全的HTTP协议订阅地址</value>
</data>
<data name="TbSettingsCurrentFontFamilyLinuxTip" xml:space="preserve">
<value>安装字体到系统中,重启设置</value>
</data>
</root>

View File

@@ -1231,4 +1231,13 @@
<data name="menuAddServerViaImage" xml:space="preserve">
<value>掃描圖片中的二維碼</value>
</data>
<data name="InvalidUrlTip" xml:space="preserve">
<value>地址(Url)無效</value>
</data>
<data name="InsecureUrlProtocol" xml:space="preserve">
<value>請不要使用不安全的HTTP協定訂閱位址</value>
</data>
<data name="TbSettingsCurrentFontFamilyLinuxTip" xml:space="preserve">
<value>安裝字體到系統中,重新啟動設定</value>
</data>
</root>

View File

@@ -42,15 +42,7 @@
]
},
{
"remarks": "代理GFW",
"outboundTag": "proxy",
"domain": [
"geosite:gfw",
"geosite:greatfire"
]
},
{
"remarks": "代理Google等",
"remarks": "代理IP",
"outboundTag": "proxy",
"ip": [
"1.0.0.1",
@@ -65,6 +57,14 @@
"geoip:twitter"
]
},
{
"remarks": "代理GFW",
"outboundTag": "proxy",
"domain": [
"geosite:gfw",
"geosite:greatfire"
]
},
{
"remarks": "最终直连",
"port": "0-65535",

View File

@@ -34,19 +34,6 @@
"geosite:private"
]
},
{
"remarks": "绕过中国域名",
"outboundTag": "direct",
"domain": [
"domain:dns.alidns.com",
"domain:doh.pub",
"domain:dot.pub",
"domain:doh.360.cn",
"domain:dot.360.cn",
"geosite:cn",
"geosite:geolocation-cn"
]
},
{
"remarks": "绕过中国IP",
"outboundTag": "direct",
@@ -73,6 +60,19 @@
"geoip:cn"
]
},
{
"remarks": "绕过中国域名",
"outboundTag": "direct",
"domain": [
"domain:dns.alidns.com",
"domain:doh.pub",
"domain:dot.pub",
"domain:doh.360.cn",
"domain:dot.360.cn",
"geosite:cn",
"geosite:geolocation-cn"
]
},
{
"remarks": "最终代理",
"port": "0-65535",

View File

@@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Exec=$ExecPath$
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name[en_US]=v2rayN
Name=v2rayN
Comment[en_US]=v2rayN
Comment=v2rayN

View File

@@ -4,9 +4,9 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>7.0.0</Version>
<Version>7.0.5</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Downloader" Version="3.2.1" />
<PackageReference Include="ReactiveUI" Version="20.1.63" />
@@ -16,9 +16,11 @@
<PackageReference Include="WebDav.Client" Version="2.8.0" />
<PackageReference Include="YamlDotNet" Version="16.1.3" />
<PackageReference Include="QRCoder" Version="1.6.0" />
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="CliWrap" Version="3.6.7" />
<PackageReference Include="SkiaSharp.QrCode" Version="0.7.0" />
<PackageReference Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />
<PackageReference Include="TaskScheduler" Version="2.11.0" />
</ItemGroup>
<ItemGroup>
@@ -41,6 +43,7 @@
<EmbeddedResource Include="Sample\tun_singbox_dns" />
<EmbeddedResource Include="Sample\tun_singbox_inbound" />
<EmbeddedResource Include="Sample\tun_singbox_rules" />
<EmbeddedResource Include="Sample\linux_autostart_config" />
</ItemGroup>

View File

@@ -114,7 +114,7 @@ namespace ServiceLib.Services
_updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart);
var subItem = await AppHandler.Instance.SubItems();
if (subItem == null || subItem.Count <= 0)
if (subItem is not { Count: > 0 })
{
_updateFunc?.Invoke(false, ResUI.MsgNoValidSubscription);
return;
@@ -122,10 +122,10 @@ namespace ServiceLib.Services
foreach (var item in subItem)
{
string id = item.Id.TrimEx();
string url = item.Url.TrimEx();
string userAgent = item.UserAgent.TrimEx();
string hashCode = $"{item.Remarks}->";
var id = item.Id.TrimEx();
var url = item.Url.TrimEx();
var userAgent = item.UserAgent.TrimEx();
var hashCode = $"{item.Remarks}->";
if (Utils.IsNullOrEmpty(id) || Utils.IsNullOrEmpty(url) || Utils.IsNotEmpty(subId) && item.Id != subId)
{
//_updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgNoValidSubscription}");
@@ -219,7 +219,7 @@ namespace ServiceLib.Services
_updateFunc?.Invoke(false, $"{hashCode}{result}");
}
int ret = await ConfigHandler.AddBatchServers(config, result, id, true);
var ret = await ConfigHandler.AddBatchServers(config, result, id, true);
if (ret <= 0)
{
Logging.SaveLog("FailedImportSubscription");
@@ -231,6 +231,8 @@ namespace ServiceLib.Services
: $"{hashCode}{ResUI.MsgFailedImportSubscription}");
}
_updateFunc?.Invoke(false, "-------------------------------------------------------");
await ConfigHandler.DedupServerList(config, id);
}
_updateFunc?.Invoke(true, $"{ResUI.MsgUpdateSubscriptionEnd}");
@@ -309,10 +311,9 @@ namespace ServiceLib.Services
{
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
string filePath = string.Empty;
foreach (string name in coreInfo.CoreExes)
foreach (var name in coreInfo.CoreExes)
{
string vName = Utils.GetExeName(name);
vName = Utils.GetBinPath(vName, coreInfo.CoreType.ToString());
var vName = Utils.GetBinPath(Utils.GetExeName(name), coreInfo.CoreType.ToString());
if (File.Exists(vName))
{
filePath = vName;

View File

@@ -99,13 +99,13 @@ namespace ServiceLib.ViewModels
{
await CheckUpdateN(EnableCheckPreReleaseUpdate);
}
else if (item.CoreType == ECoreType.mihomo.ToString())
else if (item.CoreType == ECoreType.Xray.ToString())
{
await CheckUpdateCore(item, false);
await CheckUpdateCore(item, EnableCheckPreReleaseUpdate);
}
else
{
await CheckUpdateCore(item, EnableCheckPreReleaseUpdate);
await CheckUpdateCore(item, false);
}
}

View File

@@ -96,7 +96,7 @@ namespace ServiceLib.ViewModels
private async Task Init()
{
await ProxiesReload();
await DelayTestTask();
DelayTestTask();
}
private async Task DoRulemodeSelected(bool c)
@@ -434,25 +434,29 @@ namespace ServiceLib.ViewModels
public async Task DelayTestTask()
{
var lastTime = DateTime.Now;
Task.Run(async () =>
{
while (true)
{
await Task.Delay(1000 * 60);
Observable.Interval(TimeSpan.FromSeconds(60))
.Subscribe(async x =>
{
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
{
return;
}
var dtNow = DateTime.Now;
if (_config.ClashUIItem.ProxiesAutoDelayTestInterval > 0)
{
if ((dtNow - lastTime).Minutes % _config.ClashUIItem.ProxiesAutoDelayTestInterval == 0)
{
await ProxiesDelayTest();
lastTime = dtNow;
}
Task.Delay(1000).Wait();
}
});
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
{
continue;
}
if (_config.ClashUIItem.ProxiesAutoDelayTestInterval <= 0)
{
continue;
}
var dtNow = DateTime.Now;
if ((dtNow - lastTime).Minutes % _config.ClashUIItem.ProxiesAutoDelayTestInterval != 0)
{
continue;
}
await ProxiesDelayTest();
lastTime = dtNow;
}
});
}
#endregion task

View File

@@ -208,7 +208,8 @@ namespace ServiceLib.ViewModels
await ConfigHandler.InitBuiltinRouting(_config);
await ConfigHandler.InitBuiltinDNS(_config);
CoreHandler.Instance.Init(_config, UpdateHandler);
await ProfileExHandler.Instance.Init();
await CoreHandler.Instance.Init(_config, UpdateHandler);
TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler);
if (_config.GuiItem.EnableStatistics)
@@ -312,6 +313,7 @@ namespace ServiceLib.ViewModels
{
StartInfo = new ProcessStartInfo
{
UseShellExecute = true,
FileName = fileName,
Arguments = arg.AppendQuotes(),
WorkingDirectory = Utils.StartupPath()
@@ -389,6 +391,10 @@ namespace ServiceLib.ViewModels
RefreshServers();
NoticeHandler.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
}
else
{
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
}
}
public async Task AddServerViaScanAsync()
@@ -433,6 +439,10 @@ namespace ServiceLib.ViewModels
RefreshServers();
NoticeHandler.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
}
else
{
NoticeHandler.Instance.Enqueue(ResUI.OperationFailed);
}
}
}

View File

@@ -117,6 +117,8 @@ namespace ServiceLib.ViewModels
private async Task Init()
{
await _updateView?.Invoke(EViewAction.InitSettingFont, null);
#region Core
var inbound = _config.Inbound[0];
@@ -347,14 +349,9 @@ namespace ServiceLib.ViewModels
if (await ConfigHandler.SaveConfig(_config) == 0)
{
if (needReboot)
{
NoticeHandler.Instance.Enqueue(ResUI.NeedRebootTips);
}
else
{
NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
}
await AutoStartupHandler.UpdateTask(_config);
NoticeHandler.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess);
_updateView?.Invoke(EViewAction.CloseWindow, null);
}
else

View File

@@ -176,13 +176,14 @@ namespace ServiceLib.ViewModels
return;
}
var lst = new List<RulesItem4Ray>();
var lst = new List<RulesItem>();
foreach (var it in SelectedSources ?? [SelectedSource])
{
var item = _rules.FirstOrDefault(t => t.Id == it?.Id);
if (item != null)
{
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
var item2 = JsonUtils.DeepCopy(item); //JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
item2.Id = null;
lst.Add(item2 ?? new());
}
}

View File

@@ -104,6 +104,18 @@ namespace ServiceLib.ViewModels
public StatusBarViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{
_config = AppHandler.Instance.Config;
SelectedRouting = new();
SelectedServer = new();
RunningServerToolTipText = "-";
if (_config.TunModeItem.EnableTun && AppHandler.Instance.IsAdministrator)
{
EnableTun = true;
}
else
{
_config.TunModeItem.EnableTun = EnableTun = false;
}
#region WhenAnyValue && ReactiveCommand
@@ -179,19 +191,6 @@ namespace ServiceLib.ViewModels
private async Task Init()
{
SelectedRouting = new();
SelectedServer = new();
RunningServerToolTipText = "-";
if (_config.TunModeItem.EnableTun && AppHandler.Instance.IsAdministrator)
{
EnableTun = true;
}
else
{
_config.TunModeItem.EnableTun = EnableTun = false;
}
await RefreshRoutingsMenu();
await InboundDisplayStatus();
await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, true);

View File

@@ -39,14 +39,14 @@ namespace ServiceLib.ViewModels
var uri = Utils.TryUri(url);
if (uri == null)
{
NoticeHandler.Instance.Enqueue(ResUI.LvUrl);
NoticeHandler.Instance.Enqueue(ResUI.InvalidUrlTip);
return;
}
//Do not allow http protocol
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
{
NoticeHandler.Instance.Enqueue(ResUI.LvUrl);
return;
NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol);
//return;
}
}

View File

@@ -1,5 +1,8 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using Avalonia.Styling;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
@@ -9,14 +12,11 @@ namespace v2rayN.Desktop.ViewModels
{
public class ThemeSettingViewModel : MyReactiveObject
{
[Reactive]
public bool ColorModeDark { get; set; }
[Reactive] public bool ColorModeDark { get; set; }
[Reactive]
public int CurrentFontSize { get; set; }
[Reactive] public int CurrentFontSize { get; set; }
[Reactive]
public string CurrentLanguage { get; set; }
[Reactive] public string CurrentLanguage { get; set; }
public ThemeSettingViewModel()
{
@@ -29,6 +29,7 @@ namespace v2rayN.Desktop.ViewModels
private void RestoreUI()
{
ModifyTheme(_config.UiItem.ColorModeDark);
ModifyFontFamily();
}
private void BindingUI()
@@ -38,34 +39,34 @@ namespace v2rayN.Desktop.ViewModels
CurrentLanguage = _config.UiItem.CurrentLanguage;
this.WhenAnyValue(x => x.ColorModeDark)
.Subscribe(c =>
{
if (_config.UiItem.ColorModeDark != ColorModeDark)
{
_config.UiItem.ColorModeDark = ColorModeDark;
ModifyTheme(ColorModeDark);
ConfigHandler.SaveConfig(_config);
}
});
.Subscribe(c =>
{
if (_config.UiItem.ColorModeDark != ColorModeDark)
{
_config.UiItem.ColorModeDark = ColorModeDark;
ModifyTheme(ColorModeDark);
ConfigHandler.SaveConfig(_config);
}
});
this.WhenAnyValue(
x => x.CurrentFontSize,
y => y > 0)
.Subscribe(c =>
{
if (CurrentFontSize >= Global.MinFontSize)
{
_config.UiItem.CurrentFontSize = CurrentFontSize;
double size = CurrentFontSize;
ModifyFontSize(size);
x => x.CurrentFontSize,
y => y > 0)
.Subscribe(c =>
{
if (CurrentFontSize >= Global.MinFontSize)
{
_config.UiItem.CurrentFontSize = CurrentFontSize;
double size = CurrentFontSize;
ModifyFontSize(size);
ConfigHandler.SaveConfig(_config);
}
});
ConfigHandler.SaveConfig(_config);
}
});
this.WhenAnyValue(
x => x.CurrentLanguage,
y => y != null && !y.IsNullOrEmpty())
x => x.CurrentLanguage,
y => y != null && !y.IsNullOrEmpty())
.Subscribe(c =>
{
if (Utils.IsNotEmpty(CurrentLanguage) && _config.UiItem.CurrentLanguage != CurrentLanguage)
@@ -89,53 +90,54 @@ namespace v2rayN.Desktop.ViewModels
private void ModifyFontSize(double size)
{
Style buttonStyle = new(x => x.OfType<Button>());
buttonStyle.Add(new Setter()
Style style = new(x => Selectors.Or(
x.OfType<Button>(),
x.OfType<TextBox>(),
x.OfType<TextBlock>(),
x.OfType<Menu>(),
x.OfType<ContextMenu>(),
x.OfType<DataGridRow>(),
x.OfType<ListBoxItem>()
));
style.Add(new Setter()
{
Property = Button.FontSizeProperty,
Property = TemplatedControl.FontSizeProperty,
Value = size,
});
Application.Current?.Styles.Add(buttonStyle);
Application.Current?.Styles.Add(style);
}
Style textStyle = new(x => x.OfType<TextBox>());
textStyle.Add(new Setter()
private void ModifyFontFamily()
{
var currentFontFamily = _config.UiItem.CurrentFontFamily;
if (currentFontFamily.IsNullOrEmpty())
{
Property = TextBox.FontSizeProperty,
Value = size,
});
Application.Current?.Styles.Add(textStyle);
return;
}
Style textBlockStyle = new(x => x.OfType<TextBlock>());
textBlockStyle.Add(new Setter()
try
{
Property = TextBlock.FontSizeProperty,
Value = size,
});
Application.Current?.Styles.Add(textBlockStyle);
Style menuStyle = new(x => x.OfType<Menu>());
menuStyle.Add(new Setter()
Style style = new(x => Selectors.Or(
x.OfType<Button>(),
x.OfType<TextBox>(),
x.OfType<TextBlock>(),
x.OfType<Menu>(),
x.OfType<ContextMenu>(),
x.OfType<DataGridRow>(),
x.OfType<ListBoxItem>(),
x.OfType<WindowNotificationManager>()
));
style.Add(new Setter()
{
Property = TemplatedControl.FontFamilyProperty,
Value = new FontFamily(currentFontFamily),
});
Application.Current?.Styles.Add(style);
}
catch (Exception ex)
{
Property = Menu.FontSizeProperty,
Value = size,
});
Application.Current?.Styles.Add(menuStyle);
Style dataStyle = new(x => x.OfType<DataGridRow>());
dataStyle.Add(new Setter()
{
Property = DataGridRow.FontSizeProperty,
Value = size,
});
Application.Current?.Styles.Add(dataStyle);
Style listStyle = new(x => x.OfType<ListBoxItem>());
listStyle.Add(new Setter()
{
Property = ListBoxItem.FontSizeProperty,
Value = size,
});
Application.Current?.Styles.Add(listStyle);
Logging.SaveLog("ModifyFontFamily", ex);
}
}
}
}

View File

@@ -161,10 +161,7 @@
<RowDefinition Height="8" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Text="{Binding Name}"
TextWrapping="WrapWithOverflow" />
<TextBlock Grid.Row="0" Text="{Binding Name}" />
<DockPanel Grid.Row="2">
<TextBlock
DockPanel.Dock="Right"

View File

@@ -377,7 +377,7 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!--
<TextBlock
Grid.Row="1"
Grid.Column="0"
@@ -390,7 +390,8 @@
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin8" />
<TextBlock
<!--
<TextBlock
Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Center"
@@ -533,7 +534,6 @@
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin8"
IsVisible="False"
Text="{x:Static resx:ResUI.TbSettingsCurrentFontFamily}" />
<ComboBox
x:Name="cmbcurrentFontFamily"
@@ -541,15 +541,13 @@
Grid.Column="1"
Width="200"
Classes="Margin8"
IsVisible="False"
MaxDropDownHeight="1000" />
<TextBlock
Grid.Row="16"
Grid.Column="2"
VerticalAlignment="Center"
Classes="Margin8"
IsVisible="False"
Text="{x:Static resx:ResUI.TbSettingsCurrentFontFamilyTip}"
Text="{x:Static resx:ResUI.TbSettingsCurrentFontFamilyLinuxTip}"
TextWrapping="Wrap" />
<TextBlock
@@ -677,7 +675,6 @@
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsChinaUserTip}"
TextWrapping="Wrap" />
</Grid>
</ScrollViewer>
</TabItem>

View File

@@ -15,7 +15,6 @@ namespace v2rayN.Desktop.Views
btnCancel.Click += (s, e) => this.Close();
_config = AppHandler.Instance.Config;
// var lstFonts = GetFonts(Utils.GetFontsPath());
ViewModel = new OptionSettingViewModel(UpdateViewHandler);
@@ -100,9 +99,6 @@ namespace v2rayN.Desktop.Views
cmbMainGirdOrientation.Items.Add(it.ToString());
}
//lstFonts.ForEach(it => { cmbcurrentFontFamily.Items.Add(it); });
//cmbcurrentFontFamily.Items.Add(string.Empty);
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.localPort, v => v.txtlocalPort.Text).DisposeWith(disposables);
@@ -127,7 +123,7 @@ namespace v2rayN.Desktop.Views
this.Bind(ViewModel, vm => vm.hyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.enableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.AutoRun, v => v.togAutoRun.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRun, v => v.togAutoRun.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableStatistics, v => v.togEnableStatistics.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.KeepOlderDedupl, v => v.togKeepOlderDedupl.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.IgnoreGeoUpdateCore, v => v.togIgnoreGeoUpdateCore.IsChecked).DisposeWith(disposables);
@@ -178,62 +174,56 @@ namespace v2rayN.Desktop.Views
{
switch (action)
{
case EViewAction.CloseWindow:
// WindowsUtils.SetAutoRun(Global.AutoRunRegPath, Global.AutoRunName, togAutoRun.IsChecked ?? false);
case EViewAction.CloseWindow:
this.Close(true);
break;
case EViewAction.InitSettingFont:
await InitSettingFont();
break;
}
return await Task.FromResult(true);
}
//private List<string> GetFonts(string path)
//{
// var lstFonts = new List<string>();
// try
// {
// string[] searchPatterns = { "*.ttf", "*.ttc" };
// var files = new List<string>();
// foreach (var pattern in searchPatterns)
// {
// files.AddRange(Directory.GetFiles(path, pattern));
// }
// var culture = _config.uiItem.currentLanguage == Global.Languages[0] ? "zh-cn" : "en-us";
// var culture2 = "en-us";
// foreach (var ttf in files)
// {
// var families = Fonts.GetFontFamilies(Utils.GetFontsPath(ttf));
// foreach (FontFamily family in families)
// {
// var typefaces = family.GetTypefaces();
// foreach (Typeface typeface in typefaces)
// {
// typeface.TryGetGlyphTypeface(out GlyphTypeface glyph);
// //var fontFace = glyph.Win32FaceNames[new CultureInfo("en-us")];
// //if (!fontFace.Equals("Regular") && !fontFace.Equals("Normal"))
// //{
// // continue;
// //}
// var fontFamily = glyph.Win32FamilyNames[new CultureInfo(culture)];
// if (Utils.IsNullOrEmpty(fontFamily))
// {
// fontFamily = glyph.Win32FamilyNames[new CultureInfo(culture2)];
// if (Utils.IsNullOrEmpty(fontFamily))
// {
// continue;
// }
// }
// lstFonts.Add(fontFamily);
// break;
// }
// }
// }
// }
// catch (Exception ex)
// {
// Logging.SaveLog("fill fonts error", ex);
// }
// return lstFonts;
//}
private async Task InitSettingFont()
{
var lstFonts = await GetFonts();
lstFonts.ForEach(it => { cmbcurrentFontFamily.Items.Add(it); });
cmbcurrentFontFamily.Items.Add(string.Empty);
}
private async Task<List<string>> GetFonts()
{
var lstFonts = new List<string>();
try
{
if (Utils.IsWindows())
{
return lstFonts;
}
else if (Utils.IsLinux())
{
var result = await Utils.GetLinuxFontFamily("zh");
if (result.IsNullOrEmpty())
{
return lstFonts;
}
var lst = result.Split(Environment.NewLine)
.Where(t => t.IsNotEmpty())
.ToList()
.Select(t => t.Split(",").FirstOrDefault() ?? "")
.OrderBy(t => t)
.ToList();
return lst;
}
}
catch (Exception ex)
{
Logging.SaveLog("fill fonts error", ex);
}
return lstFonts;
}
private void ClbdestOverride_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{

View File

@@ -8,6 +8,7 @@
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<Copyright>Copyright © 2017-2024 (GPLv3)</Copyright>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<AssemblyName>v2rayN</AssemblyName>
</PropertyGroup>
<ItemGroup>
@@ -26,7 +27,7 @@
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.4" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.1.4" />
<PackageReference Include="DialogHost.Avalonia" Version="0.8.1" />
<PackageReference Include="MessageBox.Avalonia" Version="3.1.6.13" />
<PackageReference Include="MessageBox.Avalonia" Version="3.2.0" />
<PackageReference Include="Semi.Avalonia" Version="11.1.0.4" />
<PackageReference Include="Semi.Avalonia.DataGrid" Version="11.1.0.4" />
<PackageReference Include="ReactiveUI" Version="20.1.63" />

View File

@@ -1,13 +1,9 @@
using Microsoft.Win32;
using Microsoft.Win32.TaskScheduler;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
@@ -55,49 +51,6 @@ namespace v2rayN
}
}
/// <summary>
/// Auto Start via TaskService
/// </summary>
/// <param name="taskName"></param>
/// <param name="fileName"></param>
/// <param name="description"></param>
/// <exception cref="ArgumentNullException"></exception>
public static void AutoStart(string taskName, string fileName, string description)
{
if (Utils.IsNullOrEmpty(taskName))
{
return;
}
string TaskName = taskName;
var logonUser = WindowsIdentity.GetCurrent().Name;
string taskDescription = description;
string deamonFileName = fileName;
using var taskService = new TaskService();
var tasks = taskService.RootFolder.GetTasks(new Regex(TaskName));
foreach (var t in tasks)
{
taskService.RootFolder.DeleteTask(t.Name);
}
if (Utils.IsNullOrEmpty(fileName))
{
return;
}
var task = taskService.NewTask();
task.RegistrationInfo.Description = taskDescription;
task.Settings.DisallowStartIfOnBatteries = false;
task.Settings.StopIfGoingOnBatteries = false;
task.Settings.RunOnlyIfIdle = false;
task.Settings.IdleSettings.StopOnIdleEnd = false;
task.Settings.ExecutionTimeLimit = TimeSpan.Zero;
task.Triggers.Add(new LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(10) });
task.Principal.RunLevel = TaskRunLevel.Highest;
task.Actions.Add(new ExecAction(deamonFileName.AppendQuotes(), null, Path.GetDirectoryName(deamonFileName)));
taskService.RootFolder.RegisterTaskDefinition(TaskName, task);
}
[DllImport("dwmapi.dll")]
public static extern int DwmSetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE attribute, ref int attributeValue, uint attributeSize);
@@ -116,58 +69,6 @@ namespace v2rayN
return value is int i && i > 0;
}
public static string? RegReadValue(string path, string name, string def)
{
RegistryKey? regKey = null;
try
{
regKey = Registry.CurrentUser.OpenSubKey(path, false);
string? value = regKey?.GetValue(name) as string;
if (Utils.IsNullOrEmpty(value))
{
return def;
}
else
{
return value;
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
finally
{
regKey?.Close();
}
return def;
}
public static void RegWriteValue(string path, string name, object value)
{
RegistryKey? regKey = null;
try
{
regKey = Registry.CurrentUser.CreateSubKey(path);
if (Utils.IsNullOrEmpty(value.ToString()))
{
regKey?.DeleteValue(name, false);
}
else
{
regKey?.SetValue(name, value);
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
finally
{
regKey?.Close();
}
}
public static void RemoveTunDevice()
{
try
@@ -209,42 +110,6 @@ namespace v2rayN
DwmSetWindowAttribute(hWnd, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, ref attribute, attributeSize);
}
/// <summary>
/// 开机自动启动
/// </summary>
/// <param name="run"></param>
/// <returns></returns>
public static void SetAutoRun(string AutoRunRegPath, string AutoRunName, bool run)
{
try
{
var autoRunName = $"{AutoRunName}_{Utils.GetMd5(Utils.StartupPath())}";
//delete first
RegWriteValue(AutoRunRegPath, autoRunName, "");
if (Utils.IsAdministrator())
{
AutoStart(autoRunName, "", "");
}
if (run)
{
string exePath = Utils.GetExePath();
if (Utils.IsAdministrator())
{
AutoStart(autoRunName, exePath, "");
}
else
{
RegWriteValue(AutoRunRegPath, autoRunName, exePath.AppendQuotes());
}
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
#region Windows API
[Flags]

View File

@@ -284,6 +284,8 @@ namespace v2rayN.Views
switch (e.Key)
{
case Key.V:
if (_backupAndRestoreView?.IsVisible == true) return;
var clipboardData = WindowsUtils.GetClipboardData();
ViewModel?.AddServerViaClipboardAsync(clipboardData);
break;

View File

@@ -17,7 +17,6 @@ namespace v2rayN.Views
this.Owner = Application.Current.MainWindow;
_config = AppHandler.Instance.Config;
var lstFonts = GetFonts(Utils.GetFontsPath());
ViewModel = new OptionSettingViewModel(UpdateViewHandler);
@@ -102,9 +101,6 @@ namespace v2rayN.Views
cmbMainGirdOrientation.Items.Add(it.ToString());
}
lstFonts.ForEach(it => { cmbcurrentFontFamily.Items.Add(it); });
cmbcurrentFontFamily.Items.Add(string.Empty);
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.localPort, v => v.txtlocalPort.Text).DisposeWith(disposables);
@@ -188,14 +184,24 @@ namespace v2rayN.Views
switch (action)
{
case EViewAction.CloseWindow:
WindowsUtils.SetAutoRun(Global.AutoRunRegPath, Global.AutoRunName, togAutoRun.IsChecked ?? false);
this.DialogResult = true;
break;
case EViewAction.InitSettingFont:
await InitSettingFont();
break;
}
return await Task.FromResult(true);
}
private List<string> GetFonts(string path)
private async Task InitSettingFont()
{
var lstFonts = await GetFonts(Utils.GetFontsPath());
lstFonts.ForEach(it => { cmbcurrentFontFamily.Items.Add(it); });
cmbcurrentFontFamily.Items.Add(string.Empty);
}
private async Task<List<string>> GetFonts(string path)
{
var lstFonts = new List<string>();
try
@@ -241,7 +247,7 @@ namespace v2rayN.Views
{
Logging.SaveLog("fill fonts error", ex);
}
return lstFonts;
return lstFonts.OrderBy(t => t).ToList();
}
private void ClbdestOverride_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)

View File

@@ -16,7 +16,6 @@
<ItemGroup>
<PackageReference Include="MaterialDesignThemes" Version="5.1.0" />
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.1.4" />
<PackageReference Include="TaskScheduler" Version="2.11.0" />
<PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageReference Include="ReactiveUI.WPF" Version="20.1.63" />
</ItemGroup>