diff --git a/CHANGES b/CHANGES index 2ddecc9..5c5aba5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,12 @@ +2.5.6 2015-08-19 +- Add portable mode. Create shadowsocks_portable_mode.txt to use it +- Support server reorder + +2.5.5 2015-08-17 +- Fix crash when enabling Availability Statistics and some servers can not be resolved +- Allow multiple instances +- Other fixes + 2.5.4 2015-08-16 - Hide Privoxy icon diff --git a/README.md b/README.md index bcd3f5c..83bff1e 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,25 @@ 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. + +#### Portable Mode + +If you want to put all temporary files into shadowsocks/temp folder instead of +system temp folder, create a `shadowsocks_portable_mode.txt` into shadowsocks folder. #### Develop @@ -58,3 +78,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/I18N.cs b/shadowsocks-csharp/Controller/I18N.cs index 76c5174..c99d0f1 100755 --- a/shadowsocks-csharp/Controller/I18N.cs +++ b/shadowsocks-csharp/Controller/I18N.cs @@ -12,7 +12,7 @@ namespace Shadowsocks.Controller static I18N() { Strings = new Dictionary(); - + if (System.Globalization.CultureInfo.CurrentCulture.IetfLanguageTag.ToLowerInvariant().StartsWith("zh")) { string[] lines = Regex.Split(Resources.cn, "\r\n|\r|\n"); diff --git a/shadowsocks-csharp/Controller/Logging.cs b/shadowsocks-csharp/Controller/Logging.cs index 354df7a..d77a3fb 100755 --- a/shadowsocks-csharp/Controller/Logging.cs +++ b/shadowsocks-csharp/Controller/Logging.cs @@ -1,4 +1,5 @@ -using System; +using Shadowsocks.Util; +using System; using System.Collections.Generic; using System.IO; using System.Net.Sockets; @@ -14,14 +15,14 @@ namespace Shadowsocks.Controller { try { - string temppath = Path.GetTempPath(); + string temppath = Utils.GetTempPath(); LogFile = Path.Combine(temppath, "shadowsocks.log"); FileStream fs = new FileStream(LogFile, FileMode.Append); StreamWriterWithTimestamp sw = new StreamWriterWithTimestamp(fs); sw.AutoFlush = true; Console.SetOut(sw); Console.SetError(sw); - + return true; } catch (IOException e) @@ -71,7 +72,6 @@ namespace Shadowsocks.Controller Console.WriteLine(e); } } - } // Simply extended System.IO.StreamWriter for adding timestamp workaround diff --git a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs index 1c69298..3919676 100644 --- a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs +++ b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs @@ -10,13 +10,15 @@ using System.Threading.Tasks; using System.Windows.Forms; using Shadowsocks.Model; using SimpleJson = SimpleJson.SimpleJson; +using Shadowsocks.Util; using Timer = System.Threading.Timer; namespace Shadowsocks.Controller { using DataUnit = KeyValuePair; using DataList = List>; - class AvailabilityStatistics + + internal class AvailabilityStatistics { private const string StatisticsFilesName = "shadowsocks.availability.csv"; private const string Delimiter = ","; @@ -33,7 +35,7 @@ namespace Shadowsocks.Controller //static constructor to initialize every public static fields before refereced static AvailabilityStatistics() { - var temppath = Path.GetTempPath(); + string temppath = Utils.GetTempPath(); AvailabilityStatisticsFile = Path.Combine(temppath, StatisticsFilesName); } @@ -41,10 +43,18 @@ namespace Shadowsocks.Controller { try { - _timer?.Dispose(); - if (!enabled) return true; - _state = new State(); - _timer = new Timer(Evaluate, _state, delayBeforeStart, Interval); + if (enabled) + { + if (_timer?.Change(0, Interval) == null) + { + _state = new State(); + _timer = new Timer(Evaluate, _state, 0, Interval); + } + } + else + { + _timer?.Dispose(); + } return true; } catch (Exception e) @@ -80,7 +90,7 @@ namespace Shadowsocks.Controller string country = obj["country"]; string city = obj["city"]; string isp = obj["isp"]; - string regionName= obj["regionName"]; + string regionName = obj["regionName"]; if (country == null || city == null || isp == null || regionName == null) return ret; ret[0] = new DataUnit(State.Geolocation, $"{country} {regionName} {city}"); ret[1] = new DataUnit(State.ISP, isp); @@ -93,7 +103,8 @@ namespace Shadowsocks.Controller if (server.server == "") return null; var ping = new Ping(); var ret = new List(); - foreach (var timestamp in Enumerable.Range(0, Repeat).Select(_ => DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))) + foreach ( + var timestamp in Enumerable.Range(0, Repeat).Select(_ => DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))) { //ICMP echo. we can also set options and special bytes try @@ -108,7 +119,7 @@ namespace Shadowsocks.Controller //new KeyValuePair("data", reply.Buffer.ToString()); // The data of reply }); } - catch (PingException e) + catch (Exception e) { Logging.LogUsefulException(e); } @@ -138,11 +149,11 @@ namespace Shadowsocks.Controller if (!File.Exists(AvailabilityStatisticsFile)) { var headerLine = string.Join(Delimiter, data.Select(kv => kv.Key).ToArray()); - lines = new[] { headerLine, dataLine }; + lines = new[] {headerLine, dataLine}; } else { - lines = new[] { dataLine }; + lines = new[] {dataLine}; } File.AppendAllLines(AvailabilityStatisticsFile, lines); } diff --git a/shadowsocks-csharp/Controller/Service/Listener.cs b/shadowsocks-csharp/Controller/Service/Listener.cs index f14bc79..fe59fe9 100644 --- a/shadowsocks-csharp/Controller/Service/Listener.cs +++ b/shadowsocks-csharp/Controller/Service/Listener.cs @@ -77,7 +77,6 @@ namespace Shadowsocks.Controller _udpSocket.Bind(localEndPoint); _tcpSocket.Listen(1024); - // Start an asynchronous socket to listen for connections. Console.WriteLine("Shadowsocks started"); _tcpSocket.BeginAccept( @@ -185,7 +184,6 @@ namespace Shadowsocks.Controller } } - private void ReceiveCallback(IAsyncResult ar) { object[] state = (object[])ar.AsyncState; diff --git a/shadowsocks-csharp/Controller/Service/PACServer.cs b/shadowsocks-csharp/Controller/Service/PACServer.cs index 038ead8..f8fc80e 100644 --- a/shadowsocks-csharp/Controller/Service/PACServer.cs +++ b/shadowsocks-csharp/Controller/Service/PACServer.cs @@ -86,7 +86,6 @@ namespace Shadowsocks.Controller } } - public string TouchPACFile() { if (File.Exists(PAC_FILE)) @@ -146,7 +145,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 261436b..5b784bc 100644 --- a/shadowsocks-csharp/Controller/Service/PolipoRunner.cs +++ b/shadowsocks-csharp/Controller/Service/PolipoRunner.cs @@ -9,6 +9,7 @@ using System.Text; using System.Net.NetworkInformation; using System.Net; using System.Runtime.InteropServices; +using Shadowsocks.Util; namespace Shadowsocks.Controller { @@ -20,7 +21,7 @@ namespace Shadowsocks.Controller static PolipoRunner() { - temppath = Path.GetTempPath(); + temppath = Utils.GetTempPath(); try { FileManager.UncompressFile(temppath + "/ss_privoxy.exe", Resources.privoxy_exe); @@ -65,10 +66,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; @@ -144,7 +148,6 @@ namespace Shadowsocks.Controller [DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam); - public void RefreshTrayArea() { IntPtr systemTrayContainerHandle = FindWindow("Shell_TrayWnd", null); @@ -161,7 +164,6 @@ namespace Shadowsocks.Controller RefreshTrayArea(notificationAreaHandle); } - private static void RefreshTrayArea(IntPtr windowHandle) { const uint wmMousemove = 0x0200; diff --git a/shadowsocks-csharp/Controller/Service/PortForwarder.cs b/shadowsocks-csharp/Controller/Service/PortForwarder.cs index e0b05ae..6384807 100644 --- a/shadowsocks-csharp/Controller/Service/PortForwarder.cs +++ b/shadowsocks-csharp/Controller/Service/PortForwarder.cs @@ -103,7 +103,6 @@ namespace Shadowsocks.Controller } } - private void StartPipe(IAsyncResult ar) { if (_closed) diff --git a/shadowsocks-csharp/Controller/Service/TCPRelay.cs b/shadowsocks-csharp/Controller/Service/TCPRelay.cs index 5c9de44..bf0497d 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(900)) + { + 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; @@ -74,7 +111,7 @@ namespace Shadowsocks.Controller private bool connectionShutdown = false; private bool remoteShutdown = false; private bool closed = false; - + private object encryptionLock = new object(); private object decryptionLock = new object(); @@ -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) @@ -152,7 +195,6 @@ namespace Shadowsocks.Controller } } - private void HandshakeReceive() { if (closed) @@ -222,7 +264,7 @@ namespace Shadowsocks.Controller try { int bytesRead = connection.EndReceive(ar); - + if (bytesRead >= 3) { command = connetionRecvBuffer[1]; @@ -272,7 +314,6 @@ namespace Shadowsocks.Controller private void ReadAll(IAsyncResult ar) { - if (closed) { return; @@ -347,7 +388,6 @@ namespace Shadowsocks.Controller } IPEndPoint remoteEP = new IPEndPoint(ipAddress, server.server_port); - remote = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); @@ -485,6 +525,7 @@ namespace Shadowsocks.Controller if (bytesRead > 0) { + this.lastActivity = DateTime.Now; int bytesToSend; lock (decryptionLock) { @@ -548,7 +589,6 @@ namespace Shadowsocks.Controller } remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeRemoteSendCallback), null); - IStrategy strategy = controller.GetCurrentStrategy(); if (strategy != null) { @@ -607,5 +647,4 @@ namespace Shadowsocks.Controller } } } - } diff --git a/shadowsocks-csharp/Controller/Service/UDPRelay.cs b/shadowsocks-csharp/Controller/Service/UDPRelay.cs index 83a8c5b..a0662dd 100644 --- a/shadowsocks-csharp/Controller/Service/UDPRelay.cs +++ b/shadowsocks-csharp/Controller/Service/UDPRelay.cs @@ -108,9 +108,11 @@ namespace Shadowsocks.Controller } catch (ObjectDisposedException) { + // TODO: handle the ObjectDisposedException } catch (Exception) { + // TODO: need more think about handle other Exceptions, or should remove this catch(). } finally { @@ -124,9 +126,11 @@ namespace Shadowsocks.Controller } catch (ObjectDisposedException) { + // TODO: handle the ObjectDisposedException } catch (Exception) { + // TODO: need more think about handle other Exceptions, or should remove this catch(). } finally { @@ -134,6 +138,8 @@ namespace Shadowsocks.Controller } } } + + // cc by-sa 3.0 http://stackoverflow.com/a/3719378/1124054 class LRUCache where V : UDPRelay.UDPHandler { @@ -196,5 +202,4 @@ namespace Shadowsocks.Controller public K key; public V value; } - } diff --git a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs index c6f6512..b2ef20d 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.6"; public void CheckUpdate(Configuration config) { @@ -53,7 +53,6 @@ namespace Shadowsocks.Controller { return CompareVersion(ParseVersionFromURL(x), ParseVersionFromURL(y)); } - } private static string ParseVersionFromURL(string url) diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index 3c05ac4..50c56b7 100755 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -39,7 +39,7 @@ namespace Shadowsocks.Controller public event EventHandler EnableStatusChanged; public event EventHandler EnableGlobalChanged; public event EventHandler ShareOverLANStatusChanged; - + // when user clicked Edit PAC, and PAC file has already created public event EventHandler PACFileReadyToOpen; public event EventHandler UserRuleFileReadyToOpen; @@ -365,17 +365,15 @@ namespace Shadowsocks.Controller } UpdateSystemProxy(); - Util.Utils.ReleaseMemory(); + Util.Utils.ReleaseMemory(true); } - protected void SaveConfig(Configuration newConfig) { Configuration.Save(newConfig); Reload(); } - private void UpdateSystemProxy() { if (_config.enabled) @@ -422,7 +420,7 @@ namespace Shadowsocks.Controller { while (true) { - Util.Utils.ReleaseMemory(); + Util.Utils.ReleaseMemory(false); Thread.Sleep(30 * 1000); } } diff --git a/shadowsocks-csharp/Controller/Strategy/IStrategy.cs b/shadowsocks-csharp/Controller/Strategy/IStrategy.cs index ca3036f..36962e4 100644 --- a/shadowsocks-csharp/Controller/Strategy/IStrategy.cs +++ b/shadowsocks-csharp/Controller/Strategy/IStrategy.cs @@ -14,17 +14,17 @@ namespace Shadowsocks.Controller.Strategy /* * IStrategy - * + * * Subclasses must be thread-safe */ public interface IStrategy { string Name { get; } - + string ID { get; } /* - * Called when servers need to be reloaded, i.e. new configuration saved + * Called when servers need to be reloaded, i.e. new configuration saved */ void ReloadServers(); diff --git a/shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs b/shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs index e0c0fc9..17bda19 100644 --- a/shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs +++ b/shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; +using System.Text; +using Shadowsocks.Model; +using System.IO; using System.Net.NetworkInformation; using System.Threading; using Shadowsocks.Model; @@ -18,15 +21,6 @@ namespace Shadowsocks.Controller.Strategy private const int CachedInterval = 30*60*1000; //choose a new server every 30 minutes private const int RetryInterval = 2*60*1000; //choose a new server every 30 minutes - public class StatisticsData - { - public int SuccessTimes; - public int TimedOutTimes; - public int AverageResponse; - public int MinResponse; - public int MaxResponse; - } - public StatisticsStrategy(ShadowsocksController controller) { _controller = controller; @@ -99,6 +93,15 @@ namespace Shadowsocks.Controller.Strategy return (double)data.SuccessTimes / (data.SuccessTimes + data.TimedOutTimes); //simply choose min package loss } + private class StatisticsData + { + public int SuccessTimes; + public int TimedOutTimes; + public int AverageResponse; + public int MinResponse; + public int MaxResponse; + } + private void ChooseNewServer(List servers) { if (_statistics == null || servers.Count == 0) @@ -118,7 +121,7 @@ namespace Shadowsocks.Controller.Strategy ).Aggregate((result1, result2) => result1.score > result2.score ? result1 : result2); if (!_currentServer.Equals(bestResult.server)) //output when enabled - { + { LogWhenEnabled($"Switch to server: {bestResult.server.FriendlyName()} by package loss:{1 - bestResult.score}"); } _currentServer = bestResult.server; @@ -137,7 +140,7 @@ namespace Shadowsocks.Controller.Strategy } } - public string ID => "com.shadowsocks.strategy.statistics"; + public string ID => "com.shadowsocks.strategy.scbs"; public string Name => I18N.GetString("Choose By Total Package Loss"); @@ -148,6 +151,9 @@ namespace Shadowsocks.Controller.Strategy { ChooseNewServer(_controller.GetCurrentConfiguration().configs); } + if (oldServer != _currentServer) + { + } return _currentServer; //current server cached for CachedInterval } @@ -176,5 +182,6 @@ namespace Shadowsocks.Controller.Strategy { //TODO: combine this part of data with ICMP statics } + } } diff --git a/shadowsocks-csharp/Controller/System/SystemProxy.cs b/shadowsocks-csharp/Controller/System/SystemProxy.cs index 84a26d0..74e4beb 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 b4c7a6e..81022f9 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,19 @@ Remarks=备注 OK=确定 Cancel=取消 New server=未配置的服务器 +Move &Up=上移(&U) +Move D&own=下移(&O) + +# Log Form + +&File=文件(&F) +&Open Location=在资源管理器中打开(&O) +E&xit=退出(&X) +&Clean logs=清空(&C) +&Font=字体(&F) +&Wrap text=自动换行(&W) +&Top most=置顶(&T) +Log Viewer=日志查看器 # QRCode Form @@ -72,7 +86,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/IVEncryptor.cs b/shadowsocks-csharp/Encryption/IVEncryptor.cs index 0a59a0d..32948d6 100755 --- a/shadowsocks-csharp/Encryption/IVEncryptor.cs +++ b/shadowsocks-csharp/Encryption/IVEncryptor.cs @@ -26,7 +26,6 @@ namespace Shadowsocks.Encryption protected int keyLen; protected int ivLen; - public IVEncryptor(string method, string password) : base(method, password) { diff --git a/shadowsocks-csharp/Encryption/PolarSSL.cs b/shadowsocks-csharp/Encryption/PolarSSL.cs index 93b662a..42ce5bf 100755 --- a/shadowsocks-csharp/Encryption/PolarSSL.cs +++ b/shadowsocks-csharp/Encryption/PolarSSL.cs @@ -1,5 +1,6 @@ using Shadowsocks.Controller; using Shadowsocks.Properties; +using Shadowsocks.Util; using System; using System.Collections.Generic; using System.IO; @@ -18,7 +19,7 @@ namespace Shadowsocks.Encryption static PolarSSL() { - string tempPath = Path.GetTempPath(); + string tempPath = Utils.GetTempPath(); string dllPath = tempPath + "/libsscrypto.dll"; try { @@ -49,7 +50,6 @@ namespace Shadowsocks.Encryption [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] public extern static int aes_crypt_cfb128(IntPtr ctx, int mode, int length, ref int iv_off, byte[] iv, byte[] input, byte[] output); - public const int ARC4_CTX_SIZE = 264; [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] diff --git a/shadowsocks-csharp/Encryption/Sodium.cs b/shadowsocks-csharp/Encryption/Sodium.cs index 9bc72d3..564aaed 100755 --- a/shadowsocks-csharp/Encryption/Sodium.cs +++ b/shadowsocks-csharp/Encryption/Sodium.cs @@ -1,5 +1,6 @@ using Shadowsocks.Controller; using Shadowsocks.Properties; +using Shadowsocks.Util; using System; using System.Collections.Generic; using System.IO; @@ -14,7 +15,7 @@ namespace Shadowsocks.Encryption static Sodium() { - string tempPath = Path.GetTempPath(); + string tempPath = Utils.GetTempPath(); string dllPath = tempPath + "/libsscrypto.dll"; try { diff --git a/shadowsocks-csharp/Encryption/SodiumEncryptor.cs b/shadowsocks-csharp/Encryption/SodiumEncryptor.cs index 5d6165e..af51d0a 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/Encryption/TableEncryptor.cs b/shadowsocks-csharp/Encryption/TableEncryptor.cs index 3dd526d..db0a7db 100644 --- a/shadowsocks-csharp/Encryption/TableEncryptor.cs +++ b/shadowsocks-csharp/Encryption/TableEncryptor.cs @@ -41,7 +41,6 @@ namespace Shadowsocks.Encryption outlength = length; } - public override void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength) { byte[] result = new byte[length]; @@ -100,7 +99,6 @@ namespace Shadowsocks.Encryption return sorted; } - public override void Dispose() { } diff --git a/shadowsocks-csharp/Program.cs b/shadowsocks-csharp/Program.cs index 8ca485d..cb40cb2 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 15463a3..1858ee9 100755 --- a/shadowsocks-csharp/Util/Util.cs +++ b/shadowsocks-csharp/Util/Util.cs @@ -5,12 +5,31 @@ using System.IO; using System.IO.Compression; using System.Runtime.InteropServices; using System.Text; +using System.Windows.Forms; namespace Shadowsocks.Util { public class Utils { - public static void ReleaseMemory() + // return path to store temporary files + public static string GetTempPath() + { + if (File.Exists(Application.StartupPath + "\\shadowsocks_portable_mode.txt")) + { + try + { + Directory.CreateDirectory(Application.StartupPath + "\\temp"); + } catch (Exception e) + { + Console.WriteLine(e); + } + // don't use "/", it will fail when we call explorer /select xxx/temp\xxx.log + return Application.StartupPath + "\\temp"; + } + return Path.GetTempPath(); + } + + public static void ReleaseMemory(bool removePages) { // release any unused pages // making the numbers look good in task manager @@ -20,8 +39,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 83dae64..f8b5940 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 675aa23..d7d5c1f 100755 --- a/shadowsocks-csharp/View/ConfigForm.cs +++ b/shadowsocks-csharp/View/ConfigForm.cs @@ -18,7 +18,7 @@ namespace Shadowsocks.View // this is a copy of configuration that we are working on private Configuration _modifiedConfiguration; - private int _oldSelectedIndex = -1; + private int _lastSelectedIndex = -1; public ConfigForm(ShadowsocksController controller) { @@ -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; @@ -70,7 +72,7 @@ namespace Shadowsocks.View { try { - if (_oldSelectedIndex == -1 || _oldSelectedIndex >= _modifiedConfiguration.configs.Count) + if (_lastSelectedIndex == -1 || _lastSelectedIndex >= _modifiedConfiguration.configs.Count) { return true; } @@ -85,9 +87,9 @@ namespace Shadowsocks.View int localPort = int.Parse(ProxyPortTextBox.Text); Configuration.CheckServer(server); Configuration.CheckLocalPort(localPort); - _modifiedConfiguration.configs[_oldSelectedIndex] = server; + _modifiedConfiguration.configs[_lastSelectedIndex] = 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; } } @@ -135,12 +131,13 @@ namespace Shadowsocks.View { _modifiedConfiguration = controller.GetConfigurationCopy(); LoadConfiguration(_modifiedConfiguration); - _oldSelectedIndex = _modifiedConfiguration.index; - if (_oldSelectedIndex < 0) + _lastSelectedIndex = _modifiedConfiguration.index; + if (_lastSelectedIndex < 0) { - _oldSelectedIndex = 0; + _lastSelectedIndex = 0; } - ServersListBox.SelectedIndex = _oldSelectedIndex; + ServersListBox.SelectedIndex = _lastSelectedIndex; + UpdateMoveUpAndDownButton(); LoadSelectedServer(); } @@ -151,7 +148,11 @@ namespace Shadowsocks.View private void ServersListBox_SelectedIndexChanged(object sender, EventArgs e) { - if (_oldSelectedIndex == ServersListBox.SelectedIndex) + if (!ServersListBox.CanSelect) + { + return; + } + if (_lastSelectedIndex == ServersListBox.SelectedIndex) { // we are moving back to oldSelectedIndex or doing a force move return; @@ -159,11 +160,13 @@ namespace Shadowsocks.View if (!SaveOldSelectedServer()) { // why this won't cause stack overflow? - ServersListBox.SelectedIndex = _oldSelectedIndex; + ServersListBox.SelectedIndex = _lastSelectedIndex; return; } + ServersListBox.Items[_lastSelectedIndex] = _modifiedConfiguration.configs[_lastSelectedIndex].FriendlyName(); + UpdateMoveUpAndDownButton(); LoadSelectedServer(); - _oldSelectedIndex = ServersListBox.SelectedIndex; + _lastSelectedIndex = ServersListBox.SelectedIndex; } private void AddButton_Click(object sender, EventArgs e) @@ -176,29 +179,30 @@ namespace Shadowsocks.View _modifiedConfiguration.configs.Add(server); LoadConfiguration(_modifiedConfiguration); ServersListBox.SelectedIndex = _modifiedConfiguration.configs.Count - 1; - _oldSelectedIndex = ServersListBox.SelectedIndex; + _lastSelectedIndex = ServersListBox.SelectedIndex; } private void DeleteButton_Click(object sender, EventArgs e) { - _oldSelectedIndex = ServersListBox.SelectedIndex; - if (_oldSelectedIndex >= 0 && _oldSelectedIndex < _modifiedConfiguration.configs.Count) + _lastSelectedIndex = ServersListBox.SelectedIndex; + if (_lastSelectedIndex >= 0 && _lastSelectedIndex < _modifiedConfiguration.configs.Count) { - _modifiedConfiguration.configs.RemoveAt(_oldSelectedIndex); + _modifiedConfiguration.configs.RemoveAt(_lastSelectedIndex); } - if (_oldSelectedIndex >= _modifiedConfiguration.configs.Count) + if (_lastSelectedIndex >= _modifiedConfiguration.configs.Count) { // can be -1 - _oldSelectedIndex = _modifiedConfiguration.configs.Count - 1; + _lastSelectedIndex = _modifiedConfiguration.configs.Count - 1; } - ServersListBox.SelectedIndex = _oldSelectedIndex; + ServersListBox.SelectedIndex = _lastSelectedIndex; LoadConfiguration(_modifiedConfiguration); - ServersListBox.SelectedIndex = _oldSelectedIndex; + ServersListBox.SelectedIndex = _lastSelectedIndex; LoadSelectedServer(); } private void OKButton_Click(object sender, EventArgs e) { + Server server = controller.GetCurrentServer(); if (!SaveOldSelectedServer()) { return; @@ -209,6 +213,7 @@ namespace Shadowsocks.View return; } controller.SaveServers(_modifiedConfiguration.configs, _modifiedConfiguration.localPort); + controller.SelectServerIndex(_modifiedConfiguration.configs.IndexOf(server)); this.Close(); } @@ -227,5 +232,70 @@ 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(); + ServersListBox.Enabled = false; + _lastSelectedIndex = index + step; + ServersListBox.Items.Remove(item); + ServersListBox.Items.Insert(index + step, item); + ServersListBox.Enabled = true; + 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 (!SaveOldSelectedServer()) + { + return; + } + if (ServersListBox.SelectedIndex > 0) + { + MoveConfigItem(-1); // -1 means move backward + } + } + + private void MoveDownButton_Click(object sender, EventArgs e) + { + if (!SaveOldSelectedServer()) + { + return; + } + 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 ff31a6d..c7e0d4b 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/LogForm.Designer.cs b/shadowsocks-csharp/View/LogForm.Designer.cs index 6320610..d0913d1 100644 --- a/shadowsocks-csharp/View/LogForm.Designer.cs +++ b/shadowsocks-csharp/View/LogForm.Designer.cs @@ -29,69 +29,143 @@ private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - this.textBox1 = new System.Windows.Forms.TextBox(); + this.LogMessageTextBox = new System.Windows.Forms.TextBox(); this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); this.mainMenu1 = new System.Windows.Forms.MainMenu(this.components); - this.menuItem1 = new System.Windows.Forms.MenuItem(); - this.menuItem2 = new System.Windows.Forms.MenuItem(); - this.menuItem4 = new System.Windows.Forms.MenuItem(); + this.FileMenuItem = new System.Windows.Forms.MenuItem(); + this.OpenLocationMenuItem = new System.Windows.Forms.MenuItem(); + this.ExitMenuItem = new System.Windows.Forms.MenuItem(); + this.panel1 = new System.Windows.Forms.Panel(); + this.ChangeFontButton = new System.Windows.Forms.Button(); + this.CleanLogsButton = new System.Windows.Forms.Button(); + this.WrapTextCheckBox = new System.Windows.Forms.CheckBox(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.TopMostCheckBox = new System.Windows.Forms.CheckBox(); + this.panel1.SuspendLayout(); + this.tableLayoutPanel1.SuspendLayout(); this.SuspendLayout(); // - // textBox1 + // LogMessageTextBox // - this.textBox1.BackColor = System.Drawing.Color.Black; - this.textBox1.Dock = System.Windows.Forms.DockStyle.Fill; - this.textBox1.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.textBox1.ForeColor = System.Drawing.Color.White; - this.textBox1.Location = new System.Drawing.Point(0, 0); - this.textBox1.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); - this.textBox1.MaxLength = 2147483647; - this.textBox1.Multiline = true; - this.textBox1.Name = "textBox1"; - this.textBox1.ReadOnly = true; - this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.textBox1.Size = new System.Drawing.Size(820, 529); - this.textBox1.TabIndex = 0; - this.textBox1.WordWrap = false; + this.LogMessageTextBox.BackColor = System.Drawing.Color.Black; + 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, 43); + 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(541, 307); + this.LogMessageTextBox.TabIndex = 0; + this.LogMessageTextBox.WordWrap = false; // // contextMenuStrip1 // - this.contextMenuStrip1.ImageScalingSize = new System.Drawing.Size(24, 24); this.contextMenuStrip1.Name = "contextMenuStrip1"; - this.contextMenuStrip1.Size = new System.Drawing.Size(74, 4); + this.contextMenuStrip1.Size = new System.Drawing.Size(61, 4); // // mainMenu1 // this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { - this.menuItem1}); + this.FileMenuItem}); // - // menuItem1 + // FileMenuItem // - this.menuItem1.Index = 0; - this.menuItem1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { - this.menuItem2, - this.menuItem4}); - this.menuItem1.Text = "&File"; + this.FileMenuItem.Index = 0; + this.FileMenuItem.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { + this.OpenLocationMenuItem, + this.ExitMenuItem}); + this.FileMenuItem.Text = "&File"; // - // menuItem2 + // OpenLocationMenuItem // - this.menuItem2.Index = 0; - this.menuItem2.Text = "&Open Location"; - this.menuItem2.Click += new System.EventHandler(this.menuItem2_Click); + this.OpenLocationMenuItem.Index = 0; + this.OpenLocationMenuItem.Text = "&Open Location"; + this.OpenLocationMenuItem.Click += new System.EventHandler(this.OpenLocationMenuItem_Click); // - // menuItem4 + // ExitMenuItem // - this.menuItem4.Index = 1; - this.menuItem4.Text = "E&xit"; - this.menuItem4.Click += new System.EventHandler(this.menuItem4_Click); + this.ExitMenuItem.Index = 1; + this.ExitMenuItem.Text = "E&xit"; + this.ExitMenuItem.Click += new System.EventHandler(this.ExitMenuItem_Click); + // + // panel1 + // + this.panel1.Controls.Add(this.TopMostCheckBox); + this.panel1.Controls.Add(this.ChangeFontButton); + this.panel1.Controls.Add(this.CleanLogsButton); + this.panel1.Controls.Add(this.WrapTextCheckBox); + this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel1.Location = new System.Drawing.Point(3, 3); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(541, 34); + this.panel1.TabIndex = 1; + // + // ChangeFontButton + // + this.ChangeFontButton.Location = new System.Drawing.Point(107, 4); + this.ChangeFontButton.Name = "ChangeFontButton"; + this.ChangeFontButton.Size = new System.Drawing.Size(75, 23); + this.ChangeFontButton.TabIndex = 2; + this.ChangeFontButton.Text = "&Font"; + this.ChangeFontButton.UseVisualStyleBackColor = true; + this.ChangeFontButton.Click += new System.EventHandler(this.ChangeFontButton_Click); + // + // CleanLogsButton + // + this.CleanLogsButton.Location = new System.Drawing.Point(9, 4); + this.CleanLogsButton.Name = "CleanLogsButton"; + this.CleanLogsButton.Size = new System.Drawing.Size(75, 23); + this.CleanLogsButton.TabIndex = 1; + this.CleanLogsButton.Text = "&Clean logs"; + this.CleanLogsButton.UseVisualStyleBackColor = true; + this.CleanLogsButton.Click += new System.EventHandler(this.CleanLogsButton_Click); + // + // WrapTextCheckBox + // + this.WrapTextCheckBox.AutoSize = true; + this.WrapTextCheckBox.Location = new System.Drawing.Point(209, 9); + this.WrapTextCheckBox.Name = "WrapTextCheckBox"; + this.WrapTextCheckBox.Size = new System.Drawing.Size(78, 16); + this.WrapTextCheckBox.TabIndex = 0; + this.WrapTextCheckBox.Text = "&Wrap text"; + this.WrapTextCheckBox.UseVisualStyleBackColor = true; + this.WrapTextCheckBox.CheckedChanged += new System.EventHandler(this.WrapTextCheckBox_CheckedChanged); + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 1; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.panel1, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.LogMessageTextBox, 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(System.Windows.Forms.SizeType.Absolute, 40F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.Size = new System.Drawing.Size(547, 353); + this.tableLayoutPanel1.TabIndex = 2; + // + // TopMostCheckBox + // + this.TopMostCheckBox.AutoSize = true; + this.TopMostCheckBox.Location = new System.Drawing.Point(311, 9); + this.TopMostCheckBox.Name = "TopMostCheckBox"; + this.TopMostCheckBox.Size = new System.Drawing.Size(72, 16); + this.TopMostCheckBox.TabIndex = 3; + this.TopMostCheckBox.Text = "&Top most"; + this.TopMostCheckBox.UseVisualStyleBackColor = true; + this.TopMostCheckBox.CheckedChanged += new System.EventHandler(this.TopMostCheckBox_CheckedChanged); // // LogForm // - this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(820, 529); - this.Controls.Add(this.textBox1); - this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.ClientSize = new System.Drawing.Size(547, 353); + this.Controls.Add(this.tableLayoutPanel1); this.Menu = this.mainMenu1; this.Name = "LogForm"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; @@ -99,18 +173,27 @@ this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.LogForm_FormClosing); this.Load += new System.EventHandler(this.LogForm_Load); this.Shown += new System.EventHandler(this.LogForm_Shown); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); this.ResumeLayout(false); - this.PerformLayout(); } #endregion - private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.TextBox LogMessageTextBox; private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; private System.Windows.Forms.MainMenu mainMenu1; - private System.Windows.Forms.MenuItem menuItem1; - private System.Windows.Forms.MenuItem menuItem2; - private System.Windows.Forms.MenuItem menuItem4; + private System.Windows.Forms.MenuItem FileMenuItem; + private System.Windows.Forms.MenuItem OpenLocationMenuItem; + private System.Windows.Forms.MenuItem ExitMenuItem; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.CheckBox WrapTextCheckBox; + private System.Windows.Forms.Button CleanLogsButton; + private System.Windows.Forms.Button ChangeFontButton; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.CheckBox TopMostCheckBox; } } \ No newline at end of file diff --git a/shadowsocks-csharp/View/LogForm.cs b/shadowsocks-csharp/View/LogForm.cs index bdb9ad5..c4fcaf5 100644 --- a/shadowsocks-csharp/View/LogForm.cs +++ b/shadowsocks-csharp/View/LogForm.cs @@ -1,4 +1,5 @@ -using Shadowsocks.Properties; +using Shadowsocks.Controller; +using Shadowsocks.Properties; using System; using System.Collections.Generic; using System.ComponentModel; @@ -23,6 +24,20 @@ namespace Shadowsocks.View this.filename = filename; InitializeComponent(); this.Icon = Icon.FromHandle(Resources.ssw128.GetHicon()); + + UpdateTexts(); + } + + private void UpdateTexts() + { + FileMenuItem.Text = I18N.GetString("&File"); + OpenLocationMenuItem.Text = I18N.GetString("&Open Location"); + ExitMenuItem.Text = I18N.GetString("E&xit"); + CleanLogsButton.Text = I18N.GetString("&Clean logs"); + ChangeFontButton.Text = I18N.GetString("&Font"); + WrapTextCheckBox.Text = I18N.GetString("&Wrap text"); + TopMostCheckBox.Text = I18N.GetString("&Top most"); + this.Text = I18N.GetString("Log Viewer"); } private void Timer_Tick(object sender, EventArgs e) @@ -43,9 +58,9 @@ namespace Shadowsocks.View string line = ""; while ((line = reader.ReadLine()) != null) - textBox1.AppendText(line + "\r\n"); + LogMessageTextBox.AppendText(line + "\r\n"); - textBox1.ScrollToCaret(); + LogMessageTextBox.ScrollToCaret(); lastOffset = reader.BaseStream.Position; } @@ -63,12 +78,12 @@ namespace Shadowsocks.View while ((line = reader.ReadLine()) != null) { changed = true; - textBox1.AppendText(line + "\r\n"); + LogMessageTextBox.AppendText(line + "\r\n"); } if (changed) { - textBox1.ScrollToCaret(); + LogMessageTextBox.ScrollToCaret(); } lastOffset = reader.BaseStream.Position; @@ -89,25 +104,47 @@ namespace Shadowsocks.View timer.Stop(); } - private void menuItem2_Click(object sender, EventArgs e) + private void OpenLocationMenuItem_Click(object sender, EventArgs e) { - string argument = @"/select, " + filename; + string argument = "/select, \"" + filename + "\""; + Console.WriteLine(argument); System.Diagnostics.Process.Start("explorer.exe", argument); } - private void menuItem3_Click(object sender, EventArgs e) - { - - } - - private void menuItem4_Click(object sender, EventArgs e) + private void ExitMenuItem_Click(object sender, EventArgs e) { this.Close(); } private void LogForm_Shown(object sender, EventArgs e) { - textBox1.ScrollToCaret(); + LogMessageTextBox.ScrollToCaret(); + } + + private void WrapTextCheckBox_CheckedChanged(object sender, EventArgs e) + { + LogMessageTextBox.WordWrap = WrapTextCheckBox.Checked; + LogMessageTextBox.ScrollToCaret(); + } + + private void CleanLogsButton_Click(object sender, EventArgs e) + { + LogMessageTextBox.Clear(); + } + + private void ChangeFontButton_Click(object sender, EventArgs e) + { + FontDialog fd = new FontDialog(); + fd.Font = LogMessageTextBox.Font; + if (fd.ShowDialog() == DialogResult.OK) + { + LogMessageTextBox.Font = fd.Font; + } + } + + private void TopMostCheckBox_CheckedChanged(object sender, EventArgs e) + { + this.TopMost = TopMostCheckBox.Checked; } } } diff --git a/shadowsocks-csharp/View/MenuViewController.cs b/shadowsocks-csharp/View/MenuViewController.cs index 02ae335..c8a4c0e 100755 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -18,7 +18,7 @@ namespace Shadowsocks.View // yes this is just a menu view controller // when config form is closed, it moves away from RAM // and it should just do anything related to the config form - + private ShadowsocksController controller; private UpdateChecker updateChecker; @@ -29,6 +29,7 @@ namespace Shadowsocks.View private MenuItem enableItem; private MenuItem modeItem; private MenuItem AutoStartupItem; + private MenuItem AvailabilityStatistics; private MenuItem ShareOverLANItem; private MenuItem SeperatorItem; private MenuItem ConfigItem; @@ -164,7 +165,6 @@ namespace Shadowsocks.View this.SeperatorItem = new MenuItem("-"), this.ConfigItem = CreateMenuItem("Edit Servers...", new EventHandler(this.Config_Click)), CreateMenuItem("Show QRCode...", new EventHandler(this.QRCodeItem_Click)), - CreateMenuItem("Statistics Stategy Options", new EventHandler(StatisticsStrategyOptionsItem_Click)), CreateMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click)) }), CreateMenuGroup("PAC ", new MenuItem[] { @@ -178,6 +178,7 @@ namespace Shadowsocks.View }), new MenuItem("-"), this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)), + this.AvailabilityStatistics = CreateMenuItem("Availability Statistics", new EventHandler(this.AvailabilityStatisticsItem_Click)), this.ShareOverLANItem = CreateMenuItem("Allow Clients from LAN", new EventHandler(this.ShareOverLANItem_Click)), new MenuItem("-"), CreateMenuItem("Show Logs...", new EventHandler(this.ShowLogItem_Click)), @@ -261,6 +262,7 @@ namespace Shadowsocks.View PACModeItem.Checked = !config.global; ShareOverLANItem.Checked = config.shareOverLan; AutoStartupItem.Checked = AutoStartup.Check(); + AvailabilityStatistics.Checked = config.availabilityStatistics; onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; localPACItem.Checked = !onlinePACItem.Checked; UpdatePACItemsEnabledStatus(); @@ -320,7 +322,7 @@ namespace Shadowsocks.View void configForm_FormClosed(object sender, FormClosedEventArgs e) { configForm = null; - Util.Utils.ReleaseMemory(); + Util.Utils.ReleaseMemory(true); ShowFirstTimeBalloon(); } @@ -424,14 +426,6 @@ namespace Shadowsocks.View qrCodeForm.Show(); } - private void StatisticsStrategyOptionsItem_Click(object sender, EventArgs e) - { - //TODO: Load options - var statisticsStrategyOptionsForm = new StatisticsStrategyConfigurationForm(controller); - statisticsStrategyOptionsForm.Show(); - //TODO: Save options - } - private void ScanQRCodeItem_Click(object sender, EventArgs e) { foreach (Screen screen in Screen.AllScreens) @@ -526,12 +520,17 @@ namespace Shadowsocks.View Process.Start(_urlToOpen); } - 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")); - } - } + 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")); + } + } + + private void AvailabilityStatisticsItem_Click(object sender, EventArgs e) { + AvailabilityStatistics.Checked = !AvailabilityStatistics.Checked; + controller.ToggleAvailabilityStatistics(AvailabilityStatistics.Checked); + } private void LocalPACItem_Click(object sender, EventArgs e) { diff --git a/shadowsocks-csharp/View/QRCodeSplashForm.cs b/shadowsocks-csharp/View/QRCodeSplashForm.cs index ac9828e..3bf4888 100755 --- a/shadowsocks-csharp/View/QRCodeSplashForm.cs +++ b/shadowsocks-csharp/View/QRCodeSplashForm.cs @@ -242,7 +242,6 @@ namespace Shadowsocks.View SetBitmap(bitmap, 255); } - /// Changes the current bitmap with a custom opacity level. Here is where all happens! public void SetBitmap(Bitmap bitmap, byte opacity) { @@ -288,7 +287,6 @@ namespace Shadowsocks.View } } - protected override CreateParams CreateParams { get