using System.Reactive; using ReactiveUI; using ReactiveUI.Fody.Helpers; using Splat; namespace ServiceLib.ViewModels; public class MainWindowViewModel : MyReactiveObject { #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 AddCustomServerCmd { 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 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 bool BlReloadEnabled { get; set; } [Reactive] public bool ShowClashUI { get; set; } [Reactive] public int TabMainSelectedIndex { get; set; } #endregion Menu private bool _hasNextReloadJob = false; #region Init public MainWindowViewModel(Func>? updateView) { _config = AppHandler.Instance.Config; _updateView = updateView; #region WhenAnyValue && ReactiveCommand //servers AddVmessServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.VMess); }); AddVlessServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.VLESS); }); AddShadowsocksServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.Shadowsocks); }); AddSocksServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.SOCKS); }); AddHttpServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.HTTP); }); AddTrojanServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.Trojan); }); AddHysteria2ServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.Hysteria2); }); AddTuicServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.TUIC); }); AddWireguardServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.WireGuard); }); AddAnytlsServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.Anytls); }); AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.Custom); }); 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(); }); GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () => { if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true) { NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); } }); RebootAsAdminCmd = ReactiveCommand.CreateFromTask(async () => { await 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 _ = Init(); } private async Task Init() { _config.UiItem.ShowInTaskbar = true; await ConfigHandler.InitBuiltinRouting(_config); await ConfigHandler.InitBuiltinDNS(_config); await ProfileExHandler.Instance.Init(); await CoreHandler.Instance.Init(_config, UpdateHandler); TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler); if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) { await StatisticsHandler.Instance.Init(_config, UpdateStatisticsHandler); } BlReloadEnabled = true; await Reload(); await AutoHideStartup(); Locator.Current.GetService()?.RefreshRoutingsMenu(); } #endregion Init #region Actions private void UpdateHandler(bool notify, string msg) { NoticeHandler.Instance.SendMessage(msg); if (notify) { NoticeHandler.Instance.Enqueue(msg); } } private void UpdateTaskHandler(bool success, string msg) { NoticeHandler.Instance.SendMessageEx(msg); if (success) { var indexIdOld = _config.IndexId; RefreshServers(); if (indexIdOld != _config.IndexId) { _ = Reload(); } if (_config.UiItem.EnableAutoAdjustMainLvColWidth) { _updateView?.Invoke(EViewAction.AdjustMainLvColWidth, null); } } } private void UpdateStatisticsHandler(ServerSpeedItem update) { if (!_config.UiItem.ShowInTaskbar) { return; } _updateView?.Invoke(EViewAction.DispatcherStatistics, update); } public void SetStatisticsResult(ServerSpeedItem update) { if (_config.GuiItem.DisplayRealTimeSpeed) { Locator.Current.GetService()?.UpdateStatistics(update); } if (_config.GuiItem.EnableStatistics && (update.ProxyUp + update.ProxyDown) > 0 && DateTime.Now.Second % 9 == 0) { Locator.Current.GetService()?.UpdateStatistics(update); } } public async Task MyAppExitAsync(bool blWindowsShutDown) { try { Logging.SaveLog("MyAppExitAsync Begin"); await SysProxyHandler.UpdateSysProxy(_config, true); MessageBus.Current.SendMessage("", EMsgCommand.AppExit.ToString()); await ConfigHandler.SaveConfig(_config); await ProfileExHandler.Instance.SaveTo(); await StatisticsHandler.Instance.SaveTo(); await CoreHandler.Instance.CoreStop(); StatisticsHandler.Instance.Close(); Logging.SaveLog("MyAppExitAsync End"); } catch { } finally { if (!blWindowsShutDown) { _updateView?.Invoke(EViewAction.Shutdown, false); } } } public async Task UpgradeApp(string arg) { if (!Utils.UpgradeAppExists(out var upgradeFileName)) { NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip); Logging.SaveLog("UpgradeApp does not exist"); return; } var id = ProcUtils.ProcessStart(upgradeFileName, arg, Utils.StartupPath()); if (id > 0) { await MyAppExitAsync(false); } } public void ShowHideWindow(bool? blShow) { _updateView?.Invoke(EViewAction.ShowHideWindow, blShow); } public void Shutdown(bool byUser) { _updateView?.Invoke(EViewAction.Shutdown, byUser); } #endregion Actions #region Servers && Groups private void RefreshServers() { MessageBus.Current.SendMessage("", EMsgCommand.RefreshProfiles.ToString()); } private void RefreshSubscriptions() { Locator.Current.GetService()?.RefreshSubscriptions(); } #endregion Servers && Groups #region Add Servers public async Task AddServerAsync(bool blNew, EConfigType eConfigType) { ProfileItem item = new() { Subid = _config.SubIndexId, ConfigType = eConfigType, IsSub = false, }; bool? ret = false; if (eConfigType == EConfigType.Custom) { ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item); } else { ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item); } if (ret == true) { RefreshServers(); if (item.IndexId == _config.IndexId) { await Reload(); } } } public async Task AddServerViaClipboardAsync(string? clipboardData) { if (clipboardData == null) { await _updateView?.Invoke(EViewAction.AddServerViaClipboard, null); return; } int ret = await ConfigHandler.AddBatchServers(_config, clipboardData, _config.SubIndexId, false); if (ret > 0) { RefreshSubscriptions(); RefreshServers(); NoticeHandler.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret)); } else { NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); } } public async Task AddServerViaScanAsync() { _updateView?.Invoke(EViewAction.ScanScreenTask, null); await Task.CompletedTask; } public async Task ScanScreenResult(byte[]? bytes) { var result = QRCodeHelper.ParseBarcode(bytes); await AddScanResultAsync(result); } public async Task AddServerViaImageAsync() { _updateView?.Invoke(EViewAction.ScanImageTask, null); await Task.CompletedTask; } public async Task ScanImageResult(string fileName) { if (fileName.IsNullOrEmpty()) { return; } var result = QRCodeHelper.ParseBarcode(fileName); await AddScanResultAsync(result); } private async Task AddScanResultAsync(string? result) { if (result.IsNullOrEmpty()) { NoticeHandler.Instance.Enqueue(ResUI.NoValidQRcodeFound); } else { int ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false); if (ret > 0) { RefreshSubscriptions(); RefreshServers(); NoticeHandler.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan); } else { NoticeHandler.Instance.Enqueue(ResUI.OperationFailed); } } } #endregion Add Servers #region Subscription private async Task SubSettingAsync() { if (await _updateView?.Invoke(EViewAction.SubSettingWindow, null) == true) { RefreshSubscriptions(); } } public async Task UpdateSubscriptionProcess(string subId, bool blProxy) { await (new UpdateService()).UpdateSubscriptionProcess(_config, subId, blProxy, UpdateTaskHandler); } #endregion Subscription #region Setting private async Task OptionSettingAsync() { var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null); if (ret == true) { Locator.Current.GetService()?.InboundDisplayStatus(); await Reload(); } } private async Task RoutingSettingAsync() { var ret = await _updateView?.Invoke(EViewAction.RoutingSettingWindow, null); if (ret == true) { await ConfigHandler.InitBuiltinRouting(_config); Locator.Current.GetService()?.RefreshRoutingsMenu(); await Reload(); } } private async Task DNSSettingAsync() { var ret = await _updateView?.Invoke(EViewAction.DNSSettingWindow, null); if (ret == true) { await Reload(); } } public async Task RebootAsAdmin() { ProcUtils.RebootAsAdmin(); await MyAppExitAsync(false); } private async Task ClearServerStatistics() { await StatisticsHandler.Instance.ClearAllServerStatistics(); RefreshServers(); } private async Task OpenTheFileLocation() { var path = Utils.StartupPath(); if (Utils.IsWindows()) { ProcUtils.ProcessStart(path); } else if (Utils.IsLinux()) { ProcUtils.ProcessStart("nautilus", path); } else if (Utils.IsOSX()) { ProcUtils.ProcessStart("open", path); } await Task.CompletedTask; } #endregion Setting #region core job public async Task Reload() { //If there are unfinished reload job, marked with next job. if (!BlReloadEnabled) { _hasNextReloadJob = true; return; } BlReloadEnabled = false; await Task.Run(async () => { await LoadCore(); await SysProxyHandler.UpdateSysProxy(_config, false); }); Locator.Current.GetService()?.TestServerAvailability(); _updateView?.Invoke(EViewAction.DispatcherReload, null); BlReloadEnabled = true; if (_hasNextReloadJob) { _hasNextReloadJob = false; await Reload(); } } public void ReloadResult() { // BlReloadEnabled = true; //Locator.Current.GetService()?.ChangeSystemProxyAsync(_config.systemProxyItem.sysProxyType, false); ShowClashUI = _config.IsRunningCore(ECoreType.sing_box); if (ShowClashUI) { Locator.Current.GetService()?.ProxiesReload(); } else { TabMainSelectedIndex = 0; } } private async Task LoadCore() { var node = await ConfigHandler.GetDefaultServer(_config); await CoreHandler.Instance.LoadCore(node); } public async Task CloseCore() { await ConfigHandler.SaveConfig(_config); await CoreHandler.Instance.CoreStop(); } private async Task AutoHideStartup() { if (_config.UiItem.AutoHideStartup) { ShowHideWindow(false); } await Task.CompletedTask; } #endregion core job #region Presets public async Task ApplyRegionalPreset(EPresetType type) { await ConfigHandler.ApplyRegionalPreset(_config, type); await ConfigHandler.InitRouting(_config); Locator.Current.GetService()?.RefreshRoutingsMenu(); await ConfigHandler.SaveConfig(_config); await new UpdateService().UpdateGeoFileAll(_config, UpdateHandler); await Reload(); } #endregion Presets }