v2rayN/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs

634 lines
19 KiB
C#
Raw Normal View History

2025-08-30 10:01:01 +00:00
using System.Reactive.Concurrency;
2023-01-01 11:42:01 +00:00
namespace ServiceLib.ViewModels;
public class MainWindowViewModel : MyReactiveObject
2023-01-01 11:42:01 +00:00
{
#region Menu
2023-01-01 11:42:01 +00:00
//servers
public ReactiveCommand<Unit, Unit> AddVmessServerCmd { get; }
2023-04-14 12:49:36 +00:00
public ReactiveCommand<Unit, Unit> AddVlessServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddShadowsocksServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddSocksServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddHttpServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddTrojanServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddWireguardServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddAnytlsServerCmd { get; }
2026-02-18 14:28:43 +00:00
public ReactiveCommand<Unit, Unit> AddNaiveServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddPolicyGroupServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddProxyChainServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaImageCmd { get; }
2023-04-14 12:49:36 +00:00
//Subscription
public ReactiveCommand<Unit, Unit> SubSettingCmd { get; }
2023-01-01 11:42:01 +00:00
public ReactiveCommand<Unit, Unit> SubUpdateCmd { get; }
public ReactiveCommand<Unit, Unit> SubUpdateViaProxyCmd { get; }
public ReactiveCommand<Unit, Unit> SubGroupUpdateCmd { get; }
public ReactiveCommand<Unit, Unit> SubGroupUpdateViaProxyCmd { get; }
2023-04-14 12:49:36 +00:00
//Setting
public ReactiveCommand<Unit, Unit> OptionSettingCmd { get; }
2023-01-01 11:42:01 +00:00
public ReactiveCommand<Unit, Unit> RoutingSettingCmd { get; }
public ReactiveCommand<Unit, Unit> DNSSettingCmd { get; }
public ReactiveCommand<Unit, Unit> FullConfigTemplateCmd { get; }
public ReactiveCommand<Unit, Unit> GlobalHotkeySettingCmd { get; }
public ReactiveCommand<Unit, Unit> RebootAsAdminCmd { get; }
public ReactiveCommand<Unit, Unit> ClearServerStatisticsCmd { get; }
public ReactiveCommand<Unit, Unit> OpenTheFileLocationCmd { get; }
2024-10-18 09:35:32 +00:00
//Presets
public ReactiveCommand<Unit, Unit> RegionalPresetDefaultCmd { get; }
public ReactiveCommand<Unit, Unit> RegionalPresetRussiaCmd { get; }
public ReactiveCommand<Unit, Unit> RegionalPresetIranCmd { get; }
2023-04-14 12:49:36 +00:00
public ReactiveCommand<Unit, Unit> ReloadCmd { get; }
2023-01-01 11:42:01 +00:00
[Reactive]
public bool BlReloadEnabled { get; set; }
[Reactive]
public bool ShowClashUI { get; set; }
2024-07-19 02:51:14 +00:00
[Reactive]
public int TabMainSelectedIndex { get; set; }
2023-01-01 11:42:01 +00:00
[Reactive] public bool BlIsWindows { get; set; }
#endregion Menu
2025-01-14 09:05:13 +00:00
#region Init
2023-01-01 11:42:01 +00:00
public MainWindowViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{
2025-08-17 09:31:55 +00:00
_config = AppManager.Instance.Config;
_updateView = updateView;
BlIsWindows = Utils.IsWindows();
2023-01-01 11:42:01 +00:00
#region WhenAnyValue && ReactiveCommand
2023-01-01 11:42:01 +00:00
//servers
AddVmessServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.VMess);
});
AddVlessServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.VLESS);
});
AddShadowsocksServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.Shadowsocks);
});
AddSocksServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.SOCKS);
});
AddHttpServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.HTTP);
});
AddTrojanServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.Trojan);
});
AddHysteria2ServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.Hysteria2);
});
AddTuicServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.TUIC);
});
AddWireguardServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.WireGuard);
});
AddAnytlsServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.Anytls);
});
2026-02-18 14:28:43 +00:00
AddNaiveServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.Naive);
});
AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.Custom);
});
AddPolicyGroupServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.PolicyGroup);
});
AddProxyChainServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-11-02 07:25:41 +00:00
await AddServerAsync(EConfigType.ProxyChain);
});
AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerViaClipboardAsync(null);
});
AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerViaScanAsync();
});
AddServerViaImageCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerViaImageAsync();
});
2024-07-18 09:39:11 +00:00
//Subscription
SubSettingCmd = ReactiveCommand.CreateFromTask(async () =>
{
await SubSettingAsync();
});
2023-01-01 11:42:01 +00:00
SubUpdateCmd = ReactiveCommand.CreateFromTask(async () =>
{
await UpdateSubscriptionProcess("", false);
});
SubUpdateViaProxyCmd = ReactiveCommand.CreateFromTask(async () =>
{
await UpdateSubscriptionProcess("", true);
});
SubGroupUpdateCmd = ReactiveCommand.CreateFromTask(async () =>
{
await UpdateSubscriptionProcess(_config.SubIndexId, false);
});
SubGroupUpdateViaProxyCmd = ReactiveCommand.CreateFromTask(async () =>
{
await UpdateSubscriptionProcess(_config.SubIndexId, true);
});
2023-01-01 11:42:01 +00:00
//Setting
OptionSettingCmd = ReactiveCommand.CreateFromTask(async () =>
{
await OptionSettingAsync();
});
RoutingSettingCmd = ReactiveCommand.CreateFromTask(async () =>
{
await RoutingSettingAsync();
});
DNSSettingCmd = ReactiveCommand.CreateFromTask(async () =>
{
await DNSSettingAsync();
});
FullConfigTemplateCmd = ReactiveCommand.CreateFromTask(async () =>
{
await FullConfigTemplateAsync();
});
GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () =>
{
if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true)
2023-01-01 11:42:01 +00:00
{
2025-08-17 09:31:55 +00:00
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
}
});
RebootAsAdminCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AppManager.Instance.RebootAsAdmin();
});
ClearServerStatisticsCmd = ReactiveCommand.CreateFromTask(async () =>
{
await ClearServerStatistics();
});
OpenTheFileLocationCmd = ReactiveCommand.CreateFromTask(async () =>
{
await OpenTheFileLocation();
});
2023-01-01 11:42:01 +00:00
ReloadCmd = ReactiveCommand.CreateFromTask(async () =>
{
await Reload();
});
RegionalPresetDefaultCmd = ReactiveCommand.CreateFromTask(async () =>
{
await ApplyRegionalPreset(EPresetType.Default);
});
RegionalPresetRussiaCmd = ReactiveCommand.CreateFromTask(async () =>
{
await ApplyRegionalPreset(EPresetType.Russia);
});
RegionalPresetIranCmd = ReactiveCommand.CreateFromTask(async () =>
{
await ApplyRegionalPreset(EPresetType.Iran);
});
2024-02-01 04:44:47 +00:00
#endregion WhenAnyValue && ReactiveCommand
2023-01-01 11:42:01 +00:00
#region AppEvents
AppEvents.ReloadRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await Reload());
AppEvents.AddServerViaScanRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await AddServerViaScanAsync());
AppEvents.AddServerViaClipboardRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await AddServerViaClipboardAsync(null));
AppEvents.SubscriptionsUpdateRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
#endregion AppEvents
_ = Init();
}
2024-10-21 05:46:13 +00:00
private async Task Init()
{
AppManager.Instance.ShowInTaskbar = true;
2023-01-01 11:42:01 +00:00
2025-09-27 10:07:20 +00:00
//await ConfigHandler.InitBuiltinRouting(_config);
await ConfigHandler.InitBuiltinDNS(_config);
await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
2025-08-17 09:31:55 +00:00
await ProfileExManager.Instance.Init();
await CoreManager.Instance.Init(_config, UpdateHandler);
TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler);
2023-01-01 11:42:01 +00:00
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
{
2025-08-17 09:31:55 +00:00
await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
2023-01-01 11:42:01 +00:00
}
await RefreshServers();
2023-04-14 12:49:36 +00:00
await Reload();
}
#endregion Init
2023-01-01 11:42:01 +00:00
#region Actions
2023-04-14 12:49:36 +00:00
private async Task UpdateHandler(bool notify, string msg)
{
2025-08-17 09:31:55 +00:00
NoticeManager.Instance.SendMessage(msg);
if (notify)
2023-01-01 11:42:01 +00:00
{
2025-08-17 09:31:55 +00:00
NoticeManager.Instance.Enqueue(msg);
2023-01-01 11:42:01 +00:00
}
2025-11-02 07:25:41 +00:00
await Task.CompletedTask;
}
2023-04-14 12:49:36 +00:00
private async Task UpdateTaskHandler(bool success, string msg)
{
2025-08-17 09:31:55 +00:00
NoticeManager.Instance.SendMessageEx(msg);
if (success)
2023-01-01 11:42:01 +00:00
{
var indexIdOld = _config.IndexId;
await RefreshServers();
if (indexIdOld != _config.IndexId)
2023-01-01 11:42:01 +00:00
{
await Reload();
}
if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
{
2025-09-25 02:56:10 +00:00
AppEvents.AdjustMainLvColWidthRequested.Publish();
2023-01-01 11:42:01 +00:00
}
}
}
2023-04-14 12:49:36 +00:00
private async Task UpdateStatisticsHandler(ServerSpeedItem update)
{
if (!AppManager.Instance.ShowInTaskbar)
{
return;
}
2025-09-25 02:56:10 +00:00
AppEvents.DispatcherStatisticsRequested.Publish(update);
2025-11-02 07:25:41 +00:00
await Task.CompletedTask;
}
2023-04-14 12:49:36 +00:00
#endregion Actions
2023-01-01 11:42:01 +00:00
#region Servers && Groups
2023-01-01 11:42:01 +00:00
private async Task RefreshServers()
{
2025-09-25 02:56:10 +00:00
AppEvents.ProfilesRefreshRequested.Publish();
await Task.Delay(200);
}
private void RefreshSubscriptions()
{
2025-09-25 02:56:10 +00:00
AppEvents.SubscriptionsRefreshRequested.Publish();
}
2023-01-01 11:42:01 +00:00
#endregion Servers && Groups
2023-04-14 12:49:36 +00:00
#region Add Servers
2025-11-02 07:25:41 +00:00
public async Task AddServerAsync(EConfigType eConfigType)
{
ProfileItem item = new()
2023-01-01 11:42:01 +00:00
{
Subid = _config.SubIndexId,
ConfigType = eConfigType,
IsSub = false,
};
2023-01-01 11:42:01 +00:00
bool? ret = false;
if (eConfigType == EConfigType.Custom)
{
ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item);
2023-01-01 11:42:01 +00:00
}
else if (eConfigType.IsGroupType())
{
ret = await _updateView?.Invoke(EViewAction.AddGroupServerWindow, item);
}
else
2023-01-01 11:42:01 +00:00
{
ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item);
}
if (ret == true)
{
await RefreshServers();
if (item.IndexId == _config.IndexId)
{
await Reload();
}
2023-01-01 11:42:01 +00:00
}
}
2023-04-14 12:49:36 +00:00
public async Task AddServerViaClipboardAsync(string? clipboardData)
{
if (clipboardData == null)
2024-10-12 07:45:21 +00:00
{
await _updateView?.Invoke(EViewAction.AddServerViaClipboard, null);
return;
2024-10-12 07:45:21 +00:00
}
var ret = await ConfigHandler.AddBatchServers(_config, clipboardData, _config.SubIndexId, false);
if (ret > 0)
2024-10-18 09:35:32 +00:00
{
RefreshSubscriptions();
await RefreshServers();
2025-08-17 09:31:55 +00:00
NoticeManager.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
2024-10-18 09:35:32 +00:00
}
else
2024-10-18 09:35:32 +00:00
{
2025-08-17 09:31:55 +00:00
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
2024-10-18 09:35:32 +00:00
}
}
2024-10-18 09:35:32 +00:00
public async Task AddServerViaScanAsync()
{
_updateView?.Invoke(EViewAction.ScanScreenTask, null);
await Task.CompletedTask;
}
public async Task ScanScreenResult(byte[]? bytes)
{
2025-08-17 08:26:13 +00:00
var result = QRCodeUtils.ParseBarcode(bytes);
await AddScanResultAsync(result);
}
public async Task AddServerViaImageAsync()
{
_updateView?.Invoke(EViewAction.ScanImageTask, null);
await Task.CompletedTask;
}
2024-10-18 09:35:32 +00:00
public async Task ScanImageResult(string fileName)
{
if (fileName.IsNullOrEmpty())
{
return;
2024-10-18 09:35:32 +00:00
}
2025-08-17 08:26:13 +00:00
var result = QRCodeUtils.ParseBarcode(fileName);
await AddScanResultAsync(result);
}
private async Task AddScanResultAsync(string? result)
{
if (result.IsNullOrEmpty())
{
2025-08-17 09:31:55 +00:00
NoticeManager.Instance.Enqueue(ResUI.NoValidQRcodeFound);
}
else
2023-01-01 11:42:01 +00:00
{
var ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false);
if (ret > 0)
2023-01-01 11:42:01 +00:00
{
RefreshSubscriptions();
await RefreshServers();
2025-08-17 09:31:55 +00:00
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
2023-01-01 11:42:01 +00:00
}
else
{
2025-08-17 09:31:55 +00:00
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
2023-01-01 11:42:01 +00:00
}
}
}
2023-04-14 12:49:36 +00:00
#endregion Add Servers
2023-01-01 11:42:01 +00:00
#region Subscription
2023-01-01 11:42:01 +00:00
private async Task SubSettingAsync()
{
if (await _updateView?.Invoke(EViewAction.SubSettingWindow, null) == true)
2023-01-01 11:42:01 +00:00
{
RefreshSubscriptions();
2023-01-01 11:42:01 +00:00
}
}
2023-01-01 11:42:01 +00:00
public async Task UpdateSubscriptionProcess(string subId, bool blProxy)
{
2025-08-27 09:14:24 +00:00
await Task.Run(async () => await SubscriptionHandler.UpdateProcess(_config, subId, blProxy, UpdateTaskHandler));
}
2023-01-01 11:42:01 +00:00
#endregion Subscription
2023-01-01 11:42:01 +00:00
#region Setting
2023-01-01 11:42:01 +00:00
private async Task OptionSettingAsync()
{
var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null);
if (ret == true)
2023-01-01 11:42:01 +00:00
{
2025-09-25 02:56:10 +00:00
AppEvents.InboundDisplayRequested.Publish();
await Reload();
2023-01-01 11:42:01 +00:00
}
}
2023-04-14 12:49:36 +00:00
private async Task RoutingSettingAsync()
{
var ret = await _updateView?.Invoke(EViewAction.RoutingSettingWindow, null);
if (ret == true)
2023-01-01 11:42:01 +00:00
{
await ConfigHandler.InitBuiltinRouting(_config);
2025-09-25 02:56:10 +00:00
AppEvents.RoutingsMenuRefreshRequested.Publish();
await Reload();
2023-01-01 11:42:01 +00:00
}
}
2023-04-27 08:20:13 +00:00
private async Task DNSSettingAsync()
{
var ret = await _updateView?.Invoke(EViewAction.DNSSettingWindow, null);
if (ret == true)
{
await Reload();
}
}
private async Task FullConfigTemplateAsync()
{
var ret = await _updateView?.Invoke(EViewAction.FullConfigTemplateWindow, null);
if (ret == true)
{
await Reload();
}
}
private async Task ClearServerStatistics()
{
2025-08-17 09:31:55 +00:00
await StatisticsManager.Instance.ClearAllServerStatistics();
await RefreshServers();
}
2023-01-01 11:42:01 +00:00
private async Task OpenTheFileLocation()
{
var path = Utils.StartupPath();
if (Utils.IsWindows())
2023-04-06 02:44:29 +00:00
{
ProcUtils.ProcessStart(path);
2023-04-06 02:44:29 +00:00
}
else if (Utils.IsLinux())
{
ProcUtils.ProcessStart("xdg-open", path);
}
else if (Utils.IsMacOS())
{
ProcUtils.ProcessStart("open", path);
}
await Task.CompletedTask;
}
#endregion Setting
2023-01-01 11:42:01 +00:00
#region core job
2023-01-01 11:42:01 +00:00
private bool _hasNextReloadJob = false;
private readonly SemaphoreSlim _reloadSemaphore = new(1, 1);
public async Task Reload()
{
//If there are unfinished reload job, marked with next job.
if (!await _reloadSemaphore.WaitAsync(0))
2023-01-01 11:42:01 +00:00
{
_hasNextReloadJob = true;
return;
}
2024-01-30 09:18:00 +00:00
try
2025-10-09 12:22:35 +00:00
{
SetReloadEnabled(false);
var profileItem = await ConfigHandler.GetDefaultServer(_config);
if (profileItem == null)
{
NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings);
return;
}
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, profileItem);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]);
if (msgs.Count > 0)
2025-10-09 12:22:35 +00:00
{
foreach (var msg in msgs)
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
2025-10-09 12:22:35 +00:00
}
await Task.Run(async () =>
{
await LoadCore(context);
await SysProxyHandler.UpdateSysProxy(_config, false);
await Task.Delay(1000);
});
AppEvents.TestServerRequested.Publish();
var showClashUI = AppManager.Instance.IsRunningCore(ECoreType.sing_box);
if (showClashUI)
{
AppEvents.ProxiesReloadRequested.Publish();
}
2025-01-14 09:05:13 +00:00
ReloadResult(showClashUI);
}
finally
{
SetReloadEnabled(true);
_reloadSemaphore.Release();
//If there is a next reload job, execute it.
if (_hasNextReloadJob)
{
_hasNextReloadJob = false;
await Reload();
}
}
}
private void ReloadResult(bool showClashUI)
{
RxApp.MainThreadScheduler.Schedule(() =>
{
ShowClashUI = showClashUI;
TabMainSelectedIndex = showClashUI ? TabMainSelectedIndex : 0;
});
}
private void SetReloadEnabled(bool enabled)
{
RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled);
}
2023-01-01 11:42:01 +00:00
private async Task LoadCore(CoreConfigContext? context)
{
await CoreManager.Instance.LoadCore(context);
}
#endregion core job
#region Presets
public async Task ApplyRegionalPreset(EPresetType type)
{
await ConfigHandler.ApplyRegionalPreset(_config, type);
await ConfigHandler.InitRouting(_config);
2025-09-25 02:56:10 +00:00
AppEvents.RoutingsMenuRefreshRequested.Publish();
await ConfigHandler.SaveConfig(_config);
await new UpdateService(_config, UpdateTaskHandler).UpdateGeoFileAll();
await Reload();
2023-01-01 11:42:01 +00:00
}
#endregion Presets
2025-01-30 09:10:05 +00:00
}