Files
netch/Netch/Forms/MainForm.cs
2021-04-30 17:20:29 +08:00

1440 lines
45 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.Win32;
using Netch.Controllers;
using Netch.Forms.Mode;
using Netch.Models;
using Netch.Properties;
using Netch.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Netch.Interfaces;
namespace Netch.Forms
{
public partial class MainForm : Form
{
private void createRouteTableModeToolStripMenuItem_Click(object sender, EventArgs e)
{
Hide();
new Route().ShowDialog();
Show();
}
#region Start
private readonly Dictionary<string, object> _mainFormText = new();
private bool _textRecorded;
public MainForm()
{
InitializeComponent();
NotifyIcon.Icon = Icon = Resources.icon;
AddAddServerToolStripMenuItems();
#region i18N Translations
_mainFormText.Add(UninstallServiceToolStripMenuItem.Name, new[] { "Uninstall {0}", "NF Service" });
#endregion
// 监听电源事件
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
CheckForIllegalCrossThreadCalls = false;
}
private void AddAddServerToolStripMenuItems()
{
foreach (var serversUtil in ServerHelper.ServerUtils.Where(i => !string.IsNullOrEmpty(i.FullName)))
{
var fullName = serversUtil.FullName;
var control = new ToolStripMenuItem
{
Name = $"Add{fullName}ServerToolStripMenuItem",
Size = new Size(259, 22),
Text = i18N.TranslateFormat("Add [{0}] Server", fullName),
Tag = serversUtil
};
_mainFormText.Add(control.Name, new[] { "Add [{0}] Server", fullName });
control.Click += AddServerToolStripMenuItem_Click;
ServerToolStripMenuItem.DropDownItems.Add(control);
}
}
private void MainForm_Load(object sender, EventArgs e)
{
// 计算 ComboBox绘制 目标宽度
RecordSize();
LoadServers();
ServerHelper.DelayTestHelper.UpdateInterval();
ModeHelper.Load();
LoadModes();
// 加载翻译
TranslateControls();
// 隐藏 NatTypeStatusLabel
NatTypeStatusText();
// 加载快速配置
LoadProfiles();
Task.Run(() =>
{
// 检查更新
if (Global.Settings.CheckUpdateWhenOpened)
CheckUpdate();
});
Task.Run(() =>
{
// 检查订阅更新
if (Global.Settings.UpdateServersWhenOpened)
UpdateServersFromSubscribe().Wait();
// 打开软件时启动加速,产生开始按钮点击事件
if (Global.Settings.StartWhenOpened)
ControlButton_Click(null, null);
});
}
private void RecordSize()
{
_numberBoxWidth = ServerComboBox.Width / 10;
_numberBoxX = _numberBoxWidth * 9;
_numberBoxWrap = _numberBoxWidth / 30;
_configurationGroupBoxHeight = ConfigurationGroupBox.Height;
_profileConfigurationHeight = ConfigurationGroupBox.Controls[0].Height / 3; // 因为 AutoSize, 所以得到的是Controls的总高度
_profileGroupBoxPaddingHeight = ProfileGroupBox.Height - ProfileTable.Height;
_profileTableHeight = ProfileTable.Height;
}
private void TranslateControls()
{
#region Record English
if (!_textRecorded)
{
void RecordText(Component component)
{
try
{
switch (component)
{
case TextBoxBase:
case ListControl:
break;
case Control c:
_mainFormText.Add(c.Name, c.Text);
break;
case ToolStripItem c:
_mainFormText.Add(c.Name, c.Text);
break;
}
}
catch (ArgumentException)
{
// ignored
}
}
Utils.Utils.ComponentIterator(this, RecordText);
Utils.Utils.ComponentIterator(NotifyMenu, RecordText);
_textRecorded = true;
}
#endregion
#region Translate
void TranslateText(Component component)
{
switch (component)
{
case TextBoxBase:
case ListControl:
break;
case Control c:
if (_mainFormText.ContainsKey(c.Name))
c.Text = ControlText(c.Name);
break;
case ToolStripItem c:
if (_mainFormText.ContainsKey(c.Name))
c.Text = ControlText(c.Name);
break;
}
string ControlText(string name)
{
var value = _mainFormText[name];
if (value.Equals(string.Empty))
return string.Empty;
if (value is object[] values)
return i18N.TranslateFormat((string)values.First(), values.Skip(1).ToArray());
return i18N.Translate(value);
}
}
Utils.Utils.ComponentIterator(this, TranslateText);
Utils.Utils.ComponentIterator(NotifyMenu, TranslateText);
#endregion
UsedBandwidthLabel.Text = $@"{i18N.Translate("Used", ": ")}0 KB";
State = State;
VersionLabel.Text = UpdateChecker.Version;
}
#endregion
#region Controls
#region MenuStrip
#region Server
private void ImportServersFromClipboardToolStripMenuItem_Click(object sender, EventArgs e)
{
var texts = Clipboard.GetText();
if (!string.IsNullOrWhiteSpace(texts))
{
var servers = ShareLink.ParseText(texts);
Global.Settings.Server.AddRange(servers);
NotifyTip(i18N.TranslateFormat("Import {0} server(s) form Clipboard", servers.Count));
LoadServers();
Configuration.Save();
}
}
private void AddServerToolStripMenuItem_Click([NotNull] object? sender, EventArgs? e)
{
if (sender == null)
throw new ArgumentNullException(nameof(sender));
var util = (IServerUtil)((ToolStripMenuItem)sender).Tag;
Hide();
util.Create();
LoadServers();
Configuration.Save();
Show();
}
#endregion
#region Mode
private void CreateProcessModeToolStripButton_Click(object sender, EventArgs e)
{
Hide();
new Process().ShowDialog();
Show();
}
#endregion
#region Subscription
private void ManageSubscribeLinksToolStripMenuItem_Click(object sender, EventArgs e)
{
Hide();
new SubscribeForm().ShowDialog();
LoadServers();
Show();
}
private async void UpdateServersFromSubscribeLinksToolStripMenuItem_Click(object sender, EventArgs e)
{
await UpdateServersFromSubscribe();
}
private async Task UpdateServersFromSubscribe()
{
void DisableItems(bool v)
{
MenuStrip.Enabled = ConfigurationGroupBox.Enabled = ProfileGroupBox.Enabled = ControlButton.Enabled = v;
}
if (Global.Settings.SubscribeLink.Count <= 0)
{
MessageBoxX.Show(i18N.Translate("No subscription link"));
return;
}
StatusText(i18N.Translate("Starting update subscription"));
DisableItems(false);
try
{
await Subscription.UpdateServersAsync();
LoadServers();
Configuration.Save();
StatusText(i18N.Translate("Subscription updated"));
}
catch (Exception e)
{
NotifyTip(i18N.Translate("update servers failed") + "\n" + e.Message, info: false);
Global.Logger.Error("更新服务器 失败!" + e);
}
finally
{
DisableItems(true);
}
}
#endregion
#region Options
private void CheckForUpdatesToolStripMenuItem_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
void OnNewVersionNotFound(object? o, EventArgs? args)
{
NotifyTip(i18N.Translate("Already latest version"));
}
void OnNewVersionFoundFailed(object? o, EventArgs? args)
{
NotifyTip(i18N.Translate("New version found failed"), info: false);
}
try
{
UpdateChecker.NewVersionNotFound += OnNewVersionNotFound;
UpdateChecker.NewVersionFoundFailed += OnNewVersionFoundFailed;
CheckUpdate();
}
finally
{
UpdateChecker.NewVersionNotFound -= OnNewVersionNotFound;
UpdateChecker.NewVersionFoundFailed -= OnNewVersionFoundFailed;
}
});
}
private void OpenDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
Utils.Utils.Open(".\\");
}
private async void CleanDNSCacheToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
await Task.Run(() =>
{
NativeMethods.RefreshDNSCache();
DnsUtils.ClearCache();
});
NotifyTip(i18N.Translate("DNS cache cleanup succeeded"));
}
catch (Exception)
{
// ignored
}
finally
{
StatusText();
}
}
private async void UninstallServiceToolStripMenuItem_Click(object sender, EventArgs e)
{
Enabled = false;
StatusText(i18N.TranslateFormat("Uninstalling {0}", "NF Service"));
try
{
await Task.Run(() =>
{
if (NFController.UninstallDriver())
NotifyTip(i18N.TranslateFormat("{0} has been uninstalled", "NF Service"));
});
}
finally
{
StatusText();
Enabled = true;
}
}
private void RemoveNetchFirewallRulesToolStripMenuItem_Click(object sender, EventArgs e)
{
Firewall.RemoveNetchFwRules();
}
#endregion
/// <summary>
/// 菜单栏强制退出
/// </summary>
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
Exit(true);
}
private void VersionLabel_Click(object sender, EventArgs e)
{
Utils.Utils.Open($"https://github.com/{UpdateChecker.Owner}/{UpdateChecker.Repo}/releases");
}
private async void NewVersionLabel_Click(object sender, EventArgs e)
{
if (ModifierKeys == Keys.Control || !UpdateChecker.LatestRelease!.assets.Any())
{
Utils.Utils.Open(UpdateChecker.LatestVersionUrl!);
return;
}
if (MessageBoxX.Show(i18N.Translate($"Download and install now?\n\n{UpdateChecker.GetLatestReleaseContent()}"), confirm: true) !=
DialogResult.OK)
return;
NotifyTip(i18N.Translate("Start downloading new version"));
NewVersionLabel.Enabled = false;
NewVersionLabel.Text = "...";
try
{
await Task.Run(() =>
{
Updater.Updater.DownloadAndUpdate(Path.Combine(Global.NetchDir, "data"),
Global.NetchDir,
(_, args) => BeginInvoke(new Action(() => NewVersionLabel.Text = $"{args.ProgressPercentage}%")));
});
}
catch (Exception exception)
{
if (exception is not MessageException)
{
Global.Logger.Error($"更新失败: {exception}");
Global.Logger.ShowLog();
}
NotifyTip(exception.Message, info: false);
}
finally
{
NewVersionLabel.Visible = false;
NewVersionLabel.Enabled = true;
}
}
private void AboutToolStripButton_Click(object sender, EventArgs e)
{
Hide();
new AboutForm().ShowDialog();
Show();
}
private void fAQToolStripMenuItem_Click(object sender, EventArgs e)
{
Utils.Utils.Open("https://netch.org/#/docs/zh-CN/faq");
}
#endregion
#region ControlButton
private async void ControlButton_Click(object? sender, EventArgs? e)
{
if (!IsWaiting())
{
// 停止
await StopAsyncCore();
return;
}
Configuration.Save();
// 服务器、模式 需选择
if (!(ServerComboBox.SelectedItem is Server server))
{
MessageBoxX.Show(i18N.Translate("Please select a server first"));
return;
}
if (!(ModeComboBox.SelectedItem is Models.Mode mode))
{
MessageBoxX.Show(i18N.Translate("Please select a mode first"));
return;
}
State = State.Starting;
try
{
await MainController.StartAsync(server, mode);
}
catch (Exception exception)
{
State = State.Stopped;
StatusText(i18N.Translate("Start failed"));
MessageBoxX.Show(exception.Message, LogLevel.ERROR);
return;
}
State = State.Started;
_ = Task.Run(Bandwidth.NetTraffic);
_ = Task.Run(NatTest);
if (Global.Settings.MinimizeWhenStarted)
Minimize();
// 自动检测延迟
_ = Task.Run(() =>
{
while (State == State.Started)
{
bool StartedPingEnabled()
{
return Global.Settings.StartedPingInterval >= 0;
}
if (StartedPingEnabled())
{
server.Test();
ServerComboBox.Refresh();
}
if (StartedPingEnabled())
Thread.Sleep(Global.Settings.StartedPingInterval * 1000);
else
Thread.Sleep(5000);
}
});
}
#endregion
#region SettingsButton
private void SettingsButton_Click(object sender, EventArgs e)
{
var oldSettings = Global.Settings.Clone();
Hide();
new SettingForm().ShowDialog();
if (oldSettings.Language != Global.Settings.Language)
{
i18N.Load(Global.Settings.Language);
TranslateControls();
LoadModes();
LoadProfiles();
}
if (oldSettings.DetectionTick != Global.Settings.DetectionTick)
ServerHelper.DelayTestHelper.UpdateInterval();
if (oldSettings.ProfileCount != Global.Settings.ProfileCount)
LoadProfiles();
Show();
}
#endregion
#region Server
private void LoadServers()
{
ServerComboBox.Items.Clear();
ServerComboBox.Items.AddRange(Global.Settings.Server.ToArray());
SelectLastServer();
}
public void SelectLastServer()
{
// 如果值合法,选中该位置
if (Global.Settings.ServerComboBoxSelectedIndex > 0 && Global.Settings.ServerComboBoxSelectedIndex < ServerComboBox.Items.Count)
ServerComboBox.SelectedIndex = Global.Settings.ServerComboBoxSelectedIndex;
// 如果值非法,且当前 ServerComboBox 中有元素,选择第一个位置
else if (ServerComboBox.Items.Count > 0)
ServerComboBox.SelectedIndex = 0;
// 如果当前 ServerComboBox 中没元素,不做处理
}
private void ServerComboBox_SelectionChangeCommitted(object sender, EventArgs o)
{
Global.Settings.ServerComboBoxSelectedIndex = ServerComboBox.SelectedIndex;
}
private void EditServerPictureBox_Click(object sender, EventArgs e)
{
// 当前ServerComboBox中至少有一项
if (!(ServerComboBox.SelectedItem is Server server))
{
MessageBoxX.Show(i18N.Translate("Please select a server first"));
return;
}
if (!server.Valid())
return;
Hide();
ServerHelper.GetUtilByTypeName(server.Type).Edit(server);
LoadServers();
Configuration.Save();
Show();
}
private void SpeedPictureBox_Click(object sender, EventArgs e)
{
Enabled = false;
StatusText(i18N.Translate("Testing"));
if (!IsWaiting() || ModifierKeys == Keys.Control)
{
(ServerComboBox.SelectedItem as Server)?.Test();
ServerComboBox.Refresh();
Enabled = true;
StatusText();
}
else
{
ServerHelper.DelayTestHelper.TestDelayFinished += OnTestDelayFinished;
_ = Task.Run(ServerHelper.DelayTestHelper.TestAllDelay);
void OnTestDelayFinished(object? o1, EventArgs? e1)
{
Refresh();
ServerHelper.DelayTestHelper.TestDelayFinished -= OnTestDelayFinished;
Enabled = true;
StatusText();
}
}
}
private void CopyLinkPictureBox_Click(object sender, EventArgs e)
{
// 当前ServerComboBox中至少有一项
if (!(ServerComboBox.SelectedItem is Server server))
{
MessageBoxX.Show(i18N.Translate("Please select a server first"));
return;
}
if (!server.Valid())
return;
try
{
//听说巨硬BUG经常会炸所以Catch一下 :D
string text;
if (ModifierKeys == Keys.Control)
text = ShareLink.GetNetchLink(server);
else
text = ShareLink.GetShareLink(server);
Clipboard.SetText(text);
}
catch (Exception)
{
// ignored
}
}
private void DeleteServerPictureBox_Click(object sender, EventArgs e)
{
// 当前 ServerComboBox 中至少有一项
if (!(ServerComboBox.SelectedItem is Server server))
{
MessageBoxX.Show(i18N.Translate("Please select a server first"));
return;
}
Global.Settings.Server.Remove(server);
LoadServers();
}
#endregion
#region Mode
public void LoadModes()
{
ModeComboBox.Items.Clear();
ModeComboBox.Items.AddRange(Global.Modes.Cast<object>().ToArray());
ModeComboBox.Tag = null;
SelectLastMode();
}
public void SelectLastMode()
{
// 如果值合法,选中该位置
if (Global.Settings.ModeComboBoxSelectedIndex > 0 && Global.Settings.ModeComboBoxSelectedIndex < ModeComboBox.Items.Count)
ModeComboBox.SelectedIndex = Global.Settings.ModeComboBoxSelectedIndex;
// 如果值非法,且当前 ModeComboBox 中有元素,选择第一个位置
else if (ModeComboBox.Items.Count > 0)
ModeComboBox.SelectedIndex = 0;
// 如果当前 ModeComboBox 中没元素,不做处理
}
private void ModeComboBox_SelectionChangeCommitted(object sender, EventArgs o)
{
try
{
Global.Settings.ModeComboBoxSelectedIndex = Global.Modes.IndexOf((Models.Mode)ModeComboBox.SelectedItem);
}
catch
{
Global.Settings.ModeComboBoxSelectedIndex = 0;
}
}
private void EditModePictureBox_Click(object sender, EventArgs e)
{
// 当前ModeComboBox中至少有一项
if (ModeComboBox.SelectedIndex == -1)
{
MessageBoxX.Show(i18N.Translate("Please select a mode first"));
return;
}
var mode = (Models.Mode)ModeComboBox.SelectedItem;
if (ModifierKeys == Keys.Control)
{
Utils.Utils.Open(ModeHelper.GetFullPath(mode.RelativePath!));
return;
}
switch (mode.Type)
{
case 0:
Hide();
new Process(mode).ShowDialog();
Show();
break;
case 1:
case 2:
Hide();
new Route(mode).ShowDialog();
Show();
break;
default:
Utils.Utils.Open(ModeHelper.GetFullPath(mode.RelativePath!));
break;
}
}
private void DeleteModePictureBox_Click(object sender, EventArgs e)
{
// 当前ModeComboBox中至少有一项
if (ModeComboBox.Items.Count <= 0 || ModeComboBox.SelectedIndex == -1)
{
MessageBoxX.Show(i18N.Translate("Please select a mode first"));
return;
}
ModeHelper.Delete((Models.Mode)ModeComboBox.SelectedItem);
SelectLastMode();
}
#endregion
#region Profile
private int _configurationGroupBoxHeight;
private int _profileConfigurationHeight;
private int _profileGroupBoxPaddingHeight;
private int _profileTableHeight;
private void LoadProfiles()
{
// Clear
foreach (var button in ProfileTable.Controls)
((Button)button).Dispose();
ProfileTable.Controls.Clear();
ProfileTable.ColumnStyles.Clear();
ProfileTable.RowStyles.Clear();
var profileCount = Global.Settings.ProfileCount;
if (profileCount == 0)
{
// Hide Profile GroupBox, Change window size
configLayoutPanel.RowStyles[2].SizeType = SizeType.Percent;
configLayoutPanel.RowStyles[2].Height = 0;
ProfileGroupBox.Visible = false;
ConfigurationGroupBox.Height = _configurationGroupBoxHeight - _profileConfigurationHeight;
}
else
{
// Load Profiles
if (Global.Settings.ProfileTableColumnCount == 0)
Global.Settings.ProfileTableColumnCount = 5;
var columnCount = Global.Settings.ProfileTableColumnCount;
ProfileTable.ColumnCount = profileCount >= columnCount ? columnCount : profileCount;
ProfileTable.RowCount = (int)Math.Ceiling(profileCount / (float)columnCount);
for (var i = 0; i < profileCount; ++i)
{
var profile = Global.Settings.Profiles.SingleOrDefault(p => p.Index == i);
var b = new Button
{
Dock = DockStyle.Fill,
Text = profile?.ProfileName ?? i18N.Translate("None"),
Tag = profile
};
b.Click += ProfileButton_Click;
ProfileTable.Controls.Add(b, i % columnCount, i / columnCount);
}
// equal column
for (var i = 1; i <= ProfileTable.RowCount; i++)
ProfileTable.RowStyles.Add(new RowStyle(SizeType.Percent, 1));
for (var i = 1; i <= ProfileTable.ColumnCount; i++)
ProfileTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 1));
configLayoutPanel.RowStyles[2].SizeType = SizeType.AutoSize;
ProfileGroupBox.Visible = true;
ProfileGroupBox.Height = ProfileTable.RowCount * _profileTableHeight + _profileGroupBoxPaddingHeight;
ConfigurationGroupBox.Height = _configurationGroupBoxHeight;
}
}
private void ActiveProfile(Profile profile)
{
ProfileNameText.Text = profile.ProfileName;
var server = ServerComboBox.Items.Cast<Server>().FirstOrDefault(s => s.Remark.Equals(profile.ServerRemark));
var mode = ModeComboBox.Items.Cast<Models.Mode>().FirstOrDefault(m => m.Remark.Equals(profile.ModeRemark));
if (server == null)
throw new Exception("Server not found.");
if (mode == null)
throw new Exception("Mode not found.");
ServerComboBox.SelectedItem = server;
ModeComboBox.SelectedItem = mode;
}
private Profile CreateProfileAtIndex(int index)
{
var server = (Server)ServerComboBox.SelectedItem;
var mode = (Models.Mode)ModeComboBox.SelectedItem;
var name = ProfileNameText.Text;
Profile? profile;
if ((profile = Global.Settings.Profiles.SingleOrDefault(p => p.Index == index)) != null)
Global.Settings.Profiles.Remove(profile);
profile = new Profile(server, mode, name, index);
Global.Settings.Profiles.Add(profile);
return profile;
}
private async void ProfileButton_Click([NotNull] object? sender, EventArgs? e)
{
if (sender == null)
throw new ArgumentNullException(nameof(sender));
var profileButton = (Button)sender;
var profile = (Profile?)profileButton.Tag;
var index = ProfileTable.Controls.IndexOf(profileButton);
switch (ModifierKeys)
{
case Keys.Control:
if (ServerComboBox.SelectedIndex == -1)
{
MessageBoxX.Show(i18N.Translate("Please select a server first"));
return;
}
if (ModeComboBox.SelectedIndex == -1)
{
MessageBoxX.Show(i18N.Translate("Please select a mode first"));
return;
}
if (ProfileNameText.Text == "")
{
MessageBoxX.Show(i18N.Translate("Please enter a profile name first"));
ProfileNameText.Focus();
return;
}
profileButton.Tag = profile = CreateProfileAtIndex(index);
profileButton.Text = profile.ProfileName;
ProfileNameText.Clear();
return;
case Keys.Shift:
if (profile == null)
return;
Global.Settings.Profiles.Remove(profile);
profileButton.Tag = null;
profileButton.Text = i18N.Translate("None");
return;
}
if (profile == null)
{
MessageBoxX.Show(i18N.Translate("No saved profile here. Save a profile first by Ctrl+Click on the button"));
return;
}
try
{
ActiveProfile(profile);
}
catch (Exception exception)
{
MessageBoxX.Show(exception.Message, LogLevel.ERROR);
return;
}
// start the profile
ControlButton_Click(null, null);
if (State == State.Stopping || State == State.Stopped)
{
while (State != State.Stopped)
await Task.Delay(250);
ControlButton_Click(null, null);
}
}
#endregion
#region State
private State _state = State.Waiting;
/// <summary>
/// 当前状态
/// </summary>
public State State
{
get => _state;
private set
{
void StartDisableItems(bool enabled)
{
ServerComboBox.Enabled = ModeComboBox.Enabled = EditModePictureBox.Enabled =
EditServerPictureBox.Enabled = DeleteModePictureBox.Enabled = DeleteServerPictureBox.Enabled = enabled;
// 启动需要禁用的控件
UninstallServiceToolStripMenuItem.Enabled = UpdateServersFromSubscribeLinksToolStripMenuItem.Enabled = enabled;
}
_state = value;
ServerHelper.DelayTestHelper.Enabled = IsWaiting(_state);
StatusText();
switch (value)
{
case State.Waiting:
ControlButton.Enabled = true;
ControlButton.Text = i18N.Translate("Start");
break;
case State.Starting:
ControlButton.Enabled = false;
ControlButton.Text = "...";
ProfileGroupBox.Enabled = false;
StartDisableItems(false);
break;
case State.Started:
ControlButton.Enabled = true;
ControlButton.Text = i18N.Translate("Stop");
ProfileGroupBox.Enabled = true;
break;
case State.Stopping:
ControlButton.Enabled = false;
ControlButton.Text = "...";
ProfileGroupBox.Enabled = false;
BandwidthState(false);
NatTypeStatusText();
break;
case State.Stopped:
ControlButton.Enabled = true;
ControlButton.Text = i18N.Translate("Start");
LastUploadBandwidth = 0;
LastDownloadBandwidth = 0;
Bandwidth.Stop();
ProfileGroupBox.Enabled = true;
StartDisableItems(true);
break;
}
}
}
private async Task StopAsyncCore()
{
State = State.Stopping;
await MainController.StopAsync();
State = State.Stopped;
}
public void Stop()
{
if (IsWaiting())
return;
if (InvokeRequired)
{
Invoke(new Action(Stop));
return;
}
StopAsyncCore().Wait();
}
private bool IsWaiting()
{
return State == State.Waiting || State == State.Stopped;
}
private static bool IsWaiting(State state)
{
return state == State.Waiting || state == State.Stopped;
}
/// <summary>
/// 更新状态栏文本
/// </summary>
/// <param name="text"></param>
public void StatusText(string? text = null)
{
if (InvokeRequired)
{
BeginInvoke(new Action<string>(StatusText), text);
return;
}
text ??= i18N.Translate(StateExtension.GetStatusString(State));
StatusLabel.Text = i18N.Translate("Status", ": ") + text;
if (_state == State.Started)
StatusLabel.Text += StatusPortInfoText.Value;
}
public void BandwidthState(bool state)
{
if (InvokeRequired)
{
BeginInvoke(new Action<bool>(BandwidthState), state);
return;
}
if (IsWaiting())
return;
UsedBandwidthLabel.Visible /*= UploadSpeedLabel.Visible*/ = DownloadSpeedLabel.Visible = state;
}
public void NatTypeStatusText(string? text = null, string? country = null)
{
if (InvokeRequired)
{
BeginInvoke(new Action<string, string>(NatTypeStatusText), text, country);
return;
}
if (State != State.Started)
{
NatTypeStatusLabel.Text = "";
NatTypeStatusLabel.Visible = NatTypeStatusLightLabel.Visible = false;
return;
}
if (!string.IsNullOrEmpty(text))
{
NatTypeStatusLabel.Text = $"NAT{i18N.Translate(": ")}{text} {(!country.IsNullOrEmpty() ? $"[{country}]" : "")}";
UpdateNatTypeLight(int.TryParse(text, out var natType) ? natType : -1);
}
else
{
NatTypeStatusLabel.Text = $@"NAT{i18N.Translate(": ", "Test failed")}";
}
NatTypeStatusLabel.Visible = true;
}
/// <summary>
/// 更新 NAT指示灯颜色
/// </summary>
/// <param name="natType"></param>
private void UpdateNatTypeLight(int natType = -1)
{
if (natType > 0 && natType < 5)
{
NatTypeStatusLightLabel.Visible = Flags.IsWindows10Upper;
var c = natType switch
{
1 => Color.LimeGreen,
2 => Color.Yellow,
3 => Color.Red,
4 => Color.Black,
_ => throw new ArgumentOutOfRangeException(nameof(natType), natType, null)
};
NatTypeStatusLightLabel.ForeColor = c;
}
else
{
NatTypeStatusLightLabel.Visible = false;
}
}
private void NatTypeStatusLabel_Click(object sender, EventArgs e)
{
if (_state == State.Started && NttTested)
NatTest();
}
private static bool NttTested;
/// <summary>
/// 测试 NAT
/// </summary>
public void NatTest()
{
if (!MainController.Mode!.TestNatRequired())
return;
NttTested = false;
Task.Run(() =>
{
NatTypeStatusText(i18N.Translate("Starting NatTester"));
var (result, localEnd, publicEnd) = MainController.NTTController.Start().Result;
if (!string.IsNullOrEmpty(publicEnd))
{
var country = Utils.Utils.GetCityCode(publicEnd!);
NatTypeStatusText(result, country);
}
else
{
NatTypeStatusText(result ?? "Error");
}
NttTested = true;
});
}
#endregion
#endregion
#region Misc
#region PowerEvent
private bool _resumeFlag;
private void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
switch (e.Mode)
{
case PowerModes.Suspend: //操作系统即将挂起
if (!IsWaiting())
{
_resumeFlag = true;
Global.Logger.Info("操作系统即将挂起,自动停止");
ControlButton_Click(null, null);
}
break;
case PowerModes.Resume: //操作系统即将从挂起状态继续
if (_resumeFlag)
{
_resumeFlag = false;
Global.Logger.Info("操作系统即将从挂起状态继续,自动重启");
ControlButton_Click(null, null);
}
break;
}
}
#endregion
private void Minimize()
{
// 使关闭时窗口向右下角缩小的效果
WindowState = FormWindowState.Minimized;
if (_isFirstCloseWindow)
{
// 显示提示语
NotifyTip(i18N.Translate("Netch is now minimized to the notification bar, double click this icon to restore."));
_isFirstCloseWindow = false;
}
Hide();
}
public async void Exit(bool forceExit = false, bool saveConfiguration = true)
{
if (!IsWaiting() && !Global.Settings.StopWhenExited && !forceExit)
{
MessageBoxX.Show(i18N.Translate("Please press Stop button first"));
NotifyIcon_MouseDoubleClick(null, null);
return;
}
State = State.Terminating;
NotifyIcon.Visible = false;
Hide();
if (saveConfiguration)
{
Configuration.Save();
}
foreach (var file in new[] { "data\\last.json", "data\\privoxy.conf" })
if (File.Exists(file))
File.Delete(file);
if (!IsWaiting())
await StopAsyncCore();
Dispose();
Environment.Exit(Environment.ExitCode);
}
#region FormClosingButton
private bool _isFirstCloseWindow = true;
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing && State != State.Terminating)
{
// 取消"关闭窗口"事件
e.Cancel = true; // 取消关闭窗体
// 如果未勾选关闭窗口时退出,隐藏至右下角托盘图标
if (!Global.Settings.ExitWhenClosed)
Minimize();
// 如果勾选了关闭时退出,自动点击退出按钮
else
Exit();
}
}
#endregion
#region Updater
private void CheckUpdate()
{
try
{
UpdateChecker.NewVersionFound += OnUpdateCheckerOnNewVersionFound;
UpdateChecker.Check(Global.Settings.CheckBetaUpdate).Wait();
if (Flags.AlwaysShowNewVersionFound)
OnUpdateCheckerOnNewVersionFound(null!, null!);
}
finally
{
UpdateChecker.NewVersionFound -= OnUpdateCheckerOnNewVersionFound;
}
void OnUpdateCheckerOnNewVersionFound(object? o, EventArgs? eventArgs)
{
NotifyTip($"{i18N.Translate(@"New version available", ": ")}{UpdateChecker.LatestVersionNumber}");
NewVersionLabel.Text = i18N.Translate("New version available");
NewVersionLabel.Enabled = true;
NewVersionLabel.Visible = true;
}
}
#endregion
#region NetTraffic
/// <summary>
/// 上一次下载的流量
/// </summary>
public ulong LastDownloadBandwidth;
/// <summary>
/// 上一次上传的流量
/// </summary>
public ulong LastUploadBandwidth;
public void OnBandwidthUpdated(ulong download)
{
if (InvokeRequired)
{
BeginInvoke(new Action<ulong>(OnBandwidthUpdated), download);
return;
}
try
{
UsedBandwidthLabel.Text = $"{i18N.Translate("Used", ": ")}{Bandwidth.Compute(download)}";
//UploadSpeedLabel.Text = $"↑: {Utils.Bandwidth.Compute(upload - LastUploadBandwidth)}/s";
DownloadSpeedLabel.Text = $"↑↓: {Bandwidth.Compute(download - LastDownloadBandwidth)}/s";
//LastUploadBandwidth = upload;
LastDownloadBandwidth = download;
Refresh();
}
catch
{
// ignored
}
}
#endregion
#region NotifyIcon
public void ShowMainFormToolStripButton_Click(object sender, EventArgs e)
{
if (InvokeRequired)
{
Invoke(new Action(() => ShowMainFormToolStripButton_Click(sender, e)));
return;
}
if (WindowState == FormWindowState.Minimized)
{
Visible = true;
ShowInTaskbar = true; // 显示在系统任务栏
WindowState = FormWindowState.Normal; // 还原窗体
}
Activate();
}
/// <summary>
/// 通知图标右键菜单退出
/// </summary>
private void ExitToolStripButton_Click(object sender, EventArgs e)
{
Exit();
}
private void NotifyIcon_MouseDoubleClick(object? sender, MouseEventArgs? e)
{
if (WindowState == FormWindowState.Minimized)
{
Visible = true;
ShowInTaskbar = true; //显示在系统任务栏
WindowState = FormWindowState.Normal; //还原窗体
}
Activate();
}
public void NotifyTip(string text, int timeout = 0, bool info = true)
{
// 会阻塞线程 timeout 秒(?)
NotifyIcon.ShowBalloonTip(timeout, UpdateChecker.Name, text, info ? ToolTipIcon.Info : ToolTipIcon.Error);
}
#endregion
#region ComboBox_DrawItem
private readonly SolidBrush _greenBrush = new(Color.FromArgb(50, 255, 56));
private int _numberBoxWidth;
private int _numberBoxX;
private int _numberBoxWrap;
private void ComboBox_DrawItem(object sender, DrawItemEventArgs e)
{
if (!(sender is ComboBox cbx))
return;
// 绘制背景颜色
e.Graphics.FillRectangle(Brushes.White, e.Bounds);
if (e.Index < 0)
return;
// 绘制 备注/名称 字符串
TextRenderer.DrawText(e.Graphics, cbx.Items[e.Index].ToString(), cbx.Font, e.Bounds, Color.Black, TextFormatFlags.Left);
switch (cbx.Items[e.Index])
{
case Server item:
{
// 计算延迟底色
var numBoxBackBrush = item.Delay switch { > 200 => Brushes.Red, > 80 => Brushes.Yellow, >= 0 => _greenBrush, _ => Brushes.Gray };
// 绘制延迟底色
e.Graphics.FillRectangle(numBoxBackBrush, _numberBoxX, e.Bounds.Y, _numberBoxWidth, e.Bounds.Height);
// 绘制延迟字符串
TextRenderer.DrawText(e.Graphics,
item.Delay.ToString(),
cbx.Font,
new Point(_numberBoxX + _numberBoxWrap, e.Bounds.Y),
Color.Black,
TextFormatFlags.Left);
break;
}
case Models.Mode item:
{
// 绘制 模式Box 底色
e.Graphics.FillRectangle(Brushes.Gray, _numberBoxX, e.Bounds.Y, _numberBoxWidth, e.Bounds.Height);
// 绘制 模式行数 字符串
TextRenderer.DrawText(e.Graphics,
item.Rule.Count.ToString(),
cbx.Font,
new Point(_numberBoxX + _numberBoxWrap, e.Bounds.Y),
Color.Black,
TextFormatFlags.Left);
break;
}
}
}
#endregion
#endregion
}
}