* 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 icontags/3.2
@@ -23,6 +23,7 @@ namespace Shadowsocks.Controller | |||||
// interacts with low level logic | // interacts with low level logic | ||||
private Thread _ramThread; | private Thread _ramThread; | ||||
private Thread _trafficThread; | |||||
private Listener _listener; | private Listener _listener; | ||||
private PACServer _pacServer; | private PACServer _pacServer; | ||||
@@ -35,6 +36,7 @@ namespace Shadowsocks.Controller | |||||
public long inboundCounter = 0; | public long inboundCounter = 0; | ||||
public long outboundCounter = 0; | public long outboundCounter = 0; | ||||
public QueueLast<TrafficPerSecond> traffic; | |||||
private bool stopped = false; | private bool stopped = false; | ||||
@@ -45,10 +47,29 @@ namespace Shadowsocks.Controller | |||||
public string Path; | public string Path; | ||||
} | } | ||||
public class QueueLast<T> : Queue<T> | |||||
{ | |||||
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 ConfigChanged; | ||||
public event EventHandler EnableStatusChanged; | public event EventHandler EnableStatusChanged; | ||||
public event EventHandler EnableGlobalChanged; | public event EventHandler EnableGlobalChanged; | ||||
public event EventHandler ShareOverLANStatusChanged; | public event EventHandler ShareOverLANStatusChanged; | ||||
public event EventHandler TrafficChanged; | |||||
// when user clicked Edit PAC, and PAC file has already created | // when user clicked Edit PAC, and PAC file has already created | ||||
public event EventHandler<PathEventArgs> PACFileReadyToOpen; | public event EventHandler<PathEventArgs> PACFileReadyToOpen; | ||||
@@ -66,6 +87,7 @@ namespace Shadowsocks.Controller | |||||
StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | ||||
_strategyManager = new StrategyManager(this); | _strategyManager = new StrategyManager(this); | ||||
StartReleasingMemory(); | StartReleasingMemory(); | ||||
StartTrafficStatistics(60); | |||||
} | } | ||||
public void Start() | public void Start() | ||||
@@ -313,7 +335,7 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
if (_config.availabilityStatistics) | 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<TrafficPerSecond>(); | |||||
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); | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
@@ -182,7 +182,27 @@ namespace Shadowsocks.Properties { | |||||
return ((System.Drawing.Bitmap)(obj)); | return ((System.Drawing.Bitmap)(obj)); | ||||
} | } | ||||
} | } | ||||
/// <summary> | |||||
/// Looks up a localized resource of type System.Drawing.Bitmap. | |||||
/// </summary> | |||||
internal static System.Drawing.Bitmap ssIn24 { | |||||
get { | |||||
object obj = ResourceManager.GetObject("ssIn24", resourceCulture); | |||||
return ((System.Drawing.Bitmap)(obj)); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Looks up a localized resource of type System.Drawing.Bitmap. | |||||
/// </summary> | |||||
internal static System.Drawing.Bitmap ssOut24 { | |||||
get { | |||||
object obj = ResourceManager.GetObject("ssOut24", resourceCulture); | |||||
return ((System.Drawing.Bitmap)(obj)); | |||||
} | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// Looks up a localized resource of type System.Drawing.Bitmap. | /// Looks up a localized resource of type System.Drawing.Bitmap. | ||||
/// </summary> | /// </summary> | ||||
@@ -148,6 +148,12 @@ | |||||
<data name="ss24" type="System.Resources.ResXFileRef, System.Windows.Forms"> | <data name="ss24" type="System.Resources.ResXFileRef, System.Windows.Forms"> | ||||
<value>..\Resources\ss24.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> | <value>..\Resources\ss24.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> | ||||
</data> | </data> | ||||
<data name="ssIn24" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||||
<value>..\Resources\ssIn24.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> | |||||
</data> | |||||
<data name="ssOut24" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||||
<value>..\Resources\ssOut24.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> | |||||
</data> | |||||
<data name="ssw128" type="System.Resources.ResXFileRef, System.Windows.Forms"> | <data name="ssw128" type="System.Resources.ResXFileRef, System.Windows.Forms"> | ||||
<value>..\Resources\ssw128.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> | <value>..\Resources\ssw128.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> | ||||
</data> | </data> | ||||
@@ -100,29 +100,49 @@ namespace Shadowsocks.Util | |||||
public static string FormatBandwidth(long n) | public static string FormatBandwidth(long n) | ||||
{ | { | ||||
var result = GetBandwidthScale(n); | |||||
return $"{result.Item1:0.##}{result.Item2}"; | |||||
} | |||||
/// <summary> | |||||
/// Return scaled bandwidth | |||||
/// </summary> | |||||
/// <param name="n">Raw bandwidth</param> | |||||
/// <returns> | |||||
/// Item1: float, bandwidth with suitable scale (eg. 56) | |||||
/// Item2: string, scale unit name (eg. KiB) | |||||
/// Item3: long, scale unit (eg. 1024) | |||||
/// </returns> | |||||
public static Tuple<float, string, long> GetBandwidthScale(long n) | |||||
{ | |||||
long scale = 1; | |||||
float f = n; | float f = n; | ||||
string unit = "B"; | string unit = "B"; | ||||
if (f > 1024) | if (f > 1024) | ||||
{ | { | ||||
f = f / 1024; | f = f / 1024; | ||||
scale <<= 10; | |||||
unit = "KiB"; | unit = "KiB"; | ||||
} | } | ||||
if (f > 1024) | if (f > 1024) | ||||
{ | { | ||||
f = f / 1024; | f = f / 1024; | ||||
scale <<= 10; | |||||
unit = "MiB"; | unit = "MiB"; | ||||
} | } | ||||
if (f > 1024) | if (f > 1024) | ||||
{ | { | ||||
f = f / 1024; | f = f / 1024; | ||||
scale <<= 10; | |||||
unit = "GiB"; | unit = "GiB"; | ||||
} | } | ||||
if (f > 1024) | if (f > 1024) | ||||
{ | { | ||||
f = f / 1024; | f = f / 1024; | ||||
scale <<= 10; | |||||
unit = "TiB"; | unit = "TiB"; | ||||
} | } | ||||
return $"{f:0.##}{unit}"; | |||||
return new Tuple<float, string, long>(f, unit, scale); | |||||
} | } | ||||
[DllImport("kernel32.dll")] | [DllImport("kernel32.dll")] | ||||
@@ -29,6 +29,10 @@ | |||||
private void InitializeComponent() | private void InitializeComponent() | ||||
{ | { | ||||
this.components = new System.ComponentModel.Container(); | 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.LogMessageTextBox = new System.Windows.Forms.TextBox(); | ||||
this.MainMenu = new System.Windows.Forms.MainMenu(this.components); | this.MainMenu = new System.Windows.Forms.MainMenu(this.components); | ||||
this.FileMenuItem = new System.Windows.Forms.MenuItem(); | this.FileMenuItem = new System.Windows.Forms.MenuItem(); | ||||
@@ -47,8 +51,15 @@ | |||||
this.WrapTextCheckBox = new System.Windows.Forms.CheckBox(); | this.WrapTextCheckBox = new System.Windows.Forms.CheckBox(); | ||||
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); | this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); | ||||
this.ToolbarFlowLayoutPanel = new System.Windows.Forms.FlowLayoutPanel(); | 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.tableLayoutPanel1.SuspendLayout(); | ||||
this.ToolbarFlowLayoutPanel.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(); | this.SuspendLayout(); | ||||
// | // | ||||
// LogMessageTextBox | // LogMessageTextBox | ||||
@@ -57,13 +68,13 @@ | |||||
this.LogMessageTextBox.Dock = System.Windows.Forms.DockStyle.Fill; | 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.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.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.MaxLength = 2147483647; | ||||
this.LogMessageTextBox.Multiline = true; | this.LogMessageTextBox.Multiline = true; | ||||
this.LogMessageTextBox.Name = "LogMessageTextBox"; | this.LogMessageTextBox.Name = "LogMessageTextBox"; | ||||
this.LogMessageTextBox.ReadOnly = true; | this.LogMessageTextBox.ReadOnly = true; | ||||
this.LogMessageTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both; | 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; | this.LogMessageTextBox.TabIndex = 0; | ||||
// | // | ||||
// MainMenu | // MainMenu | ||||
@@ -144,9 +155,9 @@ | |||||
this.TopMostCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | this.TopMostCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | ||||
| System.Windows.Forms.AnchorStyles.Left))); | | System.Windows.Forms.AnchorStyles.Left))); | ||||
this.TopMostCheckBox.AutoSize = true; | 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.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.TabIndex = 3; | ||||
this.TopMostCheckBox.Text = "&Top Most"; | this.TopMostCheckBox.Text = "&Top Most"; | ||||
this.TopMostCheckBox.UseVisualStyleBackColor = true; | this.TopMostCheckBox.UseVisualStyleBackColor = true; | ||||
@@ -157,7 +168,7 @@ | |||||
this.ChangeFontButton.AutoSize = true; | this.ChangeFontButton.AutoSize = true; | ||||
this.ChangeFontButton.Location = new System.Drawing.Point(84, 3); | this.ChangeFontButton.Location = new System.Drawing.Point(84, 3); | ||||
this.ChangeFontButton.Name = "ChangeFontButton"; | 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.TabIndex = 2; | ||||
this.ChangeFontButton.Text = "&Font"; | this.ChangeFontButton.Text = "&Font"; | ||||
this.ChangeFontButton.UseVisualStyleBackColor = true; | this.ChangeFontButton.UseVisualStyleBackColor = true; | ||||
@@ -168,7 +179,7 @@ | |||||
this.CleanLogsButton.AutoSize = true; | this.CleanLogsButton.AutoSize = true; | ||||
this.CleanLogsButton.Location = new System.Drawing.Point(3, 3); | this.CleanLogsButton.Location = new System.Drawing.Point(3, 3); | ||||
this.CleanLogsButton.Name = "CleanLogsButton"; | 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.TabIndex = 1; | ||||
this.CleanLogsButton.Text = "&Clean Logs"; | this.CleanLogsButton.Text = "&Clean Logs"; | ||||
this.CleanLogsButton.UseVisualStyleBackColor = true; | this.CleanLogsButton.UseVisualStyleBackColor = true; | ||||
@@ -181,7 +192,7 @@ | |||||
this.WrapTextCheckBox.AutoSize = true; | this.WrapTextCheckBox.AutoSize = true; | ||||
this.WrapTextCheckBox.Location = new System.Drawing.Point(165, 3); | this.WrapTextCheckBox.Location = new System.Drawing.Point(165, 3); | ||||
this.WrapTextCheckBox.Name = "WrapTextCheckBox"; | 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.TabIndex = 0; | ||||
this.WrapTextCheckBox.Text = "&Wrap Text"; | this.WrapTextCheckBox.Text = "&Wrap Text"; | ||||
this.WrapTextCheckBox.UseVisualStyleBackColor = true; | this.WrapTextCheckBox.UseVisualStyleBackColor = true; | ||||
@@ -191,15 +202,15 @@ | |||||
// | // | ||||
this.tableLayoutPanel1.ColumnCount = 1; | this.tableLayoutPanel1.ColumnCount = 1; | ||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); | 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.ToolbarFlowLayoutPanel, 0, 0); | ||||
this.tableLayoutPanel1.Controls.Add(this.splitContainer1, 0, 1); | |||||
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; | this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); | this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); | ||||
this.tableLayoutPanel1.Name = "tableLayoutPanel1"; | this.tableLayoutPanel1.Name = "tableLayoutPanel1"; | ||||
this.tableLayoutPanel1.RowCount = 2; | this.tableLayoutPanel1.RowCount = 2; | ||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); | 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; | this.tableLayoutPanel1.TabIndex = 2; | ||||
// | // | ||||
// ToolbarFlowLayoutPanel | // ToolbarFlowLayoutPanel | ||||
@@ -212,17 +223,73 @@ | |||||
this.ToolbarFlowLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill; | this.ToolbarFlowLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill; | ||||
this.ToolbarFlowLayoutPanel.Location = new System.Drawing.Point(3, 3); | this.ToolbarFlowLayoutPanel.Location = new System.Drawing.Point(3, 3); | ||||
this.ToolbarFlowLayoutPanel.Name = "ToolbarFlowLayoutPanel"; | 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; | 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 | // 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.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.Controls.Add(this.tableLayoutPanel1); | ||||
this.Menu = this.MainMenu; | this.Menu = this.MainMenu; | ||||
this.MinimumSize = new System.Drawing.Size(400, 213); | |||||
this.MinimumSize = new System.Drawing.Size(400, 200); | |||||
this.Name = "LogForm"; | this.Name = "LogForm"; | ||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; | ||||
this.Text = "Log Viewer"; | this.Text = "Log Viewer"; | ||||
@@ -233,6 +300,12 @@ | |||||
this.tableLayoutPanel1.PerformLayout(); | this.tableLayoutPanel1.PerformLayout(); | ||||
this.ToolbarFlowLayoutPanel.ResumeLayout(false); | this.ToolbarFlowLayoutPanel.ResumeLayout(false); | ||||
this.ToolbarFlowLayoutPanel.PerformLayout(); | 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); | this.ResumeLayout(false); | ||||
} | } | ||||
@@ -257,5 +330,7 @@ | |||||
private System.Windows.Forms.FlowLayoutPanel ToolbarFlowLayoutPanel; | private System.Windows.Forms.FlowLayoutPanel ToolbarFlowLayoutPanel; | ||||
private System.Windows.Forms.MenuItem MenuItemSeparater; | private System.Windows.Forms.MenuItem MenuItemSeparater; | ||||
private System.Windows.Forms.MenuItem ShowToolbarMenuItem; | private System.Windows.Forms.MenuItem ShowToolbarMenuItem; | ||||
private System.Windows.Forms.SplitContainer splitContainer1; | |||||
private System.Windows.Forms.DataVisualization.Charting.Chart trafficChart; | |||||
} | } | ||||
} | } |
@@ -2,6 +2,9 @@ | |||||
using System.Drawing; | using System.Drawing; | ||||
using System.IO; | using System.IO; | ||||
using System.Windows.Forms; | using System.Windows.Forms; | ||||
using System.Windows.Forms.DataVisualization.Charting; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using Shadowsocks.Controller; | using Shadowsocks.Controller; | ||||
using Shadowsocks.Properties; | using Shadowsocks.Properties; | ||||
@@ -18,6 +21,15 @@ namespace Shadowsocks.View | |||||
const int BACK_OFFSET = 65536; | const int BACK_OFFSET = 65536; | ||||
ShadowsocksController controller; | ShadowsocksController controller; | ||||
#region chart | |||||
List<float> inboundPoints = new List<float>(); | |||||
List<float> outboundPoints = new List<float>(); | |||||
long maxSpeed = 0; | |||||
Tuple<float, string, long> bandwidthScale = new Tuple<float, string, long>(0, "B", 1); | |||||
TextAnnotation inboundAnnotation = new TextAnnotation(); | |||||
TextAnnotation outboundAnnotation = new TextAnnotation(); | |||||
#endregion | |||||
public LogForm(ShadowsocksController controller, string filename) | public LogForm(ShadowsocksController controller, string filename) | ||||
{ | { | ||||
this.controller = controller; | this.controller = controller; | ||||
@@ -30,7 +42,8 @@ namespace Shadowsocks.View | |||||
{ | { | ||||
config = new LogViewerConfig(); | config = new LogViewerConfig(); | ||||
} | } | ||||
else { | |||||
else | |||||
{ | |||||
topMostTrigger = config.topMost; | topMostTrigger = config.topMost; | ||||
wrapTextTrigger = config.wrapText; | wrapTextTrigger = config.wrapText; | ||||
toolbarTrigger = config.toolbarShown; | toolbarTrigger = config.toolbarShown; | ||||
@@ -39,9 +52,46 @@ namespace Shadowsocks.View | |||||
LogMessageTextBox.Font = config.GetFont(); | LogMessageTextBox.Font = config.GetFont(); | ||||
} | } | ||||
controller.TrafficChanged += controller_TrafficChanged; | |||||
UpdateTexts(); | 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<float>, .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() | private void UpdateTexts() | ||||
{ | { | ||||
FileMenuItem.Text = I18N.GetString("&File"); | FileMenuItem.Text = I18N.GetString("&File"); | ||||
@@ -144,6 +194,7 @@ namespace Shadowsocks.View | |||||
private void LogForm_FormClosing(object sender, FormClosingEventArgs e) | private void LogForm_FormClosing(object sender, FormClosingEventArgs e) | ||||
{ | { | ||||
timer.Stop(); | timer.Stop(); | ||||
controller.TrafficChanged -= controller_TrafficChanged; | |||||
LogViewerConfig config = controller.GetConfigurationCopy().logViewer; | LogViewerConfig config = controller.GetConfigurationCopy().logViewer; | ||||
if (config == null) | if (config == null) | ||||
config = new LogViewerConfig(); | config = new LogViewerConfig(); | ||||
@@ -144,4 +144,7 @@ | |||||
<metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | <metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | ||||
<value>True</value> | <value>True</value> | ||||
</metadata> | </metadata> | ||||
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | |||||
<value>39</value> | |||||
</metadata> | |||||
</root> | </root> |
@@ -2,6 +2,7 @@ | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.Drawing; | using System.Drawing; | ||||
using System.Drawing.Imaging; | |||||
using System.Windows.Forms; | using System.Windows.Forms; | ||||
using ZXing; | using ZXing; | ||||
@@ -25,6 +26,8 @@ namespace Shadowsocks.View | |||||
private UpdateChecker updateChecker; | private UpdateChecker updateChecker; | ||||
private NotifyIcon _notifyIcon; | private NotifyIcon _notifyIcon; | ||||
private Bitmap icon_baseBitmap; | |||||
private Icon icon_base, icon_in, icon_out, icon_both, targetIcon; | |||||
private ContextMenu contextMenu1; | private ContextMenu contextMenu1; | ||||
private bool _isFirstRun; | private bool _isFirstRun; | ||||
@@ -74,6 +77,7 @@ namespace Shadowsocks.View | |||||
_notifyIcon.MouseClick += notifyIcon1_Click; | _notifyIcon.MouseClick += notifyIcon1_Click; | ||||
_notifyIcon.MouseDoubleClick += notifyIcon1_DoubleClick; | _notifyIcon.MouseDoubleClick += notifyIcon1_DoubleClick; | ||||
_notifyIcon.BalloonTipClosed += _notifyIcon_BalloonTipClosed; | _notifyIcon.BalloonTipClosed += _notifyIcon_BalloonTipClosed; | ||||
controller.TrafficChanged += controller_TrafficChanged; | |||||
this.updateChecker = new UpdateChecker(); | this.updateChecker = new UpdateChecker(); | ||||
updateChecker.CheckUpdateCompleted += updateChecker_CheckUpdateCompleted; | 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) | void controller_Errored(object sender, System.IO.ErrorEventArgs e) | ||||
{ | { | ||||
MessageBox.Show(e.GetException().ToString(), String.Format(I18N.GetString("Shadowsocks Error: {0}"), e.GetException().Message)); | 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); | Graphics graphics = Graphics.FromHwnd(IntPtr.Zero); | ||||
dpi = (int)graphics.DpiX; | dpi = (int)graphics.DpiX; | ||||
graphics.Dispose(); | graphics.Dispose(); | ||||
Bitmap icon = null; | |||||
icon_baseBitmap = null; | |||||
if (dpi < 97) | if (dpi < 97) | ||||
{ | { | ||||
// dpi = 96; | // dpi = 96; | ||||
icon = Resources.ss16; | |||||
icon_baseBitmap = Resources.ss16; | |||||
} | } | ||||
else if (dpi < 121) | else if (dpi < 121) | ||||
{ | { | ||||
// dpi = 120; | // dpi = 120; | ||||
icon = Resources.ss20; | |||||
icon_baseBitmap = Resources.ss20; | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
icon = Resources.ss24; | |||||
icon_baseBitmap = Resources.ss24; | |||||
} | } | ||||
Configuration config = controller.GetConfigurationCopy(); | Configuration config = controller.GetConfigurationCopy(); | ||||
bool enabled = config.enabled; | bool enabled = config.enabled; | ||||
bool global = config.global; | 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; | string serverInfo = null; | ||||
if (controller.GetCurrentStrategy() != null) | if (controller.GetCurrentStrategy() != null) | ||||
@@ -177,6 +213,19 @@ namespace Shadowsocks.View | |||||
return iconCopy; | 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) | private MenuItem CreateMenuItem(string text, EventHandler click) | ||||
{ | { | ||||
return new MenuItem(I18N.GetString(text), click); | return new MenuItem(I18N.GetString(text), click); | ||||
@@ -291,6 +291,8 @@ | |||||
<Content Include="FodyWeavers.xml"> | <Content Include="FodyWeavers.xml"> | ||||
<SubType>Designer</SubType> | <SubType>Designer</SubType> | ||||
</Content> | </Content> | ||||
<Content Include="Resources\ssIn24.png" /> | |||||
<Content Include="Resources\ssOut24.png" /> | |||||
<Content Include="shadowsocks.ico" /> | <Content Include="shadowsocks.ico" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||