namespace ServiceLib.ViewModels; public partial class StatusBarViewModel : MyReactiveObject { public Interaction SetClipboardDataInteraction { get; } = new(); public Interaction PasswordInputInteraction { get; } = new(); public Interaction DispatcherRefreshIconInteraction { get; } = new(); public EventChannel SubscriptionsUpdateRequested { get; } = new(); public EventChannel ShowHideWindowRequested { get; } = new(); private static readonly Lazy _instance = new(() => new()); public static StatusBarViewModel Instance => _instance.Value; public EventChannel SetDefaultServerRequested { get; } = new(); public EventChannel ReloadRequested { get; } = new(); public EventChannel AddServerViaScanRequested { get; } = new(); public EventChannel AddServerViaClipboardRequested { get; } = new(); #region ObservableCollection public BulkObservableCollection RoutingItems { get; } = []; public BulkObservableCollection Servers { get; } = []; [Reactive] public partial RoutingItem SelectedRouting { get; set; } [Reactive] public partial ComboItem SelectedServer { get; set; } [Reactive] public partial bool BlServers { get; set; } #endregion ObservableCollection public ReactiveCommand AddServerViaClipboardCmd { get; } public ReactiveCommand AddServerViaScanCmd { get; } public ReactiveCommand SubUpdateCmd { get; } public ReactiveCommand SubUpdateViaProxyCmd { get; } public ReactiveCommand CopyProxyCmdToClipboardCmd { get; } public ReactiveCommand NotifyLeftClickCmd { get; } public ReactiveCommand ShowWindowCmd { get; } public ReactiveCommand HideWindowCmd { get; } #region System Proxy [Reactive] public partial bool BlSystemProxyClear { get; set; } [Reactive] public partial bool BlSystemProxySet { get; set; } [Reactive] public partial bool BlSystemProxyNothing { get; set; } [Reactive] public partial bool BlSystemProxyPac { get; set; } public ReactiveCommand SystemProxyClearCmd { get; } public ReactiveCommand SystemProxySetCmd { get; } public ReactiveCommand SystemProxyNothingCmd { get; } public ReactiveCommand SystemProxyPacCmd { get; } [Reactive] public partial bool BlRouting { get; set; } [Reactive] public partial int SystemProxySelected { get; set; } [Reactive] public partial bool BlSystemProxyPacVisible { get; set; } #endregion System Proxy #region UI [Reactive] public partial string InboundDisplay { get; set; } [Reactive] public partial string InboundLanDisplay { get; set; } [Reactive] public partial string RunningServerDisplay { get; set; } [Reactive] public partial string RunningServerToolTipText { get; set; } [Reactive] public partial string RunningInfoDisplay { get; set; } [Reactive] public partial string SpeedProxyDisplay { get; set; } [Reactive] public partial string SpeedDirectDisplay { get; set; } [Reactive] public partial bool EnableTun { get; set; } [Reactive] public partial bool BlIsNonWindows { get; set; } #endregion UI public StatusBarViewModel() { _config = AppManager.Instance.Config; SelectedRouting = new(); SelectedServer = new(); RunningServerToolTipText = GetRunningServerToolTipText("-"); BlSystemProxyPacVisible = Utils.IsWindows(); BlIsNonWindows = Utils.IsNonWindows(); if (_config.TunModeItem.EnableTun && AllowEnableTun()) { EnableTun = true; } else { _config.TunModeItem.EnableTun = EnableTun = false; } #region WhenAnyValue && ReactiveCommand this.WhenAnyValue( x => x.SelectedRouting, y => y != null && !y.Remarks.IsNullOrEmpty()) .Subscribe(async c => await RoutingSelectedChangedAsync(c)); this.WhenAnyValue( x => x.SelectedServer, y => y != null && !y.Text.IsNullOrEmpty()) .Subscribe(ServerSelectedChanged); SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType; this.WhenAnyValue( x => x.SystemProxySelected, y => y >= 0) .Subscribe(async c => await DoSystemProxySelected(c)); this.WhenAnyValue( x => x.EnableTun, y => y == true) .Subscribe(async c => await DoEnableTun(c)); CopyProxyCmdToClipboardCmd = ReactiveCommand.CreateFromTask(async () => { await CopyProxyCmdToClipboard(); }); NotifyLeftClickCmd = ReactiveCommand.CreateFromTask(async () => { ShowHideWindowRequested.Publish(null); await Task.CompletedTask; }); ShowWindowCmd = ReactiveCommand.CreateFromTask(async () => { ShowHideWindowRequested.Publish(true); await Task.CompletedTask; }); HideWindowCmd = ReactiveCommand.CreateFromTask(async () => { ShowHideWindowRequested.Publish(false); await Task.CompletedTask; }); AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerViaClipboard(); }); AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerViaScan(); }); SubUpdateCmd = ReactiveCommand.CreateFromTask(async () => { await UpdateSubscriptionProcess(false); }); SubUpdateViaProxyCmd = ReactiveCommand.CreateFromTask(async () => { await UpdateSubscriptionProcess(true); }); //System proxy SystemProxyClearCmd = ReactiveCommand.CreateFromTask(async () => { await SetListenerType(ESysProxyType.ForcedClear); }); SystemProxySetCmd = ReactiveCommand.CreateFromTask(async () => { await SetListenerType(ESysProxyType.ForcedChange); }); SystemProxyNothingCmd = ReactiveCommand.CreateFromTask(async () => { await SetListenerType(ESysProxyType.Unchanged); }); SystemProxyPacCmd = ReactiveCommand.CreateFromTask(async () => { await SetListenerType(ESysProxyType.Pac); }); #endregion WhenAnyValue && ReactiveCommand #region AppEvents AppEvents.DispatcherStatisticsRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async result => await UpdateStatistics(result)); AppEvents.SysProxyChangeRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async result => await SetListenerType(result)); #endregion AppEvents _ = Init(); } private async Task Init() { await ConfigHandler.InitBuiltinRouting(_config); await RefreshRoutingsMenu(); await InboundDisplayStatus(); await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, true); BlRouting = true; } private async Task CopyProxyCmdToClipboard() { var cmd = Utils.IsWindows() ? "set" : "export"; var address = $"{Global.Loopback}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}"; var sb = new StringBuilder(); sb.AppendLine($"{cmd} http_proxy={Global.HttpProtocol}{address}"); sb.AppendLine($"{cmd} https_proxy={Global.HttpProtocol}{address}"); sb.AppendLine($"{cmd} all_proxy={Global.Socks5Protocol}{address}"); sb.AppendLine(""); sb.AppendLine($"{cmd} HTTP_PROXY={Global.HttpProtocol}{address}"); sb.AppendLine($"{cmd} HTTPS_PROXY={Global.HttpProtocol}{address}"); sb.AppendLine($"{cmd} ALL_PROXY={Global.Socks5Protocol}{address}"); await SetClipboardDataInteraction.Handle(sb.ToString()); } private async Task AddServerViaClipboard() { AddServerViaClipboardRequested.Publish(); await Task.Delay(1000); } private async Task AddServerViaScan() { AddServerViaScanRequested.Publish(); await Task.Delay(1000); } private async Task UpdateSubscriptionProcess(bool blProxy) { SubscriptionsUpdateRequested.Publish(blProxy); await Task.Delay(1000); } public async Task RefreshServersBiz() { await RefreshServersMenu(); //display running server var running = await ConfigHandler.GetDefaultServer(_config); if (running != null) { RunningServerDisplay = running.GetSummary(); RunningServerToolTipText = GetRunningServerToolTipText(RunningServerDisplay); } else { RunningServerDisplay = ResUI.CheckServerSettings; RunningServerToolTipText = GetRunningServerToolTipText(RunningServerDisplay); } } private string GetRunningServerToolTipText(string serverInfo) { return Utils.IsLinux() ? Global.AppName : serverInfo; } private async Task RefreshServersMenu() { var lstModel = await AppManager.Instance.ProfileModels(_config.SubIndexId, ""); if (lstModel?.Count > _config.GuiItem.TrayMenuServersLimit) { BlServers = false; return; } var models = new List(); BlServers = true; foreach (var it in lstModel) { var name = it.GetSummary(); var item = new ComboItem() { ID = it.IndexId, Text = name }; models.Add(item); if (_config.IndexId == it.IndexId) { SelectedServer = item; } } Servers.Clear(); Servers.AddRange(models); } private void ServerSelectedChanged(bool c) { if (!c) { return; } if (SelectedServer == null) { return; } if (SelectedServer.ID.IsNullOrEmpty()) { return; } SetDefaultServerRequested.Publish(SelectedServer.ID); } public async Task TestServerAvailability() { var item = await ConfigHandler.GetDefaultServer(_config); if (item == null) { return; } await TestServerAvailabilitySub(ResUI.Speedtesting); var msg = await Task.Run(ConnectionHandler.RunAvailabilityCheck); NoticeManager.Instance.SendMessageEx(msg); await TestServerAvailabilitySub(msg); } private async Task TestServerAvailabilitySub(string msg) { RxSchedulers.MainThreadScheduler.Schedule(() => { _ = TestServerAvailabilityResult(msg); }); await Task.CompletedTask; } public async Task TestServerAvailabilityResult(string msg) { RunningInfoDisplay = msg; await Task.CompletedTask; } #region System proxy and Routings private async Task SetListenerType(ESysProxyType type) { if (_config.SystemProxyItem.SysProxyType == type) { return; } _config.SystemProxyItem.SysProxyType = type; await ChangeSystemProxyAsync(type, true); NoticeManager.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType}"); SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType; await ConfigHandler.SaveConfig(_config); } public async Task ChangeSystemProxyAsync(ESysProxyType type, bool blChange) { await SysProxyHandler.UpdateSysProxy(_config, false); BlSystemProxyClear = type == ESysProxyType.ForcedClear; BlSystemProxySet = type == ESysProxyType.ForcedChange; BlSystemProxyNothing = type == ESysProxyType.Unchanged; BlSystemProxyPac = type == ESysProxyType.Pac; if (blChange) { try { await DispatcherRefreshIconInteraction.Handle(RxVoid.Default); } catch (UnhandledInteractionException) { // Ignore } } } public async Task RefreshRoutingsMenu() { var routings = await AppManager.Instance.RoutingItems(); RoutingItems.Clear(); RoutingItems.AddRange(routings); SelectedRouting = routings.FirstOrDefault(t => t.IsActive == true); } private async Task RoutingSelectedChangedAsync(bool c) { if (!c) { return; } if (SelectedRouting == null) { return; } var item = await AppManager.Instance.GetRoutingItem(SelectedRouting?.Id); if (item is null) { return; } if (await ConfigHandler.SetDefaultRouting(_config, item) == 0) { NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting); ReloadRequested.Publish(); await DispatcherRefreshIconInteraction.Handle(RxVoid.Default); } } private async Task DoSystemProxySelected(bool c) { if (!c) { return; } if (_config.SystemProxyItem.SysProxyType == (ESysProxyType)SystemProxySelected) { return; } await SetListenerType((ESysProxyType)SystemProxySelected); } private async Task DoEnableTun(bool c) { if (_config.TunModeItem.EnableTun == EnableTun) { return; } _config.TunModeItem.EnableTun = EnableTun; if (EnableTun && AllowEnableTun() == false) { // When running as a non-administrator, reboot to administrator mode if (Utils.IsWindows()) { _config.TunModeItem.EnableTun = false; await AppManager.Instance.RebootAsAdmin(); return; } else { var password = await PasswordInputInteraction.Handle(RxVoid.Default); if (password.IsNullOrEmpty()) { _config.TunModeItem.EnableTun = false; return; } } } await ConfigHandler.SaveConfig(_config); ReloadRequested.Publish(); } private bool AllowEnableTun() { if (Utils.IsWindows()) { return Utils.IsAdministrator(); } else if (Utils.IsLinux()) { return AppManager.Instance.LinuxSudoPwd.IsNotEmpty(); } else if (Utils.IsMacOS()) { return AppManager.Instance.LinuxSudoPwd.IsNotEmpty(); } return false; } #endregion System proxy and Routings #region UI public async Task InboundDisplayStatus() { StringBuilder sb = new(); sb.Append($"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}"); if (_config.Inbound.First().SecondLocalPortEnabled) { sb.Append($",{AppManager.Instance.GetLocalPort(EInboundProtocol.socks2)}"); } sb.Append(']'); InboundDisplay = $"{ResUI.LabLocal}:{sb}"; if (_config.Inbound.First().AllowLANConn) { var lan = _config.Inbound.First().NewPort4LAN ? $"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks3)}]" : $"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}]"; InboundLanDisplay = $"{ResUI.LabLAN}:{lan}"; } else { InboundLanDisplay = $"{ResUI.LabLAN}:{Global.None}"; } await Task.CompletedTask; } public async Task UpdateStatistics(ServerSpeedItem update) { if (!_config.GuiItem.DisplayRealTimeSpeed) { return; } try { if (AppManager.Instance.IsRunningCore(ECoreType.sing_box)) { SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, EInboundProtocol.mixed, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown)); SpeedDirectDisplay = string.Empty; } else { SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, Global.ProxyTag, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown)); SpeedDirectDisplay = string.Format(ResUI.SpeedDisplayText, Global.DirectTag, Utils.HumanFy(update.DirectUp), Utils.HumanFy(update.DirectDown)); } } catch { } await Task.CompletedTask; } #endregion UI }