@@ -1,3 +1,32 @@ | |||
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 | |||
2.5.3 2015-08-16 | |||
- Replace Polipo with Privoxy | |||
- Add Choose by Total Packet Loss | |||
2.5.2 2015-08-04 | |||
- Add log viewer | |||
2.5.1 2015-07-26 | |||
- Prevent HA from switching servers too frequently | |||
- Fix server settings can not be updated when using HA | |||
- Fix server port can't be 8123 | |||
- Other minor fixes | |||
2.5 2015-07-25 | |||
- Support load balance | |||
- Support high availability | |||
2.4 2015-07-11 | |||
- Support UDP relay | |||
- Support online PAC | |||
@@ -20,30 +20,348 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
3rd party projects | |||
================== | |||
polipo | |||
Privoxy | |||
------------------ | |||
https://github.com/jech/polipo | |||
Copyright (c) 2003-2008 by Juliusz Chroboczek | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in | |||
all copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
THE SOFTWARE. | |||
GNU GENERAL PUBLIC LICENSE | |||
Version 2, June 1991 | |||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., | |||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||
Everyone is permitted to copy and distribute verbatim copies | |||
of this license document, but changing it is not allowed. | |||
Preamble | |||
The licenses for most software are designed to take away your | |||
freedom to share and change it. By contrast, the GNU General Public | |||
License is intended to guarantee your freedom to share and change free | |||
software--to make sure the software is free for all its users. This | |||
General Public License applies to most of the Free Software | |||
Foundation's software and to any other program whose authors commit to | |||
using it. (Some other Free Software Foundation software is covered by | |||
the GNU Lesser General Public License instead.) You can apply it to | |||
your programs, too. | |||
When we speak of free software, we are referring to freedom, not | |||
price. Our General Public Licenses are designed to make sure that you | |||
have the freedom to distribute copies of free software (and charge for | |||
this service if you wish), that you receive source code or can get it | |||
if you want it, that you can change the software or use pieces of it | |||
in new free programs; and that you know you can do these things. | |||
To protect your rights, we need to make restrictions that forbid | |||
anyone to deny you these rights or to ask you to surrender the rights. | |||
These restrictions translate to certain responsibilities for you if you | |||
distribute copies of the software, or if you modify it. | |||
For example, if you distribute copies of such a program, whether | |||
gratis or for a fee, you must give the recipients all the rights that | |||
you have. You must make sure that they, too, receive or can get the | |||
source code. And you must show them these terms so they know their | |||
rights. | |||
We protect your rights with two steps: (1) copyright the software, and | |||
(2) offer you this license which gives you legal permission to copy, | |||
distribute and/or modify the software. | |||
Also, for each author's protection and ours, we want to make certain | |||
that everyone understands that there is no warranty for this free | |||
software. If the software is modified by someone else and passed on, we | |||
want its recipients to know that what they have is not the original, so | |||
that any problems introduced by others will not reflect on the original | |||
authors' reputations. | |||
Finally, any free program is threatened constantly by software | |||
patents. We wish to avoid the danger that redistributors of a free | |||
program will individually obtain patent licenses, in effect making the | |||
program proprietary. To prevent this, we have made it clear that any | |||
patent must be licensed for everyone's free use or not licensed at all. | |||
The precise terms and conditions for copying, distribution and | |||
modification follow. | |||
GNU GENERAL PUBLIC LICENSE | |||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |||
0. This License applies to any program or other work which contains | |||
a notice placed by the copyright holder saying it may be distributed | |||
under the terms of this General Public License. The "Program", below, | |||
refers to any such program or work, and a "work based on the Program" | |||
means either the Program or any derivative work under copyright law: | |||
that is to say, a work containing the Program or a portion of it, | |||
either verbatim or with modifications and/or translated into another | |||
language. (Hereinafter, translation is included without limitation in | |||
the term "modification".) Each licensee is addressed as "you". | |||
Activities other than copying, distribution and modification are not | |||
covered by this License; they are outside its scope. The act of | |||
running the Program is not restricted, and the output from the Program | |||
is covered only if its contents constitute a work based on the | |||
Program (independent of having been made by running the Program). | |||
Whether that is true depends on what the Program does. | |||
1. You may copy and distribute verbatim copies of the Program's | |||
source code as you receive it, in any medium, provided that you | |||
conspicuously and appropriately publish on each copy an appropriate | |||
copyright notice and disclaimer of warranty; keep intact all the | |||
notices that refer to this License and to the absence of any warranty; | |||
and give any other recipients of the Program a copy of this License | |||
along with the Program. | |||
You may charge a fee for the physical act of transferring a copy, and | |||
you may at your option offer warranty protection in exchange for a fee. | |||
2. You may modify your copy or copies of the Program or any portion | |||
of it, thus forming a work based on the Program, and copy and | |||
distribute such modifications or work under the terms of Section 1 | |||
above, provided that you also meet all of these conditions: | |||
a) You must cause the modified files to carry prominent notices | |||
stating that you changed the files and the date of any change. | |||
b) You must cause any work that you distribute or publish, that in | |||
whole or in part contains or is derived from the Program or any | |||
part thereof, to be licensed as a whole at no charge to all third | |||
parties under the terms of this License. | |||
c) If the modified program normally reads commands interactively | |||
when run, you must cause it, when started running for such | |||
interactive use in the most ordinary way, to print or display an | |||
announcement including an appropriate copyright notice and a | |||
notice that there is no warranty (or else, saying that you provide | |||
a warranty) and that users may redistribute the program under | |||
these conditions, and telling the user how to view a copy of this | |||
License. (Exception: if the Program itself is interactive but | |||
does not normally print such an announcement, your work based on | |||
the Program is not required to print an announcement.) | |||
These requirements apply to the modified work as a whole. If | |||
identifiable sections of that work are not derived from the Program, | |||
and can be reasonably considered independent and separate works in | |||
themselves, then this License, and its terms, do not apply to those | |||
sections when you distribute them as separate works. But when you | |||
distribute the same sections as part of a whole which is a work based | |||
on the Program, the distribution of the whole must be on the terms of | |||
this License, whose permissions for other licensees extend to the | |||
entire whole, and thus to each and every part regardless of who wrote it. | |||
Thus, it is not the intent of this section to claim rights or contest | |||
your rights to work written entirely by you; rather, the intent is to | |||
exercise the right to control the distribution of derivative or | |||
collective works based on the Program. | |||
In addition, mere aggregation of another work not based on the Program | |||
with the Program (or with a work based on the Program) on a volume of | |||
a storage or distribution medium does not bring the other work under | |||
the scope of this License. | |||
3. You may copy and distribute the Program (or a work based on it, | |||
under Section 2) in object code or executable form under the terms of | |||
Sections 1 and 2 above provided that you also do one of the following: | |||
a) Accompany it with the complete corresponding machine-readable | |||
source code, which must be distributed under the terms of Sections | |||
1 and 2 above on a medium customarily used for software interchange; or, | |||
b) Accompany it with a written offer, valid for at least three | |||
years, to give any third party, for a charge no more than your | |||
cost of physically performing source distribution, a complete | |||
machine-readable copy of the corresponding source code, to be | |||
distributed under the terms of Sections 1 and 2 above on a medium | |||
customarily used for software interchange; or, | |||
c) Accompany it with the information you received as to the offer | |||
to distribute corresponding source code. (This alternative is | |||
allowed only for noncommercial distribution and only if you | |||
received the program in object code or executable form with such | |||
an offer, in accord with Subsection b above.) | |||
The source code for a work means the preferred form of the work for | |||
making modifications to it. For an executable work, complete source | |||
code means all the source code for all modules it contains, plus any | |||
associated interface definition files, plus the scripts used to | |||
control compilation and installation of the executable. However, as a | |||
special exception, the source code distributed need not include | |||
anything that is normally distributed (in either source or binary | |||
form) with the major components (compiler, kernel, and so on) of the | |||
operating system on which the executable runs, unless that component | |||
itself accompanies the executable. | |||
If distribution of executable or object code is made by offering | |||
access to copy from a designated place, then offering equivalent | |||
access to copy the source code from the same place counts as | |||
distribution of the source code, even though third parties are not | |||
compelled to copy the source along with the object code. | |||
4. You may not copy, modify, sublicense, or distribute the Program | |||
except as expressly provided under this License. Any attempt | |||
otherwise to copy, modify, sublicense or distribute the Program is | |||
void, and will automatically terminate your rights under this License. | |||
However, parties who have received copies, or rights, from you under | |||
this License will not have their licenses terminated so long as such | |||
parties remain in full compliance. | |||
5. You are not required to accept this License, since you have not | |||
signed it. However, nothing else grants you permission to modify or | |||
distribute the Program or its derivative works. These actions are | |||
prohibited by law if you do not accept this License. Therefore, by | |||
modifying or distributing the Program (or any work based on the | |||
Program), you indicate your acceptance of this License to do so, and | |||
all its terms and conditions for copying, distributing or modifying | |||
the Program or works based on it. | |||
6. Each time you redistribute the Program (or any work based on the | |||
Program), the recipient automatically receives a license from the | |||
original licensor to copy, distribute or modify the Program subject to | |||
these terms and conditions. You may not impose any further | |||
restrictions on the recipients' exercise of the rights granted herein. | |||
You are not responsible for enforcing compliance by third parties to | |||
this License. | |||
7. If, as a consequence of a court judgment or allegation of patent | |||
infringement or for any other reason (not limited to patent issues), | |||
conditions are imposed on you (whether by court order, agreement or | |||
otherwise) that contradict the conditions of this License, they do not | |||
excuse you from the conditions of this License. If you cannot | |||
distribute so as to satisfy simultaneously your obligations under this | |||
License and any other pertinent obligations, then as a consequence you | |||
may not distribute the Program at all. For example, if a patent | |||
license would not permit royalty-free redistribution of the Program by | |||
all those who receive copies directly or indirectly through you, then | |||
the only way you could satisfy both it and this License would be to | |||
refrain entirely from distribution of the Program. | |||
If any portion of this section is held invalid or unenforceable under | |||
any particular circumstance, the balance of the section is intended to | |||
apply and the section as a whole is intended to apply in other | |||
circumstances. | |||
It is not the purpose of this section to induce you to infringe any | |||
patents or other property right claims or to contest validity of any | |||
such claims; this section has the sole purpose of protecting the | |||
integrity of the free software distribution system, which is | |||
implemented by public license practices. Many people have made | |||
generous contributions to the wide range of software distributed | |||
through that system in reliance on consistent application of that | |||
system; it is up to the author/donor to decide if he or she is willing | |||
to distribute software through any other system and a licensee cannot | |||
impose that choice. | |||
This section is intended to make thoroughly clear what is believed to | |||
be a consequence of the rest of this License. | |||
8. If the distribution and/or use of the Program is restricted in | |||
certain countries either by patents or by copyrighted interfaces, the | |||
original copyright holder who places the Program under this License | |||
may add an explicit geographical distribution limitation excluding | |||
those countries, so that distribution is permitted only in or among | |||
countries not thus excluded. In such case, this License incorporates | |||
the limitation as if written in the body of this License. | |||
9. The Free Software Foundation may publish revised and/or new versions | |||
of the General Public License from time to time. Such new versions will | |||
be similar in spirit to the present version, but may differ in detail to | |||
address new problems or concerns. | |||
Each version is given a distinguishing version number. If the Program | |||
specifies a version number of this License which applies to it and "any | |||
later version", you have the option of following the terms and conditions | |||
either of that version or of any later version published by the Free | |||
Software Foundation. If the Program does not specify a version number of | |||
this License, you may choose any version ever published by the Free Software | |||
Foundation. | |||
10. If you wish to incorporate parts of the Program into other free | |||
programs whose distribution conditions are different, write to the author | |||
to ask for permission. For software which is copyrighted by the Free | |||
Software Foundation, write to the Free Software Foundation; we sometimes | |||
make exceptions for this. Our decision will be guided by the two goals | |||
of preserving the free status of all derivatives of our free software and | |||
of promoting the sharing and reuse of software generally. | |||
NO WARRANTY | |||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY | |||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN | |||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES | |||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED | |||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS | |||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE | |||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, | |||
REPAIR OR CORRECTION. | |||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR | |||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, | |||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING | |||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED | |||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY | |||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER | |||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE | |||
POSSIBILITY OF SUCH DAMAGES. | |||
END OF TERMS AND CONDITIONS | |||
How to Apply These Terms to Your New Programs | |||
If you develop a new program, and you want it to be of the greatest | |||
possible use to the public, the best way to achieve this is to make it | |||
free software which everyone can redistribute and change under these terms. | |||
To do so, attach the following notices to the program. It is safest | |||
to attach them to the start of each source file to most effectively | |||
convey the exclusion of warranty; and each file should have at least | |||
the "copyright" line and a pointer to where the full notice is found. | |||
<one line to give the program's name and a brief idea of what it does.> | |||
Copyright (C) <year> <name of author> | |||
This program is free software; you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
the Free Software Foundation; either version 2 of the License, or | |||
(at your option) any later version. | |||
This program is distributed in the hope that it will be useful, | |||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
GNU General Public License for more details. | |||
You should have received a copy of the GNU General Public License along | |||
with this program; if not, write to the Free Software Foundation, Inc., | |||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |||
Also add information on how to contact you by electronic and paper mail. | |||
If the program is interactive, make it output a short notice like this | |||
when it starts in an interactive mode: | |||
Gnomovision version 69, Copyright (C) year name of author | |||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | |||
This is free software, and you are welcome to redistribute it | |||
under certain conditions; type `show c' for details. | |||
The hypothetical commands `show w' and `show c' should show the appropriate | |||
parts of the General Public License. Of course, the commands you use may | |||
be called something other than `show w' and `show c'; they could even be | |||
mouse-clicks or menu items--whatever suits your program. | |||
You should also get your employer (if you work as a programmer) or your | |||
school, if any, to sign a "copyright disclaimer" for the program, if | |||
necessary. Here is a sample; alter the names: | |||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program | |||
`Gnomovision' (which makes passes at compilers) written by James Hacker. | |||
<signature of Ty Coon>, 1 April 1989 | |||
Ty Coon, President of Vice | |||
This General Public License does not permit incorporating your program into | |||
proprietary programs. If your program is a subroutine library, you may | |||
consider it more useful to permit linking proprietary applications with the | |||
library. If this is what you want to do, use the GNU Lesser General | |||
Public License instead of this License. | |||
PolarSSL | |||
@@ -98,7 +416,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
libsodium | |||
--------- | |||
@@ -3,43 +3,72 @@ Shadowsocks for Windows | |||
[![Build Status]][Appveyor] | |||
[中文说明] | |||
#### Features | |||
1. System proxy configuration | |||
2. Fast profile switching | |||
3. PAC mode and global mode | |||
4. GFWList and user rules | |||
5. Supports HTTP proxy | |||
2. PAC mode and global mode | |||
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]. | |||
For >= Windows 8 or with .Net 4.0, download Shadowsocks-win-dotnet4.0-x.x.x.zip. | |||
Download the [latest release]. | |||
For <= Windows 7 or with .Net 2.0, download Shadowsocks-win-x.x.x.zip. | |||
#### Usage | |||
#### 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 | |||
5. You can change PAC rules by editing the PAC file. When you save the PAC file | |||
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 | |||
6. You can also update the PAC file from GFWList. Note your modifications to the PAC | |||
file will be lost. However you can put your rules in the user rule file for GFWList. | |||
Don't forget to update from GFWList again after you've edited the user rule | |||
7. For UDP, you need to use SocksCap or ProxyCap to force programs you want | |||
to proxy to tunnel over Shadowsocks | |||
2. You can also update PAC file from [GFWList] (maintained by 3rd party) | |||
3. You can also use online PAC URL | |||
#### Server Auto Switching | |||
1. Load balance: choosing server randomly | |||
2. High availability: choosing the best server (low latency and packet loss) | |||
3. Choose By Total Package Loss: ping and choose. Please also enable | |||
`Availability Statistics` in the menu if you want to use this | |||
4. Write your own strategy by implement IStrategy interface and send us a pull request! | |||
#### UDP | |||
For UDP, you need to use SocksCap or ProxyCap to force programs you want | |||
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 | |||
#### Develop | |||
Visual Studio Express 2012 is recommended. | |||
Visual Studio 2015 is required. | |||
#### License | |||
@@ -49,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 |
@@ -12,7 +12,7 @@ namespace Shadowsocks.Controller | |||
static I18N() | |||
{ | |||
Strings = new Dictionary<string, string>(); | |||
if (System.Globalization.CultureInfo.CurrentCulture.IetfLanguageTag.ToLowerInvariant().StartsWith("zh")) | |||
{ | |||
string[] lines = Regex.Split(Resources.cn, "\r\n|\r|\n"); | |||
@@ -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) | |||
@@ -31,6 +32,14 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
public static void Debug(object o) | |||
{ | |||
#if DEBUG | |||
Console.WriteLine(o); | |||
#endif | |||
} | |||
public static void LogUsefulException(Exception e) | |||
{ | |||
// just log useful exceptions, not all of them | |||
@@ -55,12 +64,14 @@ namespace Shadowsocks.Controller | |||
Console.WriteLine(e); | |||
} | |||
} | |||
else if (e is ObjectDisposedException) | |||
{ | |||
} | |||
else | |||
{ | |||
Console.WriteLine(e); | |||
} | |||
} | |||
} | |||
// Simply extended System.IO.StreamWriter for adding timestamp workaround | |||
@@ -0,0 +1,117 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Net.NetworkInformation; | |||
using System.Threading; | |||
using Shadowsocks.Model; | |||
using System.Reflection; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Controller | |||
{ | |||
class AvailabilityStatistics | |||
{ | |||
private static readonly string StatisticsFilesName = "shadowsocks.availability.csv"; | |||
private static readonly string Delimiter = ","; | |||
private static readonly int Timeout = 500; | |||
private static readonly int Repeat = 4; //repeat times every evaluation | |||
private static readonly int Interval = 10 * 60 * 1000; //evaluate proxies every 15 minutes | |||
private Timer timer = null; | |||
private State state = null; | |||
private List<Server> servers; | |||
public static string AvailabilityStatisticsFile; | |||
//static constructor to initialize every public static fields before refereced | |||
static AvailabilityStatistics() | |||
{ | |||
string temppath = Utils.GetTempPath(); | |||
AvailabilityStatisticsFile = Path.Combine(temppath, StatisticsFilesName); | |||
} | |||
public bool Set(bool enabled) | |||
{ | |||
try | |||
{ | |||
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) | |||
{ | |||
Logging.LogUsefulException(e); | |||
return false; | |||
} | |||
} | |||
private void Evaluate(object obj) | |||
{ | |||
Ping ping = new Ping(); | |||
State state = (State) obj; | |||
foreach (var server in servers) | |||
{ | |||
Logging.Debug("eveluating " + server.FriendlyName()); | |||
foreach (var _ in Enumerable.Range(0, Repeat)) | |||
{ | |||
//TODO: do simple analyze of data to provide friendly message, like package loss. | |||
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 | |||
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); | |||
} | |||
} | |||
} | |||
} | |||
private static void Append(List<KeyValuePair<string, string>> data) | |||
{ | |||
string dataLine = string.Join(Delimiter, data.Select(kv => kv.Value).ToArray()); | |||
string[] lines; | |||
if (!File.Exists(AvailabilityStatisticsFile)) | |||
{ | |||
string headerLine = string.Join(Delimiter, data.Select(kv => kv.Key).ToArray()); | |||
lines = new string[] { headerLine, dataLine }; | |||
} | |||
else | |||
{ | |||
lines = new string[] { dataLine }; | |||
} | |||
File.AppendAllLines(AvailabilityStatisticsFile, lines); | |||
} | |||
internal void UpdateConfiguration(Configuration _config) | |||
{ | |||
Set(_config.availabilityStatistics); | |||
servers = _config.configs; | |||
} | |||
private class State | |||
{ | |||
public List<KeyValuePair<string, string>> data = new List<KeyValuePair<string, string>>(); | |||
} | |||
} | |||
} |
@@ -18,7 +18,7 @@ namespace Shadowsocks.Controller | |||
public class UDPState | |||
{ | |||
public byte[] buffer = new byte[4096]; | |||
public EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 1); | |||
public EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); | |||
} | |||
Configuration _config; | |||
@@ -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; |
@@ -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) | |||
{ |
@@ -8,6 +8,8 @@ using System.IO.Compression; | |||
using System.Text; | |||
using System.Net.NetworkInformation; | |||
using System.Net; | |||
using System.Runtime.InteropServices; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Controller | |||
{ | |||
@@ -19,10 +21,11 @@ namespace Shadowsocks.Controller | |||
static PolipoRunner() | |||
{ | |||
temppath = Path.GetTempPath(); | |||
temppath = Utils.GetTempPath(); | |||
try | |||
{ | |||
FileManager.UncompressFile(temppath + "/ss_polipo.exe", Resources.polipo_exe); | |||
FileManager.UncompressFile(temppath + "/ss_privoxy.exe", Resources.privoxy_exe); | |||
FileManager.UncompressFile(temppath + "/mgwz.dll", Resources.mgwz_dll); | |||
} | |||
catch (IOException e) | |||
{ | |||
@@ -43,7 +46,7 @@ namespace Shadowsocks.Controller | |||
Server server = configuration.GetCurrentServer(); | |||
if (_process == null) | |||
{ | |||
Process[] existingPolipo = Process.GetProcessesByName("ss_polipo"); | |||
Process[] existingPolipo = Process.GetProcessesByName("ss_privoxy"); | |||
foreach (Process p in existingPolipo) | |||
{ | |||
try | |||
@@ -56,17 +59,20 @@ namespace Shadowsocks.Controller | |||
Console.WriteLine(e.ToString()); | |||
} | |||
} | |||
string polipoConfig = Resources.polipo_config; | |||
string polipoConfig = Resources.privoxy_conf; | |||
_runningPort = this.GetFreePort(); | |||
polipoConfig = polipoConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString()); | |||
polipoConfig = polipoConfig.Replace("__POLIPO_BIND_PORT__", _runningPort.ToString()); | |||
polipoConfig = polipoConfig.Replace("__POLIPO_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1"); | |||
FileManager.ByteArrayToFile(temppath + "/polipo.conf", System.Text.Encoding.UTF8.GetBytes(polipoConfig)); | |||
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_polipo.exe"; | |||
_process.StartInfo.Arguments = "-c \"" + temppath + "/polipo.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; | |||
@@ -74,6 +80,7 @@ namespace Shadowsocks.Controller | |||
//_process.StartInfo.RedirectStandardError = true; | |||
_process.Start(); | |||
} | |||
RefreshTrayArea(); | |||
} | |||
public void Stop() | |||
@@ -91,6 +98,7 @@ namespace Shadowsocks.Controller | |||
} | |||
_process = null; | |||
} | |||
RefreshTrayArea(); | |||
} | |||
private int GetFreePort() | |||
@@ -122,5 +130,48 @@ namespace Shadowsocks.Controller | |||
} | |||
throw new Exception("No free port found."); | |||
} | |||
[StructLayout(LayoutKind.Sequential)] | |||
public struct RECT | |||
{ | |||
public int left; | |||
public int top; | |||
public int right; | |||
public int bottom; | |||
} | |||
[DllImport("user32.dll")] | |||
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); | |||
[DllImport("user32.dll")] | |||
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); | |||
[DllImport("user32.dll")] | |||
public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); | |||
[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); | |||
IntPtr systemTrayHandle = FindWindowEx(systemTrayContainerHandle, IntPtr.Zero, "TrayNotifyWnd", null); | |||
IntPtr sysPagerHandle = FindWindowEx(systemTrayHandle, IntPtr.Zero, "SysPager", null); | |||
IntPtr notificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32", "Notification Area"); | |||
if (notificationAreaHandle == IntPtr.Zero) | |||
{ | |||
notificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32", "User Promoted Notification Area"); | |||
IntPtr notifyIconOverflowWindowHandle = FindWindow("NotifyIconOverflowWindow", null); | |||
IntPtr overflowNotificationAreaHandle = FindWindowEx(notifyIconOverflowWindowHandle, IntPtr.Zero, "ToolbarWindow32", "Overflow Notification Area"); | |||
RefreshTrayArea(overflowNotificationAreaHandle); | |||
} | |||
RefreshTrayArea(notificationAreaHandle); | |||
} | |||
private static void RefreshTrayArea(IntPtr windowHandle) | |||
{ | |||
const uint wmMousemove = 0x0200; | |||
RECT rect; | |||
GetClientRect(windowHandle, out rect); | |||
for (var x = 0; x < rect.right; x += 5) | |||
for (var y = 0; y < rect.bottom; y += 5) | |||
SendMessage(windowHandle, wmMousemove, 0, (y << 16) + x); | |||
} | |||
} | |||
} |
@@ -103,7 +103,6 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
private void StartPipe(IAsyncResult ar) | |||
{ | |||
if (_closed) |
@@ -5,16 +5,27 @@ using System.Net.Sockets; | |||
using System.Net; | |||
using Shadowsocks.Encryption; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Controller.Strategy; | |||
using System.Timers; | |||
namespace Shadowsocks.Controller | |||
{ | |||
class TCPRelay : Listener.Service | |||
{ | |||
private Configuration _config; | |||
public TCPRelay(Configuration config) | |||
private ShadowsocksController _controller; | |||
private DateTime _lastSweepTime; | |||
public ISet<Handler> Handlers | |||
{ | |||
get; set; | |||
} | |||
public TCPRelay(ShadowsocksController controller) | |||
{ | |||
this._config = config; | |||
this._controller = controller; | |||
this.Handlers = new HashSet<Handler>(); | |||
this._lastSweepTime = DateTime.Now; | |||
} | |||
public bool Handle(byte[] firstPacket, int length, Socket socket, object state) | |||
@@ -30,12 +41,34 @@ namespace Shadowsocks.Controller | |||
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | |||
Handler handler = new Handler(); | |||
handler.connection = socket; | |||
Server server = _config.GetCurrentServer(); | |||
handler.encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | |||
handler.server = server; | |||
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(900)) | |||
{ | |||
handlersToClose.Add(handler1); | |||
} | |||
} | |||
} | |||
} | |||
foreach (Handler handler1 in handlersToClose) | |||
{ | |||
Logging.Debug("Closing timed out connection"); | |||
handler1.Close(); | |||
} | |||
return true; | |||
} | |||
} | |||
@@ -47,13 +80,24 @@ namespace Shadowsocks.Controller | |||
// Client socket. | |||
public Socket remote; | |||
public Socket connection; | |||
public ShadowsocksController controller; | |||
public TCPRelay relay; | |||
public DateTime lastActivity; | |||
private int retryCount = 0; | |||
private bool connected; | |||
private byte command; | |||
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; | |||
private int totalWrite = 0; | |||
// remote receive buffer | |||
private byte[] remoteRecvBuffer = new byte[RecvSize]; | |||
// remote send buffer | |||
@@ -67,15 +111,29 @@ 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(); | |||
private DateTime _startConnectTime; | |||
public void CreateRemote() | |||
{ | |||
Server server = controller.GetAServer(IStrategyCallerType.TCP, (IPEndPoint)connection.RemoteEndPoint); | |||
if (server == null || server.server == "") | |||
{ | |||
throw new ArgumentException("No server configured"); | |||
} | |||
this.encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | |||
this.server = server; | |||
} | |||
public void Start(byte[] firstPacket, int length) | |||
{ | |||
this._firstPacket = firstPacket; | |||
this._firstPacketLength = length; | |||
this.HandshakeReceive(); | |||
this.lastActivity = DateTime.Now; | |||
} | |||
private void CheckClose() | |||
@@ -88,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) | |||
@@ -115,7 +178,7 @@ namespace Shadowsocks.Controller | |||
remote.Shutdown(SocketShutdown.Both); | |||
remote.Close(); | |||
} | |||
catch (SocketException e) | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
@@ -124,12 +187,14 @@ namespace Shadowsocks.Controller | |||
{ | |||
lock (decryptionLock) | |||
{ | |||
((IDisposable)encryptor).Dispose(); | |||
if (encryptor != null) | |||
{ | |||
((IDisposable)encryptor).Dispose(); | |||
} | |||
} | |||
} | |||
} | |||
private void HandshakeReceive() | |||
{ | |||
if (closed) | |||
@@ -199,14 +264,14 @@ namespace Shadowsocks.Controller | |||
try | |||
{ | |||
int bytesRead = connection.EndReceive(ar); | |||
if (bytesRead >= 3) | |||
{ | |||
command = connetionRecvBuffer[1]; | |||
if (command == 1) | |||
{ | |||
byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 }; | |||
connection.BeginSend(response, 0, response.Length, 0, new AsyncCallback(StartConnect), null); | |||
connection.BeginSend(response, 0, response.Length, 0, new AsyncCallback(ResponseCallback), null); | |||
} | |||
else if (command == 3) | |||
{ | |||
@@ -249,7 +314,6 @@ namespace Shadowsocks.Controller | |||
private void ReadAll(IAsyncResult ar) | |||
{ | |||
if (closed) | |||
{ | |||
return; | |||
@@ -283,12 +347,37 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
private void StartConnect(IAsyncResult ar) | |||
private void ResponseCallback(IAsyncResult ar) | |||
{ | |||
try | |||
{ | |||
connection.EndSend(ar); | |||
StartConnect(); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
} | |||
} | |||
private class ServerTimer : Timer | |||
{ | |||
public Server Server; | |||
public ServerTimer(int p) :base(p) | |||
{ | |||
} | |||
} | |||
private void StartConnect() | |||
{ | |||
try | |||
{ | |||
CreateRemote(); | |||
// TODO async resolving | |||
IPAddress ipAddress; | |||
bool parsed = IPAddress.TryParse(server.server, out ipAddress); | |||
@@ -299,14 +388,21 @@ 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); | |||
_startConnectTime = DateTime.Now; | |||
ServerTimer connectTimer = new ServerTimer(3000); | |||
connectTimer.AutoReset = false; | |||
connectTimer.Elapsed += connectTimer_Elapsed; | |||
connectTimer.Enabled = true; | |||
connectTimer.Server = server; | |||
connected = false; | |||
// Connect to the remote endpoint. | |||
remote.BeginConnect(remoteEP, | |||
new AsyncCallback(ConnectCallback), null); | |||
new AsyncCallback(ConnectCallback), connectTimer); | |||
} | |||
catch (Exception e) | |||
{ | |||
@@ -315,26 +411,84 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
private void connectTimer_Elapsed(object sender, ElapsedEventArgs e) | |||
{ | |||
if (connected) | |||
{ | |||
return; | |||
} | |||
Server server = ((ServerTimer)sender).Server; | |||
IStrategy strategy = controller.GetCurrentStrategy(); | |||
if (strategy != null) | |||
{ | |||
strategy.SetFailure(server); | |||
} | |||
Console.WriteLine(String.Format("{0} timed out", server.FriendlyName())); | |||
remote.Close(); | |||
RetryConnect(); | |||
} | |||
private void RetryConnect() | |||
{ | |||
if (retryCount < 4) | |||
{ | |||
Logging.Debug("Connection failed, retrying"); | |||
StartConnect(); | |||
retryCount++; | |||
} | |||
else | |||
{ | |||
this.Close(); | |||
} | |||
} | |||
private void ConnectCallback(IAsyncResult ar) | |||
{ | |||
Server server = null; | |||
if (closed) | |||
{ | |||
return; | |||
} | |||
try | |||
{ | |||
ServerTimer timer = (ServerTimer)ar.AsyncState; | |||
server = timer.Server; | |||
timer.Elapsed -= connectTimer_Elapsed; | |||
timer.Enabled = false; | |||
timer.Dispose(); | |||
// Complete the connection. | |||
remote.EndConnect(ar); | |||
connected = true; | |||
//Console.WriteLine("Socket connected to {0}", | |||
// remote.RemoteEndPoint.ToString()); | |||
var latency = DateTime.Now - _startConnectTime; | |||
IStrategy strategy = controller.GetCurrentStrategy(); | |||
if (strategy != null) | |||
{ | |||
strategy.UpdateLatency(server, latency); | |||
} | |||
StartPipe(); | |||
} | |||
catch (ArgumentException) | |||
{ | |||
} | |||
catch (Exception e) | |||
{ | |||
if (server != null) | |||
{ | |||
IStrategy strategy = controller.GetCurrentStrategy(); | |||
if (strategy != null) | |||
{ | |||
strategy.SetFailure(server); | |||
} | |||
} | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
RetryConnect(); | |||
} | |||
} | |||
@@ -367,9 +521,11 @@ namespace Shadowsocks.Controller | |||
try | |||
{ | |||
int bytesRead = remote.EndReceive(ar); | |||
totalRead += bytesRead; | |||
if (bytesRead > 0) | |||
{ | |||
this.lastActivity = DateTime.Now; | |||
int bytesToSend; | |||
lock (decryptionLock) | |||
{ | |||
@@ -380,6 +536,12 @@ namespace Shadowsocks.Controller | |||
encryptor.Decrypt(remoteRecvBuffer, bytesRead, remoteSendBuffer, out bytesToSend); | |||
} | |||
connection.BeginSend(remoteSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeConnectionSendCallback), null); | |||
IStrategy strategy = controller.GetCurrentStrategy(); | |||
if (strategy != null) | |||
{ | |||
strategy.UpdateLastRead(this.server); | |||
} | |||
} | |||
else | |||
{ | |||
@@ -387,6 +549,13 @@ namespace Shadowsocks.Controller | |||
connection.Shutdown(SocketShutdown.Send); | |||
connectionShutdown = true; | |||
CheckClose(); | |||
if (totalRead == 0) | |||
{ | |||
// closed before anything received, reports as failure | |||
// disable this feature | |||
// controller.GetCurrentStrategy().SetFailure(this.server); | |||
} | |||
} | |||
} | |||
catch (Exception e) | |||
@@ -405,6 +574,7 @@ namespace Shadowsocks.Controller | |||
try | |||
{ | |||
int bytesRead = connection.EndReceive(ar); | |||
totalWrite += bytesRead; | |||
if (bytesRead > 0) | |||
{ | |||
@@ -418,6 +588,12 @@ namespace Shadowsocks.Controller | |||
encryptor.Encrypt(connetionRecvBuffer, bytesRead, connetionSendBuffer, out bytesToSend); | |||
} | |||
remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeRemoteSendCallback), null); | |||
IStrategy strategy = controller.GetCurrentStrategy(); | |||
if (strategy != null) | |||
{ | |||
strategy.UpdateLastWrite(this.server); | |||
} | |||
} | |||
else | |||
{ | |||
@@ -471,5 +647,4 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
} | |||
} |
@@ -6,16 +6,17 @@ using Shadowsocks.Model; | |||
using System.Net.Sockets; | |||
using System.Net; | |||
using System.Runtime.CompilerServices; | |||
using Shadowsocks.Controller.Strategy; | |||
namespace Shadowsocks.Controller | |||
{ | |||
class UDPRelay : Listener.Service | |||
{ | |||
private Configuration _config; | |||
private ShadowsocksController _controller; | |||
private LRUCache<IPEndPoint, UDPHandler> _cache; | |||
public UDPRelay(Configuration config) | |||
public UDPRelay(ShadowsocksController controller) | |||
{ | |||
this._config = config; | |||
this._controller = controller; | |||
this._cache = new LRUCache<IPEndPoint, UDPHandler>(512); // todo: choose a smart number | |||
} | |||
@@ -34,7 +35,7 @@ namespace Shadowsocks.Controller | |||
UDPHandler handler = _cache.get(remoteEndPoint); | |||
if (handler == null) | |||
{ | |||
handler = new UDPHandler(socket, _config.GetCurrentServer(), remoteEndPoint); | |||
handler = new UDPHandler(socket, _controller.GetAServer(IStrategyCallerType.UDP, remoteEndPoint), remoteEndPoint); | |||
_cache.add(remoteEndPoint, handler); | |||
} | |||
handler.Send(firstPacket, length); | |||
@@ -107,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 | |||
{ | |||
@@ -123,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 | |||
{ | |||
@@ -133,6 +138,8 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
} | |||
// cc by-sa 3.0 http://stackoverflow.com/a/3719378/1124054 | |||
class LRUCache<K, V> where V : UDPRelay.UDPHandler | |||
{ | |||
@@ -195,5 +202,4 @@ namespace Shadowsocks.Controller | |||
public K key; | |||
public V value; | |||
} | |||
} |
@@ -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.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) | |||
@@ -80,30 +79,6 @@ namespace Shadowsocks.Controller | |||
{ | |||
return false; | |||
} | |||
// check dotnet 4.0 | |||
AssemblyName[] references = Assembly.GetExecutingAssembly().GetReferencedAssemblies(); | |||
Version dotNetVersion = Environment.Version; | |||
foreach (AssemblyName reference in references) | |||
{ | |||
if (reference.Name == "mscorlib") | |||
{ | |||
dotNetVersion = reference.Version; | |||
} | |||
} | |||
if (dotNetVersion.Major >= 4) | |||
{ | |||
if (url.IndexOf("dotnet4.0") < 0) | |||
{ | |||
return false; | |||
} | |||
} | |||
else | |||
{ | |||
if (url.IndexOf("dotnet4.0") >= 0) | |||
{ | |||
return false; | |||
} | |||
} | |||
string version = ParseVersionFromURL(url); | |||
if (version == null) | |||
{ | |||
@@ -154,7 +129,7 @@ namespace Shadowsocks.Controller | |||
} | |||
catch (Exception ex) | |||
{ | |||
Console.WriteLine(ex.ToString()); | |||
Logging.Debug(ex.ToString()); | |||
return; | |||
} | |||
} |
@@ -5,6 +5,8 @@ using System.Collections.Generic; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Net.Sockets; | |||
using Shadowsocks.Controller.Strategy; | |||
using System.Net; | |||
namespace Shadowsocks.Controller | |||
{ | |||
@@ -20,8 +22,10 @@ namespace Shadowsocks.Controller | |||
private Listener _listener; | |||
private PACServer _pacServer; | |||
private Configuration _config; | |||
private StrategyManager _strategyManager; | |||
private PolipoRunner polipoRunner; | |||
private GFWListUpdater gfwListUpdater; | |||
private AvailabilityStatistics _availabilityStatics; | |||
private bool stopped = false; | |||
private bool _systemProxyIsDirty = false; | |||
@@ -35,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<PathEventArgs> PACFileReadyToOpen; | |||
public event EventHandler<PathEventArgs> UserRuleFileReadyToOpen; | |||
@@ -49,6 +53,8 @@ namespace Shadowsocks.Controller | |||
public ShadowsocksController() | |||
{ | |||
_config = Configuration.Load(); | |||
_strategyManager = new StrategyManager(this); | |||
StartReleasingMemory(); | |||
} | |||
public void Start() | |||
@@ -70,11 +76,48 @@ namespace Shadowsocks.Controller | |||
} | |||
// always return copy | |||
public Configuration GetConfiguration() | |||
public Configuration GetConfigurationCopy() | |||
{ | |||
return Configuration.Load(); | |||
} | |||
// always return current instance | |||
public Configuration GetCurrentConfiguration() | |||
{ | |||
return _config; | |||
} | |||
public IList<IStrategy> GetStrategies() | |||
{ | |||
return _strategyManager.GetStrategies(); | |||
} | |||
public IStrategy GetCurrentStrategy() | |||
{ | |||
foreach (var strategy in _strategyManager.GetStrategies()) | |||
{ | |||
if (strategy.ID == this._config.strategy) | |||
{ | |||
return strategy; | |||
} | |||
} | |||
return null; | |||
} | |||
public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint) | |||
{ | |||
IStrategy strategy = GetCurrentStrategy(); | |||
if (strategy != null) | |||
{ | |||
return strategy.GetAServer(type, localIPEndPoint); | |||
} | |||
if (_config.index < 0) | |||
{ | |||
_config.index = 0; | |||
} | |||
return GetCurrentServer(); | |||
} | |||
public void SaveServers(List<Server> servers, int localPort) | |||
{ | |||
_config.configs = servers; | |||
@@ -134,6 +177,14 @@ namespace Shadowsocks.Controller | |||
public void SelectServerIndex(int index) | |||
{ | |||
_config.index = index; | |||
_config.strategy = null; | |||
SaveConfig(_config); | |||
} | |||
public void SelectStrategy(string strategyID) | |||
{ | |||
_config.index = -1; | |||
_config.strategy = strategyID; | |||
SaveConfig(_config); | |||
} | |||
@@ -179,6 +230,11 @@ namespace Shadowsocks.Controller | |||
public string GetQRCodeForCurrentServer() | |||
{ | |||
Server server = GetCurrentServer(); | |||
return GetQRCode(server); | |||
} | |||
public static string GetQRCode(Server server) | |||
{ | |||
string parts = server.method + ":" + server.password + "@" + server.server + ":" + server.server_port; | |||
string base64 = System.Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); | |||
return "ss://" + base64; | |||
@@ -192,6 +248,16 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
public void ToggleAvailabilityStatistics(bool enabled) | |||
{ | |||
if (_availabilityStatics != null) | |||
{ | |||
_availabilityStatics.Set(enabled); | |||
_config.availabilityStatistics = enabled; | |||
SaveConfig(_config); | |||
} | |||
} | |||
public void SavePACUrl(string pacUrl) | |||
{ | |||
_config.pacUrl = pacUrl; | |||
@@ -241,6 +307,12 @@ namespace Shadowsocks.Controller | |||
_listener.Stop(); | |||
} | |||
if (_availabilityStatics == null) | |||
{ | |||
_availabilityStatics = new AvailabilityStatistics(); | |||
_availabilityStatics.UpdateConfiguration(_config); | |||
} | |||
// don't put polipoRunner.Start() before pacServer.Stop() | |||
// or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1 | |||
// though UseShellExecute is set to true now | |||
@@ -248,10 +320,16 @@ namespace Shadowsocks.Controller | |||
polipoRunner.Stop(); | |||
try | |||
{ | |||
var strategy = GetCurrentStrategy(); | |||
if (strategy != null) | |||
{ | |||
strategy.ReloadServers(); | |||
} | |||
polipoRunner.Start(_config); | |||
TCPRelay tcpRelay = new TCPRelay(_config); | |||
UDPRelay udpRelay = new UDPRelay(_config); | |||
TCPRelay tcpRelay = new TCPRelay(this); | |||
UDPRelay udpRelay = new UDPRelay(this); | |||
List<Listener.Service> services = new List<Listener.Service>(); | |||
services.Add(tcpRelay); | |||
services.Add(udpRelay); | |||
@@ -282,17 +360,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) | |||
@@ -339,7 +415,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
while (true) | |||
{ | |||
Util.Utils.ReleaseMemory(); | |||
Util.Utils.ReleaseMemory(false); | |||
Thread.Sleep(30 * 1000); | |||
} | |||
} | |||
@@ -0,0 +1,71 @@ | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Model; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Net; | |||
using System.Text; | |||
namespace Shadowsocks.Controller.Strategy | |||
{ | |||
class BalancingStrategy : IStrategy | |||
{ | |||
ShadowsocksController _controller; | |||
Random _random; | |||
public BalancingStrategy(ShadowsocksController controller) | |||
{ | |||
_controller = controller; | |||
_random = new Random(); | |||
} | |||
public string Name | |||
{ | |||
get { return I18N.GetString("Load Balance"); } | |||
} | |||
public string ID | |||
{ | |||
get { return "com.shadowsocks.strategy.balancing"; } | |||
} | |||
public void ReloadServers() | |||
{ | |||
// do nothing | |||
} | |||
public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint) | |||
{ | |||
var configs = _controller.GetCurrentConfiguration().configs; | |||
int index; | |||
if (type == IStrategyCallerType.TCP) | |||
{ | |||
index = _random.Next(); | |||
} | |||
else | |||
{ | |||
index = localIPEndPoint.GetHashCode(); | |||
} | |||
return configs[index % configs.Count]; | |||
} | |||
public void UpdateLatency(Model.Server server, TimeSpan latency) | |||
{ | |||
// do nothing | |||
} | |||
public void UpdateLastRead(Model.Server server) | |||
{ | |||
// do nothing | |||
} | |||
public void UpdateLastWrite(Model.Server server) | |||
{ | |||
// do nothing | |||
} | |||
public void SetFailure(Model.Server server) | |||
{ | |||
// do nothing | |||
} | |||
} | |||
} |
@@ -0,0 +1,185 @@ | |||
using Shadowsocks.Model; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace Shadowsocks.Controller.Strategy | |||
{ | |||
class HighAvailabilityStrategy : IStrategy | |||
{ | |||
protected ServerStatus _currentServer; | |||
protected Dictionary<Server, ServerStatus> _serverStatus; | |||
ShadowsocksController _controller; | |||
Random _random; | |||
public class ServerStatus | |||
{ | |||
// time interval between SYN and SYN+ACK | |||
public TimeSpan latency; | |||
public DateTime lastTimeDetectLatency; | |||
// last time anything received | |||
public DateTime lastRead; | |||
// last time anything sent | |||
public DateTime lastWrite; | |||
// connection refused or closed before anything received | |||
public DateTime lastFailure; | |||
public Server server; | |||
public double score; | |||
} | |||
public HighAvailabilityStrategy(ShadowsocksController controller) | |||
{ | |||
_controller = controller; | |||
_random = new Random(); | |||
_serverStatus = new Dictionary<Server, ServerStatus>(); | |||
} | |||
public string Name | |||
{ | |||
get { return I18N.GetString("High Availability"); } | |||
} | |||
public string ID | |||
{ | |||
get { return "com.shadowsocks.strategy.ha"; } | |||
} | |||
public void ReloadServers() | |||
{ | |||
// make a copy to avoid locking | |||
var newServerStatus = new Dictionary<Server, ServerStatus>(_serverStatus); | |||
foreach (var server in _controller.GetCurrentConfiguration().configs) | |||
{ | |||
if (!newServerStatus.ContainsKey(server)) | |||
{ | |||
var status = new ServerStatus(); | |||
status.server = server; | |||
status.lastFailure = DateTime.MinValue; | |||
status.lastRead = DateTime.Now; | |||
status.lastWrite = DateTime.Now; | |||
status.latency = new TimeSpan(0, 0, 0, 0, 10); | |||
status.lastTimeDetectLatency = DateTime.Now; | |||
newServerStatus[server] = status; | |||
} | |||
else | |||
{ | |||
// update settings for existing server | |||
newServerStatus[server].server = server; | |||
} | |||
} | |||
_serverStatus = newServerStatus; | |||
ChooseNewServer(); | |||
} | |||
public Server GetAServer(IStrategyCallerType type, System.Net.IPEndPoint localIPEndPoint) | |||
{ | |||
if (type == IStrategyCallerType.TCP) | |||
{ | |||
ChooseNewServer(); | |||
} | |||
if (_currentServer == null) | |||
{ | |||
return null; | |||
} | |||
return _currentServer.server; | |||
} | |||
/** | |||
* once failed, try after 5 min | |||
* and (last write - last read) < 5s | |||
* and (now - last read) < 5s // means not stuck | |||
* and latency < 200ms, try after 30s | |||
*/ | |||
public void ChooseNewServer() | |||
{ | |||
ServerStatus oldServer = _currentServer; | |||
List<ServerStatus> servers = new List<ServerStatus>(_serverStatus.Values); | |||
DateTime now = DateTime.Now; | |||
foreach (var status in servers) | |||
{ | |||
// all of failure, latency, (lastread - lastwrite) normalized to 1000, then | |||
// 100 * failure - 2 * latency - 0.5 * (lastread - lastwrite) | |||
status.score = | |||
100 * 1000 * Math.Min(5 * 60, (now - status.lastFailure).TotalSeconds) | |||
-2 * 5 * (Math.Min(2000, status.latency.TotalMilliseconds) / (1 + (now - status.lastTimeDetectLatency).TotalSeconds / 30 / 10) + | |||
-0.5 * 200 * Math.Min(5, (status.lastRead - status.lastWrite).TotalSeconds)); | |||
Logging.Debug(String.Format("server: {0} latency:{1} score: {2}", status.server.FriendlyName(), status.latency, status.score)); | |||
} | |||
ServerStatus max = null; | |||
foreach (var status in servers) | |||
{ | |||
if (max == null) | |||
{ | |||
max = status; | |||
} | |||
else | |||
{ | |||
if (status.score >= max.score) | |||
{ | |||
max = status; | |||
} | |||
} | |||
} | |||
if (max != null) | |||
{ | |||
if (_currentServer == null || max.score - _currentServer.score > 200) | |||
{ | |||
_currentServer = max; | |||
Console.WriteLine("HA switching to server: {0}", _currentServer.server.FriendlyName()); | |||
} | |||
} | |||
} | |||
public void UpdateLatency(Model.Server server, TimeSpan latency) | |||
{ | |||
Logging.Debug(String.Format("latency: {0} {1}", server.FriendlyName(), latency)); | |||
ServerStatus status; | |||
if (_serverStatus.TryGetValue(server, out status)) | |||
{ | |||
status.latency = latency; | |||
status.lastTimeDetectLatency = DateTime.Now; | |||
} | |||
} | |||
public void UpdateLastRead(Model.Server server) | |||
{ | |||
Logging.Debug(String.Format("last read: {0}", server.FriendlyName())); | |||
ServerStatus status; | |||
if (_serverStatus.TryGetValue(server, out status)) | |||
{ | |||
status.lastRead = DateTime.Now; | |||
} | |||
} | |||
public void UpdateLastWrite(Model.Server server) | |||
{ | |||
Logging.Debug(String.Format("last write: {0}", server.FriendlyName())); | |||
ServerStatus status; | |||
if (_serverStatus.TryGetValue(server, out status)) | |||
{ | |||
status.lastWrite = DateTime.Now; | |||
} | |||
} | |||
public void SetFailure(Model.Server server) | |||
{ | |||
Logging.Debug(String.Format("failure: {0}", server.FriendlyName())); | |||
ServerStatus status; | |||
if (_serverStatus.TryGetValue(server, out status)) | |||
{ | |||
status.lastFailure = DateTime.Now; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
using Shadowsocks.Model; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Net; | |||
using System.Text; | |||
namespace Shadowsocks.Controller.Strategy | |||
{ | |||
public enum IStrategyCallerType | |||
{ | |||
TCP, | |||
UDP | |||
} | |||
/* | |||
* 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 | |||
*/ | |||
void ReloadServers(); | |||
/* | |||
* Get a new server to use in TCPRelay or UDPRelay | |||
*/ | |||
Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint); | |||
/* | |||
* TCPRelay will call this when latency of a server detected | |||
*/ | |||
void UpdateLatency(Server server, TimeSpan latency); | |||
/* | |||
* TCPRelay will call this when reading from a server | |||
*/ | |||
void UpdateLastRead(Server server); | |||
/* | |||
* TCPRelay will call this when writing to a server | |||
*/ | |||
void UpdateLastWrite(Server server); | |||
/* | |||
* TCPRelay will call this when fatal failure detected | |||
*/ | |||
void SetFailure(Server server); | |||
} | |||
} |
@@ -0,0 +1,176 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Text; | |||
using Shadowsocks.Model; | |||
using System.IO; | |||
using System.Net.NetworkInformation; | |||
using System.Threading; | |||
namespace Shadowsocks.Controller.Strategy | |||
{ | |||
class SimplyChooseByStatisticsStrategy : IStrategy | |||
{ | |||
private ShadowsocksController _controller; | |||
private Server _currentServer; | |||
private Timer timer; | |||
private Dictionary<string, StatisticsData> statistics; | |||
private static readonly int CachedInterval = 30 * 60 * 1000; //choose a new server every 30 minutes | |||
public SimplyChooseByStatisticsStrategy(ShadowsocksController controller) | |||
{ | |||
_controller = controller; | |||
var servers = controller.GetCurrentConfiguration().configs; | |||
int randomIndex = new Random().Next() % servers.Count(); | |||
_currentServer = servers[randomIndex]; //choose a server randomly at first | |||
timer = new Timer(ReloadStatisticsAndChooseAServer); | |||
} | |||
private void ReloadStatisticsAndChooseAServer(object obj) | |||
{ | |||
Logging.Debug("Reloading statistics and choose a new server...."); | |||
List<Server> servers = _controller.GetCurrentConfiguration().configs; | |||
LoadStatistics(); | |||
ChooseNewServer(servers); | |||
} | |||
/* | |||
return a dict: | |||
{ | |||
'ServerFriendlyName1':StatisticsData, | |||
'ServerFriendlyName2':... | |||
} | |||
*/ | |||
private void LoadStatistics() | |||
{ | |||
try | |||
{ | |||
var path = AvailabilityStatistics.AvailabilityStatisticsFile; | |||
Logging.Debug(string.Format("loading statistics from{0}", path)); | |||
statistics = (from l in File.ReadAllLines(path) | |||
.Skip(1) | |||
let strings = l.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) | |||
let rawData = new | |||
{ | |||
ServerName = strings[1], | |||
IPStatus = strings[2], | |||
RoundtripTime = int.Parse(strings[3]) | |||
} | |||
group rawData by rawData.ServerName into server | |||
select new | |||
{ | |||
ServerName = server.Key, | |||
data = new StatisticsData | |||
{ | |||
SuccessTimes = server.Count(data => IPStatus.Success.ToString().Equals(data.IPStatus)), | |||
TimedOutTimes = server.Count(data => IPStatus.TimedOut.ToString().Equals(data.IPStatus)), | |||
AverageResponse = Convert.ToInt32(server.Average(data => data.RoundtripTime)), | |||
MinResponse = server.Min(data => data.RoundtripTime), | |||
MaxResponse = server.Max(data => data.RoundtripTime) | |||
} | |||
}).ToDictionary(server => server.ServerName, server => server.data); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
} | |||
//return the score by data | |||
//server with highest score will be choosen | |||
private static double GetScore(StatisticsData data) | |||
{ | |||
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<Server> servers) | |||
{ | |||
if (statistics == null) | |||
{ | |||
return; | |||
} | |||
try | |||
{ | |||
var bestResult = (from server in servers | |||
let name = server.FriendlyName() | |||
where statistics.ContainsKey(name) | |||
select new | |||
{ | |||
server, | |||
score = GetScore(statistics[name]) | |||
} | |||
).Aggregate((result1, result2) => result1.score > result2.score ? result1 : result2); | |||
if (_controller.GetCurrentStrategy().ID == ID && _currentServer != bestResult.server) //output when enabled | |||
{ | |||
Console.WriteLine("Switch to server: {0} by package loss:{1}", bestResult.server.FriendlyName(), 1 - bestResult.score); | |||
} | |||
_currentServer = bestResult.server; | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
} | |||
public string ID | |||
{ | |||
get { return "com.shadowsocks.strategy.scbs"; } | |||
} | |||
public string Name | |||
{ | |||
get { return I18N.GetString("Choose By Total Package Loss"); } | |||
} | |||
public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint) | |||
{ | |||
var oldServer = _currentServer; | |||
if (oldServer == null) | |||
{ | |||
ChooseNewServer(_controller.GetCurrentConfiguration().configs); | |||
} | |||
if (oldServer != _currentServer) | |||
{ | |||
} | |||
return _currentServer; //current server cached for CachedInterval | |||
} | |||
public void ReloadServers() | |||
{ | |||
ChooseNewServer(_controller.GetCurrentConfiguration().configs); | |||
timer?.Change(0, CachedInterval); | |||
} | |||
public void SetFailure(Server server) | |||
{ | |||
Logging.Debug(String.Format("failure: {0}", server.FriendlyName())); | |||
} | |||
public void UpdateLastRead(Server server) | |||
{ | |||
//TODO: combine this part of data with ICMP statics | |||
} | |||
public void UpdateLastWrite(Server server) | |||
{ | |||
//TODO: combine this part of data with ICMP statics | |||
} | |||
public void UpdateLatency(Server server, TimeSpan latency) | |||
{ | |||
//TODO: combine this part of data with ICMP statics | |||
} | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
using Shadowsocks.Controller; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace Shadowsocks.Controller.Strategy | |||
{ | |||
class StrategyManager | |||
{ | |||
List<IStrategy> _strategies; | |||
public StrategyManager(ShadowsocksController controller) | |||
{ | |||
_strategies = new List<IStrategy>(); | |||
_strategies.Add(new BalancingStrategy(controller)); | |||
_strategies.Add(new HighAvailabilityStrategy(controller)); | |||
_strategies.Add(new SimplyChooseByStatisticsStrategy(controller)); | |||
// TODO: load DLL plugins | |||
} | |||
public IList<IStrategy> GetStrategies() | |||
{ | |||
return _strategies; | |||
} | |||
} | |||
} |
@@ -55,6 +55,7 @@ namespace Shadowsocks.Controller | |||
else | |||
pacUrl = "http://127.0.0.1:" + config.localPort.ToString() + "/pac?t=" + GetTimestamp(DateTime.Now); | |||
registry.SetValue("ProxyEnable", 0); | |||
var readProxyServer = registry.GetValue("ProxyServer"); | |||
registry.SetValue("ProxyServer", ""); | |||
registry.SetValue("AutoConfigURL", pacUrl); | |||
} | |||
@@ -65,6 +66,8 @@ namespace Shadowsocks.Controller | |||
registry.SetValue("ProxyServer", ""); | |||
registry.SetValue("AutoConfigURL", ""); | |||
} | |||
//Set AutoDetectProxy Off | |||
IEAutoDetectProxy(false); | |||
SystemProxy.NotifyIE(); | |||
//Must Notify IE first, or the connections do not chanage | |||
CopyProxySettingFromLan(); | |||
@@ -106,5 +109,30 @@ namespace Shadowsocks.Controller | |||
{ | |||
return value.ToString("yyyyMMddHHmmssffff"); | |||
} | |||
/// <summary> | |||
/// Checks or unchecks the IE Options Connection setting of "Automatically detect Proxy" | |||
/// </summary> | |||
/// <param name="set">Provide 'true' if you want to check the 'Automatically detect Proxy' check box. To uncheck, pass 'false'</param> | |||
private static void IEAutoDetectProxy(bool set) | |||
{ | |||
RegistryKey registry = | |||
Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Connections", | |||
true); | |||
byte[] defConnection = (byte[])registry.GetValue("DefaultConnectionSettings"); | |||
byte[] savedLegacySetting = (byte[])registry.GetValue("SavedLegacySettings"); | |||
if (set) | |||
{ | |||
defConnection[8] = Convert.ToByte(defConnection[8] & 8); | |||
savedLegacySetting[8] = Convert.ToByte(savedLegacySetting[8] & 8); | |||
} | |||
else | |||
{ | |||
defConnection[8] = Convert.ToByte(defConnection[8] & ~8); | |||
savedLegacySetting[8] = Convert.ToByte(savedLegacySetting[8] & ~8); | |||
} | |||
registry.SetValue("DefaultConnectionSettings", defConnection); | |||
registry.SetValue("SavedLegacySettings", savedLegacySetting); | |||
} | |||
} | |||
} |
@@ -19,10 +19,14 @@ 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=退出 | |||
Edit Servers=编辑服务器 | |||
Load Balance=负载均衡 | |||
High Availability=高可用 | |||
Choose By Total Package Loss=累计丢包率 | |||
# Config Form | |||
@@ -38,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 | |||
@@ -69,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} | |||
Running: Port {0}=正在运行:端口 {0} |
@@ -1,10 +0,0 @@ | |||
proxyAddress = "__POLIPO_BIND_IP__" | |||
proxyPort = 8123 | |||
socksParentProxy = "127.0.0.1:__SOCKS_PORT__" | |||
socksProxyType = socks5 | |||
diskCacheRoot = "" | |||
localDocumentRoot = "" | |||
allowedPorts = 1-65535 | |||
tunnelAllowedPorts = 1-65535 |
@@ -0,0 +1,5 @@ | |||
listen-address __POLIPO_BIND_IP__:8123 | |||
show-on-task-bar 0 | |||
activity-animation 0 | |||
forward-socks5 / 127.0.0.1:__SOCKS_PORT__ . | |||
hide-console |
@@ -26,7 +26,6 @@ namespace Shadowsocks.Encryption | |||
protected int keyLen; | |||
protected int ivLen; | |||
public IVEncryptor(string method, string password) | |||
: base(method, password) | |||
{ | |||
@@ -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)] | |||
@@ -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 | |||
{ | |||
@@ -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; | |||
} | |||
} | |||
} | |||
@@ -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() | |||
{ | |||
} | |||
@@ -11,6 +11,9 @@ namespace Shadowsocks.Model | |||
public class Configuration | |||
{ | |||
public List<Server> configs; | |||
// when strategy is set, index is ignored | |||
public string strategy; | |||
public int index; | |||
public bool global; | |||
public bool enabled; | |||
@@ -19,6 +22,7 @@ namespace Shadowsocks.Model | |||
public int localPort; | |||
public string pacUrl; | |||
public bool useOnlinePac; | |||
public bool availabilityStatistics; | |||
private static string CONFIG_FILE = "gui-config.json"; | |||
@@ -52,6 +56,13 @@ namespace Shadowsocks.Model | |||
{ | |||
config.localPort = 1080; | |||
} | |||
if (config.index == -1) | |||
{ | |||
if (config.strategy == null) | |||
{ | |||
config.index = 0; | |||
} | |||
} | |||
return config; | |||
} | |||
catch (Exception e) | |||
@@ -79,9 +90,16 @@ namespace Shadowsocks.Model | |||
{ | |||
config.index = config.configs.Count - 1; | |||
} | |||
if (config.index < 0) | |||
if (config.index < -1) | |||
{ | |||
config.index = -1; | |||
} | |||
if (config.index == -1) | |||
{ | |||
config.index = 0; | |||
if (config.strategy == null) | |||
{ | |||
config.index = 0; | |||
} | |||
} | |||
config.isDefault = false; | |||
try | |||
@@ -118,6 +136,11 @@ namespace Shadowsocks.Model | |||
{ | |||
throw new ArgumentException(I18N.GetString("Port out of range")); | |||
} | |||
} | |||
public static void CheckLocalPort(int port) | |||
{ | |||
CheckPort(port); | |||
if (port == 8123) | |||
{ | |||
throw new ArgumentException(I18N.GetString("Port can't be 8123")); | |||
@@ -18,6 +18,17 @@ namespace Shadowsocks.Model | |||
public string method; | |||
public string remarks; | |||
public override int GetHashCode() | |||
{ | |||
return server.GetHashCode() ^ server_port; | |||
} | |||
public override bool Equals(object obj) | |||
{ | |||
Server o2 = (Server)obj; | |||
return this.server == o2.server && this.server_port == o2.server_port; | |||
} | |||
public string FriendlyName() | |||
{ | |||
if (string.IsNullOrEmpty(server)) | |||
@@ -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); | |||
@@ -1,7 +1,7 @@ | |||
//------------------------------------------------------------------------------ | |||
// <auto-generated> | |||
// This code was generated by a tool. | |||
// Runtime Version:4.0.30319.34209 | |||
// Runtime Version:4.0.30319.42000 | |||
// | |||
// Changes to this file may cause incorrect behavior and will be lost if | |||
// the code is regenerated. | |||
@@ -113,29 +113,34 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// Looks up a localized string similar to proxyAddress = "__POLIPO_BIND_IP__" | |||
///proxyPort = 8123 | |||
/// | |||
///socksParentProxy = "127.0.0.1:__SOCKS_PORT__" | |||
///socksProxyType = socks5 | |||
///diskCacheRoot = "" | |||
///localDocumentRoot = "" | |||
/// | |||
///allowedPorts = 1-65535 | |||
///tunnelAllowedPorts = 1-65535. | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
internal static byte[] mgwz_dll { | |||
get { | |||
object obj = ResourceManager.GetObject("mgwz_dll", resourceCulture); | |||
return ((byte[])(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized string similar to listen-address __POLIPO_BIND_IP__:8123 | |||
///show-on-task-bar 0 | |||
///activity-animation 0 | |||
///forward-socks5 / 127.0.0.1:__SOCKS_PORT__ . | |||
///hide-console. | |||
/// </summary> | |||
internal static string polipo_config { | |||
internal static string privoxy_conf { | |||
get { | |||
return ResourceManager.GetString("polipo_config", resourceCulture); | |||
return ResourceManager.GetString("privoxy_conf", resourceCulture); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
internal static byte[] polipo_exe { | |||
internal static byte[] privoxy_exe { | |||
get { | |||
object obj = ResourceManager.GetObject("polipo_exe", resourceCulture); | |||
object obj = ResourceManager.GetObject("privoxy_exe", resourceCulture); | |||
return ((byte[])(obj)); | |||
} | |||
} | |||
@@ -112,12 +112,12 @@ | |||
<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> | |||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> | |||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> | |||
<data name="abp_js" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\abp.js.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
@@ -127,11 +127,14 @@ | |||
<data name="libsscrypto_dll" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\data\libsscrypto.dll.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
<data name="polipo_config" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\polipo_config.txt;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312</value> | |||
<data name="mgwz_dll" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\data\mgwz.dll.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
<data name="polipo_exe" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\polipo.exe.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
<data name="privoxy_conf" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\data\privoxy_conf.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value> | |||
</data> | |||
<data name="privoxy_exe" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\data\privoxy.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
<data name="proxy_pac_txt" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\proxy.pac.txt.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
@@ -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) | |||
@@ -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; | |||
} | |||
} | |||
@@ -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; | |||
} | |||
@@ -84,10 +86,10 @@ namespace Shadowsocks.View | |||
}; | |||
int localPort = int.Parse(ProxyPortTextBox.Text); | |||
Configuration.CheckServer(server); | |||
Configuration.CheckPort(localPort); | |||
_modifiedConfiguration.configs[_oldSelectedIndex] = server; | |||
Configuration.CheckLocalPort(localPort); | |||
_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; | |||
} | |||
} | |||
@@ -133,10 +129,15 @@ namespace Shadowsocks.View | |||
private void LoadCurrentConfiguration() | |||
{ | |||
_modifiedConfiguration = controller.GetConfiguration(); | |||
_modifiedConfiguration = controller.GetConfigurationCopy(); | |||
LoadConfiguration(_modifiedConfiguration); | |||
_oldSelectedIndex = _modifiedConfiguration.index; | |||
ServersListBox.SelectedIndex = _modifiedConfiguration.index; | |||
_lastSelectedIndex = _modifiedConfiguration.index; | |||
if (_lastSelectedIndex < 0) | |||
{ | |||
_lastSelectedIndex = 0; | |||
} | |||
ServersListBox.SelectedIndex = _lastSelectedIndex; | |||
UpdateMoveUpAndDownButton(); | |||
LoadSelectedServer(); | |||
} | |||
@@ -147,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; | |||
@@ -155,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) | |||
@@ -172,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; | |||
@@ -205,6 +213,7 @@ namespace Shadowsocks.View | |||
return; | |||
} | |||
controller.SaveServers(_modifiedConfiguration.configs, _modifiedConfiguration.localPort); | |||
controller.SelectServerIndex(_modifiedConfiguration.configs.IndexOf(server)); | |||
this.Close(); | |||
} | |||
@@ -223,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 | |||
} | |||
} | |||
} | |||
} |
@@ -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> |
@@ -0,0 +1,199 @@ | |||
namespace Shadowsocks.View | |||
{ | |||
partial class LogForm | |||
{ | |||
/// <summary> | |||
/// Required designer variable. | |||
/// </summary> | |||
private System.ComponentModel.IContainer components = null; | |||
/// <summary> | |||
/// Clean up any resources being used. | |||
/// </summary> | |||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | |||
protected override void Dispose(bool disposing) | |||
{ | |||
if (disposing && (components != null)) | |||
{ | |||
components.Dispose(); | |||
} | |||
base.Dispose(disposing); | |||
} | |||
#region Windows Form Designer generated code | |||
/// <summary> | |||
/// Required method for Designer support - do not modify | |||
/// the contents of this method with the code editor. | |||
/// </summary> | |||
private void InitializeComponent() | |||
{ | |||
this.components = new System.ComponentModel.Container(); | |||
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.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(); | |||
// | |||
// LogMessageTextBox | |||
// | |||
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.Name = "contextMenuStrip1"; | |||
this.contextMenuStrip1.Size = new System.Drawing.Size(61, 4); | |||
// | |||
// mainMenu1 | |||
// | |||
this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { | |||
this.FileMenuItem}); | |||
// | |||
// FileMenuItem | |||
// | |||
this.FileMenuItem.Index = 0; | |||
this.FileMenuItem.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { | |||
this.OpenLocationMenuItem, | |||
this.ExitMenuItem}); | |||
this.FileMenuItem.Text = "&File"; | |||
// | |||
// OpenLocationMenuItem | |||
// | |||
this.OpenLocationMenuItem.Index = 0; | |||
this.OpenLocationMenuItem.Text = "&Open Location"; | |||
this.OpenLocationMenuItem.Click += new System.EventHandler(this.OpenLocationMenuItem_Click); | |||
// | |||
// ExitMenuItem | |||
// | |||
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(6F, 12F); | |||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | |||
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; | |||
this.Text = "Log Viewer"; | |||
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); | |||
} | |||
#endregion | |||
private System.Windows.Forms.TextBox LogMessageTextBox; | |||
private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; | |||
private System.Windows.Forms.MainMenu mainMenu1; | |||
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; | |||
} | |||
} |
@@ -0,0 +1,150 @@ | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Properties; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.ComponentModel; | |||
using System.Data; | |||
using System.Drawing; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Windows.Forms; | |||
namespace Shadowsocks.View | |||
{ | |||
public partial class LogForm : Form | |||
{ | |||
long lastOffset; | |||
string filename; | |||
Timer timer; | |||
const int BACK_OFFSET = 65536; | |||
public LogForm(string filename) | |||
{ | |||
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) | |||
{ | |||
UpdateContent(); | |||
} | |||
private void InitContent() | |||
{ | |||
using (StreamReader reader = new StreamReader(new FileStream(filename, | |||
FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) | |||
{ | |||
if (reader.BaseStream.Length > BACK_OFFSET) | |||
{ | |||
reader.BaseStream.Seek(-BACK_OFFSET, SeekOrigin.End); | |||
reader.ReadLine(); | |||
} | |||
string line = ""; | |||
while ((line = reader.ReadLine()) != null) | |||
LogMessageTextBox.AppendText(line + "\r\n"); | |||
LogMessageTextBox.ScrollToCaret(); | |||
lastOffset = reader.BaseStream.Position; | |||
} | |||
} | |||
private void UpdateContent() | |||
{ | |||
using (StreamReader reader = new StreamReader(new FileStream(filename, | |||
FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) | |||
{ | |||
reader.BaseStream.Seek(lastOffset, SeekOrigin.Begin); | |||
string line = ""; | |||
bool changed = false; | |||
while ((line = reader.ReadLine()) != null) | |||
{ | |||
changed = true; | |||
LogMessageTextBox.AppendText(line + "\r\n"); | |||
} | |||
if (changed) | |||
{ | |||
LogMessageTextBox.ScrollToCaret(); | |||
} | |||
lastOffset = reader.BaseStream.Position; | |||
} | |||
} | |||
private void LogForm_Load(object sender, EventArgs e) | |||
{ | |||
InitContent(); | |||
timer = new Timer(); | |||
timer.Interval = 300; | |||
timer.Tick += Timer_Tick; | |||
timer.Start(); | |||
} | |||
private void LogForm_FormClosing(object sender, FormClosingEventArgs e) | |||
{ | |||
timer.Stop(); | |||
} | |||
private void OpenLocationMenuItem_Click(object sender, EventArgs e) | |||
{ | |||
string argument = "/select, \"" + filename + "\""; | |||
Console.WriteLine(argument); | |||
System.Diagnostics.Process.Start("explorer.exe", argument); | |||
} | |||
private void ExitMenuItem_Click(object sender, EventArgs e) | |||
{ | |||
this.Close(); | |||
} | |||
private void LogForm_Shown(object sender, EventArgs e) | |||
{ | |||
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; | |||
} | |||
} | |||
} |
@@ -0,0 +1,126 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<root> | |||
<!-- | |||
Microsoft ResX Schema | |||
Version 2.0 | |||
The primary goals of this format is to allow a simple XML format | |||
that is mostly human readable. The generation and parsing of the | |||
various data types are done through the TypeConverter classes | |||
associated with the data types. | |||
Example: | |||
... ado.net/XML headers & schema ... | |||
<resheader name="resmimetype">text/microsoft-resx</resheader> | |||
<resheader name="version">2.0</resheader> | |||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | |||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | |||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | |||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | |||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | |||
<value>[base64 mime encoded serialized .NET Framework object]</value> | |||
</data> | |||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | |||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | |||
<comment>This is a comment</comment> | |||
</data> | |||
There are any number of "resheader" rows that contain simple | |||
name/value pairs. | |||
Each data row contains a name, and value. The row also contains a | |||
type or mimetype. Type corresponds to a .NET class that support | |||
text/value conversion through the TypeConverter architecture. | |||
Classes that don't support this are serialized and stored with the | |||
mimetype set. | |||
The mimetype is used for serialized objects, and tells the | |||
ResXResourceReader how to depersist the object. This is currently not | |||
extensible. For a given mimetype the value must be set accordingly: | |||
Note - application/x-microsoft.net.object.binary.base64 is the format | |||
that the ResXResourceWriter will generate, however the reader can | |||
read any of the formats listed below. | |||
mimetype: application/x-microsoft.net.object.binary.base64 | |||
value : The object must be serialized with | |||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | |||
: and then encoded with base64 encoding. | |||
mimetype: application/x-microsoft.net.object.soap.base64 | |||
value : The object must be serialized with | |||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter | |||
: and then encoded with base64 encoding. | |||
mimetype: application/x-microsoft.net.object.bytearray.base64 | |||
value : The object must be serialized into a byte array | |||
: using a System.ComponentModel.TypeConverter | |||
: and then encoded with base64 encoding. | |||
--> | |||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | |||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | |||
<xsd:element name="root" msdata:IsDataSet="true"> | |||
<xsd:complexType> | |||
<xsd:choice maxOccurs="unbounded"> | |||
<xsd:element name="metadata"> | |||
<xsd:complexType> | |||
<xsd:sequence> | |||
<xsd:element name="value" type="xsd:string" minOccurs="0" /> | |||
</xsd:sequence> | |||
<xsd:attribute name="name" use="required" type="xsd:string" /> | |||
<xsd:attribute name="type" type="xsd:string" /> | |||
<xsd:attribute name="mimetype" type="xsd:string" /> | |||
<xsd:attribute ref="xml:space" /> | |||
</xsd:complexType> | |||
</xsd:element> | |||
<xsd:element name="assembly"> | |||
<xsd:complexType> | |||
<xsd:attribute name="alias" type="xsd:string" /> | |||
<xsd:attribute name="name" type="xsd:string" /> | |||
</xsd:complexType> | |||
</xsd:element> | |||
<xsd:element name="data"> | |||
<xsd:complexType> | |||
<xsd:sequence> | |||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | |||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | |||
</xsd:sequence> | |||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | |||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | |||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | |||
<xsd:attribute ref="xml:space" /> | |||
</xsd:complexType> | |||
</xsd:element> | |||
<xsd:element name="resheader"> | |||
<xsd:complexType> | |||
<xsd:sequence> | |||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | |||
</xsd:sequence> | |||
<xsd:attribute name="name" type="xsd:string" use="required" /> | |||
</xsd:complexType> | |||
</xsd:element> | |||
</xsd:choice> | |||
</xsd:complexType> | |||
</xsd:element> | |||
</xsd:schema> | |||
<resheader name="resmimetype"> | |||
<value>text/microsoft-resx</value> | |||
</resheader> | |||
<resheader name="version"> | |||
<value>2.0</value> | |||
</resheader> | |||
<resheader name="reader"> | |||
<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=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</resheader> | |||
<metadata name="contextMenuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> | |||
<value>17, 17</value> | |||
</metadata> | |||
<metadata name="mainMenu1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> | |||
<value>172, 17</value> | |||
</metadata> | |||
</root> |
@@ -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; | |||
@@ -71,9 +72,9 @@ namespace Shadowsocks.View | |||
LoadCurrentConfiguration(); | |||
updateChecker.CheckUpdate(controller.GetConfiguration()); | |||
updateChecker.CheckUpdate(controller.GetConfigurationCopy()); | |||
if (controller.GetConfiguration().isDefault) | |||
if (controller.GetConfigurationCopy().isDefault) | |||
{ | |||
_isFirstRun = true; | |||
ShowConfigForm(); | |||
@@ -106,7 +107,7 @@ namespace Shadowsocks.View | |||
{ | |||
icon = Resources.ss24; | |||
} | |||
Configuration config = controller.GetConfiguration(); | |||
Configuration config = controller.GetConfigurationCopy(); | |||
bool enabled = config.enabled; | |||
bool global = config.global; | |||
if (!enabled) | |||
@@ -124,12 +125,21 @@ namespace Shadowsocks.View | |||
} | |||
_notifyIcon.Icon = Icon.FromHandle(icon.GetHicon()); | |||
string serverInfo = null; | |||
if (controller.GetCurrentStrategy() != null) | |||
{ | |||
serverInfo = controller.GetCurrentStrategy().Name; | |||
} | |||
else | |||
{ | |||
serverInfo = config.GetCurrentServer().FriendlyName(); | |||
} | |||
// we want to show more details but notify icon title is limited to 63 characters | |||
string text = I18N.GetString("Shadowsocks") + " " + UpdateChecker.Version + "\n" + | |||
(enabled ? | |||
I18N.GetString("System Proxy On: ") + (global ? I18N.GetString("Global") : I18N.GetString("PAC")) : | |||
String.Format(I18N.GetString("Running: Port {0}"), config.localPort)) // this feedback is very important because they need to know Shadowsocks is running | |||
+ "\n" + config.GetCurrentServer().FriendlyName(); | |||
+ "\n" + serverInfo; | |||
_notifyIcon.Text = text.Substring(0, Math.Min(63, text.Length)); | |||
} | |||
@@ -168,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)), | |||
@@ -185,18 +196,18 @@ namespace Shadowsocks.View | |||
private void controller_EnableStatusChanged(object sender, EventArgs e) | |||
{ | |||
enableItem.Checked = controller.GetConfiguration().enabled; | |||
enableItem.Checked = controller.GetConfigurationCopy().enabled; | |||
modeItem.Enabled = enableItem.Checked; | |||
} | |||
void controller_ShareOverLANStatusChanged(object sender, EventArgs e) | |||
{ | |||
ShareOverLANItem.Checked = controller.GetConfiguration().shareOverLan; | |||
ShareOverLANItem.Checked = controller.GetConfigurationCopy().shareOverLan; | |||
} | |||
void controller_EnableGlobalChanged(object sender, EventArgs e) | |||
{ | |||
globalModeItem.Checked = controller.GetConfiguration().global; | |||
globalModeItem.Checked = controller.GetConfigurationCopy().global; | |||
PACModeItem.Checked = !globalModeItem.Checked; | |||
} | |||
@@ -243,7 +254,7 @@ namespace Shadowsocks.View | |||
private void LoadCurrentConfiguration() | |||
{ | |||
Configuration config = controller.GetConfiguration(); | |||
Configuration config = controller.GetConfigurationCopy(); | |||
UpdateServersMenu(); | |||
enableItem.Checked = config.enabled; | |||
modeItem.Enabled = config.enabled; | |||
@@ -251,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(); | |||
@@ -263,20 +275,33 @@ namespace Shadowsocks.View | |||
{ | |||
items.RemoveAt(0); | |||
} | |||
Configuration configuration = controller.GetConfiguration(); | |||
for (int i = 0; i < configuration.configs.Count; i++) | |||
int i = 0; | |||
foreach (var strategy in controller.GetStrategies()) | |||
{ | |||
MenuItem item = new MenuItem(strategy.Name); | |||
item.Tag = strategy.ID; | |||
item.Click += AStrategyItem_Click; | |||
items.Add(i, item); | |||
i++; | |||
} | |||
int strategyCount = i; | |||
Configuration configuration = controller.GetConfigurationCopy(); | |||
foreach (var server in configuration.configs) | |||
{ | |||
Server server = configuration.configs[i]; | |||
MenuItem item = new MenuItem(server.FriendlyName()); | |||
item.Tag = i; | |||
item.Tag = i - strategyCount; | |||
item.Click += AServerItem_Click; | |||
items.Add(i, item); | |||
i++; | |||
} | |||
if (configuration.index >= 0 && configuration.index < configuration.configs.Count) | |||
foreach (MenuItem item in items) | |||
{ | |||
items[configuration.index].Checked = true; | |||
if (item.Tag != null && (item.Tag.ToString() == configuration.index.ToString() || item.Tag.ToString() == configuration.strategy)) | |||
{ | |||
item.Checked = true; | |||
} | |||
} | |||
} | |||
@@ -297,7 +322,7 @@ namespace Shadowsocks.View | |||
void configForm_FormClosed(object sender, FormClosedEventArgs e) | |||
{ | |||
configForm = null; | |||
Util.Utils.ReleaseMemory(); | |||
Util.Utils.ReleaseMemory(true); | |||
ShowFirstTimeBalloon(); | |||
} | |||
@@ -327,7 +352,7 @@ namespace Shadowsocks.View | |||
private void AboutItem_Click(object sender, EventArgs e) | |||
{ | |||
Process.Start("https://github.com/shadowsocks/shadowsocks-csharp"); | |||
Process.Start("https://github.com/shadowsocks/shadowsocks-windows"); | |||
} | |||
private void notifyIcon1_DoubleClick(object sender, MouseEventArgs e) | |||
@@ -380,11 +405,17 @@ namespace Shadowsocks.View | |||
controller.SelectServerIndex((int)item.Tag); | |||
} | |||
private void AStrategyItem_Click(object sender, EventArgs e) | |||
{ | |||
MenuItem item = (MenuItem)sender; | |||
controller.SelectStrategy((string)item.Tag); | |||
} | |||
private void ShowLogItem_Click(object sender, EventArgs e) | |||
{ | |||
string argument = Logging.LogFile; | |||
System.Diagnostics.Process.Start("notepad.exe", argument); | |||
new LogForm(argument).Show(); | |||
} | |||
private void QRCodeItem_Click(object sender, EventArgs e) | |||
@@ -489,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) | |||
{ | |||
@@ -511,11 +547,11 @@ namespace Shadowsocks.View | |||
{ | |||
if (!onlinePACItem.Checked) | |||
{ | |||
if (String.IsNullOrEmpty(controller.GetConfiguration().pacUrl)) | |||
if (String.IsNullOrEmpty(controller.GetConfigurationCopy().pacUrl)) | |||
{ | |||
UpdateOnlinePACURLItem_Click(sender, e); | |||
} | |||
if (!String.IsNullOrEmpty(controller.GetConfiguration().pacUrl)) | |||
if (!String.IsNullOrEmpty(controller.GetConfigurationCopy().pacUrl)) | |||
{ | |||
localPACItem.Checked = false; | |||
onlinePACItem.Checked = true; | |||
@@ -527,7 +563,7 @@ namespace Shadowsocks.View | |||
private void UpdateOnlinePACURLItem_Click(object sender, EventArgs e) | |||
{ | |||
string origPacUrl = controller.GetConfiguration().pacUrl; | |||
string origPacUrl = controller.GetConfigurationCopy().pacUrl; | |||
string pacUrl = Microsoft.VisualBasic.Interaction.InputBox( | |||
I18N.GetString("Please input PAC Url"), | |||
I18N.GetString("Edit Online PAC URL"), | |||
@@ -29,6 +29,7 @@ | |||
private void InitializeComponent() | |||
{ | |||
this.pictureBox1 = new System.Windows.Forms.PictureBox(); | |||
this.listBox1 = new System.Windows.Forms.ListBox(); | |||
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); | |||
this.SuspendLayout(); | |||
// | |||
@@ -42,6 +43,19 @@ | |||
this.pictureBox1.TabIndex = 1; | |||
this.pictureBox1.TabStop = false; | |||
// | |||
// listBox1 | |||
// | |||
this.listBox1.DisplayMember = "Value"; | |||
this.listBox1.FormattingEnabled = true; | |||
this.listBox1.ItemHeight = 12; | |||
this.listBox1.Location = new System.Drawing.Point(224, 10); | |||
this.listBox1.Name = "listBox1"; | |||
this.listBox1.ScrollAlwaysVisible = true; | |||
this.listBox1.Size = new System.Drawing.Size(227, 208); | |||
this.listBox1.TabIndex = 2; | |||
this.listBox1.ValueMember = "Key"; | |||
this.listBox1.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged); | |||
// | |||
// QRCodeForm | |||
// | |||
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); | |||
@@ -49,7 +63,8 @@ | |||
this.AutoSize = true; | |||
this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; | |||
this.BackColor = System.Drawing.Color.White; | |||
this.ClientSize = new System.Drawing.Size(338, 274); | |||
this.ClientSize = new System.Drawing.Size(457, 228); | |||
this.Controls.Add(this.listBox1); | |||
this.Controls.Add(this.pictureBox1); | |||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; | |||
this.MaximizeBox = false; | |||
@@ -67,5 +82,6 @@ | |||
#endregion | |||
private System.Windows.Forms.PictureBox pictureBox1; | |||
private System.Windows.Forms.ListBox listBox1; | |||
} | |||
} |
@@ -8,8 +8,10 @@ using System.Data; | |||
using System.Drawing; | |||
using System.IO; | |||
using System.IO.Compression; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Windows.Forms; | |||
using Shadowsocks.Model; | |||
namespace Shadowsocks.View | |||
{ | |||
@@ -30,8 +32,8 @@ namespace Shadowsocks.View | |||
string qrText = ssconfig; | |||
QRCode code = ZXing.QrCode.Internal.Encoder.encode(qrText, ErrorCorrectionLevel.M); | |||
ByteMatrix m = code.Matrix; | |||
int blockSize = Math.Max(pictureBox1.Height / m.Height, 1); | |||
Bitmap drawArea = new Bitmap((m.Width * blockSize), (m.Height * blockSize)); | |||
int blockSize = Math.Max(pictureBox1.Height/m.Height, 1); | |||
Bitmap drawArea = new Bitmap((m.Width*blockSize), (m.Height*blockSize)); | |||
using (Graphics g = Graphics.FromImage(drawArea)) | |||
{ | |||
g.Clear(Color.White); | |||
@@ -43,7 +45,7 @@ namespace Shadowsocks.View | |||
{ | |||
if (m[row, col] != 0) | |||
{ | |||
g.FillRectangle(b, blockSize * row, blockSize * col, blockSize, blockSize); | |||
g.FillRectangle(b, blockSize*row, blockSize*col, blockSize, blockSize); | |||
} | |||
} | |||
} | |||
@@ -54,7 +56,20 @@ namespace Shadowsocks.View | |||
private void QRCodeForm_Load(object sender, EventArgs e) | |||
{ | |||
GenQR(code); | |||
var servers = Configuration.Load(); | |||
var serverDatas = servers.configs.Select( | |||
server => | |||
new KeyValuePair<string, string>(ShadowsocksController.GetQRCode(server), server.FriendlyName()) | |||
).ToList(); | |||
listBox1.DataSource = serverDatas; | |||
var selectIndex = serverDatas.FindIndex(serverData => serverData.Key.StartsWith(code)); | |||
if (selectIndex >= 0) listBox1.SetSelected(selectIndex, true); | |||
} | |||
private void listBox1_SelectedIndexChanged(object sender, EventArgs e) | |||
{ | |||
GenQR((sender as ListBox)?.SelectedValue.ToString()); | |||
} | |||
} | |||
} |
@@ -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> |
@@ -242,7 +242,6 @@ namespace Shadowsocks.View | |||
SetBitmap(bitmap, 255); | |||
} | |||
/// <para>Changes the current bitmap with a custom opacity level. Here is where all happens!</para> | |||
public void SetBitmap(Bitmap bitmap, byte opacity) | |||
{ | |||
@@ -288,7 +287,6 @@ namespace Shadowsocks.View | |||
} | |||
} | |||
protected override CreateParams CreateParams | |||
{ | |||
get | |||
@@ -1,5 +1,5 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<PropertyGroup> | |||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | |||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | |||
@@ -10,7 +10,7 @@ | |||
<AppDesignerFolder>Properties</AppDesignerFolder> | |||
<RootNamespace>Shadowsocks</RootNamespace> | |||
<AssemblyName>Shadowsocks</AssemblyName> | |||
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion> | |||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion> | |||
<FileAlignment>512</FileAlignment> | |||
<StartupObject> | |||
</StartupObject> | |||
@@ -21,8 +21,7 @@ | |||
<UpgradeBackupLocation> | |||
</UpgradeBackupLocation> | |||
<OldToolsVersion>3.5</OldToolsVersion> | |||
<TargetFrameworkProfile> | |||
</TargetFrameworkProfile> | |||
<TargetFrameworkProfile>Client</TargetFrameworkProfile> | |||
<PublishUrl>publish\</PublishUrl> | |||
<Install>true</Install> | |||
<InstallFrom>Disk</InstallFrom> | |||
@@ -124,15 +123,18 @@ | |||
<Compile Include="3rd\zxing\ResultPoint.cs" /> | |||
<Compile Include="3rd\zxing\ResultPointCallback.cs" /> | |||
<Compile Include="3rd\zxing\WriterException.cs" /> | |||
<Compile Include="Controller\AutoStartup.cs" /> | |||
<Compile Include="Controller\Service\AvailabilityStatistics.cs" /> | |||
<Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" /> | |||
<Compile Include="Controller\Strategy\SimplyChooseByStatisticsStrategy.cs" /> | |||
<Compile Include="Controller\System\AutoStartup.cs" /> | |||
<Compile Include="Controller\FileManager.cs" /> | |||
<Compile Include="Controller\GFWListUpdater.cs" /> | |||
<Compile Include="Controller\Service\GFWListUpdater.cs" /> | |||
<Compile Include="Controller\I18N.cs" /> | |||
<Compile Include="Controller\Listener.cs" /> | |||
<Compile Include="Controller\Service\Listener.cs" /> | |||
<Compile Include="Controller\Logging.cs" /> | |||
<Compile Include="Controller\PortForwarder.cs" /> | |||
<Compile Include="Controller\UDPRelay.cs" /> | |||
<Compile Include="Controller\UpdateChecker.cs" /> | |||
<Compile Include="Controller\Service\PortForwarder.cs" /> | |||
<Compile Include="Controller\Service\UDPRelay.cs" /> | |||
<Compile Include="Controller\Service\UpdateChecker.cs" /> | |||
<Compile Include="Encryption\EncryptorBase.cs" /> | |||
<Compile Include="Encryption\EncryptorFactory.cs" /> | |||
<Compile Include="Encryption\IVEncryptor.cs" /> | |||
@@ -142,7 +144,7 @@ | |||
<Compile Include="Encryption\SodiumEncryptor.cs" /> | |||
<Compile Include="Encryption\TableEncryptor.cs" /> | |||
<Compile Include="Encryption\IEncryptor.cs" /> | |||
<Compile Include="Controller\PACServer.cs" /> | |||
<Compile Include="Controller\Service\PACServer.cs" /> | |||
<Compile Include="Model\Server.cs" /> | |||
<Compile Include="Model\Configuration.cs" /> | |||
<Compile Include="Properties\Resources.Designer.cs"> | |||
@@ -150,6 +152,9 @@ | |||
<DesignTime>True</DesignTime> | |||
<DependentUpon>Resources.resx</DependentUpon> | |||
</Compile> | |||
<Compile Include="Controller\Strategy\BalancingStrategy.cs" /> | |||
<Compile Include="Controller\Strategy\StrategyManager.cs" /> | |||
<Compile Include="Controller\Strategy\IStrategy.cs" /> | |||
<Compile Include="Util\Util.cs" /> | |||
<Compile Include="View\ConfigForm.cs"> | |||
<SubType>Form</SubType> | |||
@@ -157,12 +162,18 @@ | |||
<Compile Include="View\ConfigForm.Designer.cs"> | |||
<DependentUpon>ConfigForm.cs</DependentUpon> | |||
</Compile> | |||
<Compile Include="Controller\TCPRelay.cs" /> | |||
<Compile Include="Controller\PolipoRunner.cs" /> | |||
<Compile Include="Controller\Service\TCPRelay.cs" /> | |||
<Compile Include="Controller\Service\PolipoRunner.cs" /> | |||
<Compile Include="Program.cs" /> | |||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||
<Compile Include="Controller\ShadowsocksController.cs" /> | |||
<Compile Include="Controller\SystemProxy.cs" /> | |||
<Compile Include="Controller\System\SystemProxy.cs" /> | |||
<Compile Include="View\LogForm.cs"> | |||
<SubType>Form</SubType> | |||
</Compile> | |||
<Compile Include="View\LogForm.Designer.cs"> | |||
<DependentUpon>LogForm.cs</DependentUpon> | |||
</Compile> | |||
<Compile Include="View\MenuViewController.cs" /> | |||
<Compile Include="View\QRCodeForm.cs"> | |||
<SubType>Form</SubType> | |||
@@ -182,6 +193,9 @@ | |||
<SubType>Designer</SubType> | |||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\LogForm.resx"> | |||
<DependentUpon>LogForm.cs</DependentUpon> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\QRCodeForm.resx"> | |||
<DependentUpon>QRCodeForm.cs</DependentUpon> | |||
</EmbeddedResource> | |||
@@ -191,7 +205,8 @@ | |||
</None> | |||
<None Include="Data\abp.js.gz" /> | |||
<None Include="Data\libsscrypto.dll.gz" /> | |||
<None Include="Data\polipo.exe.gz" /> | |||
<None Include="Data\mgwz.dll.gz" /> | |||
<None Include="Data\privoxy.exe.gz" /> | |||
<None Include="Data\proxy.pac.txt.gz" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
@@ -200,9 +215,9 @@ | |||
<None Include="Resources\ss24.png" /> | |||
<None Include="Resources\ssw128.png" /> | |||
<Content Include="Data\cn.txt" /> | |||
<Content Include="Data\privoxy_conf.txt" /> | |||
<Content Include="Data\user-rule.txt" /> | |||
<Content Include="shadowsocks.ico" /> | |||
<None Include="Data\polipo_config.txt" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<BootstrapperPackage Include="Microsoft.Net.Client.3.5"> | |||