v2rayN/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs

575 lines
17 KiB
C#
Raw Normal View History

2025-01-30 09:10:05 +00:00
using System.Reactive;
2025-08-30 10:01:01 +00:00
using System.Reactive.Disposables;
using System.Reactive.Linq;
2025-01-30 09:10:05 +00:00
using System.Text;
using DynamicData.Binding;
2024-10-12 07:45:21 +00:00
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace ServiceLib.ViewModels;
2024-10-12 07:45:21 +00:00
public class StatusBarViewModel : MyReactiveObject
{
2025-09-23 07:31:19 +00:00
private static readonly Lazy<StatusBarViewModel> _instance = new(() => new(null));
public static StatusBarViewModel Instance => _instance.Value;
#region ObservableCollection
2024-10-12 07:45:21 +00:00
2025-08-31 07:41:25 +00:00
public IObservableCollection<RoutingItem> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItem>();
2024-10-12 07:45:21 +00:00
2025-08-31 07:41:25 +00:00
public IObservableCollection<ComboItem> Servers { get; } = new ObservableCollectionExtended<ComboItem>();
2024-10-12 07:45:21 +00:00
[Reactive]
public RoutingItem SelectedRouting { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public ComboItem SelectedServer { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public bool BlServers { get; set; }
2024-10-12 07:45:21 +00:00
#endregion ObservableCollection
2024-10-12 07:45:21 +00:00
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
public ReactiveCommand<Unit, Unit> SubUpdateCmd { get; }
public ReactiveCommand<Unit, Unit> SubUpdateViaProxyCmd { get; }
public ReactiveCommand<Unit, Unit> CopyProxyCmdToClipboardCmd { get; }
public ReactiveCommand<Unit, Unit> NotifyLeftClickCmd { get; }
public ReactiveCommand<Unit, Unit> ShowWindowCmd { get; }
public ReactiveCommand<Unit, Unit> HideWindowCmd { get; }
2024-10-12 07:45:21 +00:00
#region System Proxy
2024-10-12 07:45:21 +00:00
[Reactive]
public bool BlSystemProxyClear { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public bool BlSystemProxySet { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public bool BlSystemProxyNothing { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public bool BlSystemProxyPac { get; set; }
2024-10-12 07:45:21 +00:00
public ReactiveCommand<Unit, Unit> SystemProxyClearCmd { get; }
public ReactiveCommand<Unit, Unit> SystemProxySetCmd { get; }
public ReactiveCommand<Unit, Unit> SystemProxyNothingCmd { get; }
public ReactiveCommand<Unit, Unit> SystemProxyPacCmd { get; }
2024-10-12 07:45:21 +00:00
[Reactive]
public bool BlRouting { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public int SystemProxySelected { get; set; }
[Reactive]
public bool BlSystemProxyPacVisible { get; set; }
2024-10-12 07:45:21 +00:00
#endregion System Proxy
2024-10-12 07:45:21 +00:00
#region UI
2024-10-12 07:45:21 +00:00
[Reactive]
public string InboundDisplay { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public string InboundLanDisplay { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public string RunningServerDisplay { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public string RunningServerToolTipText { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public string RunningInfoDisplay { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public string SpeedProxyDisplay { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public string SpeedDirectDisplay { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public bool EnableTun { get; set; }
2024-10-12 07:45:21 +00:00
[Reactive]
public bool BlIsNonWindows { get; set; }
#endregion UI
public StatusBarViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{
2025-08-17 09:31:55 +00:00
_config = AppManager.Instance.Config;
SelectedRouting = new();
SelectedServer = new();
RunningServerToolTipText = "-";
BlSystemProxyPacVisible = Utils.IsWindows();
BlIsNonWindows = Utils.IsNonWindows();
2024-10-12 07:45:21 +00:00
if (_config.TunModeItem.EnableTun && AllowEnableTun())
{
EnableTun = true;
}
else
{
_config.TunModeItem.EnableTun = EnableTun = false;
}
2024-10-12 07:45:21 +00:00
#region WhenAnyValue && ReactiveCommand
2024-10-12 07:45:21 +00:00
this.WhenAnyValue(
x => x.SelectedRouting,
y => y != null && !y.Remarks.IsNullOrEmpty())
.Subscribe(async c => await RoutingSelectedChangedAsync(c));
2024-10-12 07:45:21 +00:00
this.WhenAnyValue(
x => x.SelectedServer,
y => y != null && !y.Text.IsNullOrEmpty())
.Subscribe(c => ServerSelectedChanged(c));
2024-10-12 07:45:21 +00:00
SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType;
this.WhenAnyValue(
x => x.SystemProxySelected,
y => y >= 0)
.Subscribe(async c => await DoSystemProxySelected(c));
2024-10-12 07:45:21 +00:00
this.WhenAnyValue(
x => x.EnableTun,
y => y == true)
.Subscribe(async c => await DoEnableTun(c));
CopyProxyCmdToClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
{
await CopyProxyCmdToClipboard();
});
2024-10-12 07:45:21 +00:00
NotifyLeftClickCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-09-25 02:56:10 +00:00
AppEvents.ShowHideWindowRequested.Publish(null);
await Task.CompletedTask;
});
ShowWindowCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-09-25 02:56:10 +00:00
AppEvents.ShowHideWindowRequested.Publish(true);
await Task.CompletedTask;
});
HideWindowCmd = ReactiveCommand.CreateFromTask(async () =>
{
2025-09-25 02:56:10 +00:00
AppEvents.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);
});
2024-10-12 07:45:21 +00:00
//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);
});
2024-10-21 05:46:13 +00:00
#endregion WhenAnyValue && ReactiveCommand
2024-10-12 07:45:21 +00:00
2025-08-30 10:01:01 +00:00
#region AppEvents
if (updateView != null)
2024-10-21 05:46:13 +00:00
{
InitUpdateView(updateView);
2024-10-21 05:46:13 +00:00
}
2025-08-30 10:01:01 +00:00
AppEvents.DispatcherStatisticsRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async result => await UpdateStatistics(result));
AppEvents.RoutingsMenuRefreshRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await RefreshRoutingsMenu());
AppEvents.TestServerRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await TestServerAvailability());
AppEvents.InboundDisplayRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await InboundDisplayStatus());
AppEvents.SysProxyChangeRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async result => await SetListenerType(result));
2025-08-30 10:01:01 +00:00
#endregion AppEvents
_ = Init();
}
2024-10-21 05:46:13 +00:00
private async Task Init()
{
await RefreshRoutingsMenu();
await InboundDisplayStatus();
await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, true);
}
2024-10-12 07:45:21 +00:00
public void InitUpdateView(Func<EViewAction, object?, Task<bool>>? updateView)
{
_updateView = updateView;
if (_updateView != null)
2024-10-21 05:46:13 +00:00
{
AppEvents.ProfilesRefreshRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await RefreshServersBiz()); //.DisposeWith(_disposables);
2024-10-21 05:46:13 +00:00
}
}
2024-10-21 05:46:13 +00:00
private async Task CopyProxyCmdToClipboard()
{
var cmd = Utils.IsWindows() ? "set" : "export";
2025-08-17 09:31:55 +00:00
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 _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString());
}
private async Task AddServerViaClipboard()
{
2025-09-25 02:56:10 +00:00
AppEvents.AddServerViaClipboardRequested.Publish();
await Task.Delay(1000);
}
private async Task AddServerViaScan()
{
2025-09-25 02:56:10 +00:00
AppEvents.AddServerViaScanRequested.Publish();
await Task.Delay(1000);
}
private async Task UpdateSubscriptionProcess(bool blProxy)
{
2025-09-25 02:56:10 +00:00
AppEvents.SubscriptionsUpdateRequested.Publish(blProxy);
await Task.Delay(1000);
}
private async Task RefreshServersBiz()
{
await RefreshServersMenu();
//display running server
var running = await ConfigHandler.GetDefaultServer(_config);
if (running != null)
2024-10-12 07:45:21 +00:00
{
RunningServerDisplay =
RunningServerToolTipText = running.GetSummary();
2024-10-12 07:45:21 +00:00
}
else
2024-10-12 07:45:21 +00:00
{
RunningServerDisplay =
RunningServerToolTipText = ResUI.CheckServerSettings;
2024-10-12 07:45:21 +00:00
}
}
2024-10-12 07:45:21 +00:00
private async Task RefreshServersMenu()
{
2025-08-17 09:31:55 +00:00
var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, "");
2025-08-31 07:41:25 +00:00
Servers.Clear();
if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit)
2024-10-12 07:45:21 +00:00
{
BlServers = false;
return;
2024-10-12 07:45:21 +00:00
}
BlServers = true;
for (int k = 0; k < lstModel.Count; k++)
2024-10-12 07:45:21 +00:00
{
ProfileItem it = lstModel[k];
string name = it.GetSummary();
2024-10-12 07:45:21 +00:00
var item = new ComboItem() { ID = it.IndexId, Text = name };
2025-08-31 07:41:25 +00:00
Servers.Add(item);
if (_config.IndexId == it.IndexId)
2024-10-12 07:45:21 +00:00
{
SelectedServer = item;
2024-10-12 07:45:21 +00:00
}
}
}
2024-10-12 07:45:21 +00:00
private void ServerSelectedChanged(bool c)
{
if (!c)
2024-10-12 07:45:21 +00:00
{
return;
2024-10-12 07:45:21 +00:00
}
if (SelectedServer == null)
2024-10-12 07:45:21 +00:00
{
return;
2024-10-12 07:45:21 +00:00
}
if (SelectedServer.ID.IsNullOrEmpty())
{
return;
}
2025-09-25 02:56:10 +00:00
AppEvents.SetDefaultServerRequested.Publish(SelectedServer.ID);
}
2024-10-12 07:45:21 +00:00
public async Task TestServerAvailability()
{
var item = await ConfigHandler.GetDefaultServer(_config);
if (item == null)
2024-10-12 07:45:21 +00:00
{
return;
}
2025-02-06 07:28:51 +00:00
2025-08-30 10:01:01 +00:00
await TestServerAvailabilitySub(ResUI.Speedtesting);
2025-08-17 08:52:51 +00:00
var msg = await Task.Run(ConnectionHandler.RunAvailabilityCheck);
2025-02-06 07:28:51 +00:00
2025-08-17 09:31:55 +00:00
NoticeManager.Instance.SendMessageEx(msg);
2025-08-30 10:01:01 +00:00
await TestServerAvailabilitySub(msg);
}
2025-08-30 10:01:01 +00:00
private async Task TestServerAvailabilitySub(string msg)
{
RxApp.MainThreadScheduler.Schedule(msg, (scheduler, msg) =>
{
_ = TestServerAvailabilityResult(msg);
return Disposable.Empty;
});
}
public async Task TestServerAvailabilityResult(string msg)
{
RunningInfoDisplay = msg;
}
2024-10-12 07:45:21 +00:00
#region System proxy and Routings
private async Task SetListenerType(ESysProxyType type)
{
if (_config.SystemProxyItem.SysProxyType == type)
2024-10-12 07:45:21 +00:00
{
return;
2024-10-12 07:45:21 +00:00
}
_config.SystemProxyItem.SysProxyType = type;
await ChangeSystemProxyAsync(type, true);
2025-08-17 09:31:55 +00:00
NoticeManager.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType.ToString()}");
2024-10-12 07:45:21 +00:00
SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType;
await ConfigHandler.SaveConfig(_config);
}
2024-10-12 07:45:21 +00:00
public async Task ChangeSystemProxyAsync(ESysProxyType type, bool blChange)
{
await SysProxyHandler.UpdateSysProxy(_config, false);
2024-10-12 07:45:21 +00:00
BlSystemProxyClear = (type == ESysProxyType.ForcedClear);
BlSystemProxySet = (type == ESysProxyType.ForcedChange);
BlSystemProxyNothing = (type == ESysProxyType.Unchanged);
BlSystemProxyPac = (type == ESysProxyType.Pac);
2024-10-12 07:45:21 +00:00
if (blChange)
2024-10-12 07:45:21 +00:00
{
_updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null);
}
}
2024-10-12 07:45:21 +00:00
private async Task RefreshRoutingsMenu()
{
2025-08-31 07:41:25 +00:00
RoutingItems.Clear();
2024-10-12 07:45:21 +00:00
BlRouting = true;
2025-08-17 09:31:55 +00:00
var routings = await AppManager.Instance.RoutingItems();
foreach (var item in routings)
{
2025-08-31 07:41:25 +00:00
RoutingItems.Add(item);
if (item.IsActive)
2024-10-12 07:45:21 +00:00
{
SelectedRouting = item;
2024-10-12 07:45:21 +00:00
}
}
}
2024-10-12 07:45:21 +00:00
private async Task RoutingSelectedChangedAsync(bool c)
{
if (!c)
2024-10-12 07:45:21 +00:00
{
return;
2024-10-12 07:45:21 +00:00
}
if (SelectedRouting == null)
2024-10-12 07:45:21 +00:00
{
return;
}
2024-10-12 07:45:21 +00:00
2025-08-17 09:31:55 +00:00
var item = await AppManager.Instance.GetRoutingItem(SelectedRouting?.Id);
if (item is null)
{
return;
}
2024-10-12 07:45:21 +00:00
if (await ConfigHandler.SetDefaultRouting(_config, item) == 0)
{
2025-08-17 09:31:55 +00:00
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
2025-09-25 02:56:10 +00:00
AppEvents.ReloadRequested.Publish();
_updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null);
2024-10-12 07:45:21 +00:00
}
}
2024-10-12 07:45:21 +00:00
private async Task DoSystemProxySelected(bool c)
{
if (!c)
2024-10-12 07:45:21 +00:00
{
return;
2024-10-12 07:45:21 +00:00
}
if (_config.SystemProxyItem.SysProxyType == (ESysProxyType)SystemProxySelected)
{
return;
}
await SetListenerType((ESysProxyType)SystemProxySelected);
}
2024-10-12 07:45:21 +00:00
private async Task DoEnableTun(bool c)
{
if (_config.TunModeItem.EnableTun == EnableTun)
{
return;
}
_config.TunModeItem.EnableTun = EnableTun;
if (EnableTun && AllowEnableTun() == false)
2024-10-12 07:45:21 +00:00
{
// When running as a non-administrator, reboot to administrator mode
if (Utils.IsWindows())
2024-10-12 07:45:21 +00:00
{
_config.TunModeItem.EnableTun = false;
await AppManager.Instance.RebootAsAdmin();
return;
}
else
{
bool? passwordResult = await _updateView?.Invoke(EViewAction.PasswordInput, null);
if (passwordResult == false)
{
_config.TunModeItem.EnableTun = false;
return;
}
2024-10-12 07:45:21 +00:00
}
}
await ConfigHandler.SaveConfig(_config);
2025-09-25 02:56:10 +00:00
AppEvents.ReloadRequested.Publish();
}
2024-10-12 07:45:21 +00:00
private bool AllowEnableTun()
{
if (Utils.IsWindows())
{
2025-04-26 01:50:31 +00:00
return Utils.IsAdministrator();
}
else if (Utils.IsLinux())
{
2025-08-17 09:31:55 +00:00
return AppManager.Instance.LinuxSudoPwd.IsNotEmpty();
}
else if (Utils.IsOSX())
{
2025-08-17 09:31:55 +00:00
return AppManager.Instance.LinuxSudoPwd.IsNotEmpty();
}
return false;
}
#endregion System proxy and Routings
2024-10-12 07:45:21 +00:00
#region UI
2024-10-12 07:45:21 +00:00
private async Task InboundDisplayStatus()
{
StringBuilder sb = new();
2025-08-17 09:31:55 +00:00
sb.Append($"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}");
if (_config.Inbound.First().SecondLocalPortEnabled)
2024-10-12 07:45:21 +00:00
{
2025-08-17 09:31:55 +00:00
sb.Append($",{AppManager.Instance.GetLocalPort(EInboundProtocol.socks2)}");
2024-10-12 07:45:21 +00:00
}
sb.Append(']');
InboundDisplay = $"{ResUI.LabLocal}:{sb}";
2024-10-12 07:45:21 +00:00
if (_config.Inbound.First().AllowLANConn)
2024-10-12 07:45:21 +00:00
{
var lan = _config.Inbound.First().NewPort4LAN
2025-08-17 09:31:55 +00:00
? $"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks3)}]"
: $"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}]";
InboundLanDisplay = $"{ResUI.LabLAN}:{lan}";
}
else
{
InboundLanDisplay = $"{ResUI.LabLAN}:{Global.None}";
2024-10-12 07:45:21 +00:00
}
await Task.CompletedTask;
}
2024-10-12 07:45:21 +00:00
2025-08-30 10:01:01 +00:00
public async Task UpdateStatistics(ServerSpeedItem update)
{
2025-08-30 10:01:01 +00:00
if (!_config.GuiItem.DisplayRealTimeSpeed)
{
return;
}
try
{
if (_config.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
{
}
2024-10-12 07:45:21 +00:00
}
#endregion UI
2025-01-30 09:10:05 +00:00
}