From b0f71c43f5bf11d3cb3b3e9fbbd2e71df630a6d5 Mon Sep 17 00:00:00 2001 From: Allen Zhu Date: Thu, 4 Aug 2016 17:32:56 +0800 Subject: [PATCH] Add traffic chart and traffic icon support (#595) * Implement the line chart on Log View window for inbound/outbound traffic per second * Implement dynamic tray icon to indicate Inbound and Outbound * Refine linechart * Bugfix for dynamic icon * Refine chart presentation * Change the data points' type for more accurate calculation * Tweak inbound/outbound indicator image for tray icon --- .../Controller/ShadowsocksController.cs | 60 +++++++++++- .../Properties/Resources.Designer.cs | 22 ++++- shadowsocks-csharp/Properties/Resources.resx | 6 ++ shadowsocks-csharp/Resources/ssIn24.png | Bin 0 -> 224 bytes shadowsocks-csharp/Resources/ssOut24.png | Bin 0 -> 211 bytes shadowsocks-csharp/Util/Util.cs | 22 ++++- shadowsocks-csharp/View/LogForm.Designer.cs | 103 ++++++++++++++++++--- shadowsocks-csharp/View/LogForm.cs | 53 ++++++++++- shadowsocks-csharp/View/LogForm.resx | 3 + shadowsocks-csharp/View/MenuViewController.cs | 61 ++++++++++-- shadowsocks-csharp/shadowsocks-csharp.csproj | 2 + 11 files changed, 308 insertions(+), 24 deletions(-) create mode 100644 shadowsocks-csharp/Resources/ssIn24.png create mode 100644 shadowsocks-csharp/Resources/ssOut24.png diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index 643d68ab..b6f64ed5 100755 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -23,6 +23,7 @@ namespace Shadowsocks.Controller // interacts with low level logic private Thread _ramThread; + private Thread _trafficThread; private Listener _listener; private PACServer _pacServer; @@ -35,6 +36,7 @@ namespace Shadowsocks.Controller public long inboundCounter = 0; public long outboundCounter = 0; + public QueueLast traffic; private bool stopped = false; @@ -45,10 +47,29 @@ namespace Shadowsocks.Controller public string Path; } + public class QueueLast : Queue + { + public T Last { get; private set; } + public new void Enqueue(T item) + { + Last = item; + base.Enqueue(item); + } + } + + public class TrafficPerSecond + { + public long inboundCounter; + public long outboundCounter; + public long inboundIncreasement; + public long outboundIncreasement; + } + public event EventHandler ConfigChanged; public event EventHandler EnableStatusChanged; public event EventHandler EnableGlobalChanged; public event EventHandler ShareOverLANStatusChanged; + public event EventHandler TrafficChanged; // when user clicked Edit PAC, and PAC file has already created public event EventHandler PACFileReadyToOpen; @@ -66,6 +87,7 @@ namespace Shadowsocks.Controller StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); _strategyManager = new StrategyManager(this); StartReleasingMemory(); + StartTrafficStatistics(60); } public void Start() @@ -313,7 +335,7 @@ namespace Shadowsocks.Controller { if (_config.availabilityStatistics) { - new Task(() => availabilityStatistics.UpdateLatency(server, (int) latency.TotalMilliseconds)).Start(); + new Task(() => availabilityStatistics.UpdateLatency(server, (int)latency.TotalMilliseconds)).Start(); } } @@ -516,5 +538,41 @@ namespace Shadowsocks.Controller } } + private void StartTrafficStatistics(int queueMaxSize) + { + traffic = new QueueLast(); + for (int i = 0; i < queueMaxSize; i++) + { + traffic.Enqueue(new TrafficPerSecond()); + } + _trafficThread = new Thread(new ThreadStart(() => TrafficStatistics(queueMaxSize))); + _trafficThread.IsBackground = true; + _trafficThread.Start(); + } + + private void TrafficStatistics(int queueMaxSize) + { + while (true) + { + TrafficPerSecond previous = traffic.Last; + TrafficPerSecond current = new TrafficPerSecond(); + current.inboundCounter = inboundCounter; + current.outboundCounter = outboundCounter; + current.inboundIncreasement = inboundCounter - previous.inboundCounter; + current.outboundIncreasement = outboundCounter - previous.outboundCounter; + + traffic.Enqueue(current); + if (traffic.Count > queueMaxSize) + traffic.Dequeue(); + + if (TrafficChanged != null) + { + TrafficChanged(this, new EventArgs()); + } + + Thread.Sleep(1000); + } + } + } } diff --git a/shadowsocks-csharp/Properties/Resources.Designer.cs b/shadowsocks-csharp/Properties/Resources.Designer.cs index 1205845f..49e2151b 100644 --- a/shadowsocks-csharp/Properties/Resources.Designer.cs +++ b/shadowsocks-csharp/Properties/Resources.Designer.cs @@ -182,7 +182,27 @@ namespace Shadowsocks.Properties { return ((System.Drawing.Bitmap)(obj)); } } - + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ssIn24 { + get { + object obj = ResourceManager.GetObject("ssIn24", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ssOut24 { + get { + object obj = ResourceManager.GetObject("ssOut24", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/shadowsocks-csharp/Properties/Resources.resx b/shadowsocks-csharp/Properties/Resources.resx index aa0c2f63..12e04f8d 100755 --- a/shadowsocks-csharp/Properties/Resources.resx +++ b/shadowsocks-csharp/Properties/Resources.resx @@ -148,6 +148,12 @@ ..\Resources\ss24.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\ssIn24.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\ssOut24.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\ssw128.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/shadowsocks-csharp/Resources/ssIn24.png b/shadowsocks-csharp/Resources/ssIn24.png new file mode 100644 index 0000000000000000000000000000000000000000..8e60412c53c4bb45ac208395145ff13a01cd6c61 GIT binary patch literal 224 zcmV<603ZK}P)N2bZe?^J zG%heMIczh2P5=M^UP(kjR7gv;%)tr3Fbo7ix8nYbRPqVrF$4^o$ADNkt(o~1V~MfE zSYj+OmKaNnCB_nCiLu03Vk|M17)y*L#u8(RvBX$nJh5iq#}|xF_~{!)C;apcqZ5Am a*mD7$L!H}12;4*f0000N2bZe?^J zG%heMIczh2P5=M^QAtEWR7gwh&A|zP01O1d^k0q*Mhh4eVg30S0t-iGcR$5gVk|M1 z7)y*L#u8(RvBX$nEHRcCON=GP5@U(6#8_f1F_vbJ>)yfWjsL-eofF#{uloi-ws8Oe N002ovPDHLkV1l2>LNNdU literal 0 KcmV+b0RR6000031 diff --git a/shadowsocks-csharp/Util/Util.cs b/shadowsocks-csharp/Util/Util.cs index 178c994b..207e5289 100755 --- a/shadowsocks-csharp/Util/Util.cs +++ b/shadowsocks-csharp/Util/Util.cs @@ -100,29 +100,49 @@ namespace Shadowsocks.Util public static string FormatBandwidth(long n) { + var result = GetBandwidthScale(n); + return $"{result.Item1:0.##}{result.Item2}"; + } + + /// + /// Return scaled bandwidth + /// + /// Raw bandwidth + /// + /// Item1: float, bandwidth with suitable scale (eg. 56) + /// Item2: string, scale unit name (eg. KiB) + /// Item3: long, scale unit (eg. 1024) + /// + public static Tuple GetBandwidthScale(long n) + { + long scale = 1; float f = n; string unit = "B"; if (f > 1024) { f = f / 1024; + scale <<= 10; unit = "KiB"; } if (f > 1024) { f = f / 1024; + scale <<= 10; unit = "MiB"; } if (f > 1024) { f = f / 1024; + scale <<= 10; unit = "GiB"; } if (f > 1024) { f = f / 1024; + scale <<= 10; unit = "TiB"; } - return $"{f:0.##}{unit}"; + return new Tuple(f, unit, scale); } [DllImport("kernel32.dll")] diff --git a/shadowsocks-csharp/View/LogForm.Designer.cs b/shadowsocks-csharp/View/LogForm.Designer.cs index 3ac114ba..1add4487 100644 --- a/shadowsocks-csharp/View/LogForm.Designer.cs +++ b/shadowsocks-csharp/View/LogForm.Designer.cs @@ -29,6 +29,10 @@ private void InitializeComponent() { this.components = new System.ComponentModel.Container(); + System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea(); + System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend(); + System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series(); + System.Windows.Forms.DataVisualization.Charting.Series series2 = new System.Windows.Forms.DataVisualization.Charting.Series(); this.LogMessageTextBox = new System.Windows.Forms.TextBox(); this.MainMenu = new System.Windows.Forms.MainMenu(this.components); this.FileMenuItem = new System.Windows.Forms.MenuItem(); @@ -47,8 +51,15 @@ this.WrapTextCheckBox = new System.Windows.Forms.CheckBox(); this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); this.ToolbarFlowLayoutPanel = new System.Windows.Forms.FlowLayoutPanel(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.trafficChart = new System.Windows.Forms.DataVisualization.Charting.Chart(); this.tableLayoutPanel1.SuspendLayout(); this.ToolbarFlowLayoutPanel.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trafficChart)).BeginInit(); this.SuspendLayout(); // // LogMessageTextBox @@ -57,13 +68,13 @@ this.LogMessageTextBox.Dock = System.Windows.Forms.DockStyle.Fill; this.LogMessageTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.LogMessageTextBox.ForeColor = System.Drawing.Color.White; - this.LogMessageTextBox.Location = new System.Drawing.Point(3, 40); + this.LogMessageTextBox.Location = new System.Drawing.Point(0, 0); this.LogMessageTextBox.MaxLength = 2147483647; this.LogMessageTextBox.Multiline = true; this.LogMessageTextBox.Name = "LogMessageTextBox"; this.LogMessageTextBox.ReadOnly = true; this.LogMessageTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.LogMessageTextBox.Size = new System.Drawing.Size(378, 131); + this.LogMessageTextBox.Size = new System.Drawing.Size(378, 74); this.LogMessageTextBox.TabIndex = 0; // // MainMenu @@ -144,9 +155,9 @@ this.TopMostCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left))); this.TopMostCheckBox.AutoSize = true; - this.TopMostCheckBox.Location = new System.Drawing.Point(247, 3); + this.TopMostCheckBox.Location = new System.Drawing.Point(249, 3); this.TopMostCheckBox.Name = "TopMostCheckBox"; - this.TopMostCheckBox.Size = new System.Drawing.Size(71, 25); + this.TopMostCheckBox.Size = new System.Drawing.Size(72, 23); this.TopMostCheckBox.TabIndex = 3; this.TopMostCheckBox.Text = "&Top Most"; this.TopMostCheckBox.UseVisualStyleBackColor = true; @@ -157,7 +168,7 @@ this.ChangeFontButton.AutoSize = true; this.ChangeFontButton.Location = new System.Drawing.Point(84, 3); this.ChangeFontButton.Name = "ChangeFontButton"; - this.ChangeFontButton.Size = new System.Drawing.Size(75, 25); + this.ChangeFontButton.Size = new System.Drawing.Size(75, 23); this.ChangeFontButton.TabIndex = 2; this.ChangeFontButton.Text = "&Font"; this.ChangeFontButton.UseVisualStyleBackColor = true; @@ -168,7 +179,7 @@ this.CleanLogsButton.AutoSize = true; this.CleanLogsButton.Location = new System.Drawing.Point(3, 3); this.CleanLogsButton.Name = "CleanLogsButton"; - this.CleanLogsButton.Size = new System.Drawing.Size(75, 25); + this.CleanLogsButton.Size = new System.Drawing.Size(75, 23); this.CleanLogsButton.TabIndex = 1; this.CleanLogsButton.Text = "&Clean Logs"; this.CleanLogsButton.UseVisualStyleBackColor = true; @@ -181,7 +192,7 @@ this.WrapTextCheckBox.AutoSize = true; this.WrapTextCheckBox.Location = new System.Drawing.Point(165, 3); this.WrapTextCheckBox.Name = "WrapTextCheckBox"; - this.WrapTextCheckBox.Size = new System.Drawing.Size(76, 25); + this.WrapTextCheckBox.Size = new System.Drawing.Size(78, 23); this.WrapTextCheckBox.TabIndex = 0; this.WrapTextCheckBox.Text = "&Wrap Text"; this.WrapTextCheckBox.UseVisualStyleBackColor = true; @@ -191,15 +202,15 @@ // this.tableLayoutPanel1.ColumnCount = 1; this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel1.Controls.Add(this.LogMessageTextBox, 0, 1); this.tableLayoutPanel1.Controls.Add(this.ToolbarFlowLayoutPanel, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.splitContainer1, 0, 1); this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; this.tableLayoutPanel1.RowCount = 2; this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel1.Size = new System.Drawing.Size(384, 174); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(384, 161); this.tableLayoutPanel1.TabIndex = 2; // // ToolbarFlowLayoutPanel @@ -212,17 +223,73 @@ this.ToolbarFlowLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill; this.ToolbarFlowLayoutPanel.Location = new System.Drawing.Point(3, 3); this.ToolbarFlowLayoutPanel.Name = "ToolbarFlowLayoutPanel"; - this.ToolbarFlowLayoutPanel.Size = new System.Drawing.Size(378, 31); + this.ToolbarFlowLayoutPanel.Size = new System.Drawing.Size(378, 29); this.ToolbarFlowLayoutPanel.TabIndex = 2; // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.Location = new System.Drawing.Point(3, 38); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.LogMessageTextBox); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.trafficChart); + this.splitContainer1.Size = new System.Drawing.Size(378, 120); + this.splitContainer1.SplitterDistance = 74; + this.splitContainer1.TabIndex = 3; + // + // trafficChart + // + chartArea1.AxisX.LabelStyle.Enabled = false; + chartArea1.AxisX.MajorGrid.Interval = 5D; + chartArea1.AxisX.MajorGrid.LineColor = System.Drawing.Color.LightGray; + chartArea1.AxisX.MajorTickMark.Enabled = false; + chartArea1.AxisY.IntervalAutoMode = System.Windows.Forms.DataVisualization.Charting.IntervalAutoMode.VariableCount; + chartArea1.AxisY.LabelAutoFitMaxFontSize = 8; + chartArea1.AxisY.LabelStyle.Interval = 0D; + chartArea1.AxisY.MajorGrid.LineColor = System.Drawing.Color.LightGray; + chartArea1.AxisY.MajorTickMark.Enabled = false; + chartArea1.AxisY2.MajorGrid.LineColor = System.Drawing.Color.LightGray; + chartArea1.AxisY2.Minimum = 0D; + chartArea1.Name = "ChartArea1"; + this.trafficChart.ChartAreas.Add(chartArea1); + this.trafficChart.Dock = System.Windows.Forms.DockStyle.Fill; + legend1.MaximumAutoSize = 25F; + legend1.Name = "Legend1"; + this.trafficChart.Legends.Add(legend1); + this.trafficChart.Location = new System.Drawing.Point(0, 0); + this.trafficChart.Name = "trafficChart"; + this.trafficChart.Palette = System.Windows.Forms.DataVisualization.Charting.ChartColorPalette.None; + series1.ChartArea = "ChartArea1"; + series1.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line; + series1.IsXValueIndexed = true; + series1.Legend = "Legend1"; + series1.Name = "Inbound"; + series2.ChartArea = "ChartArea1"; + series2.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line; + series2.IsXValueIndexed = true; + series2.Legend = "Legend1"; + series2.Name = "Outbound"; + this.trafficChart.Series.Add(series1); + this.trafficChart.Series.Add(series2); + this.trafficChart.Size = new System.Drawing.Size(378, 42); + this.trafficChart.TabIndex = 0; + this.trafficChart.Text = "chart1"; + // // LogForm // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(384, 174); + this.ClientSize = new System.Drawing.Size(384, 161); this.Controls.Add(this.tableLayoutPanel1); this.Menu = this.MainMenu; - this.MinimumSize = new System.Drawing.Size(400, 213); + this.MinimumSize = new System.Drawing.Size(400, 200); this.Name = "LogForm"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Log Viewer"; @@ -233,6 +300,12 @@ this.tableLayoutPanel1.PerformLayout(); this.ToolbarFlowLayoutPanel.ResumeLayout(false); this.ToolbarFlowLayoutPanel.PerformLayout(); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel1.PerformLayout(); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.trafficChart)).EndInit(); this.ResumeLayout(false); } @@ -257,5 +330,7 @@ private System.Windows.Forms.FlowLayoutPanel ToolbarFlowLayoutPanel; private System.Windows.Forms.MenuItem MenuItemSeparater; private System.Windows.Forms.MenuItem ShowToolbarMenuItem; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.DataVisualization.Charting.Chart trafficChart; } } \ No newline at end of file diff --git a/shadowsocks-csharp/View/LogForm.cs b/shadowsocks-csharp/View/LogForm.cs index d49cd1af..5a83d96e 100644 --- a/shadowsocks-csharp/View/LogForm.cs +++ b/shadowsocks-csharp/View/LogForm.cs @@ -2,6 +2,9 @@ using System.Drawing; using System.IO; using System.Windows.Forms; +using System.Windows.Forms.DataVisualization.Charting; +using System.Collections.Generic; +using System.Linq; using Shadowsocks.Controller; using Shadowsocks.Properties; @@ -18,6 +21,15 @@ namespace Shadowsocks.View const int BACK_OFFSET = 65536; ShadowsocksController controller; + #region chart + List inboundPoints = new List(); + List outboundPoints = new List(); + long maxSpeed = 0; + Tuple bandwidthScale = new Tuple(0, "B", 1); + TextAnnotation inboundAnnotation = new TextAnnotation(); + TextAnnotation outboundAnnotation = new TextAnnotation(); + #endregion + public LogForm(ShadowsocksController controller, string filename) { this.controller = controller; @@ -30,7 +42,8 @@ namespace Shadowsocks.View { config = new LogViewerConfig(); } - else { + else + { topMostTrigger = config.topMost; wrapTextTrigger = config.wrapText; toolbarTrigger = config.toolbarShown; @@ -39,9 +52,46 @@ namespace Shadowsocks.View LogMessageTextBox.Font = config.GetFont(); } + controller.TrafficChanged += controller_TrafficChanged; + UpdateTexts(); } + private void controller_TrafficChanged(object sender, EventArgs e) + { + inboundPoints.Clear(); + outboundPoints.Clear(); + foreach (var trafficPerSecond in controller.traffic) + { + inboundPoints.Add(trafficPerSecond.inboundIncreasement); + outboundPoints.Add(trafficPerSecond.outboundIncreasement); + maxSpeed = Math.Max(maxSpeed, Math.Max(trafficPerSecond.inboundIncreasement, trafficPerSecond.outboundIncreasement)); + } + + bandwidthScale = Utils.GetBandwidthScale(maxSpeed); + + //rescale the original data points, since it is List, .ForEach does not work + inboundPoints = inboundPoints.Select(p => p / bandwidthScale.Item3).ToList(); + outboundPoints = outboundPoints.Select(p => p / bandwidthScale.Item3).ToList(); + + if (trafficChart.InvokeRequired) + { + trafficChart.Invoke(new Action(() => + { + trafficChart.Series["Inbound"].Points.DataBindY(inboundPoints); + trafficChart.Series["Outbound"].Points.DataBindY(outboundPoints); + trafficChart.ChartAreas[0].AxisY.LabelStyle.Format = "{0:0.##} " + bandwidthScale.Item2; + inboundAnnotation.AnchorDataPoint = trafficChart.Series["Inbound"].Points.Last(); + inboundAnnotation.Text = Utils.FormatBandwidth(controller.traffic.Last.inboundIncreasement); + outboundAnnotation.AnchorDataPoint = trafficChart.Series["Outbound"].Points.Last(); + outboundAnnotation.Text = Utils.FormatBandwidth(controller.traffic.Last.outboundIncreasement); + trafficChart.Annotations.Clear(); + trafficChart.Annotations.Add(inboundAnnotation); + trafficChart.Annotations.Add(outboundAnnotation); + })); + } + } + private void UpdateTexts() { FileMenuItem.Text = I18N.GetString("&File"); @@ -144,6 +194,7 @@ namespace Shadowsocks.View private void LogForm_FormClosing(object sender, FormClosingEventArgs e) { timer.Stop(); + controller.TrafficChanged -= controller_TrafficChanged; LogViewerConfig config = controller.GetConfigurationCopy().logViewer; if (config == null) config = new LogViewerConfig(); diff --git a/shadowsocks-csharp/View/LogForm.resx b/shadowsocks-csharp/View/LogForm.resx index c921ecfb..8f20a9f7 100644 --- a/shadowsocks-csharp/View/LogForm.resx +++ b/shadowsocks-csharp/View/LogForm.resx @@ -144,4 +144,7 @@ True + + 39 + \ No newline at end of file diff --git a/shadowsocks-csharp/View/MenuViewController.cs b/shadowsocks-csharp/View/MenuViewController.cs index fab3d729..3a5e5367 100755 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; +using System.Drawing.Imaging; using System.Windows.Forms; using ZXing; @@ -25,6 +26,8 @@ namespace Shadowsocks.View private UpdateChecker updateChecker; private NotifyIcon _notifyIcon; + private Bitmap icon_baseBitmap; + private Icon icon_base, icon_in, icon_out, icon_both, targetIcon; private ContextMenu contextMenu1; private bool _isFirstRun; @@ -74,6 +77,7 @@ namespace Shadowsocks.View _notifyIcon.MouseClick += notifyIcon1_Click; _notifyIcon.MouseDoubleClick += notifyIcon1_DoubleClick; _notifyIcon.BalloonTipClosed += _notifyIcon_BalloonTipClosed; + controller.TrafficChanged += controller_TrafficChanged; this.updateChecker = new UpdateChecker(); updateChecker.CheckUpdateCompleted += updateChecker_CheckUpdateCompleted; @@ -94,6 +98,32 @@ namespace Shadowsocks.View } } + private void controller_TrafficChanged(object sender, EventArgs e) + { + if (icon_baseBitmap == null) + return; + + Icon newIcon; + + bool hasInbound = controller.traffic.Last.inboundIncreasement > 0; + bool hasOutbound = controller.traffic.Last.outboundIncreasement > 0; + + if (hasInbound && hasOutbound) + newIcon = icon_both; + else if (hasInbound) + newIcon = icon_in; + else if (hasOutbound) + newIcon = icon_out; + else + newIcon = icon_base; + + if (newIcon != this.targetIcon) + { + this.targetIcon = newIcon; + _notifyIcon.Icon = newIcon; + } + } + void controller_Errored(object sender, System.IO.ErrorEventArgs e) { MessageBox.Show(e.GetException().ToString(), String.Format(I18N.GetString("Shadowsocks Error: {0}"), e.GetException().Message)); @@ -105,26 +135,32 @@ namespace Shadowsocks.View Graphics graphics = Graphics.FromHwnd(IntPtr.Zero); dpi = (int)graphics.DpiX; graphics.Dispose(); - Bitmap icon = null; + icon_baseBitmap = null; if (dpi < 97) { // dpi = 96; - icon = Resources.ss16; + icon_baseBitmap = Resources.ss16; } else if (dpi < 121) { // dpi = 120; - icon = Resources.ss20; + icon_baseBitmap = Resources.ss20; } else { - icon = Resources.ss24; + icon_baseBitmap = Resources.ss24; } Configuration config = controller.GetConfigurationCopy(); bool enabled = config.enabled; bool global = config.global; - icon = getTrayIconByState(icon, enabled, global); - _notifyIcon.Icon = Icon.FromHandle(icon.GetHicon()); + icon_baseBitmap = getTrayIconByState(icon_baseBitmap, enabled, global); + + icon_base = Icon.FromHandle(icon_baseBitmap.GetHicon()); + targetIcon = icon_base; + icon_in = Icon.FromHandle(AddBitmapOverlay(icon_baseBitmap, Resources.ssIn24).GetHicon()); + icon_out = Icon.FromHandle(AddBitmapOverlay(icon_baseBitmap, Resources.ssOut24).GetHicon()); + icon_both = Icon.FromHandle(AddBitmapOverlay(icon_baseBitmap, Resources.ssIn24, Resources.ssOut24).GetHicon()); + _notifyIcon.Icon = targetIcon; string serverInfo = null; if (controller.GetCurrentStrategy() != null) @@ -177,6 +213,19 @@ namespace Shadowsocks.View return iconCopy; } + private Bitmap AddBitmapOverlay(Bitmap original, params Bitmap[] overlays) + { + Bitmap bitmap = new Bitmap(original.Width, original.Height, PixelFormat.Format64bppArgb); + Graphics canvas = Graphics.FromImage(bitmap); + canvas.DrawImage(original, new Point(0, 0)); + foreach (Bitmap overlay in overlays) + { + canvas.DrawImage(new Bitmap(overlay, original.Size), new Point(0, 0)); + } + canvas.Save(); + return bitmap; + } + private MenuItem CreateMenuItem(string text, EventHandler click) { return new MenuItem(I18N.GetString(text), click); diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index 008134ae..15046bb9 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -291,6 +291,8 @@ Designer + +