@@ -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 |
@@ -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<KeyValuePair<string, string>>(); | |||
state.data.Add(new KeyValuePair<string, string>("Timestamp", timestamp)); | |||
state.data.Add(new KeyValuePair<string, string>("Server", server.FriendlyName())); | |||
state.data.Add(new KeyValuePair<string, string>("Status", reply.Status.ToString())); | |||
state.data.Add(new KeyValuePair<string, string>("RoundtripTime", reply.RoundtripTime.ToString())); | |||
//state.data.Add(new KeyValuePair<string, string>("data", reply.Buffer.ToString())); // The data of reply | |||
Append(state.data); | |||
try | |||
{ | |||
PingReply reply = ping.Send(server.server, Timeout); | |||
state.data = new List<KeyValuePair<string, string>>(); | |||
state.data.Add(new KeyValuePair<string, string>("Timestamp", timestamp)); | |||
state.data.Add(new KeyValuePair<string, string>("Server", server.FriendlyName())); | |||
state.data.Add(new KeyValuePair<string, string>("Status", reply.Status.ToString())); | |||
state.data.Add(new KeyValuePair<string, string>("RoundtripTime", reply.RoundtripTime.ToString())); | |||
//state.data.Add(new KeyValuePair<string, string>("data", reply.Buffer.ToString())); // The data of reply | |||
Append(state.data); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
} | |||
} | |||
} | |||
@@ -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) | |||
{ | |||
@@ -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; | |||
@@ -14,9 +14,18 @@ namespace Shadowsocks.Controller | |||
class TCPRelay : Listener.Service | |||
{ | |||
private ShadowsocksController _controller; | |||
private DateTime _lastSweepTime; | |||
public ISet<Handler> Handlers | |||
{ | |||
get; set; | |||
} | |||
public TCPRelay(ShadowsocksController controller) | |||
{ | |||
this._controller = controller; | |||
this.Handlers = new HashSet<Handler>(); | |||
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<Handler> handlersToClose = new List<Handler>(); | |||
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) | |||
{ | |||
@@ -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) | |||
{ | |||
@@ -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); | |||
} | |||
} | |||
@@ -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 | |||
@@ -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} | |||
Running: Port {0}=正在运行:端口 {0} |
@@ -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<string, int[]> _ciphers = new Dictionary<string, int[]> { | |||
@@ -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; | |||
} | |||
} | |||
} | |||
@@ -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); | |||
@@ -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) | |||
@@ -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; | |||
} | |||
} | |||
@@ -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 | |||
} | |||
} | |||
} | |||
} |
@@ -112,9 +112,9 @@ | |||
<value>2.0</value> | |||
</resheader> | |||
<resheader name="reader"> | |||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</resheader> | |||
<resheader name="writer"> | |||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</resheader> | |||
</root> |
@@ -322,7 +322,7 @@ namespace Shadowsocks.View | |||
void configForm_FormClosed(object sender, FormClosedEventArgs e) | |||
{ | |||
configForm = null; | |||
Util.Utils.ReleaseMemory(); | |||
Util.Utils.ReleaseMemory(true); | |||
ShowFirstTimeBalloon(); | |||
} | |||