diff --git a/README.md b/README.md index bcd3f5cf..54d604bf 100644 --- a/README.md +++ b/README.md @@ -3,34 +3,36 @@ Shadowsocks for Windows [![Build Status]][Appveyor] +[中文说明] + #### Features 1. System proxy configuration 2. PAC mode and global mode -3. GFWList and user rules +3. [GFWList] and user rules 4. Supports HTTP proxy 5. Supports server auto switching 6. Supports UDP relay (see Usage) #### Download -Download a [latest release]. +Download the [latest release]. #### Basic 1. Find Shadowsocks icon in the notification tray 2. You can add multiple servers in servers menu -3. Select Enable System Proxy menu to enable system proxy. Please disable other +3. Select `Enable System Proxy` menu to enable system proxy. Please disable other proxy addons in your browser, or set them to use system proxy 4. You can also configure your browser proxy manually if you don't want to enable system proxy. Set Socks5 or HTTP proxy to 127.0.0.1:1080. You can change this -port in Server -> Edit Servers +port in `Servers -> Edit Servers` #### PAC 1. You can change PAC rules by editing the PAC file. When you save the PAC file with any editor, Shadowsocks will notify browsers about the change automatically -2. You can also update PAC file from GFWList (maintained by 3rd party) +2. You can also update PAC file from [GFWList] (maintained by 3rd party) 3. You can also use online PAC URL #### Server Auto Switching @@ -44,7 +46,20 @@ with any editor, Shadowsocks will notify browsers about the change automatically #### UDP For UDP, you need to use SocksCap or ProxyCap to force programs you want -to proxy to tunnel over Shadowsocks +to be proxied to tunnel over Shadowsocks + +#### Multiple Instances + +If you want to manage multiple servers using other tools like SwitchyOmega, +you can start multiple Shadowsocks instances. To avoid configuration conflicts, +copy Shadowsocks to a new directory and choose a different local port. + +Also, make sure to use `SOCKS5` proxy in SwitchyOmega, since we have only +one HTTP proxy instance. + +#### Server Configuration + +Please visit [Servers] for more information. #### Develop @@ -58,3 +73,6 @@ GPLv3 [Appveyor]: https://ci.appveyor.com/project/clowwindy/shadowsocks-csharp [Build Status]: https://ci.appveyor.com/api/projects/status/gknc8l1lxy423ehv/branch/master [latest release]: https://github.com/shadowsocks/shadowsocks-csharp/releases +[GFWList]: https://github.com/gfwlist/gfwlist +[Servers]: https://github.com/shadowsocks/shadowsocks/wiki/Ports-and-Clients#linux--server-side +[中文说明]: https://github.com/shadowsocks/shadowsocks-windows/wiki/Shadowsocks-Windows-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E diff --git a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs index 5a95dbc2..0d8463a6 100644 --- a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs +++ b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs @@ -67,14 +67,21 @@ namespace Shadowsocks.Controller string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); //ICMP echo. we can also set options and special bytes //seems no need to use SendPingAsync - PingReply reply = ping.Send(server.server, Timeout); - state.data = new List>(); - state.data.Add(new KeyValuePair("Timestamp", timestamp)); - state.data.Add(new KeyValuePair("Server", server.FriendlyName())); - state.data.Add(new KeyValuePair("Status", reply.Status.ToString())); - state.data.Add(new KeyValuePair("RoundtripTime", reply.RoundtripTime.ToString())); - //state.data.Add(new KeyValuePair("data", reply.Buffer.ToString())); // The data of reply - Append(state.data); + try + { + PingReply reply = ping.Send(server.server, Timeout); + state.data = new List>(); + state.data.Add(new KeyValuePair("Timestamp", timestamp)); + state.data.Add(new KeyValuePair("Server", server.FriendlyName())); + state.data.Add(new KeyValuePair("Status", reply.Status.ToString())); + state.data.Add(new KeyValuePair("RoundtripTime", reply.RoundtripTime.ToString())); + //state.data.Add(new KeyValuePair("data", reply.Buffer.ToString())); // The data of reply + Append(state.data); + } + catch (Exception e) + { + Logging.LogUsefulException(e); + } } } } diff --git a/shadowsocks-csharp/Controller/Service/PACServer.cs b/shadowsocks-csharp/Controller/Service/PACServer.cs index 038ead8c..e915681e 100644 --- a/shadowsocks-csharp/Controller/Service/PACServer.cs +++ b/shadowsocks-csharp/Controller/Service/PACServer.cs @@ -146,7 +146,7 @@ Connection: Close ", System.Text.Encoding.UTF8.GetBytes(pac).Length) + pac; byte[] response = System.Text.Encoding.UTF8.GetBytes(text); socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket); - Util.Utils.ReleaseMemory(); + Util.Utils.ReleaseMemory(true); } catch (Exception e) { diff --git a/shadowsocks-csharp/Controller/Service/PolipoRunner.cs b/shadowsocks-csharp/Controller/Service/PolipoRunner.cs index 261436be..9581e05f 100644 --- a/shadowsocks-csharp/Controller/Service/PolipoRunner.cs +++ b/shadowsocks-csharp/Controller/Service/PolipoRunner.cs @@ -65,10 +65,13 @@ namespace Shadowsocks.Controller polipoConfig = polipoConfig.Replace("__POLIPO_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1"); FileManager.ByteArrayToFile(temppath + "/privoxy.conf", System.Text.Encoding.UTF8.GetBytes(polipoConfig)); + if (!(temppath.EndsWith("\\") || temppath.EndsWith("/"))) { + temppath = temppath + "\\"; + } _process = new Process(); // Configure the process using the StartInfo properties. - _process.StartInfo.FileName = temppath + "/ss_privoxy.exe"; - _process.StartInfo.Arguments = " \"" + temppath + "/privoxy.conf\""; + _process.StartInfo.FileName = temppath + "ss_privoxy.exe"; + _process.StartInfo.Arguments = " \"" + temppath + "privoxy.conf\""; _process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; _process.StartInfo.UseShellExecute = true; _process.StartInfo.CreateNoWindow = true; diff --git a/shadowsocks-csharp/Controller/Service/TCPRelay.cs b/shadowsocks-csharp/Controller/Service/TCPRelay.cs index 5c9de444..667a11a6 100644 --- a/shadowsocks-csharp/Controller/Service/TCPRelay.cs +++ b/shadowsocks-csharp/Controller/Service/TCPRelay.cs @@ -14,9 +14,18 @@ namespace Shadowsocks.Controller class TCPRelay : Listener.Service { private ShadowsocksController _controller; + private DateTime _lastSweepTime; + + public ISet Handlers + { + get; set; + } + public TCPRelay(ShadowsocksController controller) { this._controller = controller; + this.Handlers = new HashSet(); + this._lastSweepTime = DateTime.Now; } public bool Handle(byte[] firstPacket, int length, Socket socket, object state) @@ -33,9 +42,33 @@ namespace Shadowsocks.Controller Handler handler = new Handler(); handler.connection = socket; handler.controller = _controller; + handler.relay = this; handler.Start(firstPacket, length); - return true; + IList handlersToClose = new List(); + lock (this.Handlers) + { + this.Handlers.Add(handler); + Logging.Debug($"connections: {Handlers.Count}"); + DateTime now = DateTime.Now; + if (now - _lastSweepTime > TimeSpan.FromSeconds(1)) + { + _lastSweepTime = now; + foreach (Handler handler1 in this.Handlers) + { + if (now - handler1.lastActivity > TimeSpan.FromSeconds(1800)) + { + handlersToClose.Add(handler1); + } + } + } + } + foreach (Handler handler1 in handlersToClose) + { + Logging.Debug("Closing timed out connection"); + handler1.Close(); + } + return true; } } @@ -48,6 +81,10 @@ namespace Shadowsocks.Controller public Socket remote; public Socket connection; public ShadowsocksController controller; + public TCPRelay relay; + + public DateTime lastActivity; + private int retryCount = 0; private bool connected; @@ -55,7 +92,7 @@ namespace Shadowsocks.Controller private byte[] _firstPacket; private int _firstPacketLength; // Size of receive buffer. - public const int RecvSize = 16384; + public const int RecvSize = 8192; public const int BufferSize = RecvSize + 32; private int totalRead = 0; @@ -96,6 +133,7 @@ namespace Shadowsocks.Controller this._firstPacket = firstPacket; this._firstPacketLength = length; this.HandshakeReceive(); + this.lastActivity = DateTime.Now; } private void CheckClose() @@ -108,6 +146,11 @@ namespace Shadowsocks.Controller public void Close() { + lock (relay.Handlers) + { + Logging.Debug($"connections: {relay.Handlers.Count}"); + relay.Handlers.Remove(this); + } lock (this) { if (closed) @@ -485,6 +528,7 @@ namespace Shadowsocks.Controller if (bytesRead > 0) { + this.lastActivity = DateTime.Now; int bytesToSend; lock (decryptionLock) { diff --git a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs index c6f65121..22aa2916 100644 --- a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs +++ b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs @@ -12,13 +12,13 @@ namespace Shadowsocks.Controller { public class UpdateChecker { - private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-csharp/releases"; + private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-windows/releases"; public string LatestVersionNumber; public string LatestVersionURL; public event EventHandler NewVersionFound; - public const string Version = "2.5.4"; + public const string Version = "2.5.5"; public void CheckUpdate(Configuration config) { diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index aac1419a..b02771f1 100755 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -360,7 +360,7 @@ namespace Shadowsocks.Controller } UpdateSystemProxy(); - Util.Utils.ReleaseMemory(); + Util.Utils.ReleaseMemory(true); } @@ -417,7 +417,7 @@ namespace Shadowsocks.Controller { while (true) { - Util.Utils.ReleaseMemory(); + Util.Utils.ReleaseMemory(false); Thread.Sleep(30 * 1000); } } diff --git a/shadowsocks-csharp/Controller/System/SystemProxy.cs b/shadowsocks-csharp/Controller/System/SystemProxy.cs index 84a26d06..74e4beb4 100644 --- a/shadowsocks-csharp/Controller/System/SystemProxy.cs +++ b/shadowsocks-csharp/Controller/System/SystemProxy.cs @@ -56,18 +56,14 @@ namespace Shadowsocks.Controller pacUrl = "http://127.0.0.1:" + config.localPort.ToString() + "/pac?t=" + GetTimestamp(DateTime.Now); registry.SetValue("ProxyEnable", 0); var readProxyServer = registry.GetValue("ProxyServer"); - if (readProxyServer != null && readProxyServer.Equals("127.0.0.1:" + config.localPort.ToString())) - registry.SetValue("ProxyServer", ""); + registry.SetValue("ProxyServer", ""); registry.SetValue("AutoConfigURL", pacUrl); } } else { registry.SetValue("ProxyEnable", 0); - if (global) - { - registry.SetValue("ProxyServer", ""); - } + registry.SetValue("ProxyServer", ""); registry.SetValue("AutoConfigURL", ""); } //Set AutoDetectProxy Off diff --git a/shadowsocks-csharp/Data/cn.txt b/shadowsocks-csharp/Data/cn.txt index b4c7a6ee..fd09606d 100644 --- a/shadowsocks-csharp/Data/cn.txt +++ b/shadowsocks-csharp/Data/cn.txt @@ -19,6 +19,7 @@ Update Local PAC from GFWList=从 GFWList 更新本地 PAC Edit User Rule for GFWList...=编辑 GFWList 的用户规则... Show QRCode...=显示二维码... Scan QRCode from Screen...=扫描屏幕上的二维码... +Availability Statistics=统计可用性 Show Logs...=显示日志... About...=关于... Quit=退出 @@ -41,6 +42,8 @@ Remarks=备注 OK=确定 Cancel=取消 New server=未配置的服务器 +Move &Up=上移 +Move D&own=下移 # QRCode Form @@ -72,7 +75,10 @@ Failed to update PAC file =更新 PAC 文件失败 PAC updated=更新 PAC 成功 No updates found. Please report to GFWList if you have problems with it.=未发现更新。如有问题请提交给 GFWList。 No QRCode found. Try to zoom in or move it to the center of the screen.=未发现二维码,尝试把它放大或移动到靠近屏幕中间的位置 +Shadowsocks is already running.=Shadowsocks 已经在运行。 +Find Shadowsocks icon in your notify tray.=请在任务栏里寻找 Shadowsocks 图标。 +If you want to start multiple Shadowsocks, make a copy in another directory.=如果想同时启动多个,可以另外复制一份到别的目录。 Failed to decode QRCode=无法解析二维码 Failed to update registry=无法修改注册表 System Proxy On: =系统代理已启用: -Running: Port {0}=正在运行:端口 {0} \ No newline at end of file +Running: Port {0}=正在运行:端口 {0} diff --git a/shadowsocks-csharp/Encryption/SodiumEncryptor.cs b/shadowsocks-csharp/Encryption/SodiumEncryptor.cs index 5d6165eb..af51d0ac 100755 --- a/shadowsocks-csharp/Encryption/SodiumEncryptor.cs +++ b/shadowsocks-csharp/Encryption/SodiumEncryptor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading; namespace Shadowsocks.Encryption { @@ -12,19 +13,17 @@ namespace Shadowsocks.Encryption const int SODIUM_BLOCK_SIZE = 64; + static byte[] sodiumBuf = new byte[MAX_INPUT_SIZE + SODIUM_BLOCK_SIZE]; + protected int _encryptBytesRemaining; protected int _decryptBytesRemaining; protected ulong _encryptIC; protected ulong _decryptIC; - protected byte[] _encryptBuf; - protected byte[] _decryptBuf; public SodiumEncryptor(string method, string password) : base(method, password) { InitKey(method, password); - _encryptBuf = new byte[MAX_INPUT_SIZE + SODIUM_BLOCK_SIZE]; - _decryptBuf = new byte[MAX_INPUT_SIZE + SODIUM_BLOCK_SIZE]; } private static Dictionary _ciphers = new Dictionary { @@ -47,48 +46,51 @@ namespace Shadowsocks.Encryption // TODO write a unidirection cipher so we don't have to if if if int bytesRemaining; ulong ic; - byte[] sodiumBuf; byte[] iv; - if (isCipher) - { - bytesRemaining = _encryptBytesRemaining; - ic = _encryptIC; - sodiumBuf = _encryptBuf; - iv = _encryptIV; - } - else - { - bytesRemaining = _decryptBytesRemaining; - ic = _decryptIC; - sodiumBuf = _decryptBuf; - iv = _decryptIV; - } - int padding = bytesRemaining; - Buffer.BlockCopy(buf, 0, sodiumBuf, padding, length); - switch (_cipher) + // I'm tired. just add a big lock + // let's optimize for RAM instead of CPU + lock(sodiumBuf) { - case CIPHER_SALSA20: - Sodium.crypto_stream_salsa20_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, ic, _key); - break; - case CIPHER_CHACHA20: - Sodium.crypto_stream_chacha20_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, ic, _key); - break; - } - Buffer.BlockCopy(sodiumBuf, padding, outbuf, 0, length); - padding += length; - ic += (ulong)padding / SODIUM_BLOCK_SIZE; - bytesRemaining = padding % SODIUM_BLOCK_SIZE; + if (isCipher) + { + bytesRemaining = _encryptBytesRemaining; + ic = _encryptIC; + iv = _encryptIV; + } + else + { + bytesRemaining = _decryptBytesRemaining; + ic = _decryptIC; + iv = _decryptIV; + } + int padding = bytesRemaining; + Buffer.BlockCopy(buf, 0, sodiumBuf, padding, length); - if (isCipher) - { - _encryptBytesRemaining = bytesRemaining; - _encryptIC = ic; - } - else - { - _decryptBytesRemaining = bytesRemaining; - _decryptIC = ic; + switch (_cipher) + { + case CIPHER_SALSA20: + Sodium.crypto_stream_salsa20_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, ic, _key); + break; + case CIPHER_CHACHA20: + Sodium.crypto_stream_chacha20_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, ic, _key); + break; + } + Buffer.BlockCopy(sodiumBuf, padding, outbuf, 0, length); + padding += length; + ic += (ulong)padding / SODIUM_BLOCK_SIZE; + bytesRemaining = padding % SODIUM_BLOCK_SIZE; + + if (isCipher) + { + _encryptBytesRemaining = bytesRemaining; + _encryptIC = ic; + } + else + { + _decryptBytesRemaining = bytesRemaining; + _decryptIC = ic; + } } } diff --git a/shadowsocks-csharp/Program.cs b/shadowsocks-csharp/Program.cs index 8ca485d7..cb40cb23 100755 --- a/shadowsocks-csharp/Program.cs +++ b/shadowsocks-csharp/Program.cs @@ -18,8 +18,8 @@ namespace Shadowsocks [STAThread] static void Main() { - Util.Utils.ReleaseMemory(); - using (Mutex mutex = new Mutex(false, "Global\\" + "71981632-A427-497F-AB91-241CD227EC1F")) + Util.Utils.ReleaseMemory(true); + using (Mutex mutex = new Mutex(false, "Global\\Shadowsocks_" + Application.StartupPath.GetHashCode())) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); @@ -31,7 +31,9 @@ namespace Shadowsocks { Process oldProcess = oldProcesses[0]; } - MessageBox.Show("Shadowsocks is already running.\n\nFind Shadowsocks icon in your notify tray."); + MessageBox.Show(I18N.GetString("Find Shadowsocks icon in your notify tray.") + "\n" + + I18N.GetString("If you want to start multiple Shadowsocks, make a copy in another directory."), + I18N.GetString("Shadowsocks is already running.")); return; } Directory.SetCurrentDirectory(Application.StartupPath); diff --git a/shadowsocks-csharp/Util/Util.cs b/shadowsocks-csharp/Util/Util.cs index 15463a3a..004128af 100755 --- a/shadowsocks-csharp/Util/Util.cs +++ b/shadowsocks-csharp/Util/Util.cs @@ -10,7 +10,7 @@ namespace Shadowsocks.Util { public class Utils { - public static void ReleaseMemory() + public static void ReleaseMemory(bool removePages) { // release any unused pages // making the numbers look good in task manager @@ -20,8 +20,29 @@ namespace Shadowsocks.Util // which is part of user experience GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); - SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, - (UIntPtr)0xFFFFFFFF, (UIntPtr)0xFFFFFFFF); + if (removePages) + { + // as some users have pointed out + // removing pages from working set will cause some IO + // which lowered user experience for another group of users + // + // so we do 2 more things here to satisfy them: + // 1. only remove pages once when configuration is changed + // 2. add more comments here to tell users that calling + // this function will not be more frequent than + // IM apps writing chat logs, or web browsers writing cache files + // if they're so concerned about their disk, they should + // uninstall all IM apps and web browsers + // + // please open an issue if you're worried about anything else in your computer + // no matter it's GPU performance, monitor contrast, audio fidelity + // or anything else in the task manager + // we'll do as much as we can to help you + // + // just kidding + SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, + (UIntPtr)0xFFFFFFFF, (UIntPtr)0xFFFFFFFF); + } } public static string UnGzip(byte[] buf) diff --git a/shadowsocks-csharp/View/ConfigForm.Designer.cs b/shadowsocks-csharp/View/ConfigForm.Designer.cs index 83dae64a..f8b5940a 100755 --- a/shadowsocks-csharp/View/ConfigForm.Designer.cs +++ b/shadowsocks-csharp/View/ConfigForm.Designer.cs @@ -47,6 +47,9 @@ this.ServerGroupBox = new System.Windows.Forms.GroupBox(); this.ServersListBox = new System.Windows.Forms.ListBox(); this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.tableLayoutPanel6 = new System.Windows.Forms.TableLayoutPanel(); + this.MoveDownButton = new System.Windows.Forms.Button(); + this.MoveUpButton = new System.Windows.Forms.Button(); this.tableLayoutPanel5 = new System.Windows.Forms.TableLayoutPanel(); this.ProxyPortTextBox = new System.Windows.Forms.TextBox(); this.ProxyPortLabel = new System.Windows.Forms.Label(); @@ -55,6 +58,7 @@ this.tableLayoutPanel1.SuspendLayout(); this.ServerGroupBox.SuspendLayout(); this.tableLayoutPanel2.SuspendLayout(); + this.tableLayoutPanel6.SuspendLayout(); this.tableLayoutPanel5.SuspendLayout(); this.tableLayoutPanel3.SuspendLayout(); this.tableLayoutPanel4.SuspendLayout(); @@ -184,7 +188,7 @@ // // EncryptionSelect // - this.EncryptionSelect.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.EncryptionSelect.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.EncryptionSelect.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.EncryptionSelect.FormattingEnabled = true; @@ -281,6 +285,7 @@ // this.ServersListBox.FormattingEnabled = true; this.ServersListBox.IntegralHeight = false; + this.ServersListBox.ItemHeight = 12; this.ServersListBox.Location = new System.Drawing.Point(0, 0); this.ServersListBox.Margin = new System.Windows.Forms.Padding(0); this.ServersListBox.Name = "ServersListBox"; @@ -295,6 +300,7 @@ this.tableLayoutPanel2.ColumnCount = 2; this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.Controls.Add(this.tableLayoutPanel6, 0, 2); this.tableLayoutPanel2.Controls.Add(this.tableLayoutPanel5, 1, 1); this.tableLayoutPanel2.Controls.Add(this.tableLayoutPanel3, 1, 2); this.tableLayoutPanel2.Controls.Add(this.ServersListBox, 0, 0); @@ -310,6 +316,48 @@ this.tableLayoutPanel2.Size = new System.Drawing.Size(427, 238); this.tableLayoutPanel2.TabIndex = 7; // + // tableLayoutPanel6 + // + this.tableLayoutPanel6.AutoSize = true; + this.tableLayoutPanel6.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.tableLayoutPanel6.ColumnCount = 2; + this.tableLayoutPanel6.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel6.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel6.Controls.Add(this.MoveDownButton, 1, 0); + this.tableLayoutPanel6.Controls.Add(this.MoveUpButton, 0, 0); + this.tableLayoutPanel6.Dock = System.Windows.Forms.DockStyle.Top; + this.tableLayoutPanel6.Location = new System.Drawing.Point(0, 211); + this.tableLayoutPanel6.Margin = new System.Windows.Forms.Padding(0); + this.tableLayoutPanel6.Name = "tableLayoutPanel6"; + this.tableLayoutPanel6.RowCount = 1; + this.tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel6.Size = new System.Drawing.Size(166, 32); + this.tableLayoutPanel6.TabIndex = 10; + // + // MoveDownButton + // + this.MoveDownButton.Dock = System.Windows.Forms.DockStyle.Right; + this.MoveDownButton.Location = new System.Drawing.Point(86, 6); + this.MoveDownButton.Margin = new System.Windows.Forms.Padding(3, 6, 0, 3); + this.MoveDownButton.Name = "MoveDownButton"; + this.MoveDownButton.Size = new System.Drawing.Size(80, 23); + this.MoveDownButton.TabIndex = 7; + this.MoveDownButton.Text = "Move D&own"; + this.MoveDownButton.UseVisualStyleBackColor = true; + this.MoveDownButton.Click += new System.EventHandler(this.MoveDownButton_Click); + // + // MoveUpButton + // + this.MoveUpButton.Dock = System.Windows.Forms.DockStyle.Left; + this.MoveUpButton.Location = new System.Drawing.Point(0, 6); + this.MoveUpButton.Margin = new System.Windows.Forms.Padding(0, 6, 3, 3); + this.MoveUpButton.Name = "MoveUpButton"; + this.MoveUpButton.Size = new System.Drawing.Size(80, 23); + this.MoveUpButton.TabIndex = 6; + this.MoveUpButton.Text = "Move &Up"; + this.MoveUpButton.UseVisualStyleBackColor = true; + this.MoveUpButton.Click += new System.EventHandler(this.MoveUpButton_Click); + // // tableLayoutPanel5 // this.tableLayoutPanel5.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) @@ -418,6 +466,7 @@ this.ServerGroupBox.PerformLayout(); this.tableLayoutPanel2.ResumeLayout(false); this.tableLayoutPanel2.PerformLayout(); + this.tableLayoutPanel6.ResumeLayout(false); this.tableLayoutPanel5.ResumeLayout(false); this.tableLayoutPanel5.PerformLayout(); this.tableLayoutPanel3.ResumeLayout(false); @@ -453,6 +502,9 @@ private System.Windows.Forms.TableLayoutPanel tableLayoutPanel5; private System.Windows.Forms.TextBox ProxyPortTextBox; private System.Windows.Forms.Label ProxyPortLabel; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel6; + private System.Windows.Forms.Button MoveDownButton; + private System.Windows.Forms.Button MoveUpButton; } } diff --git a/shadowsocks-csharp/View/ConfigForm.cs b/shadowsocks-csharp/View/ConfigForm.cs index 675aa23e..edcb405e 100755 --- a/shadowsocks-csharp/View/ConfigForm.cs +++ b/shadowsocks-csharp/View/ConfigForm.cs @@ -51,6 +51,8 @@ namespace Shadowsocks.View ServerGroupBox.Text = I18N.GetString("Server"); OKButton.Text = I18N.GetString("OK"); MyCancelButton.Text = I18N.GetString("Cancel"); + MoveUpButton.Text = I18N.GetString("Move &Up"); + MoveDownButton.Text = I18N.GetString("Move D&own"); this.Text = I18N.GetString("Edit Servers"); } @@ -58,7 +60,7 @@ namespace Shadowsocks.View { LoadCurrentConfiguration(); } - + private void ShowWindow() { this.Opacity = 1; @@ -87,7 +89,7 @@ namespace Shadowsocks.View Configuration.CheckLocalPort(localPort); _modifiedConfiguration.configs[_oldSelectedIndex] = server; _modifiedConfiguration.localPort = localPort; - + return true; } catch (FormatException) @@ -113,12 +115,6 @@ namespace Shadowsocks.View ProxyPortTextBox.Text = _modifiedConfiguration.localPort.ToString(); EncryptionSelect.Text = server.method ?? "aes-256-cfb"; RemarksTextBox.Text = server.remarks; - ServerGroupBox.Visible = true; - //IPTextBox.Focus(); - } - else - { - ServerGroupBox.Visible = false; } } @@ -141,6 +137,7 @@ namespace Shadowsocks.View _oldSelectedIndex = 0; } ServersListBox.SelectedIndex = _oldSelectedIndex; + UpdateMoveUpAndDownButton(); LoadSelectedServer(); } @@ -162,6 +159,7 @@ namespace Shadowsocks.View ServersListBox.SelectedIndex = _oldSelectedIndex; return; } + UpdateMoveUpAndDownButton(); LoadSelectedServer(); _oldSelectedIndex = ServersListBox.SelectedIndex; } @@ -208,7 +206,9 @@ namespace Shadowsocks.View MessageBox.Show(I18N.GetString("Please add at least one server")); return; } + int index = _modifiedConfiguration.index; controller.SaveServers(_modifiedConfiguration.configs, _modifiedConfiguration.localPort); + controller.SelectServerIndex(index); this.Close(); } @@ -227,5 +227,61 @@ namespace Shadowsocks.View controller.ConfigChanged -= controller_ConfigChanged; } + private void MoveConfigItem(int step) + { + int index = ServersListBox.SelectedIndex; + Server server = _modifiedConfiguration.configs[index]; + object item = ServersListBox.SelectedItem; + + _modifiedConfiguration.configs.Remove(server); + _modifiedConfiguration.configs.Insert(index + step, server); + _modifiedConfiguration.index += step; + + ServersListBox.BeginUpdate(); + _oldSelectedIndex = index + step; + ServersListBox.Items.Remove(item); + ServersListBox.Items.Insert(index + step, item); + ServersListBox.SelectedIndex = index + step; + ServersListBox.EndUpdate(); + + UpdateMoveUpAndDownButton(); + } + + private void UpdateMoveUpAndDownButton() + { + if (ServersListBox.SelectedIndex == 0) + { + MoveUpButton.Enabled = false; + } + else + { + MoveUpButton.Enabled = true; + } + if (ServersListBox.SelectedIndex == ServersListBox.Items.Count - 1) + { + MoveDownButton.Enabled = false; + } + else + { + MoveDownButton.Enabled = true; + } + } + + private void MoveUpButton_Click(object sender, EventArgs e) + { + if (ServersListBox.SelectedIndex > 0) + { + MoveConfigItem(-1); // -1 means move backward + } + } + + private void MoveDownButton_Click(object sender, EventArgs e) + { + if (ServersListBox.SelectedIndex < ServersListBox.Items.Count - 1) + { + MoveConfigItem(+1); // +1 means move forward + } + } + } } diff --git a/shadowsocks-csharp/View/ConfigForm.resx b/shadowsocks-csharp/View/ConfigForm.resx index ff31a6db..c7e0d4bd 100755 --- a/shadowsocks-csharp/View/ConfigForm.resx +++ b/shadowsocks-csharp/View/ConfigForm.resx @@ -112,9 +112,9 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 \ No newline at end of file diff --git a/shadowsocks-csharp/View/MenuViewController.cs b/shadowsocks-csharp/View/MenuViewController.cs index b8c9bb81..5081ec35 100755 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -322,7 +322,7 @@ namespace Shadowsocks.View void configForm_FormClosed(object sender, FormClosedEventArgs e) { configForm = null; - Util.Utils.ReleaseMemory(); + Util.Utils.ReleaseMemory(true); ShowFirstTimeBalloon(); }