// From https://github.com/2dust/v2rayN under GPL-3.0 license using DynamicData; using DynamicData.Binding; using MaterialDesignColors; using MaterialDesignColors.ColorManipulation; using MaterialDesignThemes.Wpf; using ReactiveUI; using ReactiveUI.Fody.Helpers; using Splat; using System.Drawing; using System.IO; using System.Reactive; using System.Reactive.Linq; using System.Text; using System.Windows; using System.Windows.Forms; using System.Windows.Media; using v2rayN.Base; using v2rayN.Handler; using v2rayN.Mode; using v2rayN.Resx; using v2rayN.Tool; using v2rayN.Views; using Application = System.Windows.Application; namespace v2rayN.ViewModels { public class MainWindowViewModel : ReactiveObject { #region private prop private CoreHandler _coreHandler; private StatisticsHandler _statistics; private List _lstProfile; private string _subId = string.Empty; private string _serverFilter = string.Empty; private static Config _config; private NoticeHandler? _noticeHandler; private readonly PaletteHelper _paletteHelper = new(); private Dictionary _dicHeaderSort = new(); private Action _updateView; #endregion #region ObservableCollection private IObservableCollection _profileItems = new ObservableCollectionExtended(); public IObservableCollection ProfileItems => _profileItems; private IObservableCollection _subItems = new ObservableCollectionExtended(); public IObservableCollection SubItems => _subItems; private IObservableCollection _routingItems = new ObservableCollectionExtended(); public IObservableCollection RoutingItems => _routingItems; private IObservableCollection _servers = new ObservableCollectionExtended(); public IObservableCollection Servers => _servers; [Reactive] public ProfileItemModel SelectedProfile { get; set; } public IList SelectedProfiles { get; set; } [Reactive] public SubItem SelectedSub { get; set; } [Reactive] public SubItem SelectedMoveToGroup { get; set; } [Reactive] public RoutingItem SelectedRouting { get; set; } [Reactive] public ComboItem SelectedServer { get; set; } [Reactive] public string ServerFilter { get; set; } [Reactive] public bool BlServers { get; set; } #endregion #region Menu //servers public ReactiveCommand AddVmessServerCmd { get; } public ReactiveCommand AddVlessServerCmd { get; } public ReactiveCommand AddShadowsocksServerCmd { get; } public ReactiveCommand AddSocksServerCmd { get; } public ReactiveCommand AddTrojanServerCmd { get; } public ReactiveCommand AddCustomServerCmd { get; } public ReactiveCommand AddServerViaClipboardCmd { get; } public ReactiveCommand AddServerViaScanCmd { get; } //servers delete public ReactiveCommand EditServerCmd { get; } public ReactiveCommand RemoveServerCmd { get; } public ReactiveCommand RemoveDuplicateServerCmd { get; } public ReactiveCommand CopyServerCmd { get; } public ReactiveCommand SetDefaultServerCmd { get; } public ReactiveCommand ShareServerCmd { get; } //servers move public ReactiveCommand MoveTopCmd { get; } public ReactiveCommand MoveUpCmd { get; } public ReactiveCommand MoveDownCmd { get; } public ReactiveCommand MoveBottomCmd { get; } //servers ping public ReactiveCommand MixedTestServerCmd { get; } public ReactiveCommand PingServerCmd { get; } public ReactiveCommand TcpingServerCmd { get; } public ReactiveCommand RealPingServerCmd { get; } public ReactiveCommand SpeedServerCmd { get; } public ReactiveCommand SortServerResultCmd { get; } //servers export public ReactiveCommand Export2ClientConfigCmd { get; } public ReactiveCommand Export2ServerConfigCmd { get; } public ReactiveCommand Export2ShareUrlCmd { get; } public ReactiveCommand Export2SubContentCmd { get; } //Subscription public ReactiveCommand SubSettingCmd { get; } public ReactiveCommand AddSubCmd { 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 GlobalHotkeySettingCmd { get; } public ReactiveCommand ClearServerStatisticsCmd { get; } public ReactiveCommand ImportOldGuiConfigCmd { get; } //CheckUpdate public ReactiveCommand CheckUpdateNCmd { get; } public ReactiveCommand CheckUpdateV2flyCoreCmd { get; } public ReactiveCommand CheckUpdateSagerNetCoreCmd { get; } public ReactiveCommand CheckUpdateXrayCoreCmd { get; } public ReactiveCommand CheckUpdateClashCoreCmd { get; } public ReactiveCommand CheckUpdateClashMetaCoreCmd { get; } public ReactiveCommand CheckUpdateGeoCmd { get; } public ReactiveCommand ReloadCmd { get; } [Reactive] public bool BlReloadEnabled { get; set; } public ReactiveCommand NotifyLeftClickCmd { get; } [Reactive] public Icon NotifyIcon { get; set; } [Reactive] public ImageSource AppIcon { get; set; } #endregion #region System Proxy [Reactive] public bool BlSystemProxyClear { get; set; } [Reactive] public bool BlSystemProxySet { get; set; } [Reactive] public bool BlSystemProxyNothing { get; set; } [Reactive] public bool BlSystemProxyPac { get; set; } public ReactiveCommand SystemProxyClearCmd { get; } public ReactiveCommand SystemProxySetCmd { get; } public ReactiveCommand SystemProxyNothingCmd { get; } public ReactiveCommand SystemProxyPacCmd { get; } [Reactive] public bool BlRouting { get; set; } [Reactive] public int SystemProxySelected { get; set; } #endregion #region UI [Reactive] public string InboundDisplay { get; set; } [Reactive] public string InboundLanDisplay { get; set; } [Reactive] public string RunningServerDisplay { get; set; } [Reactive] public string RunningServerToolTipText { get; set; } [Reactive] public string RunningInfoDisplay { get; set; } [Reactive] public string SpeedProxyDisplay { get; set; } [Reactive] public string SpeedDirectDisplay { get; set; } [Reactive] public bool EnableTun { get; set; } [Reactive] public bool ColorModeDark { get; set; } private IObservableCollection _swatches = new ObservableCollectionExtended(); public IObservableCollection Swatches => _swatches; [Reactive] public Swatch SelectedSwatch { get; set; } [Reactive] public int CurrentFontSize { get; set; } [Reactive] public string CurrentLanguage { get; set; } #endregion #region Init public MainWindowViewModel(ISnackbarMessageQueue snackbarMessageQueue, Action updateView) { _updateView = updateView; ThreadPool.RegisterWaitForSingleObject(App.ProgramStarted, OnProgramStarted, null, -1, false); Locator.CurrentMutable.RegisterLazySingleton(() => new NoticeHandler(snackbarMessageQueue), typeof(NoticeHandler)); _noticeHandler = Locator.Current.GetService(); _config = LazyConfig.Instance.GetConfig(); //ThreadPool.RegisterWaitForSingleObject(App.ProgramStarted, OnProgramStarted, null, -1, false); Init(); SelectedProfile = new(); SelectedSub = new(); SelectedMoveToGroup = new(); SelectedRouting = new(); SelectedServer = new(); if (_config.tunModeItem.enableTun && Utils.IsAdministrator()) { EnableTun = true; } _subId = _config.subIndexId; InitSubscriptionView(); RefreshRoutingsMenu(); RefreshServers(); var canEditRemove = this.WhenAnyValue( x => x.SelectedProfile, selectedSource => selectedSource != null && !selectedSource.indexId.IsNullOrEmpty()); this.WhenAnyValue( x => x.SelectedSub, y => y != null && !y.remarks.IsNullOrEmpty() && _subId != y.id) .Subscribe(c => SubSelectedChanged(c)); this.WhenAnyValue( x => x.SelectedMoveToGroup, y => y != null && !y.remarks.IsNullOrEmpty()) .Subscribe(c => MoveToGroup(c)); this.WhenAnyValue( x => x.SelectedRouting, y => y != null && !y.remarks.IsNullOrEmpty()) .Subscribe(c => RoutingSelectedChanged(c)); this.WhenAnyValue( x => x.SelectedServer, y => y != null && !y.Text.IsNullOrEmpty()) .Subscribe(c => ServerSelectedChanged(c)); this.WhenAnyValue( x => x.ServerFilter, y => y != null && _serverFilter != y) .Subscribe(c => ServerFilterChanged(c)); SystemProxySelected = (int)_config.sysProxyType; this.WhenAnyValue( x => x.SystemProxySelected, y => y >= 0) .Subscribe(c => DoSystemProxySelected(c)); this.WhenAnyValue( x => x.EnableTun, y => y == true) .Subscribe(c => DoEnableTun(c)); BindingUI(); RestoreUI(); AutoHideStartup(); //servers AddVmessServerCmd = ReactiveCommand.Create(() => { EditServer(true, EConfigType.VMess); }); AddVlessServerCmd = ReactiveCommand.Create(() => { EditServer(true, EConfigType.VLESS); }); AddShadowsocksServerCmd = ReactiveCommand.Create(() => { EditServer(true, EConfigType.Shadowsocks); }); AddSocksServerCmd = ReactiveCommand.Create(() => { EditServer(true, EConfigType.Socks); }); AddTrojanServerCmd = ReactiveCommand.Create(() => { EditServer(true, EConfigType.Trojan); }); AddCustomServerCmd = ReactiveCommand.Create(() => { EditServer(true, EConfigType.Custom); }); AddServerViaClipboardCmd = ReactiveCommand.Create(() => { AddServerViaClipboard(); }); AddServerViaScanCmd = ReactiveCommand.CreateFromTask(() => { return ScanScreenTaskAsync(); }); //servers delete EditServerCmd = ReactiveCommand.Create(() => { EditServer(false, EConfigType.Custom); }, canEditRemove); RemoveServerCmd = ReactiveCommand.Create(() => { RemoveServer(); }, canEditRemove); RemoveDuplicateServerCmd = ReactiveCommand.Create(() => { RemoveDuplicateServer(); }); CopyServerCmd = ReactiveCommand.Create(() => { CopyServer(); }, canEditRemove); SetDefaultServerCmd = ReactiveCommand.Create(() => { SetDefaultServer(); }, canEditRemove); ShareServerCmd = ReactiveCommand.Create(() => { ShareServer(); }, canEditRemove); //servers move MoveTopCmd = ReactiveCommand.Create(() => { MoveServer(EMove.Top); }, canEditRemove); MoveUpCmd = ReactiveCommand.Create(() => { MoveServer(EMove.Up); }, canEditRemove); MoveDownCmd = ReactiveCommand.Create(() => { MoveServer(EMove.Down); }, canEditRemove); MoveBottomCmd = ReactiveCommand.Create(() => { MoveServer(EMove.Bottom); }, canEditRemove); //servers ping MixedTestServerCmd = ReactiveCommand.Create(() => { ServerSpeedtest(ESpeedActionType.Mixedtest); }); PingServerCmd = ReactiveCommand.Create(() => { ServerSpeedtest(ESpeedActionType.Ping); }, canEditRemove); TcpingServerCmd = ReactiveCommand.Create(() => { ServerSpeedtest(ESpeedActionType.Tcping); }, canEditRemove); RealPingServerCmd = ReactiveCommand.Create(() => { ServerSpeedtest(ESpeedActionType.Realping); }, canEditRemove); SpeedServerCmd = ReactiveCommand.Create(() => { ServerSpeedtest(ESpeedActionType.Speedtest); }, canEditRemove); SortServerResultCmd = ReactiveCommand.Create(() => { SortServer(EServerColName.delayVal.ToString()); }); //servers export Export2ClientConfigCmd = ReactiveCommand.Create(() => { Export2ClientConfig(); }, canEditRemove); Export2ServerConfigCmd = ReactiveCommand.Create(() => { Export2ServerConfig(); }, canEditRemove); Export2ShareUrlCmd = ReactiveCommand.Create(() => { Export2ShareUrl(); }, canEditRemove); Export2SubContentCmd = ReactiveCommand.Create(() => { Export2SubContent(); }, canEditRemove); //Subscription SubSettingCmd = ReactiveCommand.Create(() => { SubSetting(); }); AddSubCmd = ReactiveCommand.Create(() => { AddSub(); }); SubUpdateCmd = ReactiveCommand.Create(() => { UpdateSubscriptionProcess("", false); }); SubUpdateViaProxyCmd = ReactiveCommand.Create(() => { UpdateSubscriptionProcess("", true); }); SubGroupUpdateCmd = ReactiveCommand.Create(() => { UpdateSubscriptionProcess(_subId, false); }); SubGroupUpdateViaProxyCmd = ReactiveCommand.Create(() => { UpdateSubscriptionProcess(_subId, true); }); //Setting OptionSettingCmd = ReactiveCommand.Create(() => { OptionSetting(); }); RoutingSettingCmd = ReactiveCommand.Create(() => { RoutingSetting(); }); GlobalHotkeySettingCmd = ReactiveCommand.Create(() => { if ((new GlobalHotkeySettingWindow()).ShowDialog() == true) { _noticeHandler?.Enqueue(ResUI.OperationSuccess); } }); ClearServerStatisticsCmd = ReactiveCommand.Create(() => { _statistics?.ClearAllServerStatistics(); RefreshServers(); }); ImportOldGuiConfigCmd = ReactiveCommand.Create(() => { ImportOldGuiConfig(); }); //CheckUpdate CheckUpdateNCmd = ReactiveCommand.Create(() => { CheckUpdateN(); }); CheckUpdateV2flyCoreCmd = ReactiveCommand.Create(() => { CheckUpdateCore(ECoreType.v2fly_v5); }); CheckUpdateSagerNetCoreCmd = ReactiveCommand.Create(() => { CheckUpdateCore(ECoreType.SagerNet); }); CheckUpdateXrayCoreCmd = ReactiveCommand.Create(() => { CheckUpdateCore(ECoreType.Xray); }); CheckUpdateClashCoreCmd = ReactiveCommand.Create(() => { CheckUpdateCore(ECoreType.clash); }); CheckUpdateClashMetaCoreCmd = ReactiveCommand.Create(() => { CheckUpdateCore(ECoreType.clash_meta); }); CheckUpdateGeoCmd = ReactiveCommand.Create(() => { CheckUpdateGeo(); }); ReloadCmd = ReactiveCommand.Create(() => { Reload(); }); NotifyLeftClickCmd = ReactiveCommand.Create(() => { ShowHideWindow(null); }); //System proxy SystemProxyClearCmd = ReactiveCommand.Create(() => { SetListenerType(ESysProxyType.ForcedClear); }); SystemProxySetCmd = ReactiveCommand.Create(() => { SetListenerType(ESysProxyType.ForcedChange); }); SystemProxyNothingCmd = ReactiveCommand.Create(() => { SetListenerType(ESysProxyType.Unchanged); }); SystemProxyPacCmd = ReactiveCommand.Create(() => { SetListenerType(ESysProxyType.Pac); }); Global.ShowInTaskbar = true; } private void Init() { ConfigHandler.InitBuiltinRouting(ref _config); //MainFormHandler.Instance.BackupGuiNConfig(_config, true); _coreHandler = new CoreHandler(UpdateHandler); if (_config.guiItem.enableStatistics) { _statistics = new StatisticsHandler(_config, UpdateStatisticsHandler); } MainFormHandler.Instance.UpdateTask(_config, UpdateTaskHandler); MainFormHandler.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, UpdateTaskHandler); Reload(); ChangeSystemProxyStatus(_config.sysProxyType, true); } private void OnProgramStarted(object state, bool timeout) { Application.Current.Dispatcher.Invoke((Action)(() => { ShowHideWindow(true); })); } #endregion #region Actions private void UpdateHandler(bool notify, string msg) { _noticeHandler?.SendMessage(msg); } private void UpdateTaskHandler(bool success, string msg) { _noticeHandler?.SendMessage(msg); if (success) { var indexIdOld = _config.indexId; RefreshServers(); if (indexIdOld != _config.indexId) { Reload(); } if (_config.uiItem.enableAutoAdjustMainLvColWidth) { _updateView("AdjustMainLvColWidth"); } } } private void UpdateStatisticsHandler(ServerSpeedItem update) { try { Application.Current.Dispatcher.Invoke((Action)(() => { if (!Global.ShowInTaskbar) { return; } SpeedProxyDisplay = string.Format("{0}:{1}/s¡ü | {2}/s¡ý", Global.agentTag, Utils.HumanFy(update.proxyUp), Utils.HumanFy(update.proxyDown)); SpeedDirectDisplay = string.Format("{0}:{1}/s¡ü | {2}/s¡ý", Global.directTag, Utils.HumanFy(update.directUp), Utils.HumanFy(update.directDown)); if (update.proxyUp + update.proxyDown > 0) { var second = DateTime.Now.Second; if (second % 3 == 0) { var item = _profileItems.Where(it => it.indexId == update.indexId).FirstOrDefault(); if (item != null) { item.todayDown = Utils.HumanFy(update.todayDown); item.todayUp = Utils.HumanFy(update.todayUp); item.totalDown = Utils.HumanFy(update.totalDown); item.totalUp = Utils.HumanFy(update.totalUp); if (SelectedProfile?.indexId == item.indexId) { var temp = Utils.DeepCopy(item); _profileItems.Replace(item, temp); SelectedProfile = temp; } else { _profileItems.Replace(item, Utils.DeepCopy(item)); } } } } })); } catch (Exception ex) { Utils.SaveLog(ex.Message, ex); } }