using NLog; using Shadowsocks.Controller; using Shadowsocks.Localization; using Shadowsocks.Model; using Shadowsocks.Properties; using Shadowsocks.Util; using Shadowsocks.Util.SystemProxy; using Shadowsocks.Views; using System; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; using System.Windows.Forms.Integration; using System.Windows.Threading; using ZXing; using ZXing.Common; using ZXing.QrCode; namespace Shadowsocks.View { public class MenuViewController { private readonly Logger logger = LogManager.GetCurrentClassLogger(); private ShadowsocksController controller; public UpdateChecker updateChecker; private NotifyIcon _notifyIcon; private Icon icon, icon_in, icon_out, icon_both, previousIcon; private bool _isStartupCheck; private string _urlToOpen; private ContextMenuStrip contextMenu1; private ToolStripMenuItem disableItem; private ToolStripMenuItem AutoStartupItem; private ToolStripMenuItem ShareOverLANItem; private ToolStripSeparator SeperatorItem; private ToolStripMenuItem ConfigItem; private ToolStripMenuItem ProtocolHandlerItem; private ToolStripMenuItem ServersItem; private ToolStripMenuItem globalModeItem; private ToolStripMenuItem PACModeItem; private ToolStripMenuItem localPACItem; private ToolStripMenuItem onlinePACItem; private ToolStripMenuItem editLocalPACItem; private ToolStripMenuItem updateFromGeositeItem; private ToolStripMenuItem editGFWUserRuleItem; private ToolStripMenuItem editOnlinePACItem; private ToolStripMenuItem secureLocalPacUrlToggleItem; private ToolStripMenuItem regenerateLocalPacOnUpdateItem; private ToolStripMenuItem autoCheckUpdatesToggleItem; private ToolStripMenuItem checkPreReleaseToggleItem; private ToolStripMenuItem proxyItem; private ToolStripMenuItem hotKeyItem; private ToolStripMenuItem VerboseLoggingToggleItem; private ToolStripMenuItem ShowPluginOutputToggleItem; private ToolStripMenuItem WriteI18NFileItem; private ToolStripMenuItem onlineConfigItem; private ConfigForm configForm; private LogForm logForm; private System.Windows.Window serverSharingWindow; private System.Windows.Window hotkeysWindow; private System.Windows.Window forwardProxyWindow; private System.Windows.Window onlineConfigWindow; // color definition for icon color transformation private readonly Color colorMaskBlue = Color.FromArgb(255, 25, 125, 191); private readonly Color colorMaskDarkSilver = Color.FromArgb(128, 192, 192, 192); private readonly Color colorMaskLightSilver = Color.FromArgb(192, 192, 192); private readonly Color colorMaskEclipse = Color.FromArgb(192, 64, 64, 64); public MenuViewController(ShadowsocksController controller) { this.controller = controller; LoadMenu(); controller.EnableStatusChanged += controller_EnableStatusChanged; controller.ConfigChanged += controller_ConfigChanged; controller.PACFileReadyToOpen += controller_FileReadyToOpen; controller.UserRuleFileReadyToOpen += controller_FileReadyToOpen; controller.ShareOverLANStatusChanged += controller_ShareOverLANStatusChanged; controller.VerboseLoggingStatusChanged += controller_VerboseLoggingStatusChanged; controller.ShowPluginOutputChanged += controller_ShowPluginOutputChanged; controller.EnableGlobalChanged += controller_EnableGlobalChanged; controller.Errored += controller_Errored; controller.UpdatePACFromGeositeCompleted += controller_UpdatePACFromGeositeCompleted; controller.UpdatePACFromGeositeError += controller_UpdatePACFromGeositeError; _notifyIcon = new NotifyIcon(); UpdateTrayIconAndNotifyText(); _notifyIcon.Visible = true; _notifyIcon.ContextMenuStrip = contextMenu1; _notifyIcon.BalloonTipClicked += notifyIcon1_BalloonTipClicked; _notifyIcon.MouseClick += notifyIcon1_Click; _notifyIcon.MouseDoubleClick += notifyIcon1_DoubleClick; _notifyIcon.BalloonTipClosed += _notifyIcon_BalloonTipClosed; controller.TrafficChanged += controller_TrafficChanged; updateChecker = new UpdateChecker(); updateChecker.CheckUpdateCompleted += updateChecker_CheckUpdateCompleted; LoadCurrentConfiguration(); Configuration config = controller.GetCurrentConfiguration(); if (config.firstRun) { ShowConfigForm(); } else if (config.autoCheckUpdate) { _isStartupCheck = true; Dispatcher.CurrentDispatcher.Invoke(() => updateChecker.CheckForVersionUpdate(3000)); } } #region Tray Icon private void UpdateTrayIconAndNotifyText() { Configuration config = controller.GetCurrentConfiguration(); bool enabled = config.enabled; bool global = config.global; Color colorMask = SelectColorMask(enabled, global); Size iconSize = SelectIconSize(); UpdateIconSet(colorMask, iconSize, out icon, out icon_in, out icon_out, out icon_both); previousIcon = icon; _notifyIcon.Icon = previousIcon; string serverInfo = null; if (controller.GetCurrentStrategy() != null) { serverInfo = controller.GetCurrentStrategy().Name; } else { serverInfo = config.GetCurrentServer().ToString(); } // show more info by hacking the P/Invoke declaration for NOTIFYICONDATA inside Windows Forms string text = I18N.GetString("Shadowsocks") + " " + UpdateChecker.Version + "\n" + (enabled ? I18N.GetString("System Proxy On: ") + (global ? I18N.GetString("Global") : I18N.GetString("PAC")) : I18N.GetString("Running: Port {0}", config.localPort)) // this feedback is very important because they need to know Shadowsocks is running + "\n" + serverInfo; if (text.Length > 127) { text = text.Substring(0, 126 - 3) + "..."; } ViewUtils.SetNotifyIconText(_notifyIcon, text); } /// /// Determine the icon size based on the screen DPI. /// /// /// https://stackoverflow.com/a/40851713/2075611 private Size SelectIconSize() { Size size = new Size(32, 32); int dpi = ViewUtils.GetScreenDpi(); if (dpi < 97) { // dpi = 96; size = new Size(16, 16); } else if (dpi < 121) { // dpi = 120; size = new Size(20, 20); } else if (dpi < 145) { // dpi = 144; size = new Size(24, 24); } else { // dpi = 168; size = new Size(28, 28); } return size; } private Color SelectColorMask(bool isProxyEnabled, bool isGlobalProxy) { Color colorMask = Color.White; Utils.WindowsThemeMode currentWindowsThemeMode = Utils.GetWindows10SystemThemeSetting(); if (isProxyEnabled) { if (isGlobalProxy) // global { colorMask = colorMaskBlue; } else // PAC { if (currentWindowsThemeMode == Utils.WindowsThemeMode.Light) { colorMask = colorMaskEclipse; } } } else // disabled { if (currentWindowsThemeMode == Utils.WindowsThemeMode.Light) { colorMask = colorMaskDarkSilver; } else { colorMask = colorMaskLightSilver; } } return colorMask; } private void UpdateIconSet(Color colorMask, Size size, out Icon icon, out Icon icon_in, out Icon icon_out, out Icon icon_both) { Bitmap iconBitmap; // generate the base icon iconBitmap = ViewUtils.ChangeBitmapColor(Resources.ss32Fill, colorMask); iconBitmap = ViewUtils.AddBitmapOverlay(iconBitmap, Resources.ss32Outline); icon = Icon.FromHandle(ViewUtils.ResizeBitmap(iconBitmap, size.Width, size.Height).GetHicon()); icon_in = Icon.FromHandle(ViewUtils.ResizeBitmap(ViewUtils.AddBitmapOverlay(iconBitmap, Resources.ss32In), size.Width, size.Height).GetHicon()); icon_out = Icon.FromHandle(ViewUtils.ResizeBitmap(ViewUtils.AddBitmapOverlay(iconBitmap, Resources.ss32In), size.Width, size.Height).GetHicon()); icon_both = Icon.FromHandle(ViewUtils.ResizeBitmap(ViewUtils.AddBitmapOverlay(iconBitmap, Resources.ss32In, Resources.ss32Out), size.Width, size.Height).GetHicon()); } #endregion #region ToolStripMenuItems and MenuGroups private ToolStripMenuItem CreateToolStripMenuItem(string text, EventHandler click) { return new ToolStripMenuItem(I18N.GetString(text), null, click); } private ToolStripMenuItem CreateMenuGroup(string text, ToolStripItem[] items) { return new ToolStripMenuItem(I18N.GetString(text), null, items); } private void LoadMenu() { this.contextMenu1 = new ContextMenuStrip(); contextMenu1.Items.AddRange(new ToolStripItem[]{ CreateMenuGroup("System Proxy", new ToolStripMenuItem[] { this.disableItem = CreateToolStripMenuItem("Disable", new EventHandler(this.EnableItem_Click)), this.PACModeItem = CreateToolStripMenuItem("PAC", new EventHandler(this.PACModeItem_Click)), this.globalModeItem = CreateToolStripMenuItem("Global", new EventHandler(this.GlobalModeItem_Click)) }), this.ServersItem = CreateMenuGroup("Servers", new ToolStripItem [] { this.SeperatorItem = new ToolStripSeparator(), this.ConfigItem = CreateToolStripMenuItem("Edit Servers...", new EventHandler(this.Config_Click)), new ToolStripSeparator(), CreateToolStripMenuItem("Share Server Config...", new EventHandler(this.QRCodeItem_Click)), CreateToolStripMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click)), CreateToolStripMenuItem("Import URL from Clipboard...", new EventHandler(this.ImportURLItem_Click)) }), CreateMenuGroup("PAC ", new ToolStripItem[] { this.localPACItem = CreateToolStripMenuItem("Local PAC", new EventHandler(this.LocalPACItem_Click)), this.onlinePACItem = CreateToolStripMenuItem("Online PAC", new EventHandler(this.OnlinePACItem_Click)), new ToolStripSeparator(), this.editLocalPACItem = CreateToolStripMenuItem("Edit Local PAC File...", new EventHandler(this.EditPACFileItem_Click)), this.updateFromGeositeItem = CreateToolStripMenuItem("Update Local PAC from Geosite", new EventHandler(this.UpdatePACFromGeositeItem_Click)), this.editGFWUserRuleItem = CreateToolStripMenuItem("Edit User Rule for Geosite...", new EventHandler(this.EditUserRuleFileForGeositeItem_Click)), this.secureLocalPacUrlToggleItem = CreateToolStripMenuItem("Secure Local PAC", new EventHandler(this.SecureLocalPacUrlToggleItem_Click)), this.regenerateLocalPacOnUpdateItem = CreateToolStripMenuItem("Regenerate local PAC on version update", new EventHandler(this.RegenerateLocalPacOnUpdateItem_Click)), CreateToolStripMenuItem("Copy Local PAC URL", new EventHandler(this.CopyLocalPacUrlItem_Click)), this.editOnlinePACItem = CreateToolStripMenuItem("Edit Online PAC URL...", new EventHandler(this.UpdateOnlinePACURLItem_Click)), }), this.proxyItem = CreateToolStripMenuItem("Forward Proxy...", new EventHandler(this.proxyItem_Click)), this.onlineConfigItem = CreateToolStripMenuItem("Online Config...", new EventHandler(this.OnlineConfig_Click)), new ToolStripSeparator( ), this.AutoStartupItem = CreateToolStripMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)), this.ProtocolHandlerItem = CreateToolStripMenuItem("Associate ss:// Links", new EventHandler(this.ProtocolHandlerItem_Click)), this.ShareOverLANItem = CreateToolStripMenuItem("Allow other Devices to connect", new EventHandler(this.ShareOverLANItem_Click)), new ToolStripSeparator( ), this.hotKeyItem = CreateToolStripMenuItem("Edit Hotkeys...", new EventHandler(this.hotKeyItem_Click)), CreateMenuGroup("Help", new ToolStripItem[] { CreateToolStripMenuItem("Show Logs...", new EventHandler(this.ShowLogItem_Click)), this.VerboseLoggingToggleItem = CreateToolStripMenuItem( "Verbose Logging", new EventHandler(this.VerboseLoggingToggleItem_Click) ), this.ShowPluginOutputToggleItem = CreateToolStripMenuItem("Show Plugin Output", new EventHandler(this.ShowPluginOutputToggleItem_Click)), this.WriteI18NFileItem = CreateToolStripMenuItem("Write translation template",new EventHandler(WriteI18NFileItem_Click)), CreateMenuGroup("Updates...", new ToolStripItem[] { CreateToolStripMenuItem("Check for Updates...", new EventHandler(this.checkUpdatesItem_Click)), new ToolStripSeparator(), this.autoCheckUpdatesToggleItem = CreateToolStripMenuItem("Check for Updates at Startup", new EventHandler(this.autoCheckUpdatesToggleItem_Click)), this.checkPreReleaseToggleItem = CreateToolStripMenuItem("Check Pre-release Version", new EventHandler(this.checkPreReleaseToggleItem_Click)), }), CreateToolStripMenuItem("About...", new EventHandler(this.AboutItem_Click)), }), new ToolStripSeparator(), CreateToolStripMenuItem("Quit", new EventHandler(this.Quit_Click)) }); } #endregion private void controller_TrafficChanged(object sender, EventArgs e) { if (icon == null) return; Icon newIcon; bool hasInbound = controller.trafficPerSecondQueue.Last().inboundIncreasement > 0; bool hasOutbound = controller.trafficPerSecondQueue.Last().outboundIncreasement > 0; if (hasInbound && hasOutbound) newIcon = icon_both; else if (hasInbound) newIcon = icon_in; else if (hasOutbound) newIcon = icon_out; else newIcon = icon; if (newIcon != this.previousIcon) { this.previousIcon = newIcon; _notifyIcon.Icon = newIcon; } } void controller_Errored(object sender, ErrorEventArgs e) { MessageBox.Show(e.GetException().ToString(), I18N.GetString("Shadowsocks Error: {0}", e.GetException().Message)); } private void controller_ConfigChanged(object sender, EventArgs e) { LoadCurrentConfiguration(); UpdateTrayIconAndNotifyText(); } private void LoadCurrentConfiguration() { Configuration config = controller.GetCurrentConfiguration(); UpdateServersMenu(); UpdateSystemProxyItemsEnabledStatus(config); ShareOverLANItem.Checked = config.shareOverLan; VerboseLoggingToggleItem.Checked = config.isVerboseLogging; ShowPluginOutputToggleItem.Checked = config.showPluginOutput; AutoStartupItem.Checked = AutoStartup.Check(); ProtocolHandlerItem.Checked = ProtocolHandler.Check(); onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; localPACItem.Checked = !onlinePACItem.Checked; secureLocalPacUrlToggleItem.Checked = config.secureLocalPac; regenerateLocalPacOnUpdateItem.Checked = config.regeneratePacOnUpdate; UpdatePACItemsEnabledStatus(); UpdateUpdateMenu(); } #region Forms private void ShowConfigForm() { if (configForm != null) { configForm.Activate(); } else { configForm = new ConfigForm(controller); configForm.Show(); configForm.Activate(); configForm.FormClosed += configForm_FormClosed; } } private void ShowLogForm() { if (logForm != null) { logForm.Activate(); } else { logForm = new LogForm(controller); logForm.Show(); logForm.Activate(); logForm.FormClosed += logForm_FormClosed; } } void logForm_FormClosed(object sender, FormClosedEventArgs e) { logForm.Dispose(); logForm = null; } void configForm_FormClosed(object sender, FormClosedEventArgs e) { configForm.Dispose(); configForm = null; var config = controller.GetCurrentConfiguration(); if (config.firstRun) { CheckUpdateForFirstRun(); ShowBalloonTip( I18N.GetString("Shadowsocks is here"), I18N.GetString("You can turn on/off Shadowsocks in the context menu"), ToolTipIcon.Info, 0 ); config.firstRun = false; } } #endregion #region Misc void ShowBalloonTip(string title, string content, ToolTipIcon icon, int timeout) { _notifyIcon.BalloonTipTitle = title; _notifyIcon.BalloonTipText = content; _notifyIcon.BalloonTipIcon = icon; _notifyIcon.ShowBalloonTip(timeout); } void notifyIcon1_BalloonTipClicked(object sender, EventArgs e) { } private void _notifyIcon_BalloonTipClosed(object sender, EventArgs e) { } private void notifyIcon1_Click(object sender, MouseEventArgs e) { UpdateTrayIconAndNotifyText(); if (e.Button == MouseButtons.Middle) { ShowLogForm(); } } private void notifyIcon1_DoubleClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { ShowConfigForm(); } } private void CheckUpdateForFirstRun() { Configuration config = controller.GetCurrentConfiguration(); if (config.firstRun) return; _isStartupCheck = true; Dispatcher.CurrentDispatcher.Invoke(() => updateChecker.CheckForVersionUpdate(3000)); } public void ShowLogForm_HotKey() { ShowLogForm(); } #endregion #region Main menu void controller_ShareOverLANStatusChanged(object sender, EventArgs e) { ShareOverLANItem.Checked = controller.GetCurrentConfiguration().shareOverLan; } private void proxyItem_Click(object sender, EventArgs e) { if (forwardProxyWindow == null) { forwardProxyWindow = new System.Windows.Window() { Title = LocalizationProvider.GetLocalizedValue("ForwardProxy"), Height = 400, Width = 280, MinHeight = 400, MinWidth = 280, Content = new ForwardProxyView() }; forwardProxyWindow.Closed += ForwardProxyWindow_Closed; ElementHost.EnableModelessKeyboardInterop(forwardProxyWindow); forwardProxyWindow.Show(); } forwardProxyWindow.Activate(); } private void ForwardProxyWindow_Closed(object sender, EventArgs e) { forwardProxyWindow = null; } public void CloseForwardProxyWindow() => forwardProxyWindow.Close(); private void OnlineConfig_Click(object sender, EventArgs e) { if (onlineConfigWindow == null) { onlineConfigWindow = new System.Windows.Window() { Title = LocalizationProvider.GetLocalizedValue("OnlineConfigDelivery"), Height = 510, Width = 480, MinHeight = 510, MinWidth = 480, Content = new OnlineConfigView() }; onlineConfigWindow.Closed += OnlineConfigWindow_Closed; ElementHost.EnableModelessKeyboardInterop(onlineConfigWindow); onlineConfigWindow.Show(); } onlineConfigWindow.Activate(); } private void OnlineConfigWindow_Closed(object sender, EventArgs e) { onlineConfigWindow = null; } private void hotKeyItem_Click(object sender, EventArgs e) { if (hotkeysWindow == null) { hotkeysWindow = new System.Windows.Window() { Title = LocalizationProvider.GetLocalizedValue("Hotkeys"), Height = 260, Width = 320, MinHeight = 260, MinWidth = 320, Content = new HotkeysView() }; hotkeysWindow.Closed += HotkeysWindow_Closed; ElementHost.EnableModelessKeyboardInterop(hotkeysWindow); hotkeysWindow.Show(); } hotkeysWindow.Activate(); } private void HotkeysWindow_Closed(object sender, EventArgs e) { hotkeysWindow = null; } public void CloseHotkeysWindow() => hotkeysWindow.Close(); private void ShareOverLANItem_Click(object sender, EventArgs e) { ShareOverLANItem.Checked = !ShareOverLANItem.Checked; controller.ToggleShareOverLAN(ShareOverLANItem.Checked); } private void AutoStartupItem_Click(object sender, EventArgs e) { AutoStartupItem.Checked = !AutoStartupItem.Checked; if (!AutoStartup.Set(AutoStartupItem.Checked)) { MessageBox.Show(I18N.GetString("Failed to update registry")); } LoadCurrentConfiguration(); } private void ProtocolHandlerItem_Click(object sender, EventArgs e) { ProtocolHandlerItem.Checked = !ProtocolHandlerItem.Checked; if (!ProtocolHandler.Set(ProtocolHandlerItem.Checked)) { MessageBox.Show(I18N.GetString("Failed to update registry")); } LoadCurrentConfiguration(); } private void Quit_Click(object sender, EventArgs e) { controller.Stop(); _notifyIcon.Visible = false; Application.Exit(); } #endregion #region System proxy private void controller_EnableStatusChanged(object sender, EventArgs e) { disableItem.Checked = !controller.GetCurrentConfiguration().enabled; } private void EnableItem_Click(object sender, EventArgs e) { controller.ToggleEnable(false); Configuration config = controller.GetCurrentConfiguration(); UpdateSystemProxyItemsEnabledStatus(config); } void controller_EnableGlobalChanged(object sender, EventArgs e) { globalModeItem.Checked = controller.GetCurrentConfiguration().global; PACModeItem.Checked = !globalModeItem.Checked; } private void UpdateSystemProxyItemsEnabledStatus(Configuration config) { disableItem.Checked = !config.enabled; if (!config.enabled) { globalModeItem.Checked = false; PACModeItem.Checked = false; } else { globalModeItem.Checked = config.global; PACModeItem.Checked = !config.global; } globalModeItem.Enabled = WinINet.operational; PACModeItem.Enabled = WinINet.operational; } private void GlobalModeItem_Click(object sender, EventArgs e) { controller.ToggleEnable(true); controller.ToggleGlobal(true); Configuration config = controller.GetCurrentConfiguration(); UpdateSystemProxyItemsEnabledStatus(config); } private void PACModeItem_Click(object sender, EventArgs e) { controller.ToggleEnable(true); controller.ToggleGlobal(false); Configuration config = controller.GetCurrentConfiguration(); UpdateSystemProxyItemsEnabledStatus(config); } #endregion #region Server private void UpdateServersMenu() { var items = ServersItem.DropDownItems; while (items[0] != SeperatorItem) { items.RemoveAt(0); } int strategyCount = 0; foreach (var strategy in controller.GetStrategies()) { if (!items.OfType().Any(ts => ts.Text == strategy.Name)) { ToolStripMenuItem item = new ToolStripMenuItem(strategy.Name); item.Tag = strategy.ID; item.Click += AStrategyItem_Click; items.Add(item); strategyCount++; } } if (!items.OfType().Any(ts => ts.Tag?.ToString() == "-server-")) { // user wants a seperator item between strategy and servers menugroup items.Add(new ToolStripSeparator() { Tag = "-server-" }); } int serverCount = 0; Configuration configuration = controller.GetCurrentConfiguration(); foreach (var server in configuration.configs) { try { Configuration.CheckServer(server); var name = server.ToString(); if (!items.OfType().Any(ts => ts.Text == name)) { ToolStripMenuItem item = new ToolStripMenuItem(name); item.Tag = configuration.configs.FindIndex(s => s == server); item.Click += AServerItem_Click; items.Add(item); serverCount++; } } catch { } } foreach (var item in items) { var menuItem = item as ToolStripMenuItem; if (menuItem == null || menuItem.Tag == null) continue; if ( menuItem.Tag.ToString() == configuration.index.ToString() || menuItem.Tag.ToString() == configuration.strategy ) { menuItem.Checked = true; } else { menuItem.Checked = false; } } } private void AServerItem_Click(object sender, EventArgs e) { ToolStripMenuItem item = (ToolStripMenuItem)sender; controller.SelectServerIndex((int)item.Tag); } private void AStrategyItem_Click(object sender, EventArgs e) { ToolStripMenuItem item = (ToolStripMenuItem)sender; controller.SelectStrategy((string)item.Tag); } private void Config_Click(object sender, EventArgs e) { ShowConfigForm(); } void openURLFromQRCode() { Utils.OpenInBrowser(_urlToOpen); } private void QRCodeItem_Click(object sender, EventArgs e) { if (serverSharingWindow == null) { serverSharingWindow = new System.Windows.Window() { Title = LocalizationProvider.GetLocalizedValue("ServerSharing"), Height = 400, Width = 660, MinHeight = 400, MinWidth = 660, Content = new ServerSharingView() }; serverSharingWindow.Closed += ServerSharingWindow_Closed; ElementHost.EnableModelessKeyboardInterop(serverSharingWindow); serverSharingWindow.Show(); } serverSharingWindow.Activate(); } private void ServerSharingWindow_Closed(object sender, EventArgs e) { serverSharingWindow = null; } private void ScanQRCodeItem_Click(object sender, EventArgs e) { var result = Utils.ScanQRCodeFromScreen(); if (result != null) { if (result.ToLowerInvariant().StartsWith("http://") || result.ToLowerInvariant().StartsWith("https://")) { _urlToOpen = result; openURLFromQRCode(); } else if (controller.AddServerBySSURL(result)) { ShowConfigForm(); } else { MessageBox.Show(I18N.GetString("Invalid QR Code content: {0}", result)); } return; } else MessageBox.Show(I18N.GetString("No QRCode found. Try to zoom in or move it to the center of the screen.")); } private void ImportURLItem_Click(object sender, EventArgs e) { if (controller.AskAddServerBySSURL(Clipboard.GetText(TextDataFormat.Text))) { ShowConfigForm(); } } #endregion #region PAC private void LocalPACItem_Click(object sender, EventArgs e) { if (!localPACItem.Checked) { localPACItem.Checked = true; onlinePACItem.Checked = false; controller.UseOnlinePAC(false); UpdatePACItemsEnabledStatus(); } } private void OnlinePACItem_Click(object sender, EventArgs e) { if (!onlinePACItem.Checked) { if (string.IsNullOrEmpty(controller.GetCurrentConfiguration().pacUrl)) { UpdateOnlinePACURLItem_Click(sender, e); } if (!string.IsNullOrEmpty(controller.GetCurrentConfiguration().pacUrl)) { localPACItem.Checked = false; onlinePACItem.Checked = true; controller.UseOnlinePAC(true); } UpdatePACItemsEnabledStatus(); } } private void UpdateOnlinePACURLItem_Click(object sender, EventArgs e) { string origPacUrl = controller.GetCurrentConfiguration().pacUrl; string pacUrl = ViewUtils.InputBox( I18N.GetString("Please input PAC Url"), I18N.GetString("Edit Online PAC URL"), origPacUrl, -1, -1); if (!string.IsNullOrEmpty(pacUrl) && pacUrl != origPacUrl) { controller.SavePACUrl(pacUrl); } } private void SecureLocalPacUrlToggleItem_Click(object sender, EventArgs e) { Configuration configuration = controller.GetCurrentConfiguration(); controller.ToggleSecureLocalPac(!configuration.secureLocalPac); } private void RegenerateLocalPacOnUpdateItem_Click(object sender, EventArgs e) { var config = controller.GetCurrentConfiguration(); controller.ToggleRegeneratePacOnUpdate(!config.regeneratePacOnUpdate); } private void CopyLocalPacUrlItem_Click(object sender, EventArgs e) { controller.CopyPacUrl(); } private void UpdatePACItemsEnabledStatus() { if (this.localPACItem.Checked) { this.editLocalPACItem.Enabled = true; this.updateFromGeositeItem.Enabled = true; this.editGFWUserRuleItem.Enabled = true; this.editOnlinePACItem.Enabled = false; } else { this.editLocalPACItem.Enabled = false; this.updateFromGeositeItem.Enabled = false; this.editGFWUserRuleItem.Enabled = false; this.editOnlinePACItem.Enabled = true; } } private void EditPACFileItem_Click(object sender, EventArgs e) { controller.TouchPACFile(); } private async void UpdatePACFromGeositeItem_Click(object sender, EventArgs e) { await GeositeUpdater.UpdatePACFromGeosite(); } private void EditUserRuleFileForGeositeItem_Click(object sender, EventArgs e) { controller.TouchUserRuleFile(); } void controller_FileReadyToOpen(object sender, ShadowsocksController.PathEventArgs e) { string argument = @"/select, " + e.Path; Process.Start("explorer.exe", argument); } void controller_UpdatePACFromGeositeError(object sender, System.IO.ErrorEventArgs e) { ShowBalloonTip(I18N.GetString("Failed to update PAC file"), e.GetException().Message, ToolTipIcon.Error, 5000); logger.LogUsefulException(e.GetException()); } void controller_UpdatePACFromGeositeCompleted(object sender, GeositeResultEventArgs e) { string result = e.Success ? I18N.GetString("PAC updated") : I18N.GetString("No updates found. Please report to Geosite if you have problems with it."); ShowBalloonTip(I18N.GetString("Shadowsocks"), result, ToolTipIcon.Info, 1000); } #endregion #region Help void controller_VerboseLoggingStatusChanged(object sender, EventArgs e) { VerboseLoggingToggleItem.Checked = controller.GetCurrentConfiguration().isVerboseLogging; } void controller_ShowPluginOutputChanged(object sender, EventArgs e) { ShowPluginOutputToggleItem.Checked = controller.GetCurrentConfiguration().showPluginOutput; } private void VerboseLoggingToggleItem_Click(object sender, EventArgs e) { VerboseLoggingToggleItem.Checked = !VerboseLoggingToggleItem.Checked; controller.ToggleVerboseLogging(VerboseLoggingToggleItem.Checked); } private void ShowLogItem_Click(object sender, EventArgs e) { ShowLogForm(); } private void ShowPluginOutputToggleItem_Click(object sender, EventArgs e) { ShowPluginOutputToggleItem.Checked = !ShowPluginOutputToggleItem.Checked; controller.ToggleShowPluginOutput(ShowPluginOutputToggleItem.Checked); } private void WriteI18NFileItem_Click(object sender, EventArgs e) { File.WriteAllText(I18N.I18N_FILE, Resources.i18n_csv, Encoding.UTF8); } #endregion #region Update void updateChecker_CheckUpdateCompleted(object sender, EventArgs e) { if (!_isStartupCheck && updateChecker.NewReleaseZipFilename == null) { ShowBalloonTip(I18N.GetString("Shadowsocks"), I18N.GetString("No update is available"), ToolTipIcon.Info, 5000); } _isStartupCheck = false; } private void UpdateUpdateMenu() { Configuration configuration = controller.GetCurrentConfiguration(); autoCheckUpdatesToggleItem.Checked = configuration.autoCheckUpdate; checkPreReleaseToggleItem.Checked = configuration.checkPreRelease; } private void autoCheckUpdatesToggleItem_Click(object sender, EventArgs e) { Configuration configuration = controller.GetCurrentConfiguration(); controller.ToggleCheckingUpdate(!configuration.autoCheckUpdate); UpdateUpdateMenu(); } private void checkPreReleaseToggleItem_Click(object sender, EventArgs e) { Configuration configuration = controller.GetCurrentConfiguration(); controller.ToggleCheckingPreRelease(!configuration.checkPreRelease); UpdateUpdateMenu(); } private async void checkUpdatesItem_Click(object sender, EventArgs e) { await updateChecker.CheckForVersionUpdate(); } private void AboutItem_Click(object sender, EventArgs e) { Utils.OpenInBrowser("https://github.com/shadowsocks/shadowsocks-windows"); } #endregion } }