namespace ServiceLib.ViewModels; public partial class MainWindowViewModel : MyReactiveObject { public Interaction ReadTextFromClipboardInteraction { get; } = new(); public Interaction ScanScreenInteraction { get; } = new(); public Interaction BrowseImageFileInteraction { get; } = new(); public Interaction 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 AddVmessServerCmd { get; } public ReactiveCommand AddVlessServerCmd { get; } public ReactiveCommand AddShadowsocksServerCmd { get; } public ReactiveCommand AddSocksServerCmd { get; } public ReactiveCommand AddHttpServerCmd { get; } public ReactiveCommand AddTrojanServerCmd { get; } public ReactiveCommand AddHysteria2ServerCmd { get; } public ReactiveCommand AddTuicServerCmd { get; } public ReactiveCommand AddWireguardServerCmd { get; } public ReactiveCommand AddAnytlsServerCmd { get; } public ReactiveCommand AddNaiveServerCmd { get; } public ReactiveCommand AddCustomServerCmd { get; } public ReactiveCommand AddPolicyGroupServerCmd { get; } public ReactiveCommand AddProxyChainServerCmd { get; } public ReactiveCommand AddServerViaClipboardCmd { get; } public ReactiveCommand AddServerViaScanCmd { get; } public ReactiveCommand AddServerViaImageCmd { get; } //Subscription public ReactiveCommand SubSettingCmd { get; } public ReactiveCommand SubUpdateCmd { get; } public ReactiveCommand SubUpdateViaProxyCmd { get; } public ReactiveCommand SubGroupUpdateCmd { get; } public ReactiveCommand SubGroupUpdateViaProxyCmd { get; } //Setting public ReactiveCommand OptionSettingCmd { get; } public ReactiveCommand RoutingSettingCmd { get; } public ReactiveCommand DNSSettingCmd { get; } public ReactiveCommand FullConfigTemplateCmd { get; } public ReactiveCommand GlobalHotkeySettingCmd { get; } public ReactiveCommand RebootAsAdminCmd { get; } public ReactiveCommand ClearServerStatisticsCmd { get; } public ReactiveCommand OpenTheFileLocationCmd { get; } //Presets public ReactiveCommand RegionalPresetDefaultCmd { get; } public ReactiveCommand RegionalPresetRussiaCmd { get; } public ReactiveCommand RegionalPresetIranCmd { get; } public ReactiveCommand ReloadCmd { get; } [Reactive] public partial bool BlReloadEnabled { get; set; } [Reactive] public partial bool ShowClashUI { get; set; } [Reactive] public partial int TabMainSelectedIndex { get; set; } [Reactive] public partial bool BlIsWindows { get; set; } [Reactive] public partial bool BlNewUpdate { get; set; } [Reactive] public partial EGirdOrientation MainGirdOrientation { get; set; } #endregion Menu private readonly SynchronizationContext _uiContext = SynchronizationContext.Current; #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> { 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); _uiContext?.Post(_ => _ = RefreshServers(), null); } private async Task RefreshSubscriptions() { //await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler); _uiContext?.Post(_ => _ = ProfilesViewModel.RefreshSubscriptions(), null); } #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(RxVoid.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(RxVoid.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(RxVoid.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 }