Files
v2rayN/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
2026-07-16 17:36:51 +08:00

746 lines
24 KiB
C#

using System.Reactive.Concurrency;
namespace ServiceLib.ViewModels;
public class MainWindowViewModel : MyReactiveObject
{
public Interaction<Unit, string?> ReadTextFromClipboardInteraction { get; } = new();
public Interaction<Unit, byte[]?> ScanScreenInteraction { get; } = new();
public Interaction<Unit, string?> BrowseImageFileInteraction { get; } = new();
public Interaction<bool?, Unit> ShowHideWindowInteraction { get; } = new();
public bool DesignMode { get; set; }
public ProfilesViewModel ProfilesViewModel { get; } = new();
public MsgViewModel MsgViewModel { get; } = new();
public ClashProxiesViewModel ClashProxiesViewModel { get; } = new();
public ClashConnectionsViewModel ClashConnectionsViewModel { get; } = new();
public CheckUpdateViewModel CheckUpdateViewModel { get; } = new();
public BackupAndRestoreViewModel BackupAndRestoreViewModel { get; } = new();
public StatusBarViewModel StatusBarViewModel { get; } = StatusBarViewModel.Instance;
#region Menu
//servers
public ReactiveCommand<Unit, Unit> AddVmessServerCmd { get; }
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; }
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; }
//Subscription
public ReactiveCommand<Unit, Unit> SubSettingCmd { get; }
public ReactiveCommand<Unit, Unit> SubUpdateCmd { get; }
public ReactiveCommand<Unit, Unit> SubUpdateViaProxyCmd { get; }
public ReactiveCommand<Unit, Unit> SubGroupUpdateCmd { get; }
public ReactiveCommand<Unit, Unit> SubGroupUpdateViaProxyCmd { get; }
//Setting
public ReactiveCommand<Unit, Unit> OptionSettingCmd { get; }
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; }
//Presets
public ReactiveCommand<Unit, Unit> RegionalPresetDefaultCmd { get; }
public ReactiveCommand<Unit, Unit> RegionalPresetRussiaCmd { get; }
public ReactiveCommand<Unit, Unit> RegionalPresetIranCmd { get; }
public ReactiveCommand<Unit, Unit> ReloadCmd { get; }
[Reactive]
public bool BlReloadEnabled { get; set; }
[Reactive]
public bool ShowClashUI { get; set; }
[Reactive]
public int TabMainSelectedIndex { get; set; }
[Reactive] public bool BlIsWindows { get; set; }
[Reactive] public bool BlNewUpdate { get; set; }
[Reactive] public EGirdOrientation MainGirdOrientation { get; set; }
#endregion Menu
#region Init
public MainWindowViewModel()
{
_config = AppManager.Instance.Config;
BlIsWindows = Utils.IsWindows();
MainGirdOrientation = _config.UiItem.MainGirdOrientation;
#region WhenAnyValue && ReactiveCommand
//servers
AddVmessServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.VMess);
});
AddVlessServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.VLESS);
});
AddShadowsocksServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.Shadowsocks);
});
AddSocksServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.SOCKS);
});
AddHttpServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.HTTP);
});
AddTrojanServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.Trojan);
});
AddHysteria2ServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.Hysteria2);
});
AddTuicServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.TUIC);
});
AddWireguardServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.WireGuard);
});
AddAnytlsServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.Anytls);
});
AddNaiveServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.Naive);
});
AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.Custom);
});
AddPolicyGroupServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.PolicyGroup);
});
AddProxyChainServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.ProxyChain);
});
AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerViaClipboardAsync(null);
});
AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerViaScanAsync();
});
AddServerViaImageCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerViaImageAsync();
});
//Subscription
SubSettingCmd = ReactiveCommand.CreateFromTask(async () =>
{
await SubSettingAsync();
});
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);
});
//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 () =>
{
var globalHotkeySettingViewModel = new GlobalHotkeySettingViewModel();
if (await AppManager.Instance.WindowDialog.ShowDialogAsync(globalHotkeySettingViewModel) == true)
{
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();
});
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);
});
#endregion WhenAnyValue && ReactiveCommand
#region AppEvents
AppEvents.AddServerViaClipboardRequested
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async _ => await AddServerViaClipboardAsync(null));
AppEvents.HasUpdateNotified
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async bl => BlNewUpdate = bl);
#endregion AppEvents
ProfilesViewModel.RefreshServersRequested
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async _ => await RefreshServers());
var vmReloadRequestedList = new List<IObservable<Unit>>
{
ProfilesViewModel.ReloadRequested.AsObservable(),
StatusBarViewModel.ReloadRequested.AsObservable(),
CheckUpdateViewModel.ReloadRequested.AsObservable(),
};
foreach (var reloadRequested in vmReloadRequestedList)
{
reloadRequested
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async _ => await Reload());
}
StatusBarViewModel.AddServerViaScanRequested
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async _ => await AddServerViaScanAsync());
StatusBarViewModel.AddServerViaClipboardRequested
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async _ => await AddServerViaClipboardAsync(null));
StatusBarViewModel.ShowHideWindowRequested
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async blShow =>
{
await ShowHideWindowInteraction.Handle(blShow);
});
StatusBarViewModel.SetDefaultServerRequested
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async indexId => await ProfilesViewModel.SetDefaultServer(indexId));
StatusBarViewModel.SubscriptionsUpdateRequested
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
_ = Init();
}
private async Task Init()
{
AppManager.Instance.ShowInTaskbar = true;
if (DesignMode)
{
return;
}
//await ConfigHandler.InitBuiltinRouting(_config);
await ConfigHandler.InitBuiltinDNS(_config);
await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
await ProfileExManager.Instance.Init();
await CoreManager.Instance.Init(_config, UpdateHandler);
await CertPemManager.Instance.Init(_config);
TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler);
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
{
await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
}
await RefreshServersDispatcherAsync();
await Reload();
}
#endregion Init
#region Actions
private async Task UpdateHandler(bool notify, string msg)
{
NoticeManager.Instance.SendMessage(msg);
if (notify)
{
NoticeManager.Instance.Enqueue(msg);
}
await Task.CompletedTask;
}
private async Task UpdateTaskHandler(bool success, string msg)
{
NoticeManager.Instance.SendMessageEx(msg);
if (success)
{
var indexIdOld = _config.IndexId;
await RefreshServersDispatcherAsync();
// If indexId changed or subIndexId is empty, directly reload.
if (indexIdOld != _config.IndexId || _config.SubIndexId.IsNullOrEmpty())
{
await Reload();
}
else
{
// The activity config belongs to the current group.
var profile = await AppManager.Instance.GetProfileItem(_config.IndexId);
if (profile != null && profile.Subid == _config.SubIndexId)
{
await Reload();
}
}
if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
{
await ProfilesViewModel.AdjustMainLvColWidth();
}
}
}
private async Task UpdateStatisticsHandler(ServerSpeedItem update)
{
if (!AppManager.Instance.ShowInTaskbar)
{
return;
}
AppEvents.DispatcherStatisticsRequested.Publish(update);
await Task.CompletedTask;
}
#endregion Actions
#region Servers && Groups
private async Task RefreshServers()
{
await ProfilesViewModel.RefreshServersBiz();
await StatusBarViewModel.RefreshServersBiz();
// await Task.Delay(200);
}
private async Task RefreshServersDispatcherAsync()
{
await Observable.Start(async () => await RefreshServers(), RxSchedulers.MainThreadScheduler);
}
private async Task RefreshSubscriptions()
{
await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
}
#endregion Servers && Groups
#region Add Servers
public async Task AddServerAsync(EConfigType eConfigType)
{
ProfileItem item = new()
{
Subid = _config.SubIndexId,
ConfigType = eConfigType,
IsSub = false,
};
bool? ret = false;
if (eConfigType == EConfigType.Custom)
{
var addServer2ViewModel = new AddServer2ViewModel(item);
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServer2ViewModel);
}
else if (eConfigType.IsGroupType())
{
var addGroupServerViewModel = new AddGroupServerViewModel(item);
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addGroupServerViewModel);
}
else
{
var addServerViewModel = new AddServerViewModel(item);
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServerViewModel);
}
if (ret == true)
{
await RefreshServersDispatcherAsync();
if (item.IndexId == _config.IndexId)
{
await Reload();
}
}
}
public async Task AddServerViaClipboardAsync(string? clipboardData)
{
var stringData = clipboardData;
if (clipboardData == null)
{
var result = await ReadTextFromClipboardInteraction.Handle(Unit.Default);
if (result.IsNullOrEmpty())
{
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
return;
}
stringData = result;
}
var ret = await ConfigHandler.AddBatchServers(_config, stringData, _config.SubIndexId, false);
if (ret > 0)
{
await RefreshSubscriptions();
await RefreshServersDispatcherAsync();
NoticeManager.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
}
else
{
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
}
}
public async Task AddServerViaScanAsync()
{
var result = await ScanScreenInteraction.Handle(Unit.Default);
await ScanScreenResult(result);
}
public async Task ScanScreenResult(byte[]? bytes)
{
var result = QRCodeUtils.ParseBarcode(bytes);
await AddScanResultAsync(result);
}
public async Task AddServerViaImageAsync()
{
var imageFileName = await BrowseImageFileInteraction.Handle(Unit.Default);
await AddScanResultAsync(imageFileName);
}
public async Task ScanImageResult(string fileName)
{
if (fileName.IsNullOrEmpty())
{
return;
}
var result = QRCodeUtils.ParseBarcode(fileName);
await AddScanResultAsync(result);
}
private async Task AddScanResultAsync(string? result)
{
if (result.IsNullOrEmpty())
{
NoticeManager.Instance.Enqueue(ResUI.NoValidQRcodeFound);
}
else
{
var ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false);
if (ret > 0)
{
await RefreshSubscriptions();
await RefreshServersDispatcherAsync();
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
}
else
{
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
}
}
}
#endregion Add Servers
#region Subscription
private async Task SubSettingAsync()
{
var subSettingViewModel = new SubSettingViewModel();
if (await AppManager.Instance.WindowDialog.ShowDialogAsync(subSettingViewModel) == true)
{
await RefreshSubscriptions();
}
}
public async Task UpdateSubscriptionProcess(string subId, bool blProxy)
{
await Task.Run(async () => await SubscriptionHandler.UpdateProcess(_config, subId, blProxy, UpdateTaskHandler));
}
#endregion Subscription
#region Setting
private async Task OptionSettingAsync()
{
var settingViewModel = new OptionSettingViewModel();
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(settingViewModel);
if (ret == true)
{
MainGirdOrientation = _config.UiItem.MainGirdOrientation;
RxSchedulers.MainThreadScheduler.Schedule(async () =>
{
await StatusBarViewModel.InboundDisplayStatus();
});
await Reload();
}
}
private async Task RoutingSettingAsync()
{
var routingSettingViewModel = new RoutingSettingViewModel();
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(routingSettingViewModel);
if (ret == true)
{
await ConfigHandler.InitBuiltinRouting(_config);
RxSchedulers.MainThreadScheduler.Schedule(async () =>
{
await StatusBarViewModel.RefreshRoutingsMenu();
});
await Reload();
}
}
private async Task DNSSettingAsync()
{
var dnsSettingViewModel = new DNSSettingViewModel();
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(dnsSettingViewModel);
if (ret == true)
{
await Reload();
}
}
private async Task FullConfigTemplateAsync()
{
var fullConfigTemplateViewModel = new FullConfigTemplateViewModel();
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(fullConfigTemplateViewModel);
if (ret == true)
{
await Reload();
}
}
private async Task ClearServerStatistics()
{
await StatisticsManager.Instance.ClearAllServerStatistics();
await RefreshServersDispatcherAsync();
}
private async Task OpenTheFileLocation()
{
var path = Utils.StartupPath();
if (Utils.IsWindows())
{
ProcUtils.ProcessStart(path);
}
else if (Utils.IsLinux())
{
ProcUtils.ProcessStart("xdg-open", path);
}
else if (Utils.IsMacOS())
{
ProcUtils.ProcessStart("open", path);
}
await Task.CompletedTask;
}
#endregion Setting
#region core job
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))
{
_hasNextReloadJob = true;
return;
}
if (DesignMode)
{
_reloadSemaphore.Release();
return;
}
try
{
SetReloadEnabled(false);
var profileItem = await ConfigHandler.GetDefaultServer(_config);
if (profileItem == null)
{
NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings);
return;
}
var allResult = await CoreConfigContextBuilder.BuildAll(_config, profileItem);
if (NoticeManager.Instance.NotifyValidatorResult(allResult.CombinedValidatorResult) && !allResult.Success)
{
return;
}
await Task.Run(async () =>
{
await LoadCore(allResult.MainResult.Context, allResult.PreSocksResult?.Context);
await SysProxyHandler.UpdateSysProxy(_config, false);
await Task.Delay(1000);
});
RxSchedulers.MainThreadScheduler.Schedule(async () =>
{
await StatusBarViewModel.TestServerAvailability();
});
var showClashUI = AppManager.Instance.IsRunningCore(ECoreType.sing_box);
if (showClashUI)
{
//await Observable.Start(async () =>
//{
// await ClashProxiesViewModel.ProxiesReload();
//}, RxSchedulers.MainThreadScheduler);
RxSchedulers.MainThreadScheduler.Schedule(async () =>
{
await ClashProxiesViewModel.ProxiesReload();
});
}
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)
{
RxSchedulers.MainThreadScheduler.Schedule(() =>
{
ShowClashUI = showClashUI;
TabMainSelectedIndex = showClashUI ? TabMainSelectedIndex : 0;
});
}
private void SetReloadEnabled(bool enabled)
{
RxSchedulers.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled);
}
private async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
{
await CoreManager.Instance.LoadCore(mainContext, preContext);
}
#endregion core job
#region Presets
public async Task ApplyRegionalPreset(EPresetType type)
{
await ConfigHandler.ApplyRegionalPreset(_config, type);
await ConfigHandler.InitRouting(_config);
RxSchedulers.MainThreadScheduler.Schedule(async () =>
{
await StatusBarViewModel.RefreshRoutingsMenu();
});
await ConfigHandler.SaveConfig(_config);
await new UpdateService(_config, UpdateTaskHandler).UpdateGeoFileAll();
await Reload();
}
#endregion Presets
}