Browse Source

🔀 Merge branch 'cleanup-qr-capture' into v5/master

pull/2977/head
database64128 3 years ago
parent
commit
138b75cf79
No known key found for this signature in database GPG Key ID: 1CA27546BEDB8B01
72 changed files with 4042 additions and 5048 deletions
  1. +12
    -0
      CHANGES
  2. +52
    -37
      README.md
  3. +2
    -2
      shadowsocks-csharp/Controller/HotkeyReg.cs
  4. +0
    -534
      shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs
  5. +149
    -79
      shadowsocks-csharp/Controller/Service/GeositeUpdater.cs
  6. +2
    -14
      shadowsocks-csharp/Controller/Service/OnlineConfigResolver.cs
  7. +2
    -2
      shadowsocks-csharp/Controller/Service/PACDaemon.cs
  8. +1
    -1
      shadowsocks-csharp/Controller/Service/PACServer.cs
  9. +2
    -1
      shadowsocks-csharp/Controller/Service/Sip003Plugin.cs
  10. +3
    -3
      shadowsocks-csharp/Controller/Service/TCPRelay.cs
  11. +119
    -196
      shadowsocks-csharp/Controller/Service/UpdateChecker.cs
  12. +74
    -82
      shadowsocks-csharp/Controller/ShadowsocksController.cs
  13. +0
    -170
      shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs
  14. +0
    -1
      shadowsocks-csharp/Controller/Strategy/StrategyManager.cs
  15. +6
    -7
      shadowsocks-csharp/Controller/System/Hotkeys/HotkeyCallbacks.cs
  16. +1
    -1
      shadowsocks-csharp/Controller/System/Hotkeys/Hotkeys.cs
  17. +1
    -1
      shadowsocks-csharp/Controller/System/SystemProxy.cs
  18. +2
    -67
      shadowsocks-csharp/Data/i18n.csv
  19. +1
    -1
      shadowsocks-csharp/Encryption/EncryptorFactory.cs
  20. +1
    -0
      shadowsocks-csharp/FodyWeavers.xml
  21. +1
    -0
      shadowsocks-csharp/FodyWeavers.xsd
  22. +13
    -0
      shadowsocks-csharp/Localization/LocalizationProvider.cs
  23. +432
    -0
      shadowsocks-csharp/Localization/Strings.Designer.cs
  24. +182
    -119
      shadowsocks-csharp/Localization/Strings.fr.resx
  25. +179
    -119
      shadowsocks-csharp/Localization/Strings.ja.resx
  26. +60
    -9
      shadowsocks-csharp/Localization/Strings.ko.resx
  27. +243
    -0
      shadowsocks-csharp/Localization/Strings.resx
  28. +182
    -119
      shadowsocks-csharp/Localization/Strings.ru.resx
  29. +237
    -0
      shadowsocks-csharp/Localization/Strings.zh-Hans.resx
  30. +189
    -0
      shadowsocks-csharp/Localization/Strings.zh-Hant.resx
  31. +160
    -96
      shadowsocks-csharp/Model/Configuration.cs
  32. +43
    -43
      shadowsocks-csharp/Model/ForwardProxyConfig.cs
  33. +6
    -10
      shadowsocks-csharp/Model/Server.cs
  34. +0
    -95
      shadowsocks-csharp/Model/StatisticsRecord.cs
  35. +0
    -69
      shadowsocks-csharp/Model/StatisticsStrategyConfiguration.cs
  36. +15
    -1
      shadowsocks-csharp/Program.cs
  37. +0
    -10
      shadowsocks-csharp/Properties/DataSources/Shadowsocks.Model.StatisticsStrategyConfiguration.datasource
  38. +57
    -54
      shadowsocks-csharp/Properties/Resources.Designer.cs
  39. +1
    -3
      shadowsocks-csharp/Proxy/HttpProxy.cs
  40. +0
    -314
      shadowsocks-csharp/StringEx.cs
  41. +46
    -5
      shadowsocks-csharp/Util/Util.cs
  42. +0
    -114
      shadowsocks-csharp/View/CalculationControl.Designer.cs
  43. +0
    -25
      shadowsocks-csharp/View/CalculationControl.cs
  44. +2
    -2
      shadowsocks-csharp/View/ConfigForm.cs
  45. +0
    -190
      shadowsocks-csharp/View/HotkeySettingsForm.cs
  46. +0
    -346
      shadowsocks-csharp/View/HotkeySettingsForm.designer.cs
  47. +0
    -183
      shadowsocks-csharp/View/HotkeySettingsForm.resx
  48. +3
    -3
      shadowsocks-csharp/View/LogForm.cs
  49. +229
    -221
      shadowsocks-csharp/View/MenuViewController.cs
  50. +0
    -333
      shadowsocks-csharp/View/ProxyForm.Designer.cs
  51. +0
    -176
      shadowsocks-csharp/View/ProxyForm.cs
  52. +0
    -100
      shadowsocks-csharp/View/QRCodeForm.Designer.cs
  53. +0
    -90
      shadowsocks-csharp/View/QRCodeForm.cs
  54. +0
    -281
      shadowsocks-csharp/View/QRCodeSplashForm.cs
  55. +0
    -537
      shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.Designer.cs
  56. +0
    -170
      shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs
  57. +131
    -0
      shadowsocks-csharp/ViewModels/ForwardProxyViewModel.cs
  58. +189
    -0
      shadowsocks-csharp/ViewModels/HotkeysViewModel.cs
  59. +115
    -0
      shadowsocks-csharp/ViewModels/OnlineConfigViewModel.cs
  60. +96
    -0
      shadowsocks-csharp/ViewModels/ServerSharingViewModel.cs
  61. +34
    -0
      shadowsocks-csharp/ViewModels/VersionUpdatePromptViewModel.cs
  62. +100
    -0
      shadowsocks-csharp/Views/ForwardProxyView.xaml
  63. +84
    -0
      shadowsocks-csharp/Views/ForwardProxyView.xaml.cs
  64. +115
    -0
      shadowsocks-csharp/Views/HotkeysView.xaml
  65. +169
    -0
      shadowsocks-csharp/Views/HotkeysView.xaml.cs
  66. +61
    -0
      shadowsocks-csharp/Views/OnlineConfigView.xaml
  67. +54
    -0
      shadowsocks-csharp/Views/OnlineConfigView.xaml.cs
  68. +53
    -0
      shadowsocks-csharp/Views/ServerSharingView.xaml
  69. +48
    -0
      shadowsocks-csharp/Views/ServerSharingView.xaml.cs
  70. +43
    -0
      shadowsocks-csharp/Views/VersionUpdatePromptView.xaml
  71. +40
    -0
      shadowsocks-csharp/Views/VersionUpdatePromptView.xaml.cs
  72. +0
    -12
      test/UnitTest.cs

+ 12
- 0
CHANGES View File

@@ -1,3 +1,15 @@
4.3.0.0 2020-10-19
- Cleanup and update dependencies (#2983)
- Geosite group validation + PAC regeneration on version update (#2988)
- PAC: add options for direct and proxied groups (#2990)
- Transition to WPF: ForwardProxyView + HotkeysView + OnlineConfigView + VersionUpdatePromptView (#2991)
4.2.1.0 2020-10-12
- SIP008 support (#2942)
- Exclude @cn from PAC proxied list (#2982)
- Transition to WPF: ReactiveUI and ServerSharingView (#2959)
- User-Agent for OnlineConfigResolver and GeositeUpdater (#2978)
4.2.0.1 2020-09-26 4.2.0.1 2020-09-26
- Fix domain rule handling in PAC script (#2956) - Fix domain rule handling in PAC script (#2956)


+ 52
- 37
README.md View File

@@ -5,7 +5,7 @@
[中文说明] [中文说明]
#### Features
## Features
1. System proxy configuration 1. System proxy configuration
2. PAC mode and global mode 2. PAC mode and global mode
@@ -15,15 +15,15 @@
6. Supports UDP relay (see Usage) 6. Supports UDP relay (see Usage)
7. Supports plugins 7. Supports plugins
#### Download
## Downloads
Download the latest release from [release page]. Download the latest release from [release page].
#### Requirements
## Requirements
Microsoft [.NET Framework 4.7.2] or higher, Microsoft [Visual C++ 2015 Redistributable] (x86) . Microsoft [.NET Framework 4.7.2] or higher, Microsoft [Visual C++ 2015 Redistributable] (x86) .
#### Basic
## Basics
1. Find Shadowsocks icon in the notification tray 1. Find Shadowsocks icon in the notification tray
2. You can add multiple servers in servers menu 2. You can add multiple servers in servers menu
@@ -33,19 +33,32 @@ proxy addons in your browser, or set them to use system proxy
system proxy. Set Socks5 or HTTP proxy to 127.0.0.1:1080. You can change this system proxy. Set Socks5 or HTTP proxy to 127.0.0.1:1080. You can change this
port in `Servers -> Edit Servers` port in `Servers -> Edit Servers`
#### PAC
## PAC
1. You can change PAC rules by editing the PAC file. When you save the PAC file
with any editor, Shadowsocks will notify browsers about the change automatically
2. You can also update PAC file from [GeoSite] \(maintained by 3rd party)
3. You can also use online PAC URL
- The PAC rules are generated from the geosite database in [v2fly/domain-list-community](https://github.com/v2fly/domain-list-community).
- Generation modes: whitelist mode and blacklist mode.
- Domain groups: `geositeDirectGroups` and `geositeProxiedGroups`.
- `geositeDirectGroups` is initialized with `cn` and `geolocation-!cn@cn`.
- `geositeProxiedGroups` is initialized with `geolocation-!cn`.
- To switch between different modes, modify the `geositePreferDirect` property in `gui-config.json`
- When `geositePreferDirect` is false (default), PAC works in whitelist mode. Exception rules are generated from `geositeDirectGroups`. Unmatched domains goes through the proxy.
- When `geositePreferDirect` is true, PAC works in blacklist mode. Blocking rules are generated from `geositeProxiedGroups`. Exception rules are generated from `geositeDirectGroups`. Unmatched domains are connected to directly.
- Starting from 4.3.0.0, shadowsocks-windows defaults to whitelist mode with Chinese domains excluded from connecting via the proxy.
- The new default values make sure that:
- When in whitelist mode, Chinese domains, including non-Chinese companies' Chinese CDNs, are connected to directly.
- When in blacklist mode, only non-Chinese domains goes through the proxy. Chinese domains, as well as non-Chinese companies' Chinese CDNs, are connected to directly.
### User-defined rules
- To define your own PAC rules, it's recommended to use the `user-rule.txt` file.
- You can also modify `pac.txt` directly. But your modifications won't persist after updating geosite from the upstream.
For Windows10 Store and related applications, please execute the following command under Admin privilege: For Windows10 Store and related applications, please execute the following command under Admin privilege:
``` ```
netsh winhttp import proxy source=ie netsh winhttp import proxy source=ie
``` ```
#### Server Auto Switching
## Server Auto Switching
1. Load balance: choosing server randomly 1. Load balance: choosing server randomly
2. High availability: choosing the best server (low latency and packet loss) 2. High availability: choosing the best server (low latency and packet loss)
@@ -53,18 +66,18 @@ netsh winhttp import proxy source=ie
`Availability Statistics` in the menu if you want to use this `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! 4. Write your own strategy by implement IStrategy interface and send us a pull request!
#### UDP
## UDP
For UDP, you need to use SocksCap or ProxyCap to force programs you want For UDP, you need to use SocksCap or ProxyCap to force programs you want
to be proxied to tunnel over Shadowsocks to be proxied to tunnel over Shadowsocks
#### Multiple Instances
## Multiple Instances
If you want to manage multiple servers using other tools like SwitchyOmega, If you want to manage multiple servers using other tools like SwitchyOmega,
you can start multiple Shadowsocks instances. To avoid configuration conflicts, you can start multiple Shadowsocks instances. To avoid configuration conflicts,
copy Shadowsocks to a new directory and choose a different local port. copy Shadowsocks to a new directory and choose a different local port.
#### Plugins
## Plugins
If you would like to connect to server via a plugin, please set the plugin's If you would like to connect to server via a plugin, please set the plugin's
path (relative or absolute) on Edit Servers form. path (relative or absolute) on Edit Servers form.
@@ -73,70 +86,72 @@ _Note_: Forward Proxy will not be used while a plugin is enabled.
Details: Details:
[Working with non SIP003 standard Plugin]. [Working with non SIP003 standard Plugin].
#### Global hotkeys
## Global hotkeys
Hotkeys could be registered automatically on startup. Hotkeys could be registered automatically on startup.
If you are using multiple instances of Shadowsocks, If you are using multiple instances of Shadowsocks,
you must set different key combination for each instance. you must set different key combination for each instance.
##### How to input?
### How to input?
1. Put focus in the corresponding textbox. 1. Put focus in the corresponding textbox.
2. Press the key combination that you want to use. 2. Press the key combination that you want to use.
3. Release all keys when you think it is ready. 3. Release all keys when you think it is ready.
4. Your input appears in the textbox. 4. Your input appears in the textbox.
##### How to change?
### How to change?
1. Put focus in the corresponding textbox. 1. Put focus in the corresponding textbox.
2. Press BackSpace key to clear content. 2. Press BackSpace key to clear content.
3. Re-input new key combination. 3. Re-input new key combination.
##### How to deactivate?
### How to deactivate?
1. Clear content in the textbox that you want to deactivate, 1. Clear content in the textbox that you want to deactivate,
if you want to deactivate all, please clear all textboxes. if you want to deactivate all, please clear all textboxes.
2. Press OK button to confirm. 2. Press OK button to confirm.
##### Meaning of label color
### Meaning of label color
- Green: This key combination is not occupied by other programs and register successfully. - Green: This key combination is not occupied by other programs and register successfully.
- Yellow: This key combination is occupied by other programs and you have to change to another one. - Yellow: This key combination is occupied by other programs and you have to change to another one.
- Transparent without color: The initial status. - Transparent without color: The initial status.
#### Server Configuration
## Server Configuration
Please visit [Servers] for more information. Please visit [Servers] for more information.
#### Experimental
## Experimental
[Experimental Features] [Experimental Features]
#### Development
## Development
1. [Visual Studio 2017] & [.NET Framework 4.7.2 Developer Pack] are required.
1. [Visual Studio 2019] & [.NET Framework 4.7.2 Developer Pack] are required.
2. It is recommended to share your idea on the Issue Board before you start to work, 2. It is recommended to share your idea on the Issue Board before you start to work,
especially for feature development. especially for feature development.
#### License
## License
[GPLv3] [GPLv3]
#### Open Source Components / Libraries
## Open Source Components / Libraries
``` ```
Caseless.Fody (MIT) https://github.com/Fody/Caseless
Costura.Fody (MIT) https://github.com/Fody/Costura
Fody (MIT) https://github.com/Fody/Fody
GlobalHotKey (GPLv3) https://github.com/kirmir/GlobalHotKey
Newtonsoft.Json (MIT) https://www.newtonsoft.com/json
StringEx.CS () https://github.com/LazyMode/StringEx
ZXing.Net (Apache 2.0) https://github.com/micjahn/ZXing.Net
BouncyCastle.NetCore (MIT) https://github.com/chrishaly/bc-csharp
NaCl.Core (MIT) https://github.com/idaviddesmet/NaCl.Core
Privoxy (GPLv2) https://www.privoxy.org
Sysproxy () https://github.com/Noisyfox/sysproxy
BouncyCastle.NetCore (MIT) https://github.com/chrishaly/bc-csharp
Caseless.Fody (MIT) https://github.com/Fody/Caseless
Costura.Fody (MIT) https://github.com/Fody/Costura
Fody (MIT) https://github.com/Fody/Fody
GlobalHotKey (GPLv3) https://github.com/kirmir/GlobalHotKey
MdXaml (MIT) https://github.com/whistyun/MdXaml
Newtonsoft.Json (MIT) https://www.newtonsoft.com/json
Privoxy (GPLv2) https://www.privoxy.org
ReactiveUI.WPF (MIT) https://github.com/reactiveui/ReactiveUI
ReactiveUI.Events.WPF (MIT) https://github.com/reactiveui/ReactiveUI
ReactiveUI.Fody (MIT) https://github.com/reactiveui/ReactiveUI
ReactiveUI.Validation (MIT) https://github.com/reactiveui/ReactiveUI.Validation
WPFLocalizationExtension (MS-PL) https://github.com/XAMLMarkupExtensions/WPFLocalizationExtension/
ZXing.Net (Apache 2.0) https://github.com/micjahn/ZXing.Net
``` ```
@@ -153,4 +168,4 @@ Sysproxy () https://github.com/Noisyfox/sysproxy
[Visual C++ 2015 Redistributable]: https://www.microsoft.com/en-us/download/details.aspx?id=53840 [Visual C++ 2015 Redistributable]: https://www.microsoft.com/en-us/download/details.aspx?id=53840
[GPLv3]: https://github.com/shadowsocks/shadowsocks-windows/blob/master/LICENSE.txt [GPLv3]: https://github.com/shadowsocks/shadowsocks-windows/blob/master/LICENSE.txt
[Working with non SIP003 standard Plugin]: https://github.com/shadowsocks/shadowsocks-windows/wiki/Working-with-non-SIP003-standard-Plugin [Working with non SIP003 standard Plugin]: https://github.com/shadowsocks/shadowsocks-windows/wiki/Working-with-non-SIP003-standard-Plugin
[Experimental Features]: https://github.com/shadowsocks/shadowsocks-windows/wiki/Experimental
[Experimental Features]: https://github.com/shadowsocks/shadowsocks-windows/wiki/Experimental

+ 2
- 2
shadowsocks-csharp/Controller/HotkeyReg.cs View File

@@ -12,7 +12,7 @@ namespace Shadowsocks.Controller
private static Logger logger = LogManager.GetCurrentClassLogger(); private static Logger logger = LogManager.GetCurrentClassLogger();
public static void RegAllHotkeys() public static void RegAllHotkeys()
{ {
var hotkeyConfig = Configuration.Load().hotkey;
var hotkeyConfig = Program.MainController.GetCurrentConfiguration().hotkey;


if (hotkeyConfig == null || !hotkeyConfig.RegHotkeysAtStartup) if (hotkeyConfig == null || !hotkeyConfig.RegHotkeysAtStartup)
return; return;
@@ -50,7 +50,7 @@ namespace Shadowsocks.Controller


var callback = _callback as HotKeys.HotKeyCallBackHandler; var callback = _callback as HotKeys.HotKeyCallBackHandler;


if (hotkeyStr.IsNullOrEmpty())
if (string.IsNullOrEmpty(hotkeyStr))
{ {
HotKeys.UnregExistingHotkey(callback); HotKeys.UnregExistingHotkey(callback);
onComplete?.Invoke(RegResult.UnregSuccess); onComplete?.Invoke(RegResult.UnregSuccess);


+ 0
- 534
shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs View File

@@ -1,534 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NLog;
using Shadowsocks.Model;
using Shadowsocks.Util;
namespace Shadowsocks.Controller
{
using Statistics = Dictionary<string, List<StatisticsRecord>>;
public sealed class AvailabilityStatistics : IDisposable
{
private static Logger logger = LogManager.GetCurrentClassLogger();
public const string DateTimePattern = "yyyy-MM-dd HH:mm:ss";
private const string StatisticsFilesName = "shadowsocks.availability.json";
public static string AvailabilityStatisticsFile;
//static constructor to initialize every public static fields before refereced
static AvailabilityStatistics()
{
AvailabilityStatisticsFile = Utils.GetTempPath(StatisticsFilesName);
}
//arguments for ICMP tests
private int Repeat => Config.RepeatTimesNum;
public const int TimeoutMilliseconds = 500;
//records cache for current server in {_monitorInterval} minutes
private readonly ConcurrentDictionary<string, List<int>> _latencyRecords = new ConcurrentDictionary<string, List<int>>();
//speed in KiB/s
private readonly ConcurrentDictionary<string, List<int>> _inboundSpeedRecords = new ConcurrentDictionary<string, List<int>>();
private readonly ConcurrentDictionary<string, List<int>> _outboundSpeedRecords = new ConcurrentDictionary<string, List<int>>();
private readonly ConcurrentDictionary<string, InOutBoundRecord> _inOutBoundRecords = new ConcurrentDictionary<string, InOutBoundRecord>();
private class InOutBoundRecord
{
private long _inbound;
private long _lastInbound;
private long _outbound;
private long _lastOutbound;
public void UpdateInbound(long delta)
{
Interlocked.Add(ref _inbound, delta);
}
public void UpdateOutbound(long delta)
{
Interlocked.Add(ref _outbound, delta);
}
public void GetDelta(out long inboundDelta, out long outboundDelta)
{
var i = Interlocked.Read(ref _inbound);
var il = Interlocked.Exchange(ref _lastInbound, i);
inboundDelta = i - il;
var o = Interlocked.Read(ref _outbound);
var ol = Interlocked.Exchange(ref _lastOutbound, o);
outboundDelta = o - ol;
}
}
//tasks
private readonly TimeSpan _delayBeforeStart = TimeSpan.FromSeconds(1);
private readonly TimeSpan _retryInterval = TimeSpan.FromMinutes(2);
private TimeSpan RecordingInterval => TimeSpan.FromMinutes(Config.DataCollectionMinutes);
private Timer _perSecondTimer; //analyze and save cached records to RawStatistics and filter records
private readonly TimeSpan _monitorInterval = TimeSpan.FromSeconds(1);
//private Timer _writer; //write RawStatistics to file
//private readonly TimeSpan _writingInterval = TimeSpan.FromMinutes(1);
private ShadowsocksController _controller;
private StatisticsStrategyConfiguration Config => _controller.StatisticsConfiguration;
// Static Singleton Initialization
public static AvailabilityStatistics Instance { get; } = new AvailabilityStatistics();
public Statistics RawStatistics { get; private set; }
public Statistics FilteredStatistics { get; private set; }
private AvailabilityStatistics()
{
RawStatistics = new Statistics();
}
internal void UpdateConfiguration(ShadowsocksController controller)
{
_controller = controller;
Reset();
try
{
if (Config.StatisticsEnabled)
{
LoadRawStatistics();
if (_perSecondTimer == null)
{
_perSecondTimer = new Timer(OperationsPerSecond, new Counter(), _delayBeforeStart, TimeSpan.FromSeconds(1));
}
}
else
{
_perSecondTimer?.Dispose();
}
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
private void OperationsPerSecond(object state)
{
lock(state)
{
var counter = state as Counter;
if (counter.count % _monitorInterval.TotalSeconds == 0)
{
UpdateSpeed();
}
if (counter.count % RecordingInterval.TotalSeconds == 0)
{
Run();
}
counter.count++;
}
}
private void UpdateSpeed()
{
foreach (var kv in _inOutBoundRecords)
{
var id = kv.Key;
var record = kv.Value;
long inboundDelta, outboundDelta;
record.GetDelta(out inboundDelta, out outboundDelta);
var inboundSpeed = GetSpeedInKiBPerSecond(inboundDelta, _monitorInterval.TotalSeconds);
var outboundSpeed = GetSpeedInKiBPerSecond(outboundDelta, _monitorInterval.TotalSeconds);
// not thread safe
var inR = _inboundSpeedRecords.GetOrAdd(id, (k) => new List<int>());
var outR = _outboundSpeedRecords.GetOrAdd(id, (k) => new List<int>());
inR.Add(inboundSpeed);
outR.Add(outboundSpeed);
logger.Debug(
$"{id}: current/max inbound {inboundSpeed}/{inR.Max()} KiB/s, current/max outbound {outboundSpeed}/{outR.Max()} KiB/s");
}
}
private void Reset()
{
_inboundSpeedRecords.Clear();
_outboundSpeedRecords.Clear();
_latencyRecords.Clear();
}
private void Run()
{
UpdateRecords();
Reset();
}
private void UpdateRecords()
{
var records = new Dictionary<string, StatisticsRecord>();
UpdateRecordsState state = new UpdateRecordsState();
int serverCount = _controller.GetCurrentConfiguration().configs.Count;
state.counter = serverCount;
bool isPing = Config.Ping;
for (int i = 0; i < serverCount; i++)
{
try
{
var server = _controller.GetCurrentConfiguration().configs[i];
var id = server.Identifier();
List<int> inboundSpeedRecords = null;
List<int> outboundSpeedRecords = null;
List<int> latencyRecords = null;
_inboundSpeedRecords.TryGetValue(id, out inboundSpeedRecords);
_outboundSpeedRecords.TryGetValue(id, out outboundSpeedRecords);
_latencyRecords.TryGetValue(id, out latencyRecords);
StatisticsRecord record = new StatisticsRecord(id, inboundSpeedRecords, outboundSpeedRecords, latencyRecords);
/* duplicate server identifier */
if (records.ContainsKey(id))
records[id] = record;
else
records.Add(id, record);
if (isPing)
{
// FIXME: on ping completed, every thing could be asynchrously changed.
// focus on: Config/ RawStatistics
MyPing ping = new MyPing(server, Repeat);
ping.Completed += ping_Completed;
ping.Start(new PingState { state = state, record = record });
}
else if (!record.IsEmptyData())
{
AppendRecord(id, record);
}
}
catch (Exception e)
{
logger.Debug("config changed asynchrously, just ignore this server");
}
}
if (!isPing)
{
Save();
FilterRawStatistics();
}
}
private void ping_Completed(object sender, MyPing.CompletedEventArgs e)
{
PingState pingState = (PingState)e.UserState;
UpdateRecordsState state = pingState.state;
Server server = e.Server;
StatisticsRecord record = pingState.record;
record.SetResponse(e.RoundtripTime);
if (!record.IsEmptyData())
{
AppendRecord(server.Identifier(), record);
}
logger.Debug($"Ping {server} {e.RoundtripTime.Count} times, {(100 - record.PackageLoss * 100)}% packages loss, min {record.MinResponse} ms, max {record.MaxResponse} ms, avg {record.AverageResponse} ms");
if (Interlocked.Decrement(ref state.counter) == 0)
{
Save();
FilterRawStatistics();
}
}
private void AppendRecord(string serverIdentifier, StatisticsRecord record)
{
try
{
List<StatisticsRecord> records;
lock (RawStatistics)
{
if (!RawStatistics.TryGetValue(serverIdentifier, out records))
{
records = new List<StatisticsRecord>();
RawStatistics[serverIdentifier] = records;
}
}
records.Add(record);
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
private void Save()
{
logger.Debug($"save statistics to {AvailabilityStatisticsFile}");
if (RawStatistics.Count == 0)
{
return;
}
try
{
string content;
#if DEBUG
content = JsonConvert.SerializeObject(RawStatistics, Formatting.Indented);
#else
content = JsonConvert.SerializeObject(RawStatistics, Formatting.None);
#endif
File.WriteAllText(AvailabilityStatisticsFile, content);
}
catch (IOException e)
{
logger.LogUsefulException(e);
}
}
private bool IsValidRecord(StatisticsRecord record)
{
if (Config.ByHourOfDay)
{
if (!record.Timestamp.Hour.Equals(DateTime.Now.Hour)) return false;
}
return true;
}
private void FilterRawStatistics()
{
try
{
logger.Debug("filter raw statistics");
if (RawStatistics == null) return;
var filteredStatistics = new Statistics();
foreach (var serverAndRecords in RawStatistics)
{
var server = serverAndRecords.Key;
var filteredRecords = serverAndRecords.Value.FindAll(IsValidRecord);
filteredStatistics[server] = filteredRecords;
}
FilteredStatistics = filteredStatistics;
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
private void LoadRawStatistics()
{
try
{
var path = AvailabilityStatisticsFile;
logger.Debug($"loading statistics from {path}");
if (!File.Exists(path))
{
using (File.Create(path))
{
//do nothing
}
}
var content = File.ReadAllText(path);
RawStatistics = JsonConvert.DeserializeObject<Statistics>(content) ?? RawStatistics;
}
catch (Exception e)
{
logger.LogUsefulException(e);
Console.WriteLine($"failed to load statistics; use runtime statistics, some data may be lost");
}
}
private static int GetSpeedInKiBPerSecond(long bytes, double seconds)
{
var result = (int)(bytes / seconds) / 1024;
return result;
}
public void Dispose()
{
_perSecondTimer.Dispose();
}
public void UpdateLatency(Server server, int latency)
{
_latencyRecords.GetOrAdd(server.Identifier(), (k) =>
{
List<int> records = new List<int>();
records.Add(latency);
return records;
});
}
public void UpdateInboundCounter(Server server, long n)
{
_inOutBoundRecords.AddOrUpdate(server.Identifier(), (k) =>
{
var r = new InOutBoundRecord();
r.UpdateInbound(n);
return r;
}, (k, v) =>
{
v.UpdateInbound(n);
return v;
});
}
public void UpdateOutboundCounter(Server server, long n)
{
_inOutBoundRecords.AddOrUpdate(server.Identifier(), (k) =>
{
var r = new InOutBoundRecord();
r.UpdateOutbound(n);
return r;
}, (k, v) =>
{
v.UpdateOutbound(n);
return v;
});
}
private class Counter
{
public int count = 0;
}
class UpdateRecordsState
{
public int counter;
}
class PingState
{
public UpdateRecordsState state;
public StatisticsRecord record;
}
class MyPing
{
private static Logger logger = LogManager.GetCurrentClassLogger();
//arguments for ICMP tests
public const int TimeoutMilliseconds = 500;
public EventHandler<CompletedEventArgs> Completed;
private Server server;
private int repeat;
private IPAddress ip;
private Ping ping;
private List<int?> RoundtripTime;
public MyPing(Server server, int repeat)
{
this.server = server;
this.repeat = repeat;
RoundtripTime = new List<int?>(repeat);
ping = new Ping();
ping.PingCompleted += Ping_PingCompleted;
}
public void Start(object userstate)
{
if (server.server == "")
{
FireCompleted(new Exception("Invalid Server"), userstate);
return;
}
new Task(() => ICMPTest(0, userstate)).Start();
}
private void ICMPTest(int delay, object userstate)
{
try
{
logger.Debug($"Ping {server}");
if (ip == null)
{
ip = Dns.GetHostAddresses(server.server)
.First(
ip =>
ip.AddressFamily == AddressFamily.InterNetwork ||
ip.AddressFamily == AddressFamily.InterNetworkV6);
}
repeat--;
if (delay > 0)
Thread.Sleep(delay);
ping.SendAsync(ip, TimeoutMilliseconds, userstate);
}
catch (Exception e)
{
logger.Error($"An exception occured while eveluating {server}");
logger.LogUsefulException(e);
FireCompleted(e, userstate);
}
}
private void Ping_PingCompleted(object sender, PingCompletedEventArgs e)
{
try
{
if (e.Reply.Status == IPStatus.Success)
{
logger.Debug($"Ping {server} {e.Reply.RoundtripTime} ms");
RoundtripTime.Add((int?)e.Reply.RoundtripTime);
}
else
{
logger.Debug($"Ping {server} timeout");
RoundtripTime.Add(null);
}
TestNext(e.UserState);
}
catch (Exception ex)
{
logger.Error($"An exception occured while eveluating {server}");
logger.LogUsefulException(ex);
FireCompleted(ex, e.UserState);
}
}
private void TestNext(object userstate)
{
if (repeat > 0)
{
//Do ICMPTest in a random frequency
int delay = TimeoutMilliseconds + new Random().Next() % TimeoutMilliseconds;
new Task(() => ICMPTest(delay, userstate)).Start();
}
else
{
FireCompleted(null, userstate);
}
}
private void FireCompleted(Exception error, object userstate)
{
Completed?.Invoke(this, new CompletedEventArgs
{
Error = error,
Server = server,
RoundtripTime = RoundtripTime,
UserState = userstate
});
}
public class CompletedEventArgs : EventArgs
{
public Exception Error;
public Server Server;
public List<int?> RoundtripTime;
public object UserState;
}
}
}
}

+ 149
- 79
shadowsocks-csharp/Controller/Service/GeositeUpdater.cs View File

@@ -35,8 +35,6 @@ namespace Shadowsocks.Controller


private static readonly string DATABASE_PATH = Utils.GetTempPath("dlc.dat"); private static readonly string DATABASE_PATH = Utils.GetTempPath("dlc.dat");


private static SocketsHttpHandler socketsHttpHandler;
private static HttpClient httpClient;
private static readonly string GEOSITE_URL = "https://github.com/v2fly/domain-list-community/raw/release/dlc.dat"; private static readonly string GEOSITE_URL = "https://github.com/v2fly/domain-list-community/raw/release/dlc.dat";
private static readonly string GEOSITE_SHA256SUM_URL = "https://github.com/v2fly/domain-list-community/raw/release/dlc.dat.sha256sum"; private static readonly string GEOSITE_SHA256SUM_URL = "https://github.com/v2fly/domain-list-community/raw/release/dlc.dat.sha256sum";
private static byte[] geositeDB; private static byte[] geositeDB;
@@ -45,9 +43,6 @@ namespace Shadowsocks.Controller


static GeositeUpdater() static GeositeUpdater()
{ {
//socketsHttpHandler = new SocketsHttpHandler();
//httpClient = new HttpClient(socketsHttpHandler);
if (File.Exists(DATABASE_PATH) && new FileInfo(DATABASE_PATH).Length > 0) if (File.Exists(DATABASE_PATH) && new FileInfo(DATABASE_PATH).Length > 0)
{ {
geositeDB = File.ReadAllBytes(DATABASE_PATH); geositeDB = File.ReadAllBytes(DATABASE_PATH);
@@ -68,7 +63,7 @@ namespace Shadowsocks.Controller
var list = GeositeList.Parser.ParseFrom(geositeDB); var list = GeositeList.Parser.ParseFrom(geositeDB);
foreach (var item in list.Entries) foreach (var item in list.Entries)
{ {
Geosites[item.GroupName.ToLower()] = item.Domains;
Geosites[item.GroupName.ToLowerInvariant()] = item.Domains;
} }
} }


@@ -84,9 +79,9 @@ namespace Shadowsocks.Controller
string geositeSha256sumUrl = GEOSITE_SHA256SUM_URL; string geositeSha256sumUrl = GEOSITE_SHA256SUM_URL;
SHA256 mySHA256 = SHA256.Create(); SHA256 mySHA256 = SHA256.Create();
var config = Program.MainController.GetCurrentConfiguration(); var config = Program.MainController.GetCurrentConfiguration();
string group = config.geositeGroup;
bool blacklist = config.geositeBlacklistMode;
bool blacklist = config.geositePreferDirect;
var httpClient = Program.MainController.GetHttpClient();
if (!string.IsNullOrWhiteSpace(config.geositeUrl)) if (!string.IsNullOrWhiteSpace(config.geositeUrl))
{ {
logger.Info("Found custom Geosite URL in config file"); logger.Info("Found custom Geosite URL in config file");
@@ -94,21 +89,6 @@ namespace Shadowsocks.Controller
} }
logger.Info($"Checking Geosite from {geositeUrl}"); logger.Info($"Checking Geosite from {geositeUrl}");


// use System.Net.Http.HttpClient to download GeoSite db.
// NASTY workaround: new HttpClient every update
// because we can't change proxy on existing socketsHttpHandler instance
socketsHttpHandler = new SocketsHttpHandler();
httpClient = new HttpClient(socketsHttpHandler);
if (config.enabled)
{
socketsHttpHandler.UseProxy = true;
socketsHttpHandler.Proxy = new WebProxy(
config.isIPv6Enabled
? $"[{IPAddress.IPv6Loopback}]"
: IPAddress.Loopback.ToString(),
config.localPort);
}

try try
{ {
// download checksum first // download checksum first
@@ -150,32 +130,26 @@ namespace Shadowsocks.Controller
// update stuff // update stuff
geositeDB = downloadedBytes; geositeDB = downloadedBytes;
LoadGeositeList(); LoadGeositeList();
bool pacFileChanged = MergeAndWritePACFile(group, blacklist);
bool pacFileChanged = MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, blacklist);
UpdateCompleted?.Invoke(null, new GeositeResultEventArgs(pacFileChanged)); UpdateCompleted?.Invoke(null, new GeositeResultEventArgs(pacFileChanged));
} }
catch (Exception ex) catch (Exception ex)
{ {
Error?.Invoke(null, new ErrorEventArgs(ex)); Error?.Invoke(null, new ErrorEventArgs(ex));
} }
finally
{
if (socketsHttpHandler != null)
{
socketsHttpHandler.Dispose();
socketsHttpHandler = null;
}
if (httpClient != null)
{
httpClient.Dispose();
httpClient = null;
}
}
} }


public static bool MergeAndWritePACFile(string group, bool blacklist)
/// <summary>
/// Merge and write pac.txt from geosite.
/// Used at multiple places.
/// </summary>
/// <param name="directGroups">A list of geosite groups configured for direct connection.</param>
/// <param name="proxiedGroups">A list of geosite groups configured for proxied connection.</param>
/// <param name="blacklist">Whether to use blacklist mode. False for whitelist.</param>
/// <returns></returns>
public static bool MergeAndWritePACFile(List<string> directGroups, List<string> proxiedGroups, bool blacklist)
{ {
IList<DomainObject> domains = Geosites[group];
string abpContent = MergePACFile(domains, blacklist);
string abpContent = MergePACFile(directGroups, proxiedGroups, blacklist);
if (File.Exists(PACDaemon.PAC_FILE)) if (File.Exists(PACDaemon.PAC_FILE))
{ {
string original = FileManager.NonExclusiveReadAllText(PACDaemon.PAC_FILE, Encoding.UTF8); string original = FileManager.NonExclusiveReadAllText(PACDaemon.PAC_FILE, Encoding.UTF8);
@@ -188,7 +162,44 @@ namespace Shadowsocks.Controller
return true; return true;
} }


private static string MergePACFile(IList<DomainObject> domains, bool blacklist)
/// <summary>
/// Checks if the specified group exists in GeoSite database.
/// </summary>
/// <param name="group">The group name to check for.</param>
/// <returns>True if the group exists. False if the group doesn't exist.</returns>
public static bool CheckGeositeGroup(string group) => SeparateAttributeFromGroupName(group, out string groupName, out _) && Geosites.ContainsKey(groupName);

/// <summary>
/// Separates the attribute (e.g. @cn) from a group name.
/// No checks are performed.
/// </summary>
/// <param name="group">A group name potentially with a trailing attribute.</param>
/// <param name="groupName">The group name with the attribute stripped.</param>
/// <param name="attribute">The attribute.</param>
/// <returns>True for success. False for more than one '@'.</returns>
private static bool SeparateAttributeFromGroupName(string group, out string groupName, out string attribute)
{
var splitGroupAttributeList = group.Split('@');
if (splitGroupAttributeList.Length == 1) // no attribute
{
groupName = splitGroupAttributeList[0];
attribute = "";
}
else if (splitGroupAttributeList.Length == 2) // has attribute
{
groupName = splitGroupAttributeList[0];
attribute = splitGroupAttributeList[1];
}
else
{
groupName = "";
attribute = "";
return false;
}
return true;
}

private static string MergePACFile(List<string> directGroups, List<string> proxiedGroups, bool blacklist)
{ {
string abpContent; string abpContent;
if (File.Exists(PACDaemon.USER_ABP_FILE)) if (File.Exists(PACDaemon.USER_ABP_FILE))
@@ -204,27 +215,25 @@ namespace Shadowsocks.Controller
if (File.Exists(PACDaemon.USER_RULE_FILE)) if (File.Exists(PACDaemon.USER_RULE_FILE))
{ {
string userrulesString = FileManager.NonExclusiveReadAllText(PACDaemon.USER_RULE_FILE, Encoding.UTF8); string userrulesString = FileManager.NonExclusiveReadAllText(PACDaemon.USER_RULE_FILE, Encoding.UTF8);
userruleLines = PreProcessGFWList(userrulesString);
userruleLines = ProcessUserRules(userrulesString);
} }


List<string> gfwLines = GeositeToGFWList(domains, blacklist);
List<string> ruleLines = GenerateRules(directGroups, proxiedGroups, blacklist);
abpContent = abpContent =
$@"var __USERRULES__ = {JsonConvert.SerializeObject(userruleLines, Formatting.Indented)}; $@"var __USERRULES__ = {JsonConvert.SerializeObject(userruleLines, Formatting.Indented)};
var __RULES__ = {JsonConvert.SerializeObject(gfwLines, Formatting.Indented)};
var __RULES__ = {JsonConvert.SerializeObject(ruleLines, Formatting.Indented)};
{abpContent}"; {abpContent}";
return abpContent; return abpContent;
} }


private static readonly IEnumerable<char> IgnoredLineBegins = new[] { '!', '[' };

private static List<string> PreProcessGFWList(string content)
private static List<string> ProcessUserRules(string content)
{ {
List<string> valid_lines = new List<string>(); List<string> valid_lines = new List<string>();
using (var sr = new StringReader(content))
using (var stringReader = new StringReader(content))
{ {
foreach (var line in sr.NonWhiteSpaceLines())
for (string line = stringReader.ReadLine(); line != null; line = stringReader.ReadLine())
{ {
if (line.BeginWithAny(IgnoredLineBegins))
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("!") || line.StartsWith("["))
continue; continue;
valid_lines.Add(line); valid_lines.Add(line);
} }
@@ -232,44 +241,105 @@ var __RULES__ = {JsonConvert.SerializeObject(gfwLines, Formatting.Indented)};
return valid_lines; return valid_lines;
} }


private static List<string> GeositeToGFWList(IList<DomainObject> domains, bool blacklist)
/// <summary>
/// Generates rule lines based on user preference.
/// </summary>
/// <param name="directGroups">A list of geosite groups configured for direct connection.</param>
/// <param name="proxiedGroups">A list of geosite groups configured for proxied connection.</param>
/// <param name="blacklist">Whether to use blacklist mode. False for whitelist.</param>
/// <returns>A list of rule lines.</returns>
private static List<string> GenerateRules(List<string> directGroups, List<string> proxiedGroups, bool blacklist)
{ {
return blacklist ? GeositeToGFWListBlack(domains) : GeositeToGFWListWhite(domains);
List<string> ruleLines;
if (blacklist) // blocking + exception rules
{
ruleLines = GenerateBlockingRules(proxiedGroups);
ruleLines.AddRange(GenerateExceptionRules(directGroups));
}
else // proxy all + exception rules
{
ruleLines = new List<string>()
{
"/.*/" // block/proxy all unmatched domains
};
ruleLines.AddRange(GenerateExceptionRules(directGroups));
}
return ruleLines;
} }


private static List<string> GeositeToGFWListBlack(IList<DomainObject> domains)
/// <summary>
/// Generates rules that match domains that should be proxied.
/// </summary>
/// <param name="groups">A list of source groups.</param>
/// <returns>A list of rule lines.</returns>
private static List<string> GenerateBlockingRules(List<string> groups)
{ {
List<string> ret = new List<string>(domains.Count + 100);// 100 overhead
foreach (var d in domains)
List<string> ruleLines = new List<string>();
foreach (var group in groups)
{ {
string domain = d.Value;

switch (d.Type)
// separate group name and attribute
SeparateAttributeFromGroupName(group, out string groupName, out string attribute);
var domainObjects = Geosites[groupName];
if (!string.IsNullOrEmpty(attribute)) // has attribute
{ {
case DomainObject.Types.Type.Plain:
ret.Add(domain);
break;
case DomainObject.Types.Type.Regex:
ret.Add($"/{domain}/");
break;
case DomainObject.Types.Type.Domain:
ret.Add($"||{domain}");
break;
case DomainObject.Types.Type.Full:
ret.Add($"|http://{domain}");
ret.Add($"|https://{domain}");
break;
var attributeObject = new DomainObject.Types.Attribute
{
Key = attribute,
BoolValue = true
};
foreach (var domainObject in domainObjects)
{
if (domainObject.Attribute.Contains(attributeObject))
switch (domainObject.Type)
{
case DomainObject.Types.Type.Plain:
ruleLines.Add(domainObject.Value);
break;
case DomainObject.Types.Type.Regex:
ruleLines.Add($"/{domainObject.Value}/");
break;
case DomainObject.Types.Type.Domain:
ruleLines.Add($"||{domainObject.Value}");
break;
case DomainObject.Types.Type.Full:
ruleLines.Add($"|http://{domainObject.Value}");
ruleLines.Add($"|https://{domainObject.Value}");
break;
}
}
} }
else // no attribute
foreach (var domainObject in domainObjects)
{
switch (domainObject.Type)
{
case DomainObject.Types.Type.Plain:
ruleLines.Add(domainObject.Value);
break;
case DomainObject.Types.Type.Regex:
ruleLines.Add($"/{domainObject.Value}/");
break;
case DomainObject.Types.Type.Domain:
ruleLines.Add($"||{domainObject.Value}");
break;
case DomainObject.Types.Type.Full:
ruleLines.Add($"|http://{domainObject.Value}");
ruleLines.Add($"|https://{domainObject.Value}");
break;
}
}
} }
return ret;
return ruleLines;
} }


private static List<string> GeositeToGFWListWhite(IList<DomainObject> domains)
{
return GeositeToGFWListBlack(domains)
.Select(r => $"@@{r}") // convert to whitelist
.Prepend("/.*/") // blacklist all other site
/// <summary>
/// Generates rules that match domains that should be connected directly without a proxy.
/// </summary>
/// <param name="groups">A list of source groups.</param>
/// <returns>A list of rule lines.</returns>
private static List<string> GenerateExceptionRules(List<string> groups)
=> GenerateBlockingRules(groups)
.Select(r => $"@@{r}") // convert blocking rules to exception rules
.ToList(); .ToList();
}
} }
} }

+ 2
- 14
shadowsocks-csharp/Controller/Service/OnlineConfigResolver.cs View File

@@ -11,27 +11,15 @@ namespace Shadowsocks.Controller.Service
{ {
public class OnlineConfigResolver public class OnlineConfigResolver
{ {
public static async Task<List<Server>> GetOnline(string url, IWebProxy proxy = null)
public static async Task<List<Server>> GetOnline(string url)
{ {
var socketsHttpHandler = new SocketsHttpHandler()
{
UseProxy = proxy != null,
Proxy = proxy
};
var httpClient = new HttpClient(socketsHttpHandler)
{
Timeout = TimeSpan.FromSeconds(15)
};

var httpClient = Program.MainController.GetHttpClient();
string server_json = await httpClient.GetStringAsync(url); string server_json = await httpClient.GetStringAsync(url);

var servers = server_json.GetServers(); var servers = server_json.GetServers();

foreach (var server in servers) foreach (var server in servers)
{ {
server.group = url; server.group = url;
} }

return servers.ToList(); return servers.ToList();
} }
} }


+ 2
- 2
shadowsocks-csharp/Controller/Service/PACDaemon.cs View File

@@ -45,7 +45,7 @@ namespace Shadowsocks.Controller
{ {
if (!File.Exists(PAC_FILE)) if (!File.Exists(PAC_FILE))
{ {
GeositeUpdater.MergeAndWritePACFile(config.geositeGroup, config.geositeBlacklistMode);
GeositeUpdater.MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, config.geositePreferDirect);
} }
return PAC_FILE; return PAC_FILE;
} }
@@ -63,7 +63,7 @@ namespace Shadowsocks.Controller
{ {
if (!File.Exists(PAC_FILE)) if (!File.Exists(PAC_FILE))
{ {
GeositeUpdater.MergeAndWritePACFile(config.geositeGroup, config.geositeBlacklistMode);
GeositeUpdater.MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, config.geositePreferDirect);
} }
return File.ReadAllText(PAC_FILE, Encoding.UTF8); return File.ReadAllText(PAC_FILE, Encoding.UTF8);
} }


+ 1
- 1
shadowsocks-csharp/Controller/Service/PACServer.cs View File

@@ -43,7 +43,7 @@ namespace Shadowsocks.Controller
_config = config; _config = config;
string usedSecret = _config.secureLocalPac ? $"&secret={PacSecret}" : ""; string usedSecret = _config.secureLocalPac ? $"&secret={PacSecret}" : "";
string contentHash = GetHash(_pacDaemon.GetPACContent()); string contentHash = GetHash(_pacDaemon.GetPACContent());
PacUrl = $"http://{config.localHost}:{config.localPort}/{RESOURCE_NAME}?hash={contentHash}{usedSecret}";
PacUrl = $"http://{config.LocalHost}:{config.localPort}/{RESOURCE_NAME}?hash={contentHash}{usedSecret}";
logger.Debug("Set PAC URL:" + PacUrl); logger.Debug("Set PAC URL:" + PacUrl);
} }


+ 2
- 1
shadowsocks-csharp/Controller/Service/Sip003Plugin.cs View File

@@ -122,12 +122,13 @@ namespace Shadowsocks.Controller.Service
public string ExpandEnvironmentVariables(string name, StringDictionary environmentVariables = null) public string ExpandEnvironmentVariables(string name, StringDictionary environmentVariables = null)
{ {
name = name.ToLower();
// Expand the environment variables from the new process itself // Expand the environment variables from the new process itself
if (environmentVariables != null) if (environmentVariables != null)
{ {
foreach(string key in environmentVariables.Keys) foreach(string key in environmentVariables.Keys)
{ {
name = name.Replace($"%{key}%", environmentVariables[key], StringComparison.OrdinalIgnoreCase);
name = name.Replace($"%{key.ToLower()}%", environmentVariables[key]);
} }
} }
// Also expand the environment variables from current main process (system) // Also expand the environment variables from current main process (system)


+ 3
- 3
shadowsocks-csharp/Controller/Service/TCPRelay.cs View File

@@ -239,7 +239,7 @@ namespace Shadowsocks.Controller
public DateTime lastActivity; public DateTime lastActivity;
private readonly ShadowsocksController _controller; private readonly ShadowsocksController _controller;
private readonly ProxyConfig _config;
private readonly ForwardProxyConfig _config;
private readonly Socket _connection; private readonly Socket _connection;
private IEncryptor encryptor; private IEncryptor encryptor;
@@ -708,10 +708,10 @@ namespace Shadowsocks.Controller
{ {
switch (_config.proxyType) switch (_config.proxyType)
{ {
case ProxyConfig.PROXY_SOCKS5:
case ForwardProxyConfig.PROXY_SOCKS5:
remote = new Socks5Proxy(); remote = new Socks5Proxy();
break; break;
case ProxyConfig.PROXY_HTTP:
case ForwardProxyConfig.PROXY_HTTP:
remote = new HttpProxy(); remote = new HttpProxy();
break; break;
default: default:


+ 119
- 196
shadowsocks-csharp/Controller/Service/UpdateChecker.cs View File

@@ -1,55 +1,36 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net; using System.Net;
using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NLog; using NLog;
using Shadowsocks.Localization;
using Shadowsocks.Model; using Shadowsocks.Model;
using Shadowsocks.Util; using Shadowsocks.Util;
using Shadowsocks.Views;
namespace Shadowsocks.Controller namespace Shadowsocks.Controller
{ {
public class UpdateChecker public class UpdateChecker
{ {
private static Logger logger = LogManager.GetCurrentClassLogger();
private readonly Logger logger;
private readonly HttpClient httpClient;
// https://developer.github.com/v3/repos/releases/
private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-windows/releases"; private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-windows/releases";
private const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36";
private Configuration config;
public bool NewVersionFound;
public string LatestVersionNumber;
public string LatestVersionSuffix;
public string LatestVersionName;
public string LatestVersionURL;
public string LatestVersionLocalName;
public event EventHandler CheckUpdateCompleted;
public static readonly string Version = Assembly.GetEntryAssembly().GetName().Version.ToString();
private class CheckUpdateTimer : System.Timers.Timer
{
public Configuration config;
public CheckUpdateTimer(int p) : base(p)
{
}
}
private Configuration _config;
private Window versionUpdatePromptWindow;
private JToken _releaseObject;
public void CheckUpdate(Configuration config, int delay)
{
#if DEBUG
return;
#pragma warning disable CS0162 // 检测到无法访问的代码
#endif
CheckUpdateTimer timer = new CheckUpdateTimer(delay);
timer.AutoReset = false;
timer.Elapsed += Timer_Elapsed;
timer.config = config;
timer.Enabled = true;
}
public string NewReleaseVersion { get; private set; }
public string NewReleaseZipFilename { get; private set; }
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{ {
@@ -60,204 +41,146 @@ namespace Shadowsocks.Controller
timer.Dispose(); timer.Dispose();
CheckUpdate(config); CheckUpdate(config);
} }
public event EventHandler CheckUpdateCompleted;
public void CheckUpdate(Configuration config)
{
#if DEBUG
return;
#endif
this.config = config;
public static readonly string Version = Assembly.GetEntryAssembly().GetName().Version.ToString();
private readonly Version _version;
try
{
logger.Info("Checking updates...");
WebClient http = CreateWebClient();
http.DownloadStringCompleted += http_DownloadStringCompleted;
http.DownloadStringAsync(new Uri(UpdateURL));
}
catch (Exception ex)
{
logger.LogUsefulException(ex);
}
public UpdateChecker()
{
logger = LogManager.GetCurrentClassLogger();
httpClient = Program.MainController.GetHttpClient();
_version = new Version(Version);
_config = Program.MainController.GetCurrentConfiguration();
} }
private void http_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
/// <summary>
/// Checks for updates and asks the user if updates are found.
/// </summary>
/// <param name="millisecondsDelay">A delay in milliseconds before checking.</param>
/// <returns></returns>
public async Task CheckForVersionUpdate(int millisecondsDelay = 0)
{ {
// delay
logger.Info($"Waiting for {millisecondsDelay}ms before checking for version update.");
await Task.Delay(millisecondsDelay);
// update _config so we would know if the user checked or unchecked pre-release checks
_config = Program.MainController.GetCurrentConfiguration();
// start
logger.Info($"Checking for version update.");
try try
{ {
string response = e.Result;
JArray result = JArray.Parse(response);
List<Asset> asserts = new List<Asset>();
if (result != null)
// list releases via API
var releasesListJsonString = await httpClient.GetStringAsync(UpdateURL);
// parse
var releasesJArray = JArray.Parse(releasesListJsonString);
foreach (var releaseObject in releasesJArray)
{ {
foreach (JObject release in result)
var releaseTagName = (string)releaseObject["tag_name"];
var releaseVersion = new Version(releaseTagName);
if (releaseTagName == _config.skippedUpdateVersion) // finished checking
break;
if (releaseVersion.CompareTo(_version) > 0 &&
(!(bool)releaseObject["prerelease"] || _config.checkPreRelease && (bool)releaseObject["prerelease"])) // selected
{ {
var isPreRelease = (bool)release["prerelease"];
if (isPreRelease && !config.checkPreRelease)
{
continue;
}
foreach (JObject asset in (JArray)release["assets"])
{
Asset ass = Asset.ParseAsset(asset);
if (ass != null)
{
ass.prerelease = isPreRelease;
if (ass.IsNewVersion(Version, config.checkPreRelease))
{
asserts.Add(ass);
}
}
}
}
}
if (asserts.Count != 0)
{
SortByVersions(asserts);
Asset asset = asserts[asserts.Count - 1];
NewVersionFound = true;
LatestVersionURL = asset.browser_download_url;
LatestVersionNumber = asset.version;
LatestVersionName = asset.name;
LatestVersionSuffix = asset.suffix == null ? "" : $"-{asset.suffix}";
startDownload();
}
else
{
logger.Info("No update is available");
if (CheckUpdateCompleted != null)
{
CheckUpdateCompleted(this, new EventArgs());
logger.Info($"Found new version {releaseTagName}.");
_releaseObject = releaseObject;
NewReleaseVersion = releaseTagName;
AskToUpdate(releaseObject);
return;
} }
} }
logger.Info($"No new versions found.");
CheckUpdateCompleted?.Invoke(this, new EventArgs());
} }
catch (Exception ex)
catch (Exception e)
{ {
logger.LogUsefulException(ex);
logger.LogUsefulException(e);
} }
} }
private void startDownload()
/// <summary>
/// Opens a window to show the update's information.
/// </summary>
/// <param name="releaseObject">The update release object.</param>
private void AskToUpdate(JToken releaseObject)
{ {
try
{
LatestVersionLocalName = Utils.GetTempPath(LatestVersionName);
WebClient http = CreateWebClient();
http.DownloadFileCompleted += Http_DownloadFileCompleted;
http.DownloadFileAsync(new Uri(LatestVersionURL), LatestVersionLocalName);
}
catch (Exception ex)
if (versionUpdatePromptWindow == null)
{ {
logger.LogUsefulException(ex);
versionUpdatePromptWindow = new Window()
{
Title = LocalizationProvider.GetLocalizedValue<string>("VersionUpdate"),
Height = 480,
Width = 640,
MinHeight = 480,
MinWidth = 640,
Content = new VersionUpdatePromptView(releaseObject)
};
versionUpdatePromptWindow.Closed += VersionUpdatePromptWindow_Closed;
versionUpdatePromptWindow.Show();
} }
versionUpdatePromptWindow.Activate();
} }
private void Http_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
private void VersionUpdatePromptWindow_Closed(object sender, EventArgs e)
{
versionUpdatePromptWindow = null;
}
/// <summary>
/// Downloads the selected update and notifies the user.
/// </summary>
/// <returns></returns>
public async Task DoUpdate()
{ {
try try
{ {
if (e.Error != null)
{
logger.LogUsefulException(e.Error);
return;
}
logger.Info($"New version {LatestVersionNumber}{LatestVersionSuffix} found: {LatestVersionLocalName}");
if (CheckUpdateCompleted != null)
var assets = (JArray)_releaseObject["assets"];
// download all assets
foreach (JObject asset in assets)
{ {
CheckUpdateCompleted(this, new EventArgs());
var filename = (string)asset["name"];
var browser_download_url = (string)asset["browser_download_url"];
var response = await httpClient.GetAsync(browser_download_url);
using (var downloadedFileStream = File.Create(Utils.GetTempPath(filename)))
await response.Content.CopyToAsync(downloadedFileStream);
logger.Info($"Downloaded {filename}.");
// store .zip filename
if (filename.EndsWith(".zip"))
NewReleaseZipFilename = filename;
} }
logger.Info("Finished downloading.");
// notify user
CloseVersionUpdatePromptWindow();
Process.Start("explorer.exe", $"/select, \"{Utils.GetTempPath(NewReleaseZipFilename)}\"");
} }
catch (Exception ex)
catch (Exception e)
{ {
logger.LogUsefulException(ex);
logger.LogUsefulException(e);
} }
} }
private WebClient CreateWebClient()
/// <summary>
/// Saves the skipped update version.
/// </summary>
public void SkipUpdate()
{ {
WebClient http = new WebClient();
http.Headers.Add("User-Agent", UserAgent);
http.Proxy = new WebProxy(config.localHost, config.localPort);
return http;
var version = (string)_releaseObject["tag_name"] ?? "";
_config.skippedUpdateVersion = version;
Program.MainController.SaveSkippedUpdateVerion(version);
logger.Info($"The update {version} has been skipped and will be ignored next time.");
CloseVersionUpdatePromptWindow();
} }
private void SortByVersions(List<Asset> asserts)
/// <summary>
/// Closes the update prompt window.
/// </summary>
public void CloseVersionUpdatePromptWindow()
{ {
asserts.Sort();
}
public class Asset : IComparable<Asset>
{
public bool prerelease;
public string name;
public string version;
public string browser_download_url;
public string suffix;
public static Asset ParseAsset(JObject assertJObject)
{
var name = (string)assertJObject["name"];
Match match = Regex.Match(name, @"^Shadowsocks-(?<version>\d+(?:\.\d+)*)(?:|-(?<suffix>.+))\.\w+$",
RegexOptions.IgnoreCase);
if (match.Success)
{
string version = match.Groups["version"].Value;
var asset = new Asset
{
browser_download_url = (string)assertJObject["browser_download_url"],
name = name,
version = version
};
if (match.Groups["suffix"].Success)
{
asset.suffix = match.Groups["suffix"].Value;
}
return asset;
}
return null;
}
public bool IsNewVersion(string currentVersion, bool checkPreRelease)
{
if (prerelease && !checkPreRelease)
{
return false;
}
if (version == null)
{
return false;
}
var cmp = CompareVersion(version, currentVersion);
return cmp > 0;
}
public static int CompareVersion(string l, string r)
{
var ls = l.Split('.');
var rs = r.Split('.');
for (int i = 0; i < Math.Max(ls.Length, rs.Length); i++)
{
int lp = (i < ls.Length) ? int.Parse(ls[i]) : 0;
int rp = (i < rs.Length) ? int.Parse(rs[i]) : 0;
if (lp != rp)
{
return lp - rp;
}
}
return 0;
}
public int CompareTo(Asset other)
if (versionUpdatePromptWindow != null)
{ {
return CompareVersion(version, other.version);
versionUpdatePromptWindow.Close();
versionUpdatePromptWindow = null;
} }
} }
} }


+ 74
- 82
shadowsocks-csharp/Controller/ShadowsocksController.cs View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@@ -15,12 +16,14 @@ using Shadowsocks.Controller.Service;
using Shadowsocks.Controller.Strategy; using Shadowsocks.Controller.Strategy;
using Shadowsocks.Model; using Shadowsocks.Model;
using Shadowsocks.Util; using Shadowsocks.Util;
using WPFLocalizeExtension.Engine;
namespace Shadowsocks.Controller namespace Shadowsocks.Controller
{ {
public class ShadowsocksController public class ShadowsocksController
{ {
private static Logger logger = LogManager.GetCurrentClassLogger();
private readonly Logger logger;
private readonly HttpClient httpClient;
// controller: // controller:
// handle user actions // handle user actions
@@ -38,9 +41,6 @@ namespace Shadowsocks.Controller
private PrivoxyRunner privoxyRunner; private PrivoxyRunner privoxyRunner;
private readonly ConcurrentDictionary<Server, Sip003Plugin> _pluginsByServer; private readonly ConcurrentDictionary<Server, Sip003Plugin> _pluginsByServer;
public AvailabilityStatistics availabilityStatistics = AvailabilityStatistics.Instance;
public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; }
private long _inboundCounter = 0; private long _inboundCounter = 0;
private long _outboundCounter = 0; private long _outboundCounter = 0;
public long InboundCounter => Interlocked.Read(ref _inboundCounter); public long InboundCounter => Interlocked.Read(ref _inboundCounter);
@@ -92,8 +92,10 @@ namespace Shadowsocks.Controller
public ShadowsocksController() public ShadowsocksController()
{ {
logger = LogManager.GetCurrentClassLogger();
httpClient = new HttpClient();
_config = Configuration.Load(); _config = Configuration.Load();
StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
Configuration.Process(ref _config);
_strategyManager = new StrategyManager(this); _strategyManager = new StrategyManager(this);
_pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>(); _pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>();
StartTrafficStatistics(61); StartTrafficStatistics(61);
@@ -106,23 +108,34 @@ namespace Shadowsocks.Controller
#region Basic #region Basic
public void Start(bool regHotkeys = true)
public void Start(bool systemWakeUp = false)
{ {
if (_config.updated && regHotkeys)
if (_config.firstRunOnNewVersion && !systemWakeUp)
{ {
_config.updated = false;
ProgramUpdated.Invoke(this, new UpdatedEventArgs() ProgramUpdated.Invoke(this, new UpdatedEventArgs()
{ {
OldVersion = _config.version, OldVersion = _config.version,
NewVersion = UpdateChecker.Version, NewVersion = UpdateChecker.Version,
}); });
// delete pac.txt when regeneratePacOnUpdate is true
if (_config.regeneratePacOnUpdate)
try
{
File.Delete(PACDaemon.PAC_FILE);
logger.Info("Deleted pac.txt from previous version.");
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
// finish up first run of new version
_config.firstRunOnNewVersion = false;
_config.version = UpdateChecker.Version;
Configuration.Save(_config); Configuration.Save(_config);
} }
Reload(); Reload();
if (regHotkeys)
{
if (!systemWakeUp)
HotkeyReg.RegAllHotkeys(); HotkeyReg.RegAllHotkeys();
}
} }
public void Stop() public void Stop()
@@ -150,10 +163,24 @@ namespace Shadowsocks.Controller
Encryption.RNG.Reload(); Encryption.RNG.Reload();
// some logic in configuration updated the config when saving, we need to read it again // some logic in configuration updated the config when saving, we need to read it again
_config = Configuration.Load(); _config = Configuration.Load();
Configuration.Process(ref _config);
NLogConfig.LoadConfiguration(); NLogConfig.LoadConfiguration();
StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
logger.Info($"WPF Localization Extension|Current culture: {LocalizeDictionary.CurrentCulture}");
// set User-Agent for httpClient
try
{
if (!string.IsNullOrWhiteSpace(_config.userAgentString))
httpClient.DefaultRequestHeaders.Add("User-Agent", _config.userAgentString);
}
catch
{
// reset userAgent to default and reapply
Configuration.ResetUserAgent(_config);
httpClient.DefaultRequestHeaders.Add("User-Agent", _config.userAgentString);
}
privoxyRunner = privoxyRunner ?? new PrivoxyRunner(); privoxyRunner = privoxyRunner ?? new PrivoxyRunner();
@@ -167,7 +194,6 @@ namespace Shadowsocks.Controller
GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted; GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted;
GeositeUpdater.Error += PacServer_PACUpdateError; GeositeUpdater.Error += PacServer_PACUpdateError;
availabilityStatistics.UpdateConfiguration(this);
_tcpListener?.Stop(); _tcpListener?.Stop();
_udpListener?.Stop(); _udpListener?.Stop();
StopPlugins(); StopPlugins();
@@ -186,7 +212,6 @@ namespace Shadowsocks.Controller
privoxyRunner.Start(_config); privoxyRunner.Start(_config);
TCPRelay tcpRelay = new TCPRelay(this, _config); TCPRelay tcpRelay = new TCPRelay(this, _config);
tcpRelay.OnConnected += UpdateLatency;
tcpRelay.OnInbound += UpdateInboundCounter; tcpRelay.OnInbound += UpdateInboundCounter;
tcpRelay.OnOutbound += UpdateOutboundCounter; tcpRelay.OnOutbound += UpdateOutboundCounter;
tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server); tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server);
@@ -239,22 +264,9 @@ namespace Shadowsocks.Controller
Errored?.Invoke(this, new ErrorEventArgs(e)); Errored?.Invoke(this, new ErrorEventArgs(e));
} }
public Server GetCurrentServer()
{
return _config.GetCurrentServer();
}
// always return copy
public Configuration GetConfigurationCopy()
{
return Configuration.Load();
}
// always return current instance
public Configuration GetCurrentConfiguration()
{
return _config;
}
public HttpClient GetHttpClient() => httpClient;
public Server GetCurrentServer() => _config.GetCurrentServer();
public Configuration GetCurrentConfiguration() => _config;
public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint, EndPoint destEndPoint) public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint, EndPoint destEndPoint)
{ {
@@ -270,13 +282,6 @@ namespace Shadowsocks.Controller
return GetCurrentServer(); return GetCurrentServer();
} }
public void SelectServerIndex(int index)
{
_config.index = index;
_config.strategy = null;
SaveConfig(_config);
}
public void SaveServers(List<Server> servers, int localPort, bool portableMode) public void SaveServers(List<Server> servers, int localPort, bool portableMode)
{ {
_config.configs = servers; _config.configs = servers;
@@ -285,6 +290,13 @@ namespace Shadowsocks.Controller
Configuration.Save(_config); Configuration.Save(_config);
} }
public void SelectServerIndex(int index)
{
_config.index = index;
_config.strategy = null;
SaveConfig(_config);
}
public void ToggleShareOverLAN(bool enabled) public void ToggleShareOverLAN(bool enabled)
{ {
_config.shareOverLan = enabled; _config.shareOverLan = enabled;
@@ -313,7 +325,7 @@ namespace Shadowsocks.Controller
EnableGlobalChanged?.Invoke(this, new EventArgs()); EnableGlobalChanged?.Invoke(this, new EventArgs());
} }
public void SaveProxy(ProxyConfig proxyConfig)
public void SaveProxy(ForwardProxyConfig proxyConfig)
{ {
_config.proxy = proxyConfig; _config.proxy = proxyConfig;
SaveConfig(_config); SaveConfig(_config);
@@ -346,7 +358,7 @@ namespace Shadowsocks.Controller
private static readonly IEnumerable<char> IgnoredLineBegins = new[] { '!', '[' }; private static readonly IEnumerable<char> IgnoredLineBegins = new[] { '!', '[' };
private void PacDaemon_UserRuleFileChanged(object sender, EventArgs e) private void PacDaemon_UserRuleFileChanged(object sender, EventArgs e)
{ {
GeositeUpdater.MergeAndWritePACFile(_config.geositeGroup, _config.geositeBlacklistMode);
GeositeUpdater.MergeAndWritePACFile(_config.geositeDirectGroups, _config.geositeProxiedGroups, _config.geositePreferDirect);
UpdateSystemProxy(); UpdateSystemProxy();
} }
@@ -393,6 +405,13 @@ namespace Shadowsocks.Controller
ConfigChanged?.Invoke(this, new EventArgs()); ConfigChanged?.Invoke(this, new EventArgs());
} }
public void ToggleRegeneratePacOnUpdate(bool enabled)
{
_config.regeneratePacOnUpdate = enabled;
SaveConfig(_config);
ConfigChanged?.Invoke(this, new EventArgs());
}
#endregion #endregion
#region SIP002 #region SIP002
@@ -419,7 +438,7 @@ namespace Shadowsocks.Controller
{ {
try try
{ {
if (ssURL.IsNullOrEmpty() || ssURL.IsWhiteSpace())
if (string.IsNullOrWhiteSpace(ssURL))
return false; return false;
var servers = Server.GetServers(ssURL); var servers = Server.GetServers(ssURL);
@@ -474,6 +493,12 @@ namespace Shadowsocks.Controller
ConfigChanged?.Invoke(this, new EventArgs()); ConfigChanged?.Invoke(this, new EventArgs());
} }
public void SaveSkippedUpdateVerion(string version)
{
_config.skippedUpdateVersion = version;
Configuration.Save(_config);
}
public void SaveLogViewerConfig(LogViewerConfig newConfig) public void SaveLogViewerConfig(LogViewerConfig newConfig)
{ {
_config.logViewer = newConfig; _config.logViewer = newConfig;
@@ -493,7 +518,7 @@ namespace Shadowsocks.Controller
#endregion #endregion
#region Statistic
#region Strategy
public void SelectStrategy(string strategyID) public void SelectStrategy(string strategyID)
{ {
@@ -502,12 +527,6 @@ namespace Shadowsocks.Controller
SaveConfig(_config); SaveConfig(_config);
} }
public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration)
{
StatisticsConfiguration = configuration;
StatisticsStrategyConfiguration.Save(configuration);
}
public IList<IStrategy> GetStrategies() public IList<IStrategy> GetStrategies()
{ {
return _strategyManager.GetStrategies(); return _strategyManager.GetStrategies();
@@ -525,43 +544,16 @@ namespace Shadowsocks.Controller
return null; return null;
} }
public void UpdateStatisticsConfiguration(bool enabled)
{
if (availabilityStatistics != null)
{
availabilityStatistics.UpdateConfiguration(this);
_config.availabilityStatistics = enabled;
SaveConfig(_config);
}
}
public void UpdateLatency(object sender, SSTCPConnectedEventArgs args)
{
GetCurrentStrategy()?.UpdateLatency(args.server, args.latency);
if (_config.availabilityStatistics)
{
availabilityStatistics.UpdateLatency(args.server, (int)args.latency.TotalMilliseconds);
}
}
public void UpdateInboundCounter(object sender, SSTransmitEventArgs args) public void UpdateInboundCounter(object sender, SSTransmitEventArgs args)
{ {
GetCurrentStrategy()?.UpdateLastRead(args.server); GetCurrentStrategy()?.UpdateLastRead(args.server);
Interlocked.Add(ref _inboundCounter, args.length); Interlocked.Add(ref _inboundCounter, args.length);
if (_config.availabilityStatistics)
{
availabilityStatistics.UpdateInboundCounter(args.server, args.length);
}
} }
public void UpdateOutboundCounter(object sender, SSTransmitEventArgs args) public void UpdateOutboundCounter(object sender, SSTransmitEventArgs args)
{ {
GetCurrentStrategy()?.UpdateLastWrite(args.server); GetCurrentStrategy()?.UpdateLastWrite(args.server);
Interlocked.Add(ref _outboundCounter, args.length); Interlocked.Add(ref _outboundCounter, args.length);
if (_config.availabilityStatistics)
{
availabilityStatistics.UpdateOutboundCounter(args.server, args.length);
}
} }
#endregion #endregion
@@ -620,7 +612,7 @@ namespace Shadowsocks.Controller
} }
#endregion #endregion
#region Traffic Statistics #region Traffic Statistics
private void StartTrafficStatistics(int queueMaxSize) private void StartTrafficStatistics(int queueMaxSize)
@@ -668,7 +660,7 @@ namespace Shadowsocks.Controller
public async Task<int> UpdateOnlineConfigInternal(string url) public async Task<int> UpdateOnlineConfigInternal(string url)
{ {
var onlineServer = await OnlineConfigResolver.GetOnline(url, _config.WebProxy);
var onlineServer = await OnlineConfigResolver.GetOnline(url);
_config.configs = Configuration.SortByOnlineConfig( _config.configs = Configuration.SortByOnlineConfig(
_config.configs _config.configs
.Where(c => c.group != url) .Where(c => c.group != url)
@@ -695,10 +687,10 @@ namespace Shadowsocks.Controller
return true; return true;
} }
public async Task<int> UpdateAllOnlineConfig()
public async Task<List<string>> UpdateAllOnlineConfig()
{ {
var selected = GetCurrentServer(); var selected = GetCurrentServer();
int failCount = 0;
var failedUrls = new List<string>();
foreach (var url in _config.onlineConfigSource) foreach (var url in _config.onlineConfigSource)
{ {
try try
@@ -708,18 +700,18 @@ namespace Shadowsocks.Controller
catch (Exception e) catch (Exception e)
{ {
logger.LogUsefulException(e); logger.LogUsefulException(e);
failCount++;
failedUrls.Add(url);
} }
} }
_config.index = _config.configs.IndexOf(selected); _config.index = _config.configs.IndexOf(selected);
SaveConfig(_config); SaveConfig(_config);
return failCount;
return failedUrls;
} }
public void SaveOnlineConfigSource(IEnumerable<string> vs)
public void SaveOnlineConfigSource(List<string> sources)
{ {
_config.onlineConfigSource = vs.ToList();
_config.onlineConfigSource = sources;
SaveConfig(_config); SaveConfig(_config);
} }


+ 0
- 170
shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs View File

@@ -1,170 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;

using Newtonsoft.Json;
using NLog;
using Shadowsocks.Model;

namespace Shadowsocks.Controller.Strategy
{
using Statistics = Dictionary<string, List<StatisticsRecord>>;

internal class StatisticsStrategy : IStrategy, IDisposable
{
private static Logger logger = LogManager.GetCurrentClassLogger();

private readonly ShadowsocksController _controller;
private Server _currentServer;
private readonly Timer _timer;
private Statistics _filteredStatistics;
private AvailabilityStatistics Service => _controller.availabilityStatistics;
private int ChoiceKeptMilliseconds
=> (int)TimeSpan.FromMinutes(_controller.StatisticsConfiguration.ChoiceKeptMinutes).TotalMilliseconds;

public StatisticsStrategy(ShadowsocksController controller)
{
_controller = controller;
var servers = controller.GetCurrentConfiguration().configs;
var randomIndex = new Random().Next() % servers.Count;
_currentServer = servers[randomIndex]; //choose a server randomly at first
// FIXME: consider Statistics and Config changing asynchrously.
_timer = new Timer(ReloadStatisticsAndChooseAServer);
}

private void ReloadStatisticsAndChooseAServer(object obj)
{
logger.Debug("Reloading statistics and choose a new server....");
var servers = _controller.GetCurrentConfiguration().configs;
LoadStatistics();
ChooseNewServer(servers);
}

private void LoadStatistics()
{
_filteredStatistics =
Service.FilteredStatistics ??
Service.RawStatistics ??
_filteredStatistics;
}

//return the score by data
//server with highest score will be choosen
private float? GetScore(string identifier, List<StatisticsRecord> records)
{
var config = _controller.StatisticsConfiguration;
float? score = null;

var averageRecord = new StatisticsRecord(identifier,
records.Where(record => record.MaxInboundSpeed != null).Select(record => record.MaxInboundSpeed.Value).ToList(),
records.Where(record => record.MaxOutboundSpeed != null).Select(record => record.MaxOutboundSpeed.Value).ToList(),
records.Where(record => record.AverageLatency != null).Select(record => record.AverageLatency.Value).ToList());
averageRecord.SetResponse(records.Select(record => record.AverageResponse).ToList());

foreach (var calculation in config.Calculations)
{
var name = calculation.Key;
var field = typeof (StatisticsRecord).GetField(name);
dynamic value = field?.GetValue(averageRecord);
var factor = calculation.Value;
if (value == null || factor.Equals(0)) continue;
score = score ?? 0;
score += value * factor;
}

if (score != null)
{
logger.Debug($"Highest score: {score} {JsonConvert.SerializeObject(averageRecord, Formatting.Indented)}");
}
return score;
}

private void ChooseNewServer(List<Server> servers)
{
if (_filteredStatistics == null || servers.Count == 0)
{
return;
}
try
{
var serversWithStatistics = (from server in servers
let id = server.Identifier()
where _filteredStatistics.ContainsKey(id)
let score = GetScore(id, _filteredStatistics[id])
where score != null
select new
{
server,
score
}).ToArray();

if (serversWithStatistics.Length < 2)
{
LogWhenEnabled("no enough statistics data or all factors in calculations are 0");
return;
}

var bestResult = serversWithStatistics
.Aggregate((server1, server2) => server1.score > server2.score ? server1 : server2);

LogWhenEnabled($"Switch to server: {bestResult.server.ToString()} by statistics: score {bestResult.score}");
_currentServer = bestResult.server;
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}

private void LogWhenEnabled(string log)
{
if (_controller.GetCurrentStrategy()?.ID == ID) //output when enabled
{
Console.WriteLine(log);
}
}

public string ID => "com.shadowsocks.strategy.scbs";

public string Name => I18N.GetString("Choose by statistics");

public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint, EndPoint destEndPoint)
{
if (_currentServer == null)
{
ChooseNewServer(_controller.GetCurrentConfiguration().configs);
}
return _currentServer; //current server cached for CachedInterval
}

public void ReloadServers()
{
ChooseNewServer(_controller.GetCurrentConfiguration().configs);
_timer?.Change(0, ChoiceKeptMilliseconds);
}

public void SetFailure(Server server)
{
logger.Debug($"failure: {server.ToString()}");
}

public void UpdateLastRead(Server server)
{
}

public void UpdateLastWrite(Server server)
{
}

public void UpdateLatency(Server server, TimeSpan latency)
{
}

public void Dispose()
{
_timer.Dispose();
}
}
}

+ 0
- 1
shadowsocks-csharp/Controller/Strategy/StrategyManager.cs View File

@@ -13,7 +13,6 @@ namespace Shadowsocks.Controller.Strategy
_strategies = new List<IStrategy>(); _strategies = new List<IStrategy>();
_strategies.Add(new BalancingStrategy(controller)); _strategies.Add(new BalancingStrategy(controller));
_strategies.Add(new HighAvailabilityStrategy(controller)); _strategies.Add(new HighAvailabilityStrategy(controller));
_strategies.Add(new StatisticsStrategy(controller));
// TODO: load DLL plugins // TODO: load DLL plugins
} }
public IList<IStrategy> GetStrategies() public IList<IStrategy> GetStrategies()


+ 6
- 7
shadowsocks-csharp/Controller/System/Hotkeys/HotkeyCallbacks.cs View File

@@ -23,7 +23,7 @@ namespace Shadowsocks.Controller.Hotkeys
/// <returns></returns> /// <returns></returns>
public static Delegate GetCallback(string methodname) public static Delegate GetCallback(string methodname)
{ {
if (methodname.IsNullOrEmpty()) throw new ArgumentException(nameof(methodname));
if (string.IsNullOrEmpty(methodname)) throw new ArgumentException(nameof(methodname));
MethodInfo dynMethod = typeof(HotkeyCallbacks).GetMethod(methodname, MethodInfo dynMethod = typeof(HotkeyCallbacks).GetMethod(methodname,
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase); BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
return dynMethod == null ? null : Delegate.CreateDelegate(typeof(HotKeys.HotKeyCallBackHandler), Instance, dynMethod); return dynMethod == null ? null : Delegate.CreateDelegate(typeof(HotKeys.HotKeyCallBackHandler), Instance, dynMethod);
@@ -46,21 +46,20 @@ namespace Shadowsocks.Controller.Hotkeys


private void SwitchSystemProxyCallback() private void SwitchSystemProxyCallback()
{ {
bool enabled = _controller.GetConfigurationCopy().enabled;
bool enabled = _controller.GetCurrentConfiguration().enabled;
_controller.ToggleEnable(!enabled); _controller.ToggleEnable(!enabled);
} }


private void SwitchSystemProxyModeCallback() private void SwitchSystemProxyModeCallback()
{ {
var config = _controller.GetConfigurationCopy();
if (config.enabled == false) return;
var currStatus = config.global;
_controller.ToggleGlobal(!currStatus);
var config = _controller.GetCurrentConfiguration();
if (config.enabled)
_controller.ToggleGlobal(!config.global);
} }


private void SwitchAllowLanCallback() private void SwitchAllowLanCallback()
{ {
var status = _controller.GetConfigurationCopy().shareOverLan;
var status = _controller.GetCurrentConfiguration().shareOverLan;
_controller.ToggleShareOverLAN(!status); _controller.ToggleShareOverLAN(!status);
} }




+ 1
- 1
shadowsocks-csharp/Controller/System/Hotkeys/Hotkeys.cs View File

@@ -110,7 +110,7 @@ namespace Shadowsocks.Controller.Hotkeys
{ {
try try
{ {
if (s.IsNullOrEmpty()) return null;
if (string.IsNullOrEmpty(s)) return null;
int offset = s.LastIndexOf("+", StringComparison.OrdinalIgnoreCase); int offset = s.LastIndexOf("+", StringComparison.OrdinalIgnoreCase);
if (offset <= 0) return null; if (offset <= 0) return null;
string modifierStr = s.Substring(0, offset).Trim(); string modifierStr = s.Substring(0, offset).Trim();


+ 1
- 1
shadowsocks-csharp/Controller/System/SystemProxy.cs View File

@@ -31,7 +31,7 @@ namespace Shadowsocks.Controller
else else
{ {
string pacUrl; string pacUrl;
if (config.useOnlinePac && !config.pacUrl.IsNullOrEmpty())
if (config.useOnlinePac && !string.IsNullOrEmpty(config.pacUrl))
{ {
pacUrl = config.pacUrl; pacUrl = config.pacUrl;
} }


+ 2
- 67
shadowsocks-csharp/Data/i18n.csv View File

@@ -16,7 +16,6 @@ PAC,Сценарий настройки (PAC),PAC 模式,PAC 模式,PACモード
Global,Для всей системы,全局模式,全局模式,グローバルプロキシ,전역,Global Global,Для всей системы,全局模式,全局模式,グローバルプロキシ,전역,Global
Servers,Серверы,服务器,伺服器,サーバー,서버,Serveurs Servers,Серверы,服务器,伺服器,サーバー,서버,Serveurs
Edit Servers...,Редактировать серверы…,编辑服务器...,編輯伺服器...,サーバーの編集...,서버 수정…,Éditer serveurs… Edit Servers...,Редактировать серверы…,编辑服务器...,編輯伺服器...,サーバーの編集...,서버 수정…,Éditer serveurs…
Statistics Config...,Настройки статистики…,统计配置...,統計設定檔...,統計情報の設定...,통계 설정,Configuration des statistiques…
Online Config...,,在线配置...,線上配置...,,, Online Config...,,在线配置...,線上配置...,,,
Start on Boot,Автозагрузка,开机启动,開機啟動,システム起動時に実行,시스템 시작 시에 시작하기,Démarrage automatique Start on Boot,Автозагрузка,开机启动,開機啟動,システム起動時に実行,시스템 시작 시에 시작하기,Démarrage automatique
Associate ss:// Links,Ассоциированный ss:// Ссылки,关联 ss:// 链接,關聯 ss:// 鏈接,ss:// リンクの関連付け,ss:// 링크 연결, Associate ss:// Links,Ассоциированный ss:// Ссылки,关联 ss:// 链接,關聯 ss:// 鏈接,ss:// リンクの関連付け,ss:// 링크 연결,
@@ -28,6 +27,7 @@ Edit Local PAC File...,Редактировать локальный PAC…,编
Update Local PAC from Geosite,Обновить локальный PAC из Geosite,从 Geosite 更新本地 PAC,從 Geosite 更新本機 PAC,Geosite からローカル PAC を更新,Geosite에서 로컬 프록시 자동 구성 파일 업데이트,Mettre à jour le PAC local à partir de Geosite Update Local PAC from Geosite,Обновить локальный PAC из Geosite,从 Geosite 更新本地 PAC,從 Geosite 更新本機 PAC,Geosite からローカル PAC を更新,Geosite에서 로컬 프록시 자동 구성 파일 업데이트,Mettre à jour le PAC local à partir de Geosite
Edit User Rule for Geosite...,Редактировать свои правила для Geosite,编辑 Geosite 的用户规则...,編輯 Geosite 的使用者規則...,ユーザールールの編集...,Geosite 사용자 수정,Modifier la règle utilisateur pour Geosite ... Edit User Rule for Geosite...,Редактировать свои правила для Geosite,编辑 Geosite 的用户规则...,編輯 Geosite 的使用者規則...,ユーザールールの編集...,Geosite 사용자 수정,Modifier la règle utilisateur pour Geosite ...
Secure Local PAC,Безопасный URL локального PAC,保护本地 PAC,安全本機 PAC,ローカル PAC を保護,로컬 프록시 자동 구성 파일 암호화,Sécuriser PAC local Secure Local PAC,Безопасный URL локального PAC,保护本地 PAC,安全本機 PAC,ローカル PAC を保護,로컬 프록시 자동 구성 파일 암호화,Sécuriser PAC local
Regenerate local PAC on version update,,版本更新后重新生成本地 PAC,版本更新後重新生成本地 PAC,,,
Copy Local PAC URL,Копировать URL локального PAC,复制本地 PAC 网址,複製本機 PAC 網址,ローカル PAC URL をコピー,로컬 프록시 자동 구성 파일 URL 복사,Copier l'URL du PAC local Copy Local PAC URL,Копировать URL локального PAC,复制本地 PAC 网址,複製本機 PAC 網址,ローカル PAC URL をコピー,로컬 프록시 자동 구성 파일 URL 복사,Copier l'URL du PAC local
Share Server Config...,Поделиться конфигурацией сервера…,分享服务器配置...,分享伺服器設定檔...,サーバーの設定を共有...,서버 설정 공유,Partager la configuration du serveur ... Share Server Config...,Поделиться конфигурацией сервера…,分享服务器配置...,分享伺服器設定檔...,サーバーの設定を共有...,서버 설정 공유,Partager la configuration du serveur ...
Scan QRCode from Screen...,Сканировать QRCode с экрана…,扫描屏幕上的二维码...,掃描螢幕上的 QR 碼...,画面から QR コードをスキャン...,화면에서 QR코드 스캔,Scanner le QRCode à partir de l'écran ... Scan QRCode from Screen...,Сканировать QRCode с экрана…,扫描屏幕上的二维码...,掃描螢幕上的 QR 碼...,画面から QR コードをスキャン...,화면에서 QR코드 스캔,Scanner le QRCode à partir de l'écran ...
@@ -46,7 +46,6 @@ Quit,Выход,退出,結束,終了,종료,Quitter
Edit Servers,Редактирование серверов,编辑服务器,編輯伺服器,サーバーの編集,서버 수정,Éditer serveurs Edit Servers,Редактирование серверов,编辑服务器,編輯伺服器,サーバーの編集,서버 수정,Éditer serveurs
Load Balance,Балансировка нагрузки,负载均衡,負載平衡,サーバーロードバランス,로드밸런싱,Répartition de charge Load Balance,Балансировка нагрузки,负载均衡,負載平衡,サーバーロードバランス,로드밸런싱,Répartition de charge
High Availability,Высокая доступность,高可用,高可用性,高可用性,고가용성,Haute disponibilité High Availability,Высокая доступность,高可用,高可用性,高可用性,고가용성,Haute disponibilité
Choose by statistics,На основе статистики,根据统计,根據統計,統計で選ぶ,통계 기반,Choisissez par statistiques
Show Plugin Output,События плагинов в журнале,显示插件输出,,プラグインの出力情報を表示,플러그인 출력 보이기,Afficher la sortie du plugin Show Plugin Output,События плагинов в журнале,显示插件输出,,プラグインの出力情報を表示,플러그인 출력 보이기,Afficher la sortie du plugin
Write translation template,Создать шаблон для перевода,写入翻译模板,,翻訳テンプレートファイルを書き込む,번역 템플릿 쓰기,Écrire un modèle de traduction Write translation template,Создать шаблон для перевода,写入翻译模板,,翻訳テンプレートファイルを書き込む,번역 템플릿 쓰기,Écrire un modèle de traduction
,,,,,, ,,,,,,
@@ -80,55 +79,6 @@ Move D&own,Ниже,下移(&O),下移 (&O),下に移動 (&O),아래로 (&O),Desc
deprecated,Устаревшее,不推荐,不推薦,非推奨,더 이상 사용되지 않음,Obsolète deprecated,Устаревшее,不推荐,不推薦,非推奨,더 이상 사용되지 않음,Obsolète
"Encryption method {0} not exist, will replace with {1}",,加密方法{0}不存在,将使用{1}代替,,暗号化方式{0}が存在しません,{1}に置換します,{0} 암호화 방식이 존재하지 않으므로 {1}로 대체될 것입니다.,"Méthode de chiffrement {0} n'existe pas, sera remplacée par {1}" "Encryption method {0} not exist, will replace with {1}",,加密方法{0}不存在,将使用{1}代替,,暗号化方式{0}が存在しません,{1}に置換します,{0} 암호화 방식이 존재하지 않으므로 {1}로 대체될 것입니다.,"Méthode de chiffrement {0} n'existe pas, sera remplacée par {1}"
,,,,,, ,,,,,,
# Online Config Form,,,,,,
,,,,,,
Online config,,在线配置,線上配置,,,
Online config URL,,在线配置链接,線上配置鏈接,,,
&Update,,更新,更新,,,
U&pdate All,,全部更新,全部更新,,,
,,,,,,
#Statistics Config,,,,,,
,,,,,,
Enable Statistics,Включить сбор статистики,启用统计,,統計を有効にする,통계 활성화,Activer statistiques
Ping Test,Проверка связи (Ping),Ping测试,,Ping測定,Ping 테스트,Test ping
packages everytime,пакета на проверку,个包/次,,パケット/回,시간 당 패킷,Packages à chaque fois
By hour of day,Ежечасно,按照每天的小时数统计,,毎日の時間数で統計,매 시간,Par heure du jour
Collect data per,Собирать данные за,收集数据每,,ごとにデータを収集,데이터 수집 기간,Recueillir des données par
Keep choice for,Хранить отбор данных за,保持选择每,,,설정 유지하기,Gardez le choix pour
minutes,мин.,分钟,,分,분,Minutes
Final Score:,Финальная оценка:,总分:,,,최종 점수:,Score final:
AverageLatency,СредЗадержка,平均延迟,,平均遅延時間,평균 지연시간,LatenceMoyenne
MinLatency,МинЗадержка,最小延迟,,最小遅延時間,최소 지연시간,MinLatence
MaxLatency,МаксЗадержка,最大延迟,,最大遅延時間,최대 지연시간,MaxLatence
AverageInboundSpeed,СредВходСкорость,平均入站速度,,平均インバウンド速度,평균 인바운드 속도,VitesseEntranteMoyenne
MinInboundSpeed,МинВходСкорость,最小入站速度,,最小インバウンド速度,최소 인바운드 속도,MinVitesseEntrante
MaxInboundSpeed,СредВходСкорость,最大入站速度,,最大インバウンド速度,최대 인바운드 속도,MaxVitesseEntrante
AverageOutboundSpeed,СредИсхСкорость,平均出站速度,,平均アウトバウンド速度,평균 아웃바운드 속도,VitesseSortanteMoyenne
MinOutboundSpeed,МинИсхСкорость,最小出站速度,,最小アウトバウンド速度,최소 아웃바운드 속도,MinVitesseSortante
MaxOutboundSpeed,МаксИсхСкорость,最大出站速度,,最大アウトバウンド速度,최대 아웃바운드 속도,MaxVitesseSortante
AverageResponse,СредВремяОтвета,平均响应速度,,平均レスポンス速度,평균 응답,RéponseMoyenne
MinResponse,МинВремяОтвета,最小响应速度,,最小レスポンス速度,최소 응답,MinRéponse
MaxResponse,МаксВремяОтвета,最大响应速度,,最大レスポンス速度,최대 응답,MaxRéponse
PackageLoss,ПотериПакетов,丢包率,,パケットロス率,패킷 손실,Perte de paquets
Speed,Скорость,速度,,速度,속도,Vitesse
Package Loss,Потери пакетов,丢包率,,パケットロス率,패킷 손실,Perte de paquets
Ping,Ping,网络延迟,,Ping,Ping,Ping
Chart Mode,График,图表模式,,図表モード,차트 모드,Mode graphique
24h,24ч,24小时,,24時間,24시간,24h
all,За все время,全部,,すべて,전체,tout
,,,,,,
# Proxy Form,,,,,,
,,,,,,
Edit Proxy,Редактирование прокси,代理设置,編輯 Proxy,プロキシの編集,프록시 수정,Modifier le proxy
Use Proxy,Использовать прокси,使用代理,使用 Proxy,プロキシを使用する,프록시 사용,Utiliser un proxy
Proxy Type,Тип прокси,代理类型,Proxy 類型,プロキシタイプ,프록시 종류,Type de proxy
Proxy Addr,Адрес прокси,代理地址,Proxy 位址,プロキシアドレス,프록시 주소,Adresse de proxy
Proxy Port,Порт прокси,代理端口,Proxy 連接埠,プロキシポート,프록시 포트,Port de proxy
"If server has a plugin, proxy will not be used","Если сервер использует плагины, прокси НЕ будет использоваться",若服务器含有插件,代理将不被使用,若伺服器含有外掛程式,Proxy 將不被使用,サーバーにプラグインがある場合、プロキシは使用されません,서버에 플러그인이 설치 되어 있는 경우 프록시를 사용할 수 없습니다.,"Si le serveur a un plugin, le proxy ne sera pas utilisé"
Use Auth,Требуется авторизация,使用认证,使用認證,認証を使用する,서버 인증 사용,Utiliser l'authentification
User Name,Пользователь,用户名,認證用戶,ユーザ名,사용자 이름,Nom d'utilisateur
Auth Pwd,Пароль,认证密码,認證口令,パスワード,비밀번호,Mot de passe d'authentification
,,,,,,
# Log Form,,,,,, # Log Form,,,,,,
,,,,,, ,,,,,,
&File,Файл,文件(&F),檔案 (&F),ファイル (&F),파일 (&F),Fichier &File,Файл,文件(&F),檔案 (&F),ファイル (&F),파일 (&F),Fichier
@@ -154,17 +104,6 @@ Edit Online PAC URL,Изменение URL удаленного PAC,编辑在线
Edit Online PAC URL...,Редактировать URL удаленного PAC…,编辑在线 PAC 网址...,編輯線上 PAC 網址...,オンライン PAC URL の編集...,온라인 프록시 자동 구성 URL 수정…,Modifier l'URL du PAC en ligne ... Edit Online PAC URL...,Редактировать URL удаленного PAC…,编辑在线 PAC 网址...,編輯線上 PAC 網址...,オンライン PAC URL の編集...,온라인 프록시 자동 구성 URL 수정…,Modifier l'URL du PAC en ligne ...
Please input PAC Url,Введите URL адрес для PAC-файла,请输入 PAC 网址,請輸入 PAC 網址,PAC URLを入力して下さい,프록시 자동 구성 URL을 입력하세요,Veuillez saisir l'URL PAC Please input PAC Url,Введите URL адрес для PAC-файла,请输入 PAC 网址,請輸入 PAC 網址,PAC URLを入力して下さい,프록시 자동 구성 URL을 입력하세요,Veuillez saisir l'URL PAC
,,,,,, ,,,,,,
# HotkeySettings Form,,,,,,
,,,,,,
Switch system proxy,ВКЛ/ВЫКЛ системный прокси-сервер,切换系统代理状态,切換系統 Proxy 狀態,システム プロキシの状態を切り替える,시스템 프록시 전환,Changer l'état de proxy système
Switch system proxy mode,Переключение режима прокси-сервера,切换系统代理模式,切換系統 Proxy 模式,プロキシモードを切り替える,시스템 프록시 모드 전환,Changer le mode de proxy système
Allow Clients from LAN,Общий доступ к подключению,切换局域网共享,切換區域網路共用,LAN からのアクセスの許可を切り替える,LAN으로부터 클라이언트 허용,Autoriser les clients du LAN
Show Logs...,Просмотр журналов,显示日志,顯示記錄檔,ログの表示,로그 보기…,Afficher les journaux
Switch to previous server,Переключить на пред. сервер,切换上个服务器,切換上一個伺服器,前のサーバーに切り替える,이전 서버로 전환,Passer au serveur précédent
Switch to next server,Переключить на след. сервер,切换下个服务器,切換下一個伺服器,次のサーバーに切り替える,다음 서버로 전환,Passer au serveur suivant
Reg All,Применить все,注册全部快捷键,註冊所有快速鍵,全部登録する,모두 등록,Enregistrer tout
Reg Hotkeys At Startup,Применять при запуске программы,启动时注册快捷键,啟動時註冊快速鍵,起動時にホットキーを登録する,시스템 시작 시 단축키 등록,Enregistrer tout au démarrage
,,,,,,
# Messages,,,,,, # Messages,,,,,,
,,,,,, ,,,,,,
Shadowsocks Error: {0},Ошибка Shadowsocks: {0},Shadowsocks 错误: {0},Shadowsocks 錯誤: {0},Shadowsocks エラー: {0},Shadowsocks 오류: {0},Erreur shadowsocks: {0} Shadowsocks Error: {0},Ошибка Shadowsocks: {0},Shadowsocks 错误: {0},Shadowsocks 錯誤: {0},Shadowsocks エラー: {0},Shadowsocks 오류: {0},Erreur shadowsocks: {0}
@@ -177,9 +116,7 @@ Server IP can not be blank,IP-адрес сервера не может быть
Password can not be blank,Пароль не может быть пустым,密码不能为空,密碼不能為空,パスワードが指定されていません。,비밀번호는 비어있으면 안됩니다.,Le mot de passe ne peut pas être vide Password can not be blank,Пароль не может быть пустым,密码不能为空,密碼不能為空,パスワードが指定されていません。,비밀번호는 비어있으면 안됩니다.,Le mot de passe ne peut pas être vide
Port out of range,Порт выходит за допустимый диапазон,端口超出范围,連接埠號碼超出範圍,ポート番号は範囲外です。,올바른 포트 범위가 아닙니다.,Port hors de portée Port out of range,Порт выходит за допустимый диапазон,端口超出范围,連接埠號碼超出範圍,ポート番号は範囲外です。,올바른 포트 범위가 아닙니다.,Port hors de portée
Port can't be 8123,Адрес порта 8123 не может быть использован,端口不能为 8123,連接埠號碼不能為 8123,8123 番以外のポート番号を指定して下さい。,8123번 포트는 사용할 수 없습니다.,Le port ne peut pas être 8123 Port can't be 8123,Адрес порта 8123 не может быть использован,端口不能为 8123,連接埠號碼不能為 8123,8123 番以外のポート番号を指定して下さい。,8123번 포트는 사용할 수 없습니다.,Le port ne peut pas être 8123
Shadowsocks {0} Update Found,Обнаружена новая версия Shadowsocks: {0},Shadowsocks {0} 更新,Shadowsocks {0} 更新,Shadowsocks バージョン {0} は利用できます。,Shadowsocks {0} 업데이트가 있습니다.,Shadowsocks {0} Mise à jour trouvée
No update is available,Обновлений не обнаружено,没有可用的更新,沒有可用的更新,お使いのバージョンは最新です。,사용 가능한 업데이트가 없습니다.,Aucune mise à jour n'est disponible No update is available,Обновлений не обнаружено,没有可用的更新,沒有可用的更新,お使いのバージョンは最新です。,사용 가능한 업데이트가 없습니다.,Aucune mise à jour n'est disponible
Click here to update,Нажмите сюда для обновления,点击这里升级,點按此處升級,クリックしてアップデートします。,여기를 클릭하여 업데이트,Cliquez ici pour mettre à jour
Shadowsocks is here,Shadowsocks находится здесь,Shadowsocks 在这里,Shadowsocks 在這裡,Shadowsocks はここです。,Shadowsocks는 여기에 있습니다,Veuillez trouver Shadowsocks ici Shadowsocks is here,Shadowsocks находится здесь,Shadowsocks 在这里,Shadowsocks 在這裡,Shadowsocks はここです。,Shadowsocks는 여기에 있습니다,Veuillez trouver Shadowsocks ici
You can turn on/off Shadowsocks in the context menu,Вы можете управлять Shadowsocks из контекстного меню,可以在右键菜单中开关 Shadowsocks,可以在右鍵選項單中開關 Shadowsocks,コンテキストメニューを使って、Shadowsocks を有効または無効にすることができます。,프로그램 메뉴에서 Shadowsocks를 끄고 켤 수 있습니다.,Vous pouvez activer / désactiver Shadowsocks dans le menu contextuel You can turn on/off Shadowsocks in the context menu,Вы можете управлять Shadowsocks из контекстного меню,可以在右键菜单中开关 Shadowsocks,可以在右鍵選項單中開關 Shadowsocks,コンテキストメニューを使って、Shadowsocks を有効または無効にすることができます。,프로그램 메뉴에서 Shadowsocks를 끄고 켤 수 있습니다.,Vous pouvez activer / désactiver Shadowsocks dans le menu contextuel
System Proxy Enabled,Системный прокси включен,系统代理已启用,系統 Proxy 已啟用,システム プロキシが有効です。,시스템 프록시가 활성화되었습니다.,Proxy système activé System Proxy Enabled,Системный прокси включен,系统代理已启用,系統 Proxy 已啟用,システム プロキシが有効です。,시스템 프록시가 활성화되었습니다.,Proxy système activé
@@ -191,7 +128,7 @@ No QRCode found. Try to zoom in or move it to the center of the screen.,QRCode
Shadowsocks is already running.,Shadowsocks уже запущен.,Shadowsocks 已经在运行。,Shadowsocks 已經在執行。,Shadowsocks 実行中,Shadowsocks가 이미 실행 중입니다.,Shadowsocks est déjà en cours d'exécution. Shadowsocks is already running.,Shadowsocks уже запущен.,Shadowsocks 已经在运行。,Shadowsocks 已經在執行。,Shadowsocks 実行中,Shadowsocks가 이미 실행 중입니다.,Shadowsocks est déjà en cours d'exécution.
Find Shadowsocks icon in your notify tray.,Значок Shadowsocks можно найти в области уведомлений.,请在任务栏里寻找 Shadowsocks 图标。,請在工作列裡尋找 Shadowsocks 圖示。,通知領域には Shadowsocks のアイコンがあります。,트레이에서 Shadowsocks를 찾아주세요.,Trouvez l'icône Shadowsocks dans votre barre de notification. Find Shadowsocks icon in your notify tray.,Значок Shadowsocks можно найти в области уведомлений.,请在任务栏里寻找 Shadowsocks 图标。,請在工作列裡尋找 Shadowsocks 圖示。,通知領域には Shadowsocks のアイコンがあります。,트레이에서 Shadowsocks를 찾아주세요.,Trouvez l'icône Shadowsocks dans votre barre de notification.
"If you want to start multiple Shadowsocks, make a copy in another directory.","Если вы хотите запустить несколько копий Shadowsocks одновременно, создайте отдельную папку на каждую копию.",如果想同时启动多个,可以另外复制一份到别的目录。,如果想同時啟動多個,可以另外複製一份至別的目錄。,複数起動したい場合は、プログラムファイルを別のフォルダーにコピーしてから、もう一度実行して下さい。,"만약 여러 개의 Shadowsocks를 사용하고 싶으시다면, 파일을 다른 곳에 복사해주세요.","Si vous souhaitez démarrer plusieurs Shadowsocks, faites une copie dans un autre répertoire." "If you want to start multiple Shadowsocks, make a copy in another directory.","Если вы хотите запустить несколько копий Shadowsocks одновременно, создайте отдельную папку на каждую копию.",如果想同时启动多个,可以另外复制一份到别的目录。,如果想同時啟動多個,可以另外複製一份至別的目錄。,複数起動したい場合は、プログラムファイルを別のフォルダーにコピーしてから、もう一度実行して下さい。,"만약 여러 개의 Shadowsocks를 사용하고 싶으시다면, 파일을 다른 곳에 복사해주세요.","Si vous souhaitez démarrer plusieurs Shadowsocks, faites une copie dans un autre répertoire."
Failed to decode QRCode,Не удалось распознать QRCode,无法解析二维码,QR 碼解碼失敗,QR コードの読み取りに失敗しました。,QR코드를 해석하는데에 실패했습니다.,Impossible de décoder le QRCode
Invalid QR Code content: {0},,无效二维码内容: {0},無效二維碼內容: {0},,,
Failed to update registry,Не удалось обновить запись в реестре,无法修改注册表,無法修改登錄檔,レジストリの更新に失敗しました。,레지스트리를 업데이트하는데에 실패했습니다.,Impossible de mettre à jour de la base de registre Failed to update registry,Не удалось обновить запись в реестре,无法修改注册表,無法修改登錄檔,レジストリの更新に失敗しました。,레지스트리를 업데이트하는데에 실패했습니다.,Impossible de mettre à jour de la base de registre
Import from URL: {0} ?,импортировать из адреса: {0} ?,从URL导入: {0} ?,從URL匯入: {0} ?,{0}:このURLからインポートしますか?,, Import from URL: {0} ?,импортировать из адреса: {0} ?,从URL导入: {0} ?,從URL匯入: {0} ?,{0}:このURLからインポートしますか?,,
Successfully imported from {0},Успешно импортировано из {0},导入成功:{0},導入成功:{0},{0}:インポートしました。,, Successfully imported from {0},Успешно импортировано из {0},导入成功:{0},導入成功:{0},{0}:インポートしました。,,
@@ -218,5 +155,3 @@ Whether to discard unconfigured servers,Внесенные изменения б
,,,,タイムアウト値のフォーマットが無効なため、オートセーブできません。変更を破棄しますか,, ,,,,タイムアウト値のフォーマットが無効なため、オートセーブできません。変更を破棄しますか,,
"Error occured when process proxy setting, do you want reset current setting and retry?",Произошла ошибка при обработке настроек. Хотите сбросить текущие настройки и попробовать снова?,处理代理设置时发生错误,是否重置当前代理设置并重试?,,プロキシ設定の処理にエラーが発生しました、現在のプロキシ設定をリセットし、再試行してもいいですか,프록시 설정을 처리하는데에 오류가 발생했습니다. 현재 설정을 폐기하고 다시 시도하시겠습니까?,Une erreur s'est produite lors du processus de configuration du proxy. Voulez-vous réinitialiser le paramètre actuel et réessayer? "Error occured when process proxy setting, do you want reset current setting and retry?",Произошла ошибка при обработке настроек. Хотите сбросить текущие настройки и попробовать снова?,处理代理设置时发生错误,是否重置当前代理设置并重试?,,プロキシ設定の処理にエラーが発生しました、現在のプロキシ設定をリセットし、再試行してもいいですか,프록시 설정을 처리하는데에 오류가 발생했습니다. 현재 설정을 폐기하고 다시 시도하시겠습니까?,Une erreur s'est produite lors du processus de configuration du proxy. Voulez-vous réinitialiser le paramètre actuel et réessayer?
"Unrecoverable proxy setting error occured, see log for detail","Произошла серьезная ошибка, подробности можно узнать в журналах",发生不可恢复的代理设置错误,查看日志以取得详情,,プロキシ設定に回復不能なエラーが発生しました、ログで詳細をご確認ください,복구 불가능한 프록시 설정 오류가 발생했습니다. 자세한 정보는 로그를 참조하세요.,"Une erreur de paramètre de proxy irrécupérable s'est produite, consultez le journal pour plus de détails" "Unrecoverable proxy setting error occured, see log for detail","Произошла серьезная ошибка, подробности можно узнать в журналах",发生不可恢复的代理设置错误,查看日志以取得详情,,プロキシ設定に回復不能なエラーが発生しました、ログで詳細をご確認ください,복구 불가능한 프록시 설정 오류가 발생했습니다. 자세한 정보는 로그를 참조하세요.,"Une erreur de paramètre de proxy irrécupérable s'est produite, consultez le journal pour plus de détails"
Auth user can not be blank,Пользователь не может быть пустым,认证用户不能为空,認證用戶不能為空,認証ユーザが指定されていません。,인증 정보의 사용자 이름은 비어있을 수 없습니다.,L'utilisateur d'authentification ne peut pas être vide
Auth pwd can not be blank,Пароль не может быть пустым,认证密码不能为空,認證口令不能為空,認証パスワードが指定されていません。,인증 정보의 비밀번호는 비어있을 수 없습니다.,Le mot de passe d'authentification ne peut pas être vide

+ 1
- 1
shadowsocks-csharp/Encryption/EncryptorFactory.cs View File

@@ -71,7 +71,7 @@ namespace Shadowsocks.Encryption
public static IEncryptor GetEncryptor(string method, string password) public static IEncryptor GetEncryptor(string method, string password)
{ {
if (method.IsNullOrEmpty())
if (string.IsNullOrEmpty(method))
{ {
method = Model.Server.DefaultMethod; method = Model.Server.DefaultMethod;
} }


+ 1
- 0
shadowsocks-csharp/FodyWeavers.xml View File

@@ -2,4 +2,5 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Caseless StringComparison="Ordinal" /> <Caseless StringComparison="Ordinal" />
<Costura /> <Costura />
<ReactiveUI />
</Weavers> </Weavers>

+ 1
- 0
shadowsocks-csharp/FodyWeavers.xsd View File

@@ -4,6 +4,7 @@
<xs:element name="Weavers"> <xs:element name="Weavers">
<xs:complexType> <xs:complexType>
<xs:all> <xs:all>
<xs:element name="ReactiveUI" minOccurs="0" maxOccurs="1" type="xs:anyType" />
<xs:element name="Costura" minOccurs="0" maxOccurs="1"> <xs:element name="Costura" minOccurs="0" maxOccurs="1">
<xs:complexType> <xs:complexType>
<xs:all> <xs:all>


+ 13
- 0
shadowsocks-csharp/Localization/LocalizationProvider.cs View File

@@ -0,0 +1,13 @@
using System.Reflection;
using WPFLocalizeExtension.Extensions;

namespace Shadowsocks.Localization
{
public static class LocalizationProvider
{
public static T GetLocalizedValue<T>(string key)
{
return LocExtension.GetLocalizedValue<T>(Assembly.GetCallingAssembly().GetName().Name + ":Strings:" + key);
}
}
}

+ 432
- 0
shadowsocks-csharp/Localization/Strings.Designer.cs View File

@@ -0,0 +1,432 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace Shadowsocks.Localization {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Strings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Strings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Shadowsocks.Localization.Strings", typeof(Strings).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to _Add.
/// </summary>
internal static string addButton_Content {
get {
return ResourceManager.GetString("addButton_Content", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Address.
/// </summary>
internal static string Address {
get {
return ResourceManager.GetString("Address", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Allow clients from LAN.
/// </summary>
internal static string AllowClientsFromLAN {
get {
return ResourceManager.GetString("AllowClientsFromLAN", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Cancel.
/// </summary>
internal static string cancelButton_Content {
get {
return ResourceManager.GetString("cancelButton_Content", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Copy.
/// </summary>
internal static string Copy {
get {
return ResourceManager.GetString("Copy", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Copy link.
/// </summary>
internal static string copyLinkButton_Content {
get {
return ResourceManager.GetString("copyLinkButton_Content", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Credentials (optional).
/// </summary>
internal static string CredentialsOptional {
get {
return ResourceManager.GetString("CredentialsOptional", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Details.
/// </summary>
internal static string Details {
get {
return ResourceManager.GetString("Details", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Forward Proxy.
/// </summary>
internal static string ForwardProxy {
get {
return ResourceManager.GetString("ForwardProxy", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Hotkeys.
/// </summary>
internal static string Hotkeys {
get {
return ResourceManager.GetString("Hotkeys", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to HTTP.
/// </summary>
internal static string HTTP {
get {
return ResourceManager.GetString("HTTP", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No proxy.
/// </summary>
internal static string NoProxy {
get {
return ResourceManager.GetString("NoProxy", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Not now.
/// </summary>
internal static string notNowButton_Content {
get {
return ResourceManager.GetString("notNowButton_Content", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _OK.
/// </summary>
internal static string okButton_Content {
get {
return ResourceManager.GetString("okButton_Content", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Online Configuration Delivery.
/// </summary>
internal static string OnlineConfigDelivery {
get {
return ResourceManager.GetString("OnlineConfigDelivery", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open logs window.
/// </summary>
internal static string OpenLogsWindow {
get {
return ResourceManager.GetString("OpenLogsWindow", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Password.
/// </summary>
internal static string Password {
get {
return ResourceManager.GetString("Password", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Port.
/// </summary>
internal static string Port {
get {
return ResourceManager.GetString("Port", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Register all.
/// </summary>
internal static string registerAllButton_Content {
get {
return ResourceManager.GetString("registerAllButton_Content", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Register hotkeys at startup.
/// </summary>
internal static string RegisterHotkeysAtStartup {
get {
return ResourceManager.GetString("RegisterHotkeysAtStartup", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove.
/// </summary>
internal static string removeButton_Content {
get {
return ResourceManager.GetString("removeButton_Content", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Save.
/// </summary>
internal static string saveButton_Content {
get {
return ResourceManager.GetString("saveButton_Content", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Server Sharing.
/// </summary>
internal static string ServerSharing {
get {
return ResourceManager.GetString("ServerSharing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The following sources failed to update:\n\n.
/// </summary>
internal static string sip008UpdateAllFailure {
get {
return ResourceManager.GetString("sip008UpdateAllFailure", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Successfully updated all sources!.
/// </summary>
internal static string sip008UpdateAllSuccess {
get {
return ResourceManager.GetString("sip008UpdateAllSuccess", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Update failed. See the logs for more information..
/// </summary>
internal static string sip008UpdateFailure {
get {
return ResourceManager.GetString("sip008UpdateFailure", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Successfully updated the selected source!.
/// </summary>
internal static string sip008UpdateSuccess {
get {
return ResourceManager.GetString("sip008UpdateSuccess", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Skip version.
/// </summary>
internal static string skipVersionButton_Content {
get {
return ResourceManager.GetString("skipVersionButton_Content", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to SOCKS5.
/// </summary>
internal static string SOCKS5 {
get {
return ResourceManager.GetString("SOCKS5", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Switch to next server.
/// </summary>
internal static string SwitchToNextServer {
get {
return ResourceManager.GetString("SwitchToNextServer", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Switch to previous server.
/// </summary>
internal static string SwitchToPreviousServer {
get {
return ResourceManager.GetString("SwitchToPreviousServer", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Timeout (sec).
/// </summary>
internal static string Timeout {
get {
return ResourceManager.GetString("Timeout", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Toggle proxy mode.
/// </summary>
internal static string ToggleProxyMode {
get {
return ResourceManager.GetString("ToggleProxyMode", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Toggle system proxy.
/// </summary>
internal static string ToggleSystemProxy {
get {
return ResourceManager.GetString("ToggleSystemProxy", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type.
/// </summary>
internal static string Type {
get {
return ResourceManager.GetString("Type", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Update all.
/// </summary>
internal static string updateAllButton_Content {
get {
return ResourceManager.GetString("updateAllButton_Content", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Update.
/// </summary>
internal static string updateButton_Content {
get {
return ResourceManager.GetString("updateButton_Content", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please read the release notes carefully. Then decide whether to update..
/// </summary>
internal static string updatePromptBody {
get {
return ResourceManager.GetString("updatePromptBody", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to An update is available..
/// </summary>
internal static string updatePromptTitle {
get {
return ResourceManager.GetString("updatePromptTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Username.
/// </summary>
internal static string Username {
get {
return ResourceManager.GetString("Username", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to VersionUpdate.
/// </summary>
internal static string VersionUpdate {
get {
return ResourceManager.GetString("VersionUpdate", resourceCulture);
}
}
}
}

shadowsocks-csharp/View/ProxyForm.resx → shadowsocks-csharp/Localization/Strings.fr.resx View File

@@ -1,120 +1,183 @@
<?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>
<?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>
<data name="Password" xml:space="preserve">
<value>Mot de passe</value>
</data>
<data name="Port" xml:space="preserve">
<value>Port</value>
</data>
<data name="Type" xml:space="preserve">
<value>Type</value>
</data>
<data name="Address" xml:space="preserve">
<value>adresse</value>
</data>
<data name="Timeout" xml:space="preserve">
<value>Délai d'attente (sec)</value>
</data>
<data name="cancelButton_Content" xml:space="preserve">
<value>Annuler</value>
</data>
<data name="okButton_Content" xml:space="preserve">
<value>OK</value>
</data>
<data name="ToggleSystemProxy" xml:space="preserve">
<value>Changer l'état de proxy système</value>
</data>
<data name="ToggleProxyMode" xml:space="preserve">
<value>Changer le mode de proxy système</value>
</data>
<data name="AllowClientsFromLAN" xml:space="preserve">
<value>Autoriser les clients du LAN</value>
</data>
<data name="OpenLogsWindow" xml:space="preserve">
<value>Afficher les journaux</value>
</data>
<data name="SwitchToPreviousServer" xml:space="preserve">
<value>Passer au serveur précédent</value>
</data>
<data name="SwitchToNextServer" xml:space="preserve">
<value>Passer au serveur suivant</value>
</data>
<data name="RegisterHotkeysAtStartup" xml:space="preserve">
<value>Enregistrer tout</value>
</data>
<data name="registerAllButton_Content" xml:space="preserve">
<value>Enregistrer tout au démarrage</value>
</data>
<data name="addButton_Content" xml:space="preserve">
<value>Ajouter</value>
</data>
<data name="skipVersionButton_Content" xml:space="preserve">
<value>sauter la version</value>
</data>
<data name="copyLinkButton_Content" xml:space="preserve">
<value>Copier le lien</value>
</data>
<data name="removeButton_Content" xml:space="preserve">
<value>retirer</value>
</data>
<data name="updateButton_Content" xml:space="preserve">
<value>mise à jour</value>
</data>
<data name="Copy" xml:space="preserve">
<value>copie</value>
</data>
</root> </root>

shadowsocks-csharp/View/QRCodeForm.resx → shadowsocks-csharp/Localization/Strings.ja.resx View File

@@ -1,120 +1,180 @@
<?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>
<?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>
<data name="Password" xml:space="preserve">
<value>パスワード</value>
</data>
<data name="Type" xml:space="preserve">
<value>タイプ</value>
</data>
<data name="Address" xml:space="preserve">
<value>アドレス</value>
</data>
<data name="Port" xml:space="preserve">
<value>ポート</value>
</data>
<data name="Timeout" xml:space="preserve">
<value>タイムアウト (秒)</value>
</data>
<data name="cancelButton_Content" xml:space="preserve">
<value>キャンセル</value>
</data>
<data name="okButton_Content" xml:space="preserve">
<value>OK</value>
</data>
<data name="ToggleSystemProxy" xml:space="preserve">
<value>システム プロキシの状態を切り替える</value>
</data>
<data name="ToggleProxyMode" xml:space="preserve">
<value>プロキシモードを切り替える</value>
</data>
<data name="AllowClientsFromLAN" xml:space="preserve">
<value>LAN からのアクセスの許可を切り替える</value>
</data>
<data name="OpenLogsWindow" xml:space="preserve">
<value>ログの表示</value>
</data>
<data name="SwitchToPreviousServer" xml:space="preserve">
<value>前のサーバーに切り替える</value>
</data>
<data name="SwitchToNextServer" xml:space="preserve">
<value>次のサーバーに切り替える</value>
</data>
<data name="RegisterHotkeysAtStartup" xml:space="preserve">
<value>全部登録する</value>
</data>
<data name="registerAllButton_Content" xml:space="preserve">
<value>起動時にホットキーを登録する</value>
</data>
<data name="addButton_Content" xml:space="preserve">
<value>新規</value>
</data>
<data name="skipVersionButton_Content" xml:space="preserve">
<value>バージョンをスキップ</value>
</data>
<data name="copyLinkButton_Content" xml:space="preserve">
<value>リンクをコピーする</value>
</data>
<data name="updateButton_Content" xml:space="preserve">
<value>更新</value>
</data>
<data name="Copy" xml:space="preserve">
<value>コピー</value>
</data>
</root> </root>

shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.resx → shadowsocks-csharp/Localization/Strings.ko.resx View File

@@ -117,13 +117,64 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.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> </resheader>
<metadata name="bindingConfiguration.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>4, 5</value>
</metadata>
<metadata name="CalculatinTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>238, 6</value>
</metadata>
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>191</value>
</metadata>
<data name="Password" xml:space="preserve">
<value>비밀번호</value>
</data>
<data name="Type" xml:space="preserve">
<value>유형</value>
</data>
<data name="Address" xml:space="preserve">
<value>주소</value>
</data>
<data name="Port" xml:space="preserve">
<value>포트</value>
</data>
<data name="Timeout" xml:space="preserve">
<value>시간 초과 (초)</value>
</data>
<data name="cancelButton_Content" xml:space="preserve">
<value>취소</value>
</data>
<data name="okButton_Content" xml:space="preserve">
<value>확인</value>
</data>
<data name="ToggleSystemProxy" xml:space="preserve">
<value>시스템 프록시 전환</value>
</data>
<data name="ToggleProxyMode" xml:space="preserve">
<value>시스템 프록시 모드 전환</value>
</data>
<data name="AllowClientsFromLAN" xml:space="preserve">
<value>LAN으로부터 클라이언트 허용</value>
</data>
<data name="OpenLogsWindow" xml:space="preserve">
<value>로그 보기…</value>
</data>
<data name="SwitchToPreviousServer" xml:space="preserve">
<value>이전 서버로 전환</value>
</data>
<data name="SwitchToNextServer" xml:space="preserve">
<value>다음 서버로 전환</value>
</data>
<data name="RegisterHotkeysAtStartup" xml:space="preserve">
<value>모두 등록</value>
</data>
<data name="registerAllButton_Content" xml:space="preserve">
<value>시스템 시작 시 단축키 등록</value>
</data>
<data name="addButton_Content" xml:space="preserve">
<value>추가</value>
</data>
<data name="skipVersionButton_Content" xml:space="preserve">
<value>버전 건너 뛰기</value>
</data>
<data name="copyLinkButton_Content" xml:space="preserve">
<value>링크 복사</value>
</data>
<data name="updateButton_Content" xml:space="preserve">
<value>새롭게 함</value>
</data>
<data name="Copy" xml:space="preserve">
<value>부</value>
</data>
</root> </root>

+ 243
- 0
shadowsocks-csharp/Localization/Strings.resx View File

@@ -0,0 +1,243 @@
<?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>
<data name="Type" xml:space="preserve">
<value>Type</value>
</data>
<data name="NoProxy" xml:space="preserve">
<value>No proxy</value>
</data>
<data name="SOCKS5" xml:space="preserve">
<value>SOCKS5</value>
</data>
<data name="HTTP" xml:space="preserve">
<value>HTTP</value>
</data>
<data name="Details" xml:space="preserve">
<value>Details</value>
</data>
<data name="Address" xml:space="preserve">
<value>Address</value>
</data>
<data name="Port" xml:space="preserve">
<value>Port</value>
</data>
<data name="Timeout" xml:space="preserve">
<value>Timeout (sec)</value>
</data>
<data name="CredentialsOptional" xml:space="preserve">
<value>Credentials (optional)</value>
</data>
<data name="Username" xml:space="preserve">
<value>Username</value>
</data>
<data name="Password" xml:space="preserve">
<value>Password</value>
</data>
<data name="saveButton_Content" xml:space="preserve">
<value>_Save</value>
</data>
<data name="cancelButton_Content" xml:space="preserve">
<value>_Cancel</value>
</data>
<data name="okButton_Content" xml:space="preserve">
<value>_OK</value>
</data>
<data name="ToggleSystemProxy" xml:space="preserve">
<value>Toggle system proxy</value>
</data>
<data name="ToggleProxyMode" xml:space="preserve">
<value>Toggle proxy mode</value>
</data>
<data name="AllowClientsFromLAN" xml:space="preserve">
<value>Allow clients from LAN</value>
</data>
<data name="OpenLogsWindow" xml:space="preserve">
<value>Open logs window</value>
</data>
<data name="SwitchToPreviousServer" xml:space="preserve">
<value>Switch to previous server</value>
</data>
<data name="SwitchToNextServer" xml:space="preserve">
<value>Switch to next server</value>
</data>
<data name="RegisterHotkeysAtStartup" xml:space="preserve">
<value>Register hotkeys at startup</value>
</data>
<data name="registerAllButton_Content" xml:space="preserve">
<value>_Register all</value>
</data>
<data name="updateButton_Content" xml:space="preserve">
<value>_Update</value>
</data>
<data name="updateAllButton_Content" xml:space="preserve">
<value>Update all</value>
</data>
<data name="copyLinkButton_Content" xml:space="preserve">
<value>_Copy link</value>
</data>
<data name="removeButton_Content" xml:space="preserve">
<value>Remove</value>
</data>
<data name="addButton_Content" xml:space="preserve">
<value>_Add</value>
</data>
<data name="updatePromptTitle" xml:space="preserve">
<value>An update is available.</value>
</data>
<data name="updatePromptBody" xml:space="preserve">
<value>Please read the release notes carefully. Then decide whether to update.</value>
</data>
<data name="skipVersionButton_Content" xml:space="preserve">
<value>_Skip version</value>
</data>
<data name="notNowButton_Content" xml:space="preserve">
<value>_Not now</value>
</data>
<data name="Copy" xml:space="preserve">
<value>_Copy</value>
</data>
<data name="ForwardProxy" xml:space="preserve">
<value>Forward Proxy</value>
</data>
<data name="ServerSharing" xml:space="preserve">
<value>Server Sharing</value>
</data>
<data name="Hotkeys" xml:space="preserve">
<value>Hotkeys</value>
</data>
<data name="OnlineConfigDelivery" xml:space="preserve">
<value>Online Configuration Delivery</value>
</data>
<data name="VersionUpdate" xml:space="preserve">
<value>VersionUpdate</value>
</data>
<data name="sip008UpdateSuccess" xml:space="preserve">
<value>Successfully updated the selected source!</value>
</data>
<data name="sip008UpdateFailure" xml:space="preserve">
<value>Update failed. See the logs for more information.</value>
</data>
<data name="sip008UpdateAllSuccess" xml:space="preserve">
<value>Successfully updated all sources!</value>
</data>
<data name="sip008UpdateAllFailure" xml:space="preserve">
<value>The following sources failed to update:\n\n</value>
</data>
</root>

shadowsocks-csharp/View/CalculationControl.resx → shadowsocks-csharp/Localization/Strings.ru.resx View File

@@ -1,120 +1,183 @@
<?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>
<?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>
<data name="Password" xml:space="preserve">
<value>Пароль</value>
</data>
<data name="Type" xml:space="preserve">
<value>тип</value>
</data>
<data name="Address" xml:space="preserve">
<value>адрес</value>
</data>
<data name="Port" xml:space="preserve">
<value>порт</value>
</data>
<data name="Timeout" xml:space="preserve">
<value>Таймаут (сек)</value>
</data>
<data name="cancelButton_Content" xml:space="preserve">
<value>Отмена</value>
</data>
<data name="okButton_Content" xml:space="preserve">
<value>ОК</value>
</data>
<data name="ToggleSystemProxy" xml:space="preserve">
<value>ВКЛ/ВЫКЛ системный прокси-сервер</value>
</data>
<data name="ToggleProxyMode" xml:space="preserve">
<value>Переключение режима прокси-сервера</value>
</data>
<data name="AllowClientsFromLAN" xml:space="preserve">
<value>Общий доступ к подключению</value>
</data>
<data name="OpenLogsWindow" xml:space="preserve">
<value>Просмотр журналов</value>
</data>
<data name="SwitchToPreviousServer" xml:space="preserve">
<value>Переключить на пред. сервер</value>
</data>
<data name="SwitchToNextServer" xml:space="preserve">
<value>Переключить на след. сервер</value>
</data>
<data name="RegisterHotkeysAtStartup" xml:space="preserve">
<value>Применить все</value>
</data>
<data name="registerAllButton_Content" xml:space="preserve">
<value>Применять при запуске программы</value>
</data>
<data name="addButton_Content" xml:space="preserve">
<value>Добавить</value>
</data>
<data name="skipVersionButton_Content" xml:space="preserve">
<value>пропустить версию</value>
</data>
<data name="copyLinkButton_Content" xml:space="preserve">
<value>копировать ссылку</value>
</data>
<data name="removeButton_Content" xml:space="preserve">
<value>Удалить</value>
</data>
<data name="updateButton_Content" xml:space="preserve">
<value>Обновить</value>
</data>
<data name="Copy" xml:space="preserve">
<value>копировать</value>
</data>
</root> </root>

+ 237
- 0
shadowsocks-csharp/Localization/Strings.zh-Hans.resx View File

@@ -0,0 +1,237 @@
<?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>
<data name="NoProxy" xml:space="preserve">
<value>不使用代理</value>
</data>
<data name="Type" xml:space="preserve">
<value>类型</value>
</data>
<data name="CredentialsOptional" xml:space="preserve">
<value>认证 (可选)</value>
</data>
<data name="Details" xml:space="preserve">
<value>详细设置</value>
</data>
<data name="Address" xml:space="preserve">
<value>地址</value>
</data>
<data name="saveButton_Content" xml:space="preserve">
<value>保存</value>
</data>
<data name="cancelButton_Content" xml:space="preserve">
<value>取消</value>
</data>
<data name="Username" xml:space="preserve">
<value>用户名</value>
</data>
<data name="Password" xml:space="preserve">
<value>密码</value>
</data>
<data name="Port" xml:space="preserve">
<value>端口</value>
</data>
<data name="Timeout" xml:space="preserve">
<value>超时 (秒)</value>
</data>
<data name="okButton_Content" xml:space="preserve">
<value>确定</value>
</data>
<data name="registerAllButton_Content" xml:space="preserve">
<value>全部注册</value>
</data>
<data name="ToggleSystemProxy" xml:space="preserve">
<value>切换系统代理</value>
</data>
<data name="ToggleProxyMode" xml:space="preserve">
<value>切换代理模式</value>
</data>
<data name="AllowClientsFromLAN" xml:space="preserve">
<value>允许内网客户端连接</value>
</data>
<data name="OpenLogsWindow" xml:space="preserve">
<value>打开日志窗口</value>
</data>
<data name="SwitchToPreviousServer" xml:space="preserve">
<value>切换到上一个服务器</value>
</data>
<data name="SwitchToNextServer" xml:space="preserve">
<value>切换到下一个服务器</value>
</data>
<data name="RegisterHotkeysAtStartup" xml:space="preserve">
<value>启动时注册快捷键</value>
</data>
<data name="updateButton_Content" xml:space="preserve">
<value>更新</value>
</data>
<data name="updateAllButton_Content" xml:space="preserve">
<value>全部更新</value>
</data>
<data name="copyLinkButton_Content" xml:space="preserve">
<value>复制链接</value>
</data>
<data name="removeButton_Content" xml:space="preserve">
<value>移除</value>
</data>
<data name="addButton_Content" xml:space="preserve">
<value>添加</value>
</data>
<data name="skipVersionButton_Content" xml:space="preserve">
<value>跳过版本</value>
</data>
<data name="notNowButton_Content" xml:space="preserve">
<value>暂不更新</value>
</data>
<data name="updatePromptTitle" xml:space="preserve">
<value>发现可用更新</value>
</data>
<data name="updatePromptBody" xml:space="preserve">
<value>请仔细阅读发布信息,然后决定是否更新。</value>
</data>
<data name="Copy" xml:space="preserve">
<value>复制</value>
</data>
<data name="ForwardProxy" xml:space="preserve">
<value>前置代理</value>
</data>
<data name="OnlineConfigDelivery" xml:space="preserve">
<value>在线配置下发</value>
</data>
<data name="Hotkeys" xml:space="preserve">
<value>热键</value>
</data>
<data name="ServerSharing" xml:space="preserve">
<value>服务器分享</value>
</data>
<data name="VersionUpdate" xml:space="preserve">
<value>版本更新</value>
</data>
<data name="sip008UpdateSuccess" xml:space="preserve">
<value>成功更新选定来源!</value>
</data>
<data name="sip008UpdateFailure" xml:space="preserve">
<value>更新失败,请查看日志获取更多信息。</value>
</data>
<data name="sip008UpdateAllSuccess" xml:space="preserve">
<value>成功更新所有来源!</value>
</data>
<data name="sip008UpdateAllFailure" xml:space="preserve">
<value>下列来源更新失败:\n\n</value>
</data>
</root>

+ 189
- 0
shadowsocks-csharp/Localization/Strings.zh-Hant.resx View File

@@ -0,0 +1,189 @@
<?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>
<data name="Password" xml:space="preserve">
<value>密碼</value>
</data>
<data name="Port" xml:space="preserve">
<value>Port</value>
</data>
<data name="Type" xml:space="preserve">
<value>類型</value>
</data>
<data name="Address" xml:space="preserve">
<value>位址</value>
</data>
<data name="Timeout" xml:space="preserve">
<value>逾時 (秒)</value>
</data>
<data name="cancelButton_Content" xml:space="preserve">
<value>取消</value>
</data>
<data name="okButton_Content" xml:space="preserve">
<value>確定</value>
</data>
<data name="ToggleSystemProxy" xml:space="preserve">
<value>切換系統 Proxy 狀態</value>
</data>
<data name="ToggleProxyMode" xml:space="preserve">
<value>切換系統 Proxy 模式</value>
</data>
<data name="AllowClientsFromLAN" xml:space="preserve">
<value>切換區域網路共用</value>
</data>
<data name="OpenLogsWindow" xml:space="preserve">
<value>顯示記錄檔</value>
</data>
<data name="SwitchToPreviousServer" xml:space="preserve">
<value>切換上一個伺服器</value>
</data>
<data name="SwitchToNextServer" xml:space="preserve">
<value>切換下一個伺服器</value>
</data>
<data name="RegisterHotkeysAtStartup" xml:space="preserve">
<value>註冊所有快速鍵</value>
</data>
<data name="registerAllButton_Content" xml:space="preserve">
<value>啟動時註冊快速鍵</value>
</data>
<data name="updateButton_Content" xml:space="preserve">
<value>更新</value>
</data>
<data name="updateAllButton_Content" xml:space="preserve">
<value>全部更新</value>
</data>
<data name="addButton_Content" xml:space="preserve">
<value>新增</value>
</data>
<data name="skipVersionButton_Content" xml:space="preserve">
<value>跳過版本</value>
</data>
<data name="notNowButton_Content" xml:space="preserve">
<value>暫不更新</value>
</data>
<data name="updatePromptTitle" xml:space="preserve">
<value>發現可用更新</value>
</data>
<data name="copyLinkButton_Content" xml:space="preserve">
<value>複製鏈接</value>
</data>
<data name="Copy" xml:space="preserve">
<value>複製</value>
</data>
</root>

+ 160
- 96
shadowsocks-csharp/Model/Configuration.cs View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Windows;
using Newtonsoft.Json; using Newtonsoft.Json;
using NLog; using NLog;
using Shadowsocks.Controller; using Shadowsocks.Controller;
@@ -27,33 +28,36 @@ namespace Shadowsocks.Model
public bool global; public bool global;
public bool enabled; public bool enabled;
public bool shareOverLan; public bool shareOverLan;
public bool isDefault;
public bool firstRun;
public int localPort; public int localPort;
public bool portableMode; public bool portableMode;
public bool showPluginOutput; public bool showPluginOutput;
public string pacUrl; public string pacUrl;
public bool useOnlinePac; public bool useOnlinePac;
public bool secureLocalPac;
public bool availabilityStatistics;
public bool secureLocalPac; // enable secret for PAC server
public bool regeneratePacOnUpdate; // regenerate pac.txt on version update
public bool autoCheckUpdate; public bool autoCheckUpdate;
public bool checkPreRelease; public bool checkPreRelease;
public string skippedUpdateVersion; // skip the update with this version number
public bool isVerboseLogging; public bool isVerboseLogging;
// hidden options // hidden options
public bool isIPv6Enabled; // for experimental ipv6 support public bool isIPv6Enabled; // for experimental ipv6 support
public bool generateLegacyUrl = false; // for pre-sip002 url compatibility
public bool generateLegacyUrl; // for pre-sip002 url compatibility
public string geositeUrl; // for custom geosite source (and rule group) public string geositeUrl; // for custom geosite source (and rule group)
public string geositeGroup;
public bool geositeBlacklistMode;
public List<string> geositeDirectGroups; // groups of domains that we connect without the proxy
public List<string> geositeProxiedGroups; // groups of domains that we connect via the proxy
public bool geositePreferDirect; // a.k.a blacklist mode
public string userAgent;
//public NLogConfig.LogLevel logLevel; //public NLogConfig.LogLevel logLevel;
public LogViewerConfig logViewer; public LogViewerConfig logViewer;
public ProxyConfig proxy;
public ForwardProxyConfig proxy;
public HotkeyConfig hotkey; public HotkeyConfig hotkey;
[JsonIgnore] [JsonIgnore]
public bool updated;
public bool firstRunOnNewVersion;
public Configuration() public Configuration()
{ {
@@ -63,35 +67,48 @@ namespace Shadowsocks.Model
global = false; global = false;
enabled = false; enabled = false;
shareOverLan = false; shareOverLan = false;
isDefault = true;
firstRun = true;
localPort = 1080; localPort = 1080;
portableMode = true; portableMode = true;
showPluginOutput = false; showPluginOutput = false;
pacUrl = ""; pacUrl = "";
useOnlinePac = false; useOnlinePac = false;
secureLocalPac = true; secureLocalPac = true;
availabilityStatistics = false;
regeneratePacOnUpdate = true;
autoCheckUpdate = false; autoCheckUpdate = false;
checkPreRelease = false; checkPreRelease = false;
skippedUpdateVersion = "";
isVerboseLogging = false; isVerboseLogging = false;
// hidden options // hidden options
isIPv6Enabled = false; isIPv6Enabled = false;
generateLegacyUrl = false; generateLegacyUrl = false;
geositeUrl = ""; geositeUrl = "";
geositeGroup = "geolocation-!cn";
geositeBlacklistMode = true;
geositeDirectGroups = new List<string>()
{
"cn",
"geolocation-!cn@cn"
};
geositeProxiedGroups = new List<string>()
{
"geolocation-!cn"
};
geositePreferDirect = false;
userAgent = "ShadowsocksWindows/$version";
logViewer = new LogViewerConfig(); logViewer = new LogViewerConfig();
proxy = new ProxyConfig();
proxy = new ForwardProxyConfig();
hotkey = new HotkeyConfig(); hotkey = new HotkeyConfig();
updated = false;
firstRunOnNewVersion = false;
configs = new List<Server>(); configs = new List<Server>();
onlineConfigSource = new List<string>(); onlineConfigSource = new List<string>();
} }
[JsonIgnore]
public string userAgentString; // $version substituted with numeral version in it
[JsonIgnore] [JsonIgnore]
NLogConfig nLogConfig; NLogConfig nLogConfig;
@@ -103,11 +120,8 @@ namespace Shadowsocks.Model
#endif #endif
[JsonIgnore] [JsonIgnore]
public string localHost => GetLocalHost();
private string GetLocalHost()
{
return isIPv6Enabled ? "[::1]" : "127.0.0.1";
}
public string LocalHost => isIPv6Enabled ? "[::1]" : "127.0.0.1";
public Server GetCurrentServer() public Server GetCurrentServer()
{ {
if (index >= 0 && index < configs.Count) if (index >= 0 && index < configs.Count)
@@ -124,6 +138,11 @@ namespace Shadowsocks.Model
localPort) localPort)
: null; : null;
/// <summary>
/// Used by multiple forms to validate a server.
/// Communication is done by throwing exceptions.
/// </summary>
/// <param name="server"></param>
public static void CheckServer(Server server) public static void CheckServer(Server server)
{ {
CheckServer(server.server); CheckServer(server.server);
@@ -132,52 +151,70 @@ namespace Shadowsocks.Model
CheckTimeout(server.timeout, Server.MaxServerTimeoutSec); CheckTimeout(server.timeout, Server.MaxServerTimeoutSec);
} }
public static bool ChecksServer(Server server)
{
try
{
CheckServer(server);
return true;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Loads the configuration from file.
/// </summary>
/// <returns>An Configuration object.</returns>
public static Configuration Load() public static Configuration Load()
{ {
Configuration config; Configuration config;
try
if (File.Exists(CONFIG_FILE))
{ {
string configContent = File.ReadAllText(CONFIG_FILE);
config = JsonConvert.DeserializeObject<Configuration>(configContent);
config.isDefault = false;
if (UpdateChecker.Asset.CompareVersion(UpdateChecker.Version, config.version ?? "0") > 0)
try
{ {
config.updated = true;
string configContent = File.ReadAllText(CONFIG_FILE);
config = JsonConvert.DeserializeObject<Configuration>(configContent, new JsonSerializerSettings()
{
ObjectCreationHandling = ObjectCreationHandling.Replace
});
return config;
} }
if (config.configs.Count == 0)
config.configs.Add(GetDefaultServer());
if (config.index == -1 && string.IsNullOrEmpty(config.strategy))
config.index = 0;
if (!System.Net.Sockets.Socket.OSSupportsIPv6)
catch (Exception e)
{ {
config.isIPv6Enabled = false; // disable IPv6 if os not support
if (!(e is FileNotFoundException))
logger.LogUsefulException(e);
} }
//TODO if remote host(server) do not support IPv6 (or DNS resolve AAAA TYPE record) disable IPv6?
config.proxy.CheckConfig();
} }
catch (Exception e)
config = new Configuration();
return config;
}
/// <summary>
/// Process the loaded configurations and set up things.
/// </summary>
/// <param name="config">A reference of Configuration object.</param>
public static void Process(ref Configuration config)
{
// Verify if the configured geosite groups exist.
// Reset to default if ANY one of the configured group doesn't exist.
if (!ValidateGeositeGroupList(config.geositeDirectGroups))
ResetGeositeDirectGroup(ref config.geositeDirectGroups);
if (!ValidateGeositeGroupList(config.geositeProxiedGroups))
ResetGeositeProxiedGroup(ref config.geositeProxiedGroups);
// Mark the first run of a new version.
var appVersion = new Version(UpdateChecker.Version);
var configVersion = new Version(config.version);
if (appVersion.CompareTo(configVersion) > 0)
{ {
if (!(e is FileNotFoundException))
logger.LogUsefulException(e);
config = new Configuration();
config.configs.Add(GetDefaultServer());
config.firstRunOnNewVersion = true;
} }
// Add an empty server configuration
if (config.configs.Count == 0)
config.configs.Add(GetDefaultServer());
// Selected server
if (config.index == -1 && string.IsNullOrEmpty(config.strategy))
config.index = 0;
if (config.index >= config.configs.Count)
config.index = config.configs.Count - 1;
// Check OS IPv6 support
if (!System.Net.Sockets.Socket.OSSupportsIPv6)
config.isIPv6Enabled = false;
config.proxy.CheckConfig();
// Replace $version with the version number.
config.userAgentString = config.userAgent.Replace("$version", config.version);
// NLog log level
try try
{ {
config.nLogConfig = NLogConfig.LoadXML(); config.nLogConfig = NLogConfig.LoadXML();
@@ -197,46 +234,42 @@ namespace Shadowsocks.Model
} }
catch (Exception e) catch (Exception e)
{ {
// todo: route the error to UI since there is no log file in this scenario
logger.Error(e, "Cannot get the log level from NLog config file. Please check if the nlog config file exists with corresponding XML nodes.");
MessageBox.Show($"Cannot get the log level from NLog config file. Please check if the nlog config file exists with corresponding XML nodes.\n{e.Message}");
} }
return config;
} }
/// <summary>
/// Saves the Configuration object to file.
/// </summary>
/// <param name="config">A Configuration object.</param>
public static void Save(Configuration config) public static void Save(Configuration config)
{ {
config.configs = SortByOnlineConfig(config.configs); config.configs = SortByOnlineConfig(config.configs);
if (config.index >= config.configs.Count)
config.index = config.configs.Count - 1;
if (config.index < -1)
config.index = -1;
if (config.index == -1 && string.IsNullOrEmpty(config.strategy))
config.index = 0;
config.isDefault = false;
FileStream configFileStream = null;
StreamWriter configStreamWriter = null;
try try
{ {
using (StreamWriter sw = new StreamWriter(File.Open(CONFIG_FILE, FileMode.Create)))
{
string jsonString = JsonConvert.SerializeObject(config, Formatting.Indented);
sw.Write(jsonString);
sw.Flush();
}
try
{
// apply changes to NLog.config
config.nLogConfig.SetLogLevel(config.isVerboseLogging ? verboseLogLevel : NLogConfig.LogLevel.Info);
NLogConfig.SaveXML(config.nLogConfig);
}
catch (Exception e)
{
logger.Error(e, "Cannot set the log level to NLog config file. Please check if the nlog config file exists with corresponding XML nodes.");
}
configFileStream = File.Open(CONFIG_FILE, FileMode.Create);
configStreamWriter = new StreamWriter(configFileStream);
var jsonString = JsonConvert.SerializeObject(config, Formatting.Indented);
configStreamWriter.Write(jsonString);
configStreamWriter.Flush();
// NLog
config.nLogConfig.SetLogLevel(config.isVerboseLogging ? verboseLogLevel : NLogConfig.LogLevel.Info);
NLogConfig.SaveXML(config.nLogConfig);
} }
catch (IOException e)
catch (Exception e)
{ {
logger.LogUsefulException(e); logger.LogUsefulException(e);
} }
finally
{
if (configStreamWriter != null)
configStreamWriter.Dispose();
if (configFileStream != null)
configFileStream.Dispose();
}
} }
public static List<Server> SortByOnlineConfig(IEnumerable<Server> servers) public static List<Server> SortByOnlineConfig(IEnumerable<Server> servers)
@@ -248,6 +281,49 @@ namespace Shadowsocks.Model
return ret; return ret;
} }
/// <summary>
/// Validates if the groups in the list are all valid.
/// </summary>
/// <param name="groups">The list of groups to validate.</param>
/// <returns>
/// True if all groups are valid.
/// False if any one of them is invalid.
/// </returns>
public static bool ValidateGeositeGroupList(List<string> groups)
{
foreach (var geositeGroup in groups)
if (!GeositeUpdater.CheckGeositeGroup(geositeGroup)) // found invalid group
{
#if DEBUG
logger.Debug($"Available groups:");
foreach (var group in GeositeUpdater.Geosites.Keys)
logger.Debug($"{group}");
#endif
logger.Warn($"The Geosite group {geositeGroup} doesn't exist. Resetting to default groups.");
return false;
}
return true;
}
public static void ResetGeositeDirectGroup(ref List<string> geositeDirectGroups)
{
geositeDirectGroups.Clear();
geositeDirectGroups.Add("cn");
geositeDirectGroups.Add("geolocation-!cn@cn");
}
public static void ResetGeositeProxiedGroup(ref List<string> geositeProxiedGroups)
{
geositeProxiedGroups.Clear();
geositeProxiedGroups.Add("geolocation-!cn");
}
public static void ResetUserAgent(Configuration config)
{
config.userAgent = "ShadowsocksWindows/$version";
config.userAgentString = config.userAgent.Replace("$version", config.version);
}
public static Server AddDefaultServerOrServer(Configuration config, Server server = null, int? index = null) public static Server AddDefaultServerOrServer(Configuration config, Server server = null, int? index = null)
{ {
if (config?.configs != null) if (config?.configs != null)
@@ -284,13 +360,13 @@ namespace Shadowsocks.Model
private static void CheckPassword(string password) private static void CheckPassword(string password)
{ {
if (password.IsNullOrEmpty())
if (string.IsNullOrEmpty(password))
throw new ArgumentException(I18N.GetString("Password can not be blank")); throw new ArgumentException(I18N.GetString("Password can not be blank"));
} }
public static void CheckServer(string server) public static void CheckServer(string server)
{ {
if (server.IsNullOrEmpty())
if (string.IsNullOrEmpty(server))
throw new ArgumentException(I18N.GetString("Server IP can not be blank")); throw new ArgumentException(I18N.GetString("Server IP can not be blank"));
} }
@@ -300,17 +376,5 @@ namespace Shadowsocks.Model
throw new ArgumentException( throw new ArgumentException(
I18N.GetString("Timeout is invalid, it should not exceed {0}", maxTimeout)); I18N.GetString("Timeout is invalid, it should not exceed {0}", maxTimeout));
} }
public static void CheckProxyAuthUser(string user)
{
if (user.IsNullOrEmpty())
throw new ArgumentException(I18N.GetString("Auth user can not be blank"));
}
public static void CheckProxyAuthPwd(string pwd)
{
if (pwd.IsNullOrEmpty())
throw new ArgumentException(I18N.GetString("Auth pwd can not be blank"));
}
} }
} }

shadowsocks-csharp/Model/ProxyConfig.cs → shadowsocks-csharp/Model/ForwardProxyConfig.cs View File

@@ -1,43 +1,43 @@
using System;
namespace Shadowsocks.Model
{
[Serializable]
public class ProxyConfig
{
public const int PROXY_SOCKS5 = 0;
public const int PROXY_HTTP = 1;
public const int MaxProxyTimeoutSec = 10;
private const int DefaultProxyTimeoutSec = 3;
public bool useProxy;
public int proxyType;
public string proxyServer;
public int proxyPort;
public int proxyTimeout;
public bool useAuth;
public string authUser;
public string authPwd;
public ProxyConfig()
{
useProxy = false;
proxyType = PROXY_SOCKS5;
proxyServer = "";
proxyPort = 0;
proxyTimeout = DefaultProxyTimeoutSec;
useAuth = false;
authUser = "";
authPwd = "";
}
public void CheckConfig()
{
if (proxyType < PROXY_SOCKS5 || proxyType > PROXY_HTTP)
{
proxyType = PROXY_SOCKS5;
}
}
}
}
using System;
namespace Shadowsocks.Model
{
[Serializable]
public class ForwardProxyConfig
{
public const int PROXY_SOCKS5 = 0;
public const int PROXY_HTTP = 1;
public const int MaxProxyTimeoutSec = 10;
private const int DefaultProxyTimeoutSec = 3;
public bool useProxy;
public int proxyType;
public string proxyServer;
public int proxyPort;
public int proxyTimeout;
public bool useAuth;
public string authUser;
public string authPwd;
public ForwardProxyConfig()
{
useProxy = false;
proxyType = PROXY_SOCKS5;
proxyServer = "";
proxyPort = 0;
proxyTimeout = DefaultProxyTimeoutSec;
useAuth = false;
authUser = "";
authPwd = "";
}
public void CheckConfig()
{
if (proxyType < PROXY_SOCKS5 || proxyType > PROXY_HTTP)
{
proxyType = PROXY_SOCKS5;
}
}
}
}

+ 6
- 10
shadowsocks-csharp/Model/Server.cs View File

@@ -55,21 +55,17 @@ namespace Shadowsocks.Model
return server.GetHashCode() ^ server_port; return server.GetHashCode() ^ server_port;
} }
public override bool Equals(object obj)
{
Server o2 = (Server)obj;
return server == o2.server && server_port == o2.server_port;
}
public override bool Equals(object obj) => obj is Server o2 && server == o2.server && server_port == o2.server_port;
public override string ToString() public override string ToString()
{ {
if (server.IsNullOrEmpty())
if (string.IsNullOrEmpty(server))
{ {
return I18N.GetString("New server"); return I18N.GetString("New server");
} }
string serverStr = $"{FormalHostName}:{server_port}"; string serverStr = $"{FormalHostName}:{server_port}";
return remarks.IsNullOrEmpty()
return string.IsNullOrEmpty(remarks)
? serverStr ? serverStr
: $"{remarks} ({serverStr})"; : $"{remarks} ({serverStr})";
} }
@@ -93,7 +89,7 @@ namespace Shadowsocks.Model
u.Port = server_port; u.Port = server_port;
u.Fragment = HttpUtility.UrlEncode(remarks, Encoding.UTF8); u.Fragment = HttpUtility.UrlEncode(remarks, Encoding.UTF8);
if (!plugin.IsNullOrWhiteSpace())
if (!string.IsNullOrWhiteSpace(plugin))
{ {
NameValueCollection param = HttpUtility.ParseQueryString(""); NameValueCollection param = HttpUtility.ParseQueryString("");
@@ -147,7 +143,7 @@ namespace Shadowsocks.Model
Server server = new Server(); Server server = new Server();
var base64 = match.Groups["base64"].Value.TrimEnd('/'); var base64 = match.Groups["base64"].Value.TrimEnd('/');
var tag = match.Groups["tag"].Value; var tag = match.Groups["tag"].Value;
if (!tag.IsNullOrEmpty())
if (!string.IsNullOrEmpty(tag))
{ {
server.remarks = HttpUtility.UrlDecode(tag, Encoding.UTF8); server.remarks = HttpUtility.UrlDecode(tag, Encoding.UTF8);
} }
@@ -173,7 +169,7 @@ namespace Shadowsocks.Model
public static Server ParseURL(string serverUrl) public static Server ParseURL(string serverUrl)
{ {
string _serverUrl = serverUrl.Trim(); string _serverUrl = serverUrl.Trim();
if (!_serverUrl.BeginWith("ss://", StringComparison.InvariantCultureIgnoreCase))
if (!_serverUrl.StartsWith("ss://", StringComparison.InvariantCultureIgnoreCase))
{ {
return null; return null;
} }


+ 0
- 95
shadowsocks-csharp/Model/StatisticsRecord.cs View File

@@ -1,95 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Shadowsocks.Model
{
// Simple processed records for a short period of time
public class StatisticsRecord
{
public DateTime Timestamp { get; set; } = DateTime.Now;
public string ServerIdentifier { get; set; }

// in ping-only records, these fields would be null
public int? AverageLatency;
public int? MinLatency;
public int? MaxLatency;

private bool EmptyLatencyData => (AverageLatency == null) && (MinLatency == null) && (MaxLatency == null);

public int? AverageInboundSpeed;
public int? MinInboundSpeed;
public int? MaxInboundSpeed;

private bool EmptyInboundSpeedData
=> (AverageInboundSpeed == null) && (MinInboundSpeed == null) && (MaxInboundSpeed == null);

public int? AverageOutboundSpeed;
public int? MinOutboundSpeed;
public int? MaxOutboundSpeed;

private bool EmptyOutboundSpeedData
=> (AverageOutboundSpeed == null) && (MinOutboundSpeed == null) && (MaxOutboundSpeed == null);

// if user disabled ping test, response would be null
public int? AverageResponse;
public int? MinResponse;
public int? MaxResponse;
public float? PackageLoss;

private bool EmptyResponseData
=> (AverageResponse == null) && (MinResponse == null) && (MaxResponse == null) && (PackageLoss == null);

public bool IsEmptyData() {
return EmptyInboundSpeedData && EmptyOutboundSpeedData && EmptyResponseData && EmptyLatencyData;
}

public StatisticsRecord()
{
}

public StatisticsRecord(string identifier, ICollection<int> inboundSpeedRecords, ICollection<int> outboundSpeedRecords, ICollection<int> latencyRecords)
{
ServerIdentifier = identifier;
var inbound = inboundSpeedRecords?.Where(s => s > 0).ToList();
if (inbound != null && inbound.Any())
{
AverageInboundSpeed = (int) inbound.Average();
MinInboundSpeed = inbound.Min();
MaxInboundSpeed = inbound.Max();
}
var outbound = outboundSpeedRecords?.Where(s => s > 0).ToList();
if (outbound!= null && outbound.Any())
{
AverageOutboundSpeed = (int) outbound.Average();
MinOutboundSpeed = outbound.Min();
MaxOutboundSpeed = outbound.Max();
}
var latency = latencyRecords?.Where(s => s > 0).ToList();
if (latency!= null && latency.Any())
{
AverageLatency = (int) latency.Average();
MinLatency = latency.Min();
MaxLatency = latency.Max();
}
}

public StatisticsRecord(string identifier, ICollection<int?> responseRecords)
{
ServerIdentifier = identifier;
SetResponse(responseRecords);
}

public void SetResponse(ICollection<int?> responseRecords)
{
if (responseRecords == null) return;
var records = responseRecords.Where(response => response != null).Select(response => response.Value).ToList();
if (!records.Any()) return;
AverageResponse = (int?) records.Average();
MinResponse = records.Min();
MaxResponse = records.Max();
PackageLoss = responseRecords.Count(response => response != null)/(float) responseRecords.Count;
}
}
}

+ 0
- 69
shadowsocks-csharp/Model/StatisticsStrategyConfiguration.cs View File

@@ -1,69 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

using Newtonsoft.Json;
using NLog;

namespace Shadowsocks.Model
{
[Serializable]
public class StatisticsStrategyConfiguration
{
private static Logger logger = LogManager.GetCurrentClassLogger();

public static readonly string ID = "com.shadowsocks.strategy.statistics";
public bool StatisticsEnabled { get; set; } = false;
public bool ByHourOfDay { get; set; } = true;
public bool Ping { get; set; }
public int ChoiceKeptMinutes { get; set; } = 10;
public int DataCollectionMinutes { get; set; } = 10;
public int RepeatTimesNum { get; set; } = 4;

private const string ConfigFile = "statistics-config.json";

public static StatisticsStrategyConfiguration Load()
{
try
{
var content = File.ReadAllText(ConfigFile);
var configuration = JsonConvert.DeserializeObject<StatisticsStrategyConfiguration>(content);
return configuration;
}
catch (FileNotFoundException)
{
var configuration = new StatisticsStrategyConfiguration();
Save(configuration);
return configuration;
}
catch (Exception e)
{
logger.LogUsefulException(e);
return new StatisticsStrategyConfiguration();
}
}

public static void Save(StatisticsStrategyConfiguration configuration)
{
try
{
var content = JsonConvert.SerializeObject(configuration, Formatting.Indented);
File.WriteAllText(ConfigFile, content);
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}

public Dictionary<string, float> Calculations;

public StatisticsStrategyConfiguration()
{
var properties = typeof(StatisticsRecord).GetFields(BindingFlags.Instance | BindingFlags.Public);
Calculations = properties.ToDictionary(p => p.Name, _ => (float)0);
}
}
}

+ 15
- 1
shadowsocks-csharp/Program.cs View File

@@ -3,6 +3,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Pipes; using System.IO.Pipes;
using System.Net; using System.Net;
using System.Reflection;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -10,10 +11,13 @@ using System.Windows.Forms;
using CommandLine; using CommandLine;
using Microsoft.Win32; using Microsoft.Win32;
using NLog; using NLog;
using ReactiveUI;
using Shadowsocks.Controller; using Shadowsocks.Controller;
using Shadowsocks.Controller.Hotkeys; using Shadowsocks.Controller.Hotkeys;
using Shadowsocks.Util; using Shadowsocks.Util;
using Shadowsocks.View; using Shadowsocks.View;
using Splat;
using WPFLocalizeExtension.Engine;
namespace Shadowsocks namespace Shadowsocks
{ {
@@ -89,6 +93,16 @@ namespace Shadowsocks
// we have to do this for self-contained executables // we have to do this for self-contained executables
Directory.SetCurrentDirectory(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName)); Directory.SetCurrentDirectory(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
// We would use this in v5.
// Parameters would have to be dropped from views' constructors (VersionUpdatePromptView)
//Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetCallingAssembly());
// Workaround for hosting WPF controls in a WinForms app.
// We have to manually set the culture for the LocalizeDictionary instance.
// https://stackoverflow.com/questions/374518/localizing-a-winforms-application-with-embedded-wpf-user-controls
// https://stackoverflow.com/questions/14668640/wpf-localize-extension-translate-window-at-run-time
LocalizeDictionary.Instance.Culture = Thread.CurrentThread.CurrentCulture;
#if DEBUG #if DEBUG
// truncate privoxy log file while debugging // truncate privoxy log file while debugging
string privoxyLogFilename = Utils.GetTempPath("privoxy.log"); string privoxyLogFilename = Utils.GetTempPath("privoxy.log");
@@ -163,7 +177,7 @@ namespace Shadowsocks
Thread.Sleep(10 * 1000); Thread.Sleep(10 * 1000);
try try
{ {
MainController.Start(false);
MainController.Start(true);
logger.Info("controller started"); logger.Info("controller started");
} }
catch (Exception ex) catch (Exception ex)


+ 0
- 10
shadowsocks-csharp/Properties/DataSources/Shadowsocks.Model.StatisticsStrategyConfiguration.datasource View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is automatically generated by Visual Studio .Net. It is
used to store generic object data source configuration information.
Renaming the file extension or editing the content of this file may
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="StatisticsStrategyConfiguration" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>Shadowsocks.Model.StatisticsStrategyConfiguration, Shadowsocks, Version=2.5.2.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>

+ 57
- 54
shadowsocks-csharp/Properties/Resources.Designer.cs View File

@@ -1,10 +1,10 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// //
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -13,16 +13,16 @@ namespace Shadowsocks.Properties {
/// <summary> /// <summary>
/// 一个强类型的资源类,用于查找本地化的字符串等。
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary> /// </summary>
// 此类是由 StronglyTypedResourceBuilder
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
public class Resources {
private static global::System.Resources.ResourceManager resourceMan; private static global::System.Resources.ResourceManager resourceMan;
@@ -33,10 +33,10 @@ namespace Shadowsocks.Properties {
} }
/// <summary> /// <summary>
/// 返回此类使用的缓存的 ResourceManager 实例。
/// Returns the cached ResourceManager instance used by this class.
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
public static global::System.Resources.ResourceManager ResourceManager {
get { get {
if (object.ReferenceEquals(resourceMan, null)) { if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Shadowsocks.Properties.Resources", typeof(Resources).Assembly); global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Shadowsocks.Properties.Resources", typeof(Resources).Assembly);
@@ -47,11 +47,11 @@ namespace Shadowsocks.Properties {
} }
/// <summary> /// <summary>
/// 重写当前线程的 CurrentUICulture 属性
/// 重写当前线程的 CurrentUICulture 属性。
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
public static global::System.Globalization.CultureInfo Culture {
get { get {
return resourceCulture; return resourceCulture;
} }
@@ -61,7 +61,7 @@ namespace Shadowsocks.Properties {
} }
/// <summary> /// <summary>
/// 查找类似 /* eslint-disable */
/// Looks up a localized string similar to /* eslint-disable */
///// Was generated by gfwlist2pac in precise mode ///// Was generated by gfwlist2pac in precise mode
///// https://github.com/clowwindy/gfwlist2pac ///// https://github.com/clowwindy/gfwlist2pac
/// ///
@@ -69,25 +69,28 @@ namespace Shadowsocks.Properties {
///// 2019-02-08: Updated to support shadowsocks-windows user rules. ///// 2019-02-08: Updated to support shadowsocks-windows user rules.
/// ///
///var proxy = __PROXY__; ///var proxy = __PROXY__;
///var userrules = __USERRULES__;
///var rules = __RULES__;
///var userrules = [];
///var rules = [];
/// ///
////*
///* This file is part of Adblock Plus &lt;http://adblockplus.org/&gt;,
///* Copyright (C) 2006-2014 Eyeo GmbH
///*
///* Adblock Plus is free software: you can redistribute it and/or [字符串的其余部分被截断]&quot;; 的本地化字符串。
///// convert to abp grammar
///for (var i = 0; i &lt; __RULES__.length; i++) {
/// var s = __RULES__[i];
/// if (s.substring(0, 2) == &quot;||&quot;) s += &quot;^&quot;;
/// rules.push(s);
///}
///
///for (var i = 0; i &lt; [rest of string was truncated]&quot;;.
/// </summary> /// </summary>
internal static string abp_js {
public static string abp_js {
get { get {
return ResourceManager.GetString("abp_js", resourceCulture); return ResourceManager.GetString("abp_js", resourceCulture);
} }
} }
/// <summary> /// <summary>
/// 查找 System.Byte[] 类型的本地化资源。
/// Looks up a localized resource of type System.Byte[].
/// </summary> /// </summary>
internal static byte[] dlc_dat {
public static byte[] dlc_dat {
get { get {
object obj = ResourceManager.GetObject("dlc_dat", resourceCulture); object obj = ResourceManager.GetObject("dlc_dat", resourceCulture);
return ((byte[])(obj)); return ((byte[])(obj));
@@ -95,7 +98,7 @@ namespace Shadowsocks.Properties {
} }
/// <summary> /// <summary>
/// 查找类似 en,ru-RU,zh-CN,zh-TW,ja,ko,fr
/// Looks up a localized string similar to en,ru-RU,zh-CN,zh-TW,ja,ko,fr
///#Restart program to apply translation,,,,,, ///#Restart program to apply translation,,,,,,
///#This is comment line,,,,,, ///#This is comment line,,,,,,
///#Always keep language name at head of file,,,,,, ///#Always keep language name at head of file,,,,,,
@@ -107,31 +110,31 @@ namespace Shadowsocks.Properties {
///,,,,,, ///,,,,,,
///#Menu,,,,,, ///#Menu,,,,,,
///,,,,,, ///,,,,,,
///System Proxy,Системный прокси-сервер,系统代理,系統代理,システムプロキシ,시스템 프록시,P [字符串的其余部分被截断]&quot;; 的本地化字符串。
///System Proxy,Системный прокси-сервер,系统代理,系統代理,システムプロキシ,시스템 프록시,P [rest of string was truncated]&quot;;.
/// </summary> /// </summary>
internal static string i18n_csv {
public static string i18n_csv {
get { get {
return ResourceManager.GetString("i18n_csv", resourceCulture); return ResourceManager.GetString("i18n_csv", resourceCulture);
} }
} }
/// <summary> /// <summary>
/// 查找类似 &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
/// Looks up a localized string similar to &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
///&lt;!-- Warning: Configuration may reset after shadowsocks upgrade. --&gt; ///&lt;!-- Warning: Configuration may reset after shadowsocks upgrade. --&gt;
///&lt;!-- If you messed it up, delete this file and Shadowsocks will create a new one. --&gt; ///&lt;!-- If you messed it up, delete this file and Shadowsocks will create a new one. --&gt;
///&lt;nlog xmlns=&quot;http://www.nlog-project.org/schemas/NLog.xsd&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt; ///&lt;nlog xmlns=&quot;http://www.nlog-project.org/schemas/NLog.xsd&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;
/// &lt;targets&gt; /// &lt;targets&gt;
/// &lt;!-- This line is managed by Shadowsocks. Do not modify it unless you know what you are doing.--&gt; /// &lt;!-- This line is managed by Shadowsocks. Do not modify it unless you know what you are doing.--&gt;
/// &lt;target name=&quot;file&quot; xsi:type=&quot;File&quot; fileName=&quot;ss_win_temp\shadowsocks.log&quot; writ [字符串的其余部分被截断]&quot;; 的本地化字符串。
/// &lt;target name=&quot;file&quot; xsi:type=&quot;File&quot; fileName=&quot;ss_win_temp\shadowsocks.log&quot; writ [rest of string was truncated]&quot;;.
/// </summary> /// </summary>
internal static string NLog_config {
public static string NLog_config {
get { get {
return ResourceManager.GetString("NLog_config", resourceCulture); return ResourceManager.GetString("NLog_config", resourceCulture);
} }
} }
/// <summary> /// <summary>
/// 查找类似 listen-address __PRIVOXY_BIND_IP__:__PRIVOXY_BIND_PORT__
/// Looks up a localized string similar to listen-address __PRIVOXY_BIND_IP__:__PRIVOXY_BIND_PORT__
///toggle 0 ///toggle 0
///logfile ss_privoxy.log ///logfile ss_privoxy.log
///show-on-task-bar 0 ///show-on-task-bar 0
@@ -139,18 +142,18 @@ namespace Shadowsocks.Properties {
///forward-socks5 / __SOCKS_HOST__:__SOCKS_PORT__ . ///forward-socks5 / __SOCKS_HOST__:__SOCKS_PORT__ .
///max-client-connections 2048 ///max-client-connections 2048
///hide-console ///hide-console
/// 的本地化字符串。
///.
/// </summary> /// </summary>
internal static string privoxy_conf {
public static string privoxy_conf {
get { get {
return ResourceManager.GetString("privoxy_conf", resourceCulture); return ResourceManager.GetString("privoxy_conf", resourceCulture);
} }
} }
/// <summary> /// <summary>
/// 查找 System.Byte[] 类型的本地化资源。
/// Looks up a localized resource of type System.Byte[].
/// </summary> /// </summary>
internal static byte[] privoxy_exe {
public static byte[] privoxy_exe {
get { get {
object obj = ResourceManager.GetObject("privoxy_exe", resourceCulture); object obj = ResourceManager.GetObject("privoxy_exe", resourceCulture);
return ((byte[])(obj)); return ((byte[])(obj));
@@ -158,9 +161,9 @@ namespace Shadowsocks.Properties {
} }
/// <summary> /// <summary>
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary> /// </summary>
internal static System.Drawing.Bitmap ss32Fill {
public static System.Drawing.Bitmap ss32Fill {
get { get {
object obj = ResourceManager.GetObject("ss32Fill", resourceCulture); object obj = ResourceManager.GetObject("ss32Fill", resourceCulture);
return ((System.Drawing.Bitmap)(obj)); return ((System.Drawing.Bitmap)(obj));
@@ -168,9 +171,9 @@ namespace Shadowsocks.Properties {
} }
/// <summary> /// <summary>
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary> /// </summary>
internal static System.Drawing.Bitmap ss32In {
public static System.Drawing.Bitmap ss32In {
get { get {
object obj = ResourceManager.GetObject("ss32In", resourceCulture); object obj = ResourceManager.GetObject("ss32In", resourceCulture);
return ((System.Drawing.Bitmap)(obj)); return ((System.Drawing.Bitmap)(obj));
@@ -178,9 +181,9 @@ namespace Shadowsocks.Properties {
} }
/// <summary> /// <summary>
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary> /// </summary>
internal static System.Drawing.Bitmap ss32Out {
public static System.Drawing.Bitmap ss32Out {
get { get {
object obj = ResourceManager.GetObject("ss32Out", resourceCulture); object obj = ResourceManager.GetObject("ss32Out", resourceCulture);
return ((System.Drawing.Bitmap)(obj)); return ((System.Drawing.Bitmap)(obj));
@@ -188,9 +191,9 @@ namespace Shadowsocks.Properties {
} }
/// <summary> /// <summary>
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary> /// </summary>
internal static System.Drawing.Bitmap ss32Outline {
public static System.Drawing.Bitmap ss32Outline {
get { get {
object obj = ResourceManager.GetObject("ss32Outline", resourceCulture); object obj = ResourceManager.GetObject("ss32Outline", resourceCulture);
return ((System.Drawing.Bitmap)(obj)); return ((System.Drawing.Bitmap)(obj));
@@ -198,9 +201,9 @@ namespace Shadowsocks.Properties {
} }
/// <summary> /// <summary>
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary> /// </summary>
internal static System.Drawing.Bitmap ssw128 {
public static System.Drawing.Bitmap ssw128 {
get { get {
object obj = ResourceManager.GetObject("ssw128", resourceCulture); object obj = ResourceManager.GetObject("ssw128", resourceCulture);
return ((System.Drawing.Bitmap)(obj)); return ((System.Drawing.Bitmap)(obj));
@@ -208,9 +211,9 @@ namespace Shadowsocks.Properties {
} }
/// <summary> /// <summary>
/// 查找 System.Byte[] 类型的本地化资源。
/// Looks up a localized resource of type System.Byte[].
/// </summary> /// </summary>
internal static byte[] sysproxy_exe {
public static byte[] sysproxy_exe {
get { get {
object obj = ResourceManager.GetObject("sysproxy_exe", resourceCulture); object obj = ResourceManager.GetObject("sysproxy_exe", resourceCulture);
return ((byte[])(obj)); return ((byte[])(obj));
@@ -218,9 +221,9 @@ namespace Shadowsocks.Properties {
} }
/// <summary> /// <summary>
/// 查找 System.Byte[] 类型的本地化资源。
/// Looks up a localized resource of type System.Byte[].
/// </summary> /// </summary>
internal static byte[] sysproxy64_exe {
public static byte[] sysproxy64_exe {
get { get {
object obj = ResourceManager.GetObject("sysproxy64_exe", resourceCulture); object obj = ResourceManager.GetObject("sysproxy64_exe", resourceCulture);
return ((byte[])(obj)); return ((byte[])(obj));
@@ -228,11 +231,11 @@ namespace Shadowsocks.Properties {
} }
/// <summary> /// <summary>
/// 查找类似 ! Put user rules line by line in this file.
/// Looks up a localized string similar to ! Put user rules line by line in this file.
///! See https://adblockplus.org/en/filter-cheatsheet ///! See https://adblockplus.org/en/filter-cheatsheet
/// 的本地化字符串。
///.
/// </summary> /// </summary>
internal static string user_rule {
public static string user_rule {
get { get {
return ResourceManager.GetString("user_rule", resourceCulture); return ResourceManager.GetString("user_rule", resourceCulture);
} }


+ 1
- 3
shadowsocks-csharp/Proxy/HttpProxy.cs View File

@@ -39,8 +39,6 @@ namespace Shadowsocks.Proxy
public object AsyncState { get; set; } public object AsyncState { get; set; }
public int BytesToRead;
public Exception ex { get; set; } public Exception ex { get; set; }
} }
@@ -199,7 +197,7 @@ namespace Shadowsocks.Proxy
} }
else else
{ {
if (line.IsNullOrEmpty())
if (string.IsNullOrEmpty(line))
{ {
return true; return true;
} }


+ 0
- 314
shadowsocks-csharp/StringEx.cs View File

@@ -1,314 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
#if EXPOSE_EVERYTHING || EXPOSE_STRINGEX
public
#endif
static partial class StringEx
{
#pragma warning disable 1591
public static StringComparison GlobalDefaultComparison { get; set; } = StringComparison.Ordinal;
[ThreadStatic]
private static StringComparison? _DefaultComparison;
public static StringComparison DefaultComparison
{
get { return _DefaultComparison ?? GlobalDefaultComparison; }
set { _DefaultComparison = value; }
}
#region basic String methods
public static bool IsNullOrEmpty(this string value)
=> string.IsNullOrEmpty(value);
public static bool IsNullOrWhiteSpace(this string value)
=> string.IsNullOrWhiteSpace(value);
public static bool IsWhiteSpace(this string value)
{
foreach (var c in value)
{
if (char.IsWhiteSpace(c)) continue;
return false;
}
return true;
}
#if !PCL
public static string IsInterned(this string value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
return string.IsInterned(value);
}
public static string Intern(this string value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
return string.Intern(value);
}
#endif
#if UNSAFE
public static unsafe string ToLowerForASCII(this string value)
{
if (value.IsNullOrWhiteSpace())
return value;
value = string.Copy(value);
fixed (char* low = value)
{
var end = low + value.Length;
for (var p = low; p < end; p++)
{
var c = *p;
if (c < 'A' || c > 'Z')
continue;
*p = (char)(c + 0x20);
}
}
return value;
}
public static unsafe string ToUpperForASCII(this string value)
{
if (value.IsNullOrWhiteSpace())
return value;
value = string.Copy(value);
fixed (char* low = value)
{
var end = low + value.Length;
for (var p = low; p < end; p++)
{
var c = *p;
if (c < 'a' || c > 'z')
continue;
*p = (char)(c - 0x20);
}
}
return value;
}
#else
public static string ToLowerForASCII(this string value)
{
if (value.IsNullOrWhiteSpace())
return value;
var sb = new StringBuilder(value.Length);
foreach (var c in value)
{
if (c < 'A' || c > 'Z')
sb.Append(c);
else
sb.Append((char)(c + 0x20));
}
return sb.ToString();
}
public static string ToUpperForASCII(this string value)
{
if (value.IsNullOrWhiteSpace())
return value;
var sb = new StringBuilder(value.Length);
foreach (var c in value)
{
if (c < 'a' || c > 'z')
sb.Append(c);
else
sb.Append((char)(c - 0x20));
}
return sb.ToString();
}
#endif
#endregion
#region comparing
#region Is
public static bool Is(this string a, string b)
=> string.Equals(a, b, DefaultComparison);
public static bool Is(this string a, string b, StringComparison comparisonType)
=> string.Equals(a, b, comparisonType);
#endregion
#region BeginWith
public static bool BeginWith(this string s, char c)
{
if (s.IsNullOrEmpty()) return false;
return s[0] == c;
}
public static bool BeginWithAny(this string s, IEnumerable<char> chars)
{
if (s.IsNullOrEmpty()) return false;
return chars.Contains(s[0]);
}
public static bool BeginWithAny(this string s, params char[] chars)
=> s.BeginWithAny(chars.AsEnumerable());
public static bool BeginWith(this string a, string b)
{
if (a == null || b == null) return false;
return a.StartsWith(b, DefaultComparison);
}
public static bool BeginWith(this string a, string b, StringComparison comparisonType)
{
if (a == null || b == null) return false;
return a.StartsWith(b, comparisonType);
}
#if !PCL
public static bool BeginWith(this string a, string b, bool ignoreCase, CultureInfo culture)
{
if (a == null || b == null) return false;
return a.StartsWith(b, ignoreCase, culture);
}
#endif
#endregion
#region FinishWith
public static bool FinishWith(this string s, char c)
{
if (s.IsNullOrEmpty()) return false;
return s.Last() == c;
}
public static bool FinishWithAny(this string s, IEnumerable<char> chars)
{
if (s.IsNullOrEmpty()) return false;
return chars.Contains(s.Last());
}
public static bool FinishWithAny(this string s, params char[] chars)
=> s.FinishWithAny(chars.AsEnumerable());
public static bool FinishWith(this string a, string b)
{
if (a == null || b == null) return false;
return a.EndsWith(b, DefaultComparison);
}
public static bool FinishWith(this string a, string b, StringComparison comparisonType)
{
if (a == null || b == null) return false;
return a.EndsWith(b, comparisonType);
}
#if !PCL
public static bool FinishWith(this string a, string b, bool ignoreCase, CultureInfo culture)
{
if (a == null || b == null) return false;
return a.EndsWith(b, ignoreCase, culture);
}
#endif
#endregion
#endregion
#region ToLines
public static IEnumerable<string> ToLines(this TextReader reader)
{
string line;
while ((line = reader.ReadLine()) != null)
yield return line;
}
public static IEnumerable<string> NonEmptyLines(this TextReader reader)
{
string line;
while ((line = reader.ReadLine()) != null)
{
if (line == "") continue;
yield return line;
}
}
public static IEnumerable<string> NonWhiteSpaceLines(this TextReader reader)
{
string line;
while ((line = reader.ReadLine()) != null)
{
if (line.IsWhiteSpace()) continue;
yield return line;
}
}
#endregion
#region others
private static readonly char[][] Quotes = new[]
{
"\"\"",
"''",
"“”",
"‘’",
"『』",
"「」",
"〖〗",
"【】",
}.Select(s => s.ToCharArray()).ToArray();
public static string Enquote(this string value)
{
if (value == null)
return "(null)";
foreach (var pair in Quotes)
{
if (value.IndexOfAny(pair) < 0)
return pair[0] + value + pair[1];
}
return '"' + value.Replace("\\", @"\\").Replace("\"", @"\""") + '"';
}
public static string Replace(this string value, string find, string rep, StringComparison comparsionType)
{
if (find.IsNullOrEmpty())
throw new ArgumentException(null, nameof(find));
if (rep == null)
rep = "";
if (value.IsNullOrEmpty())
return value;
var sb = new StringBuilder(value.Length);
var last = 0;
var len = find.Length;
var idx = value.IndexOf(find, DefaultComparison);
while (idx != -1)
{
sb.Append(value.Substring(last, idx - last));
sb.Append(rep);
idx += len;
last = idx;
idx = value.IndexOf(find, idx, comparsionType);
}
sb.Append(value.Substring(last));
return sb.ToString();
}
public static string ReplaceEx(this string value, string find, string rep)
=> value.Replace(find, rep, DefaultComparison);
#endregion
}

+ 46
- 5
shadowsocks-csharp/Util/Util.cs View File

@@ -8,6 +8,10 @@ using System.Windows.Forms;
using Microsoft.Win32; using Microsoft.Win32;
using Shadowsocks.Controller; using Shadowsocks.Controller;
using Shadowsocks.Model; using Shadowsocks.Model;
using System.Drawing;
using ZXing;
using ZXing.QrCode;
using ZXing.Common;
namespace Shadowsocks.Util namespace Shadowsocks.Util
{ {
@@ -183,7 +187,7 @@ namespace Shadowsocks.Util
// we are building x86 binary for both x86 and x64, which will // we are building x86 binary for both x86 and x64, which will
// cause problem when opening registry key // cause problem when opening registry key
// detect operating system instead of CPU // detect operating system instead of CPU
if (name.IsNullOrEmpty()) throw new ArgumentException(nameof(name));
if (string.IsNullOrEmpty(name)) throw new ArgumentException(nameof(name));
try try
{ {
RegistryKey userKey = RegistryKey.OpenBaseKey(hive, RegistryKey userKey = RegistryKey.OpenBaseKey(hive,
@@ -208,10 +212,47 @@ namespace Shadowsocks.Util
return Environment.OSVersion.Version.Major > 5; return Environment.OSVersion.Version.Major > 5;
} }
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetProcessWorkingSetSize(IntPtr process,
UIntPtr minimumWorkingSetSize, UIntPtr maximumWorkingSetSize);
public static string ScanQRCodeFromScreen()
{
foreach (Screen screen in Screen.AllScreens)
{
using (Bitmap fullImage = new Bitmap(screen.Bounds.Width,
screen.Bounds.Height))
{
using (Graphics g = Graphics.FromImage(fullImage))
{
g.CopyFromScreen(screen.Bounds.X,
screen.Bounds.Y,
0, 0,
fullImage.Size,
CopyPixelOperation.SourceCopy);
}
int maxTry = 10;
for (int i = 0; i < maxTry; i++)
{
int marginLeft = (int)((double)fullImage.Width * i / 2.5 / maxTry);
int marginTop = (int)((double)fullImage.Height * i / 2.5 / maxTry);
Rectangle cropRect = new Rectangle(marginLeft, marginTop, fullImage.Width - marginLeft * 2, fullImage.Height - marginTop * 2);
Bitmap target = new Bitmap(screen.Bounds.Width, screen.Bounds.Height);
double imageScale = (double)screen.Bounds.Width / (double)cropRect.Width;
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(fullImage, new Rectangle(0, 0, target.Width, target.Height),
cropRect,
GraphicsUnit.Pixel);
}
var source = new BitmapLuminanceSource(target);
var bitmap = new BinaryBitmap(new HybridBinarizer(source));
QRCodeReader reader = new QRCodeReader();
var result = reader.decode(bitmap);
if (result != null)
return result.Text;
}
}
}
return null;
}
public static void OpenInBrowser(string url) public static void OpenInBrowser(string url)
{ {


+ 0
- 114
shadowsocks-csharp/View/CalculationControl.Designer.cs View File

@@ -1,114 +0,0 @@
namespace Shadowsocks.View
{
partial class CalculationControl
{
/// <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 Component 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.factorNum = new System.Windows.Forms.NumericUpDown();
this.multiply = new System.Windows.Forms.Label();
this.plus = new System.Windows.Forms.Label();
this.valueLabel = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.factorNum)).BeginInit();
this.SuspendLayout();
//
// factorNum
//
this.factorNum.DecimalPlaces = 2;
this.factorNum.Dock = System.Windows.Forms.DockStyle.Right;
this.factorNum.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.factorNum.Increment = new decimal(new int[] {
1,
0,
0,
131072});
this.factorNum.Location = new System.Drawing.Point(210, 0);
this.factorNum.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.factorNum.Minimum = new decimal(new int[] {
1000,
0,
0,
-2147418112});
this.factorNum.Name = "factorNum";
this.factorNum.Size = new System.Drawing.Size(76, 30);
this.factorNum.TabIndex = 6;
//
// multiply
//
this.multiply.AutoSize = true;
this.multiply.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.multiply.Location = new System.Drawing.Point(180, 2);
this.multiply.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.multiply.Name = "multiply";
this.multiply.Size = new System.Drawing.Size(22, 23);
this.multiply.TabIndex = 2;
this.multiply.Text = "×";
//
// plus
//
this.plus.AutoSize = true;
this.plus.Font = new System.Drawing.Font("Segoe UI", 10F);
this.plus.Location = new System.Drawing.Point(4, 2);
this.plus.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.plus.Name = "plus";
this.plus.Size = new System.Drawing.Size(22, 23);
this.plus.TabIndex = 3;
this.plus.Text = "+";
//
// valueLabel
//
this.valueLabel.AutoSize = true;
this.valueLabel.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.valueLabel.Location = new System.Drawing.Point(35, 4);
this.valueLabel.Name = "valueLabel";
this.valueLabel.Size = new System.Drawing.Size(101, 20);
this.valueLabel.TabIndex = 7;
this.valueLabel.Text = "PackageLoss";
//
// CalculationControl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.valueLabel);
this.Controls.Add(this.factorNum);
this.Controls.Add(this.multiply);
this.Controls.Add(this.plus);
this.Margin = new System.Windows.Forms.Padding(0);
this.Name = "CalculationControl";
this.Size = new System.Drawing.Size(286, 26);
((System.ComponentModel.ISupportInitialize)(this.factorNum)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}

#endregion
private System.Windows.Forms.NumericUpDown factorNum;
private System.Windows.Forms.Label multiply;
private System.Windows.Forms.Label plus;
private System.Windows.Forms.Label valueLabel;
}
}

+ 0
- 25
shadowsocks-csharp/View/CalculationControl.cs View File

@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Shadowsocks.View
{
public partial class CalculationControl : UserControl
{
public CalculationControl(string text, float value)
{
InitializeComponent();
valueLabel.Text = text;
factorNum.Value = (decimal) value;
}

public string Value => valueLabel.Text;
public float Factor => (float) factorNum.Value;
}
}

+ 2
- 2
shadowsocks-csharp/View/ConfigForm.cs View File

@@ -229,7 +229,7 @@ namespace Shadowsocks.View
{ {
password = null; password = null;
string outPassword; string outPassword;
if ((outPassword = PasswordTextBox.Text).IsNullOrWhiteSpace())
if (string.IsNullOrWhiteSpace(outPassword = PasswordTextBox.Text))
{ {
if (!isSave && !isCopy && ServersListBox.Items.Count > 1 && I18N.GetString("New server").Equals(ServersListBox.Items[_lastSelectedIndex].ToString())) if (!isSave && !isCopy && ServersListBox.Items.Count > 1 && I18N.GetString("New server").Equals(ServersListBox.Items[_lastSelectedIndex].ToString()))
{ {
@@ -352,7 +352,7 @@ namespace Shadowsocks.View
private void LoadCurrentConfiguration() private void LoadCurrentConfiguration()
{ {
_modifiedConfiguration = controller.GetConfigurationCopy();
_modifiedConfiguration = controller.GetCurrentConfiguration();
LoadServerNameListToUI(_modifiedConfiguration); LoadServerNameListToUI(_modifiedConfiguration);
_lastSelectedIndex = _modifiedConfiguration.index; _lastSelectedIndex = _modifiedConfiguration.index;


+ 0
- 190
shadowsocks-csharp/View/HotkeySettingsForm.cs View File

@@ -1,190 +0,0 @@
using Shadowsocks.Controller;
using Shadowsocks.Model;
using Shadowsocks.Properties;
using System;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using static Shadowsocks.Controller.HotkeyReg;
namespace Shadowsocks.View
{
public partial class HotkeySettingsForm : Form
{
private readonly ShadowsocksController _controller;
// this is a copy of hotkey configuration that we are working on
private HotkeyConfig _modifiedHotkeyConfig;
public HotkeySettingsForm(ShadowsocksController controller)
{
InitializeComponent();
UpdateTexts();
Icon = Icon.FromHandle(Resources.ssw128.GetHicon());
_controller = controller;
_controller.ConfigChanged += controller_ConfigChanged;
LoadCurrentConfiguration();
}
private void UpdateTexts()
{
I18N.TranslateForm(this);
}
private void controller_ConfigChanged(object sender, EventArgs e)
{
LoadCurrentConfiguration();
}
private void LoadCurrentConfiguration()
{
_modifiedHotkeyConfig = _controller.GetConfigurationCopy().hotkey;
SetConfigToUI(_modifiedHotkeyConfig);
}
private void SetConfigToUI(HotkeyConfig config)
{
SwitchSystemProxyTextBox.Text = config.SwitchSystemProxy;
SwitchProxyModeTextBox.Text = config.SwitchSystemProxyMode;
SwitchAllowLanTextBox.Text = config.SwitchAllowLan;
ShowLogsTextBox.Text = config.ShowLogs;
ServerMoveUpTextBox.Text = config.ServerMoveUp;
ServerMoveDownTextBox.Text = config.ServerMoveDown;
RegHotkeysAtStartupCheckBox.Checked = config.RegHotkeysAtStartup;
}
private void SaveConfig()
{
_controller.SaveHotkeyConfig(_modifiedHotkeyConfig);
}
private HotkeyConfig GetConfigFromUI()
{
return new HotkeyConfig
{
SwitchSystemProxy = SwitchSystemProxyTextBox.Text,
SwitchSystemProxyMode = SwitchProxyModeTextBox.Text,
SwitchAllowLan = SwitchAllowLanTextBox.Text,
ShowLogs = ShowLogsTextBox.Text,
ServerMoveUp = ServerMoveUpTextBox.Text,
ServerMoveDown = ServerMoveDownTextBox.Text,
RegHotkeysAtStartup = RegHotkeysAtStartupCheckBox.Checked
};
}
/// <summary>
/// Capture hotkey - Press key
/// </summary>
private void HotkeyDown(object sender, KeyEventArgs e)
{
StringBuilder sb = new StringBuilder();
//Combination key only
if (e.Modifiers != 0)
{
// XXX: Hotkey parsing depends on the sequence, more specifically, ModifierKeysConverter.
// Windows key is reserved by operating system, we deny this key.
if (e.Control)
{
sb.Append("Ctrl+");
}
if (e.Alt)
{
sb.Append("Alt+");
}
if (e.Shift)
{
sb.Append("Shift+");
}
Keys keyvalue = (Keys)e.KeyValue;
if ((keyvalue >= Keys.PageUp && keyvalue <= Keys.Down) ||
(keyvalue >= Keys.A && keyvalue <= Keys.Z) ||
(keyvalue >= Keys.F1 && keyvalue <= Keys.F12))
{
sb.Append(e.KeyCode);
}
else if (keyvalue >= Keys.D0 && keyvalue <= Keys.D9)
{
sb.Append('D').Append((char)e.KeyValue);
}
else if (keyvalue >= Keys.NumPad0 && keyvalue <= Keys.NumPad9)
{
sb.Append("NumPad").Append((char)(e.KeyValue - 48));
}
}
((TextBox)sender).Text = sb.ToString();
}
/// <summary>
/// Capture hotkey - Release key
/// </summary>
private void HotkeyUp(object sender, KeyEventArgs e)
{
var tb = (TextBox)sender;
var content = tb.Text.TrimEnd();
if (content.Length >= 1 && content[content.Length - 1] == '+')
{
tb.Text = "";
}
}
private void CancelButton_Click(object sender, EventArgs e)
{
this.Close();
}
private void OKButton_Click(object sender, EventArgs e)
{
_modifiedHotkeyConfig = GetConfigFromUI();
// try to register, notify to change settings if failed
if (!RegisterAllHotkeys(_modifiedHotkeyConfig))
{
MessageBox.Show(I18N.GetString("Register hotkey failed"));
}
// All check passed, saving
SaveConfig();
this.Close();
}
private void RegisterAllButton_Click(object sender, EventArgs e)
{
_modifiedHotkeyConfig = GetConfigFromUI();
RegisterAllHotkeys(_modifiedHotkeyConfig);
}
private bool RegisterAllHotkeys(HotkeyConfig hotkeyConfig)
{
return
RegHotkeyFromString(hotkeyConfig.SwitchSystemProxy, "SwitchSystemProxyCallback", result => HandleRegResult(hotkeyConfig.SwitchSystemProxy, SwitchSystemProxyLabel, result))
&& RegHotkeyFromString(hotkeyConfig.SwitchSystemProxyMode, "SwitchSystemProxyModeCallback", result => HandleRegResult(hotkeyConfig.SwitchSystemProxyMode, SwitchProxyModeLabel, result))
&& RegHotkeyFromString(hotkeyConfig.SwitchAllowLan, "SwitchAllowLanCallback", result => HandleRegResult(hotkeyConfig.SwitchAllowLan, SwitchAllowLanLabel, result))
&& RegHotkeyFromString(hotkeyConfig.ShowLogs, "ShowLogsCallback", result => HandleRegResult(hotkeyConfig.ShowLogs, ShowLogsLabel, result))
&& RegHotkeyFromString(hotkeyConfig.ServerMoveUp, "ServerMoveUpCallback", result => HandleRegResult(hotkeyConfig.ServerMoveUp, ServerMoveUpLabel, result))
&& RegHotkeyFromString(hotkeyConfig.ServerMoveDown, "ServerMoveDownCallback", result => HandleRegResult(hotkeyConfig.ServerMoveDown, ServerMoveDownLabel, result));
}
private void HandleRegResult(string hotkeyStr, Label label, RegResult result)
{
switch (result)
{
case RegResult.ParseError:
MessageBox.Show(I18N.GetString("Cannot parse hotkey: {0}", hotkeyStr));
break;
case RegResult.UnregSuccess:
label.ResetBackColor();
break;
case RegResult.RegSuccess:
label.BackColor = Color.Green;
break;
case RegResult.RegFailure:
label.BackColor = Color.Red;
break;
default:
break;
}
}
}
}

+ 0
- 346
shadowsocks-csharp/View/HotkeySettingsForm.designer.cs View File

@@ -1,346 +0,0 @@
namespace Shadowsocks.View
{
partial class HotkeySettingsForm
{
/// <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()
{
System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
this.btnOK = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.btnRegisterAll = new System.Windows.Forms.Button();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.RegHotkeysAtStartupLabel = new System.Windows.Forms.Label();
this.SwitchSystemProxyLabel = new System.Windows.Forms.Label();
this.SwitchProxyModeLabel = new System.Windows.Forms.Label();
this.SwitchAllowLanLabel = new System.Windows.Forms.Label();
this.ShowLogsLabel = new System.Windows.Forms.Label();
this.ServerMoveUpLabel = new System.Windows.Forms.Label();
this.ServerMoveDownLabel = new System.Windows.Forms.Label();
this.SwitchSystemProxyTextBox = new System.Windows.Forms.TextBox();
this.SwitchProxyModeTextBox = new System.Windows.Forms.TextBox();
this.SwitchAllowLanTextBox = new System.Windows.Forms.TextBox();
this.ShowLogsTextBox = new System.Windows.Forms.TextBox();
this.ServerMoveUpTextBox = new System.Windows.Forms.TextBox();
this.ServerMoveDownTextBox = new System.Windows.Forms.TextBox();
this.RegHotkeysAtStartupCheckBox = new System.Windows.Forms.CheckBox();
flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
flowLayoutPanel1.SuspendLayout();
this.tableLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// flowLayoutPanel1
//
this.tableLayoutPanel1.SetColumnSpan(flowLayoutPanel1, 2);
flowLayoutPanel1.Controls.Add(this.btnOK);
flowLayoutPanel1.Controls.Add(this.btnCancel);
flowLayoutPanel1.Controls.Add(this.btnRegisterAll);
flowLayoutPanel1.FlowDirection = System.Windows.Forms.FlowDirection.BottomUp;
flowLayoutPanel1.Location = new System.Drawing.Point(0, 248);
flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0);
flowLayoutPanel1.Name = "flowLayoutPanel1";
flowLayoutPanel1.Padding = new System.Windows.Forms.Padding(0, 0, 20, 0);
flowLayoutPanel1.RightToLeft = System.Windows.Forms.RightToLeft.Yes;
flowLayoutPanel1.Size = new System.Drawing.Size(594, 52);
flowLayoutPanel1.TabIndex = 6;
//
// btnOK
//
this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK;
this.btnOK.Location = new System.Drawing.Point(416, 9);
this.btnOK.Margin = new System.Windows.Forms.Padding(4);
this.btnOK.Name = "btnOK";
this.btnOK.Size = new System.Drawing.Size(154, 39);
this.btnOK.TabIndex = 0;
this.btnOK.Text = "OK";
this.btnOK.UseVisualStyleBackColor = true;
this.btnOK.Click += new System.EventHandler(this.OKButton_Click);
//
// btnCancel
//
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(254, 9);
this.btnCancel.Margin = new System.Windows.Forms.Padding(4);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(154, 39);
this.btnCancel.TabIndex = 1;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.CancelButton_Click);
//
// btnRegisterAll
//
this.btnRegisterAll.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnRegisterAll.Location = new System.Drawing.Point(92, 9);
this.btnRegisterAll.Margin = new System.Windows.Forms.Padding(4);
this.btnRegisterAll.Name = "btnRegisterAll";
this.btnRegisterAll.Size = new System.Drawing.Size(154, 39);
this.btnRegisterAll.TabIndex = 2;
this.btnRegisterAll.Text = "Reg All";
this.btnRegisterAll.UseVisualStyleBackColor = true;
this.btnRegisterAll.Click += new System.EventHandler(this.RegisterAllButton_Click);
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 2;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel1.Controls.Add(this.RegHotkeysAtStartupLabel, 0, 6);
this.tableLayoutPanel1.Controls.Add(this.SwitchSystemProxyLabel, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.SwitchProxyModeLabel, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.SwitchAllowLanLabel, 0, 2);
this.tableLayoutPanel1.Controls.Add(this.ShowLogsLabel, 0, 3);
this.tableLayoutPanel1.Controls.Add(this.ServerMoveUpLabel, 0, 4);
this.tableLayoutPanel1.Controls.Add(this.ServerMoveDownLabel, 0, 5);
this.tableLayoutPanel1.Controls.Add(flowLayoutPanel1, 0, 7);
this.tableLayoutPanel1.Controls.Add(this.SwitchSystemProxyTextBox, 1, 0);
this.tableLayoutPanel1.Controls.Add(this.SwitchProxyModeTextBox, 1, 1);
this.tableLayoutPanel1.Controls.Add(this.SwitchAllowLanTextBox, 1, 2);
this.tableLayoutPanel1.Controls.Add(this.ShowLogsTextBox, 1, 3);
this.tableLayoutPanel1.Controls.Add(this.ServerMoveUpTextBox, 1, 4);
this.tableLayoutPanel1.Controls.Add(this.ServerMoveDownTextBox, 1, 5);
this.tableLayoutPanel1.Controls.Add(this.RegHotkeysAtStartupCheckBox, 1, 6);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 8;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 14.16726F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 14.16726F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 14.16726F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 14.16726F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 14.7784F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 14.38949F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 14.16309F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 50F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(582, 303);
this.tableLayoutPanel1.TabIndex = 0;
//
// RegHotkeysAtStartupLabel
//
this.RegHotkeysAtStartupLabel.AutoSize = true;
this.RegHotkeysAtStartupLabel.Dock = System.Windows.Forms.DockStyle.Right;
this.RegHotkeysAtStartupLabel.Location = new System.Drawing.Point(40, 213);
this.RegHotkeysAtStartupLabel.Margin = new System.Windows.Forms.Padding(10, 0, 10, 0);
this.RegHotkeysAtStartupLabel.Name = "RegHotkeysAtStartupLabel";
this.RegHotkeysAtStartupLabel.Size = new System.Drawing.Size(199, 35);
this.RegHotkeysAtStartupLabel.TabIndex = 16;
this.RegHotkeysAtStartupLabel.Text = "Reg Hotkeys At Startup";
this.RegHotkeysAtStartupLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// SwitchSystemProxyLabel
//
this.SwitchSystemProxyLabel.AutoSize = true;
this.SwitchSystemProxyLabel.Dock = System.Windows.Forms.DockStyle.Right;
this.SwitchSystemProxyLabel.Location = new System.Drawing.Point(63, 0);
this.SwitchSystemProxyLabel.Margin = new System.Windows.Forms.Padding(10, 0, 10, 0);
this.SwitchSystemProxyLabel.Name = "SwitchSystemProxyLabel";
this.SwitchSystemProxyLabel.Size = new System.Drawing.Size(176, 35);
this.SwitchSystemProxyLabel.TabIndex = 0;
this.SwitchSystemProxyLabel.Text = "Switch system proxy";
this.SwitchSystemProxyLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// SwitchProxyModeLabel
//
this.SwitchProxyModeLabel.AutoSize = true;
this.SwitchProxyModeLabel.Dock = System.Windows.Forms.DockStyle.Right;
this.SwitchProxyModeLabel.Location = new System.Drawing.Point(10, 35);
this.SwitchProxyModeLabel.Margin = new System.Windows.Forms.Padding(10, 0, 10, 0);
this.SwitchProxyModeLabel.Name = "SwitchProxyModeLabel";
this.SwitchProxyModeLabel.Size = new System.Drawing.Size(229, 35);
this.SwitchProxyModeLabel.TabIndex = 1;
this.SwitchProxyModeLabel.Text = "Switch system proxy mode";
this.SwitchProxyModeLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// SwitchAllowLanLabel
//
this.SwitchAllowLanLabel.AutoSize = true;
this.SwitchAllowLanLabel.Dock = System.Windows.Forms.DockStyle.Right;
this.SwitchAllowLanLabel.Location = new System.Drawing.Point(39, 70);
this.SwitchAllowLanLabel.Margin = new System.Windows.Forms.Padding(10, 0, 10, 0);
this.SwitchAllowLanLabel.Name = "SwitchAllowLanLabel";
this.SwitchAllowLanLabel.Size = new System.Drawing.Size(200, 35);
this.SwitchAllowLanLabel.TabIndex = 3;
this.SwitchAllowLanLabel.Text = "Allow Clients from LAN";
this.SwitchAllowLanLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// ShowLogsLabel
//
this.ShowLogsLabel.AutoSize = true;
this.ShowLogsLabel.Dock = System.Windows.Forms.DockStyle.Right;
this.ShowLogsLabel.Location = new System.Drawing.Point(129, 105);
this.ShowLogsLabel.Margin = new System.Windows.Forms.Padding(10, 0, 10, 0);
this.ShowLogsLabel.Name = "ShowLogsLabel";
this.ShowLogsLabel.Size = new System.Drawing.Size(110, 35);
this.ShowLogsLabel.TabIndex = 4;
this.ShowLogsLabel.Text = "Show Logs...";
this.ShowLogsLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// ServerMoveUpLabel
//
this.ServerMoveUpLabel.AutoSize = true;
this.ServerMoveUpLabel.Dock = System.Windows.Forms.DockStyle.Right;
this.ServerMoveUpLabel.Location = new System.Drawing.Point(25, 140);
this.ServerMoveUpLabel.Margin = new System.Windows.Forms.Padding(10, 0, 10, 0);
this.ServerMoveUpLabel.Name = "ServerMoveUpLabel";
this.ServerMoveUpLabel.Size = new System.Drawing.Size(214, 37);
this.ServerMoveUpLabel.TabIndex = 4;
this.ServerMoveUpLabel.Text = "Switch to previous server";
this.ServerMoveUpLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// ServerMoveDownLabel
//
this.ServerMoveDownLabel.AutoSize = true;
this.ServerMoveDownLabel.Dock = System.Windows.Forms.DockStyle.Right;
this.ServerMoveDownLabel.Location = new System.Drawing.Point(60, 177);
this.ServerMoveDownLabel.Margin = new System.Windows.Forms.Padding(10, 0, 10, 0);
this.ServerMoveDownLabel.Name = "ServerMoveDownLabel";
this.ServerMoveDownLabel.Size = new System.Drawing.Size(179, 36);
this.ServerMoveDownLabel.TabIndex = 4;
this.ServerMoveDownLabel.Text = "Switch to next server";
this.ServerMoveDownLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// SwitchSystemProxyTextBox
//
this.SwitchSystemProxyTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.SwitchSystemProxyTextBox.Location = new System.Drawing.Point(252, 3);
this.SwitchSystemProxyTextBox.Name = "SwitchSystemProxyTextBox";
this.SwitchSystemProxyTextBox.ReadOnly = true;
this.SwitchSystemProxyTextBox.Size = new System.Drawing.Size(339, 29);
this.SwitchSystemProxyTextBox.TabIndex = 7;
this.SwitchSystemProxyTextBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HotkeyDown);
this.SwitchSystemProxyTextBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.HotkeyUp);
//
// SwitchProxyModeTextBox
//
this.SwitchProxyModeTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.SwitchProxyModeTextBox.Location = new System.Drawing.Point(252, 38);
this.SwitchProxyModeTextBox.Name = "SwitchProxyModeTextBox";
this.SwitchProxyModeTextBox.ReadOnly = true;
this.SwitchProxyModeTextBox.Size = new System.Drawing.Size(339, 29);
this.SwitchProxyModeTextBox.TabIndex = 8;
this.SwitchProxyModeTextBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HotkeyDown);
this.SwitchProxyModeTextBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.HotkeyUp);
//
// SwitchAllowLanTextBox
//
this.SwitchAllowLanTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.SwitchAllowLanTextBox.Location = new System.Drawing.Point(252, 73);
this.SwitchAllowLanTextBox.Name = "SwitchAllowLanTextBox";
this.SwitchAllowLanTextBox.ReadOnly = true;
this.SwitchAllowLanTextBox.Size = new System.Drawing.Size(339, 29);
this.SwitchAllowLanTextBox.TabIndex = 10;
this.SwitchAllowLanTextBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HotkeyDown);
this.SwitchAllowLanTextBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.HotkeyUp);
//
// ShowLogsTextBox
//
this.ShowLogsTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.ShowLogsTextBox.Location = new System.Drawing.Point(252, 108);
this.ShowLogsTextBox.Name = "ShowLogsTextBox";
this.ShowLogsTextBox.ReadOnly = true;
this.ShowLogsTextBox.Size = new System.Drawing.Size(339, 29);
this.ShowLogsTextBox.TabIndex = 11;
this.ShowLogsTextBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HotkeyDown);
this.ShowLogsTextBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.HotkeyUp);
//
// ServerMoveUpTextBox
//
this.ServerMoveUpTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.ServerMoveUpTextBox.Location = new System.Drawing.Point(252, 143);
this.ServerMoveUpTextBox.Name = "ServerMoveUpTextBox";
this.ServerMoveUpTextBox.ReadOnly = true;
this.ServerMoveUpTextBox.Size = new System.Drawing.Size(339, 29);
this.ServerMoveUpTextBox.TabIndex = 12;
this.ServerMoveUpTextBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HotkeyDown);
this.ServerMoveUpTextBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.HotkeyUp);
//
// ServerMoveDownTextBox
//
this.ServerMoveDownTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.ServerMoveDownTextBox.Location = new System.Drawing.Point(252, 180);
this.ServerMoveDownTextBox.Name = "ServerMoveDownTextBox";
this.ServerMoveDownTextBox.ReadOnly = true;
this.ServerMoveDownTextBox.Size = new System.Drawing.Size(339, 29);
this.ServerMoveDownTextBox.TabIndex = 13;
this.ServerMoveDownTextBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HotkeyDown);
this.ServerMoveDownTextBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.HotkeyUp);
//
// RegHotkeysAtStartupCheckBox
//
this.RegHotkeysAtStartupCheckBox.AutoSize = true;
this.RegHotkeysAtStartupCheckBox.Dock = System.Windows.Forms.DockStyle.Left;
this.RegHotkeysAtStartupCheckBox.Location = new System.Drawing.Point(252, 216);
this.RegHotkeysAtStartupCheckBox.Name = "RegHotkeysAtStartupCheckBox";
this.RegHotkeysAtStartupCheckBox.Size = new System.Drawing.Size(18, 29);
this.RegHotkeysAtStartupCheckBox.TabIndex = 17;
this.RegHotkeysAtStartupCheckBox.UseVisualStyleBackColor = true;
//
// HotkeySettingsForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.ClientSize = new System.Drawing.Size(582, 303);
this.Controls.Add(this.tableLayoutPanel1);
this.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Margin = new System.Windows.Forms.Padding(5, 6, 5, 6);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "HotkeySettingsForm";
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Edit Hotkeys...";
flowLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Label SwitchSystemProxyLabel;
private System.Windows.Forms.Label SwitchProxyModeLabel;
private System.Windows.Forms.Label SwitchAllowLanLabel;
private System.Windows.Forms.Label ShowLogsLabel;
private System.Windows.Forms.Label ServerMoveUpLabel;
private System.Windows.Forms.Label ServerMoveDownLabel;
private System.Windows.Forms.Button btnOK;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.TextBox ShowLogsTextBox;
private System.Windows.Forms.TextBox SwitchAllowLanTextBox;
private System.Windows.Forms.TextBox SwitchProxyModeTextBox;
private System.Windows.Forms.TextBox SwitchSystemProxyTextBox;
private System.Windows.Forms.TextBox ServerMoveUpTextBox;
private System.Windows.Forms.TextBox ServerMoveDownTextBox;
private System.Windows.Forms.Button btnRegisterAll;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label RegHotkeysAtStartupLabel;
private System.Windows.Forms.CheckBox RegHotkeysAtStartupCheckBox;
}
}

+ 0
- 183
shadowsocks-csharp/View/HotkeySettingsForm.resx View File

@@ -1,183 +0,0 @@
<?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="flowLayoutPanel1.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</metadata>
<metadata name="flowLayoutPanel1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="tableLayoutPanel1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="RegHotkeysAtStartupLabel.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="SwitchSystemProxyLabel.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="SwitchProxyModeLabel.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="SwitchAllowLanLabel.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ShowLogsLabel.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ServerMoveUpLabel.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ServerMoveDownLabel.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="SwitchSystemProxyTextBox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="SwitchProxyModeTextBox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="SwitchAllowLanTextBox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ShowLogsTextBox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ServerMoveUpTextBox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ServerMoveDownTextBox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="RegHotkeysAtStartupCheckBox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="btnOK.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="btnCancel.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="btnRegisterAll.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
</root>

+ 3
- 3
shadowsocks-csharp/View/LogForm.cs View File

@@ -62,7 +62,7 @@ namespace Shadowsocks.View
LogMessageTextBox.AppendText("Cannot get the log file name from NLog config file. Please check if the nlog config file exists with corresponding XML nodes."); LogMessageTextBox.AppendText("Cannot get the log file name from NLog config file. Please check if the nlog config file exists with corresponding XML nodes.");
} }
LogViewerConfig config = controller.GetConfigurationCopy().logViewer;
LogViewerConfig config = controller.GetCurrentConfiguration().logViewer;
topMostTrigger = config.topMost; topMostTrigger = config.topMost;
wrapTextTrigger = config.wrapText; wrapTextTrigger = config.wrapText;
@@ -244,7 +244,7 @@ namespace Shadowsocks.View
timer.Tick += Timer_Tick; timer.Tick += Timer_Tick;
timer.Start(); timer.Start();
LogViewerConfig config = controller.GetConfigurationCopy().logViewer;
LogViewerConfig config = controller.GetCurrentConfiguration().logViewer;
Height = config.Height; Height = config.Height;
Width = config.Width; Width = config.Width;
@@ -270,7 +270,7 @@ namespace Shadowsocks.View
{ {
timer.Stop(); timer.Stop();
controller.TrafficChanged -= controller_TrafficChanged; controller.TrafficChanged -= controller_TrafficChanged;
LogViewerConfig config = controller.GetConfigurationCopy().logViewer;
LogViewerConfig config = controller.GetCurrentConfiguration().logViewer;
config.topMost = topMostTrigger; config.topMost = topMostTrigger;
config.wrapText = wrapTextTrigger; config.wrapText = wrapTextTrigger;


+ 229
- 221
shadowsocks-csharp/View/MenuViewController.cs View File

@@ -1,9 +1,11 @@
using NLog; using NLog;
using Shadowsocks.Controller; using Shadowsocks.Controller;
using Shadowsocks.Localization;
using Shadowsocks.Model; using Shadowsocks.Model;
using Shadowsocks.Properties; using Shadowsocks.Properties;
using Shadowsocks.Util; using Shadowsocks.Util;
using Shadowsocks.Util.SystemProxy; using Shadowsocks.Util.SystemProxy;
using Shadowsocks.Views;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
@@ -11,7 +13,8 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
using System.Windows.Media.Imaging;
using System.Windows.Forms.Integration;
using System.Windows.Threading;
using ZXing; using ZXing;
using ZXing.Common; using ZXing.Common;
using ZXing.QrCode; using ZXing.QrCode;
@@ -20,19 +23,15 @@ namespace Shadowsocks.View
{ {
public class MenuViewController public class MenuViewController
{ {
private static Logger logger = LogManager.GetCurrentClassLogger();
// 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 readonly Logger logger = LogManager.GetCurrentClassLogger();
private ShadowsocksController controller; private ShadowsocksController controller;
private UpdateChecker updateChecker;
public UpdateChecker updateChecker;
private NotifyIcon _notifyIcon; private NotifyIcon _notifyIcon;
private Icon icon, icon_in, icon_out, icon_both, previousIcon; private Icon icon, icon_in, icon_out, icon_both, previousIcon;
private bool _isFirstRun;
private bool _isStartupChecking;
private bool _isStartupCheck;
private string _urlToOpen; private string _urlToOpen;
private ContextMenuStrip contextMenu1; private ContextMenuStrip contextMenu1;
@@ -52,6 +51,7 @@ namespace Shadowsocks.View
private ToolStripMenuItem editGFWUserRuleItem; private ToolStripMenuItem editGFWUserRuleItem;
private ToolStripMenuItem editOnlinePACItem; private ToolStripMenuItem editOnlinePACItem;
private ToolStripMenuItem secureLocalPacUrlToggleItem; private ToolStripMenuItem secureLocalPacUrlToggleItem;
private ToolStripMenuItem regenerateLocalPacOnUpdateItem;
private ToolStripMenuItem autoCheckUpdatesToggleItem; private ToolStripMenuItem autoCheckUpdatesToggleItem;
private ToolStripMenuItem checkPreReleaseToggleItem; private ToolStripMenuItem checkPreReleaseToggleItem;
private ToolStripMenuItem proxyItem; private ToolStripMenuItem proxyItem;
@@ -62,12 +62,12 @@ namespace Shadowsocks.View
private ToolStripMenuItem onlineConfigItem; private ToolStripMenuItem onlineConfigItem;
private ConfigForm configForm; private ConfigForm configForm;
private ProxyForm proxyForm;
private LogForm logForm; private LogForm logForm;
private HotkeySettingsForm hotkeySettingsForm;
private OnlineConfigForm onlineConfigForm;
private System.Windows.Window serverSharingWindow;
private System.Windows.Window hotkeysWindow;
private System.Windows.Window forwardProxyWindow;
private System.Windows.Window onlineConfigWindow;
// color definition for icon color transformation // color definition for icon color transformation
private readonly Color colorMaskBlue = Color.FromArgb(255, 25, 125, 191); private readonly Color colorMaskBlue = Color.FromArgb(255, 25, 125, 191);
@@ -103,22 +103,21 @@ namespace Shadowsocks.View
_notifyIcon.BalloonTipClosed += _notifyIcon_BalloonTipClosed; _notifyIcon.BalloonTipClosed += _notifyIcon_BalloonTipClosed;
controller.TrafficChanged += controller_TrafficChanged; controller.TrafficChanged += controller_TrafficChanged;
this.updateChecker = new UpdateChecker();
updateChecker = new UpdateChecker();
updateChecker.CheckUpdateCompleted += updateChecker_CheckUpdateCompleted; updateChecker.CheckUpdateCompleted += updateChecker_CheckUpdateCompleted;
LoadCurrentConfiguration(); LoadCurrentConfiguration();
Configuration config = controller.GetConfigurationCopy();
Configuration config = controller.GetCurrentConfiguration();
if (config.isDefault)
if (config.firstRun)
{ {
_isFirstRun = true;
ShowConfigForm(); ShowConfigForm();
} }
else if (config.autoCheckUpdate) else if (config.autoCheckUpdate)
{ {
_isStartupChecking = true;
updateChecker.CheckUpdate(config, 3000);
_isStartupCheck = true;
Dispatcher.CurrentDispatcher.Invoke(() => updateChecker.CheckForVersionUpdate(3000));
} }
} }
@@ -126,7 +125,7 @@ namespace Shadowsocks.View
private void UpdateTrayIconAndNotifyText() private void UpdateTrayIconAndNotifyText()
{ {
Configuration config = controller.GetConfigurationCopy();
Configuration config = controller.GetCurrentConfiguration();
bool enabled = config.enabled; bool enabled = config.enabled;
bool global = config.global; bool global = config.global;
@@ -242,8 +241,6 @@ namespace Shadowsocks.View
icon_both = Icon.FromHandle(ViewUtils.ResizeBitmap(ViewUtils.AddBitmapOverlay(iconBitmap, Resources.ss32In, Resources.ss32Out), size.Width, size.Height).GetHicon()); icon_both = Icon.FromHandle(ViewUtils.ResizeBitmap(ViewUtils.AddBitmapOverlay(iconBitmap, Resources.ss32In, Resources.ss32Out), size.Width, size.Height).GetHicon());
} }
#endregion #endregion
#region ToolStripMenuItems and MenuGroups #region ToolStripMenuItems and MenuGroups
@@ -270,7 +267,6 @@ namespace Shadowsocks.View
this.ServersItem = CreateMenuGroup("Servers", new ToolStripItem [] { this.ServersItem = CreateMenuGroup("Servers", new ToolStripItem [] {
this.SeperatorItem = new ToolStripSeparator(), this.SeperatorItem = new ToolStripSeparator(),
this.ConfigItem = CreateToolStripMenuItem("Edit Servers...", new EventHandler(this.Config_Click)), this.ConfigItem = CreateToolStripMenuItem("Edit Servers...", new EventHandler(this.Config_Click)),
CreateToolStripMenuItem("Statistics Config...", StatisticsConfigItem_Click),
new ToolStripSeparator(), new ToolStripSeparator(),
CreateToolStripMenuItem("Share Server Config...", new EventHandler(this.QRCodeItem_Click)), CreateToolStripMenuItem("Share Server Config...", new EventHandler(this.QRCodeItem_Click)),
CreateToolStripMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click)), CreateToolStripMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click)),
@@ -284,6 +280,7 @@ namespace Shadowsocks.View
this.updateFromGeositeItem = CreateToolStripMenuItem("Update Local PAC from Geosite", new EventHandler(this.UpdatePACFromGeositeItem_Click)), this.updateFromGeositeItem = CreateToolStripMenuItem("Update Local PAC from Geosite", new EventHandler(this.UpdatePACFromGeositeItem_Click)),
this.editGFWUserRuleItem = CreateToolStripMenuItem("Edit User Rule for Geosite...", new EventHandler(this.EditUserRuleFileForGeositeItem_Click)), this.editGFWUserRuleItem = CreateToolStripMenuItem("Edit User Rule for Geosite...", new EventHandler(this.EditUserRuleFileForGeositeItem_Click)),
this.secureLocalPacUrlToggleItem = CreateToolStripMenuItem("Secure Local PAC", new EventHandler(this.SecureLocalPacUrlToggleItem_Click)), this.secureLocalPacUrlToggleItem = CreateToolStripMenuItem("Secure Local PAC", new EventHandler(this.SecureLocalPacUrlToggleItem_Click)),
this.regenerateLocalPacOnUpdateItem = CreateToolStripMenuItem("Regenerate local PAC on version update", new EventHandler(this.RegenerateLocalPacOnUpdateItem_Click)),
CreateToolStripMenuItem("Copy Local PAC URL", new EventHandler(this.CopyLocalPacUrlItem_Click)), CreateToolStripMenuItem("Copy Local PAC URL", new EventHandler(this.CopyLocalPacUrlItem_Click)),
this.editOnlinePACItem = CreateToolStripMenuItem("Edit Online PAC URL...", new EventHandler(this.UpdateOnlinePACURLItem_Click)), this.editOnlinePACItem = CreateToolStripMenuItem("Edit Online PAC URL...", new EventHandler(this.UpdateOnlinePACURLItem_Click)),
}), }),
@@ -341,7 +338,7 @@ namespace Shadowsocks.View
} }
} }
void controller_Errored(object sender, System.IO.ErrorEventArgs e)
void controller_Errored(object sender, ErrorEventArgs e)
{ {
MessageBox.Show(e.GetException().ToString(), I18N.GetString("Shadowsocks Error: {0}", e.GetException().Message)); MessageBox.Show(e.GetException().ToString(), I18N.GetString("Shadowsocks Error: {0}", e.GetException().Message));
} }
@@ -354,7 +351,7 @@ namespace Shadowsocks.View
private void LoadCurrentConfiguration() private void LoadCurrentConfiguration()
{ {
Configuration config = controller.GetConfigurationCopy();
Configuration config = controller.GetCurrentConfiguration();
UpdateServersMenu(); UpdateServersMenu();
UpdateSystemProxyItemsEnabledStatus(config); UpdateSystemProxyItemsEnabledStatus(config);
ShareOverLANItem.Checked = config.shareOverLan; ShareOverLANItem.Checked = config.shareOverLan;
@@ -365,6 +362,7 @@ namespace Shadowsocks.View
onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac;
localPACItem.Checked = !onlinePACItem.Checked; localPACItem.Checked = !onlinePACItem.Checked;
secureLocalPacUrlToggleItem.Checked = config.secureLocalPac; secureLocalPacUrlToggleItem.Checked = config.secureLocalPac;
regenerateLocalPacOnUpdateItem.Checked = config.regeneratePacOnUpdate;
UpdatePACItemsEnabledStatus(); UpdatePACItemsEnabledStatus();
UpdateUpdateMenu(); UpdateUpdateMenu();
} }
@@ -386,36 +384,6 @@ namespace Shadowsocks.View
} }
} }
private void ShowProxyForm()
{
if (proxyForm != null)
{
proxyForm.Activate();
}
else
{
proxyForm = new ProxyForm(controller);
proxyForm.Show();
proxyForm.Activate();
proxyForm.FormClosed += proxyForm_FormClosed;
}
}
private void ShowHotKeySettingsForm()
{
if (hotkeySettingsForm != null)
{
hotkeySettingsForm.Activate();
}
else
{
hotkeySettingsForm = new HotkeySettingsForm(controller);
hotkeySettingsForm.Show();
hotkeySettingsForm.Activate();
hotkeySettingsForm.FormClosed += hotkeySettingsForm_FormClosed;
}
}
private void ShowLogForm() private void ShowLogForm()
{ {
if (logForm != null) if (logForm != null)
@@ -431,22 +399,6 @@ namespace Shadowsocks.View
} }
} }
private void ShowOnlineConfigForm()
{
if (onlineConfigForm != null)
{
onlineConfigForm.Activate();
}
else
{
onlineConfigForm = new OnlineConfigForm(controller);
onlineConfigForm.Show();
onlineConfigForm.Activate();
onlineConfigForm.FormClosed += onlineConfigForm_FormClosed;
}
}
void logForm_FormClosed(object sender, FormClosedEventArgs e) void logForm_FormClosed(object sender, FormClosedEventArgs e)
{ {
logForm.Dispose(); logForm.Dispose();
@@ -457,7 +409,8 @@ namespace Shadowsocks.View
{ {
configForm.Dispose(); configForm.Dispose();
configForm = null; configForm = null;
if (_isFirstRun)
var config = controller.GetCurrentConfiguration();
if (config.firstRun)
{ {
CheckUpdateForFirstRun(); CheckUpdateForFirstRun();
ShowBalloonTip( ShowBalloonTip(
@@ -466,28 +419,10 @@ namespace Shadowsocks.View
ToolTipIcon.Info, ToolTipIcon.Info,
0 0
); );
_isFirstRun = false;
config.firstRun = false;
} }
} }
void proxyForm_FormClosed(object sender, FormClosedEventArgs e)
{
proxyForm.Dispose();
proxyForm = null;
}
void hotkeySettingsForm_FormClosed(object sender, FormClosedEventArgs e)
{
hotkeySettingsForm.Dispose();
hotkeySettingsForm = null;
}
void onlineConfigForm_FormClosed(object sender, FormClosedEventArgs e)
{
onlineConfigForm.Dispose();
onlineConfigForm = null;
}
#endregion #endregion
#region Misc #region Misc
@@ -502,23 +437,10 @@ namespace Shadowsocks.View
void notifyIcon1_BalloonTipClicked(object sender, EventArgs e) void notifyIcon1_BalloonTipClicked(object sender, EventArgs e)
{ {
if (updateChecker.NewVersionFound)
{
updateChecker.NewVersionFound = false; /* Reset the flag */
if (File.Exists(updateChecker.LatestVersionLocalName))
{
string argument = "/select, \"" + updateChecker.LatestVersionLocalName + "\"";
Process.Start("explorer.exe", argument);
}
}
} }
private void _notifyIcon_BalloonTipClosed(object sender, EventArgs e) private void _notifyIcon_BalloonTipClosed(object sender, EventArgs e)
{ {
if (updateChecker.NewVersionFound)
{
updateChecker.NewVersionFound = false; /* Reset the flag */
}
} }
private void notifyIcon1_Click(object sender, MouseEventArgs e) private void notifyIcon1_Click(object sender, MouseEventArgs e)
@@ -538,12 +460,13 @@ namespace Shadowsocks.View
} }
} }
private void CheckUpdateForFirstRun()
void notifyIcon1_BalloonTipClicked(object sender, EventArgs e)
{ {
Configuration config = controller.GetConfigurationCopy();
if (config.isDefault) return;
_isStartupChecking = true;
updateChecker.CheckUpdate(config, 3000);
Configuration config = controller.GetCurrentConfiguration();
if (config.firstRun)
return;
_isStartupCheck = true;
Dispatcher.CurrentDispatcher.Invoke(() => updateChecker.CheckForVersionUpdate(3000));
} }
public void ShowLogForm_HotKey() public void ShowLogForm_HotKey()
@@ -555,6 +478,144 @@ namespace Shadowsocks.View
#region Main menu #region Main menu
void controller_ShareOverLANStatusChanged(object sender, EventArgs e)
{
ShareOverLANItem.Checked = controller.GetCurrentConfiguration().shareOverLan;
}
private void proxyItem_Click(object sender, EventArgs e)
{
if (forwardProxyWindow == null)
{
forwardProxyWindow = new System.Windows.Window()
{
Title = LocalizationProvider.GetLocalizedValue<string>("ForwardProxy"),
Height = 400,
Width = 280,
MinHeight = 400,
MinWidth = 280,
Content = new ForwardProxyView()
};
forwardProxyWindow.Closed += ForwardProxyWindow_Closed;
ElementHost.EnableModelessKeyboardInterop(forwardProxyWindow);
forwardProxyWindow.Show();
}
forwardProxyWindow.Activate();
}
private void ForwardProxyWindow_Closed(object sender, EventArgs e)
{
forwardProxyWindow = null;
}
public void CloseForwardProxyWindow() => forwardProxyWindow.Close();
private void OnlineConfig_Click(object sender, EventArgs e)
{
if (onlineConfigWindow == null)
{
onlineConfigWindow = new System.Windows.Window()
{
Title = LocalizationProvider.GetLocalizedValue<string>("OnlineConfigDelivery"),
Height = 510,
Width = 480,
MinHeight = 510,
MinWidth = 480,
Content = new OnlineConfigView()
};
onlineConfigWindow.Closed += OnlineConfigWindow_Closed;
ElementHost.EnableModelessKeyboardInterop(onlineConfigWindow);
onlineConfigWindow.Show();
}
onlineConfigWindow.Activate();
}
private void OnlineConfigWindow_Closed(object sender, EventArgs e)
{
onlineConfigWindow = null;
}
private void hotKeyItem_Click(object sender, EventArgs e)
{
if (hotkeysWindow == null)
{
hotkeysWindow = new System.Windows.Window()
{
Title = LocalizationProvider.GetLocalizedValue<string>("Hotkeys"),
Height = 260,
Width = 320,
MinHeight = 260,
MinWidth = 320,
Content = new HotkeysView()
};
hotkeysWindow.Closed += HotkeysWindow_Closed;
ElementHost.EnableModelessKeyboardInterop(hotkeysWindow);
hotkeysWindow.Show();
}
hotkeysWindow.Activate();
}
private void HotkeysWindow_Closed(object sender, EventArgs e)
{
hotkeysWindow = null;
}
public void CloseHotkeysWindow() => hotkeysWindow.Close();
private void ShareOverLANItem_Click(object sender, EventArgs e)
{
ShareOverLANItem.Checked = !ShareOverLANItem.Checked;
controller.ToggleShareOverLAN(ShareOverLANItem.Checked);
}
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"));
}
LoadCurrentConfiguration();
}
private void ProtocolHandlerItem_Click(object sender, EventArgs e)
{
ProtocolHandlerItem.Checked = !ProtocolHandlerItem.Checked;
if (!ProtocolHandler.Set(ProtocolHandlerItem.Checked))
{
MessageBox.Show(I18N.GetString("Failed to update registry"));
}
LoadCurrentConfiguration();
}
private void Quit_Click(object sender, EventArgs e)
{
controller.Stop();
_notifyIcon.Visible = false;
Application.Exit();
}
#endregion
#region System proxy
private void controller_EnableStatusChanged(object sender, EventArgs e)
{
disableItem.Checked = !controller.GetCurrentConfiguration().enabled;
}
private void CheckUpdateForFirstRun()
{
Configuration config = controller.GetConfigurationCopy();
if (config.isDefault) return;
_isStartupChecking = true;
updateChecker.CheckUpdate(config, 3000);
}
#endregion
#region Main menu
void controller_ShareOverLANStatusChanged(object sender, EventArgs e) void controller_ShareOverLANStatusChanged(object sender, EventArgs e)
{ {
ShareOverLANItem.Checked = controller.GetConfigurationCopy().shareOverLan; ShareOverLANItem.Checked = controller.GetConfigurationCopy().shareOverLan;
@@ -620,13 +681,13 @@ namespace Shadowsocks.View
private void EnableItem_Click(object sender, EventArgs e) private void EnableItem_Click(object sender, EventArgs e)
{ {
controller.ToggleEnable(false); controller.ToggleEnable(false);
Configuration config = controller.GetConfigurationCopy();
Configuration config = controller.GetCurrentConfiguration();
UpdateSystemProxyItemsEnabledStatus(config); UpdateSystemProxyItemsEnabledStatus(config);
} }
void controller_EnableGlobalChanged(object sender, EventArgs e) void controller_EnableGlobalChanged(object sender, EventArgs e)
{ {
globalModeItem.Checked = controller.GetConfigurationCopy().global;
globalModeItem.Checked = controller.GetCurrentConfiguration().global;
PACModeItem.Checked = !globalModeItem.Checked; PACModeItem.Checked = !globalModeItem.Checked;
} }
@@ -652,7 +713,7 @@ namespace Shadowsocks.View
{ {
controller.ToggleEnable(true); controller.ToggleEnable(true);
controller.ToggleGlobal(true); controller.ToggleGlobal(true);
Configuration config = controller.GetConfigurationCopy();
Configuration config = controller.GetCurrentConfiguration();
UpdateSystemProxyItemsEnabledStatus(config); UpdateSystemProxyItemsEnabledStatus(config);
} }
@@ -660,7 +721,7 @@ namespace Shadowsocks.View
{ {
controller.ToggleEnable(true); controller.ToggleEnable(true);
controller.ToggleGlobal(false); controller.ToggleGlobal(false);
Configuration config = controller.GetConfigurationCopy();
Configuration config = controller.GetCurrentConfiguration();
UpdateSystemProxyItemsEnabledStatus(config); UpdateSystemProxyItemsEnabledStatus(config);
} }
@@ -693,7 +754,7 @@ namespace Shadowsocks.View
items.Add(new ToolStripSeparator() { Tag = "-server-" }); items.Add(new ToolStripSeparator() { Tag = "-server-" });
} }
int serverCount = 0; int serverCount = 0;
Configuration configuration = controller.GetConfigurationCopy();
Configuration configuration = controller.GetCurrentConfiguration();
foreach (var server in configuration.configs) foreach (var server in configuration.configs)
{ {
if (Configuration.ChecksServer(server)) if (Configuration.ChecksServer(server))
@@ -746,112 +807,58 @@ namespace Shadowsocks.View
ShowConfigForm(); ShowConfigForm();
} }
void splash_FormClosed(object sender, FormClosedEventArgs e)
{
ShowConfigForm();
}
void openURLFromQRCode(object sender, FormClosedEventArgs e) void openURLFromQRCode(object sender, FormClosedEventArgs e)
{ {
Utils.OpenInBrowser(_urlToOpen); Utils.OpenInBrowser(_urlToOpen);
} }
private void StatisticsConfigItem_Click(object sender, EventArgs e)
private void QRCodeItem_Click(object sender, EventArgs e)
{ {
StatisticsStrategyConfigurationForm form = new StatisticsStrategyConfigurationForm(controller);
form.Show();
if (serverSharingWindow == null)
{
serverSharingWindow = new System.Windows.Window()
{
Title = LocalizationProvider.GetLocalizedValue<string>("ServerSharing"),
Height = 400,
Width = 660,
MinHeight = 400,
MinWidth = 660,
Content = new ServerSharingView()
};
serverSharingWindow.Closed += ServerSharingWindow_Closed;
ElementHost.EnableModelessKeyboardInterop(serverSharingWindow);
serverSharingWindow.Show();
}
serverSharingWindow.Activate();
} }
private void QRCodeItem_Click(object sender, EventArgs e)
private void ServerSharingWindow_Closed(object sender, EventArgs e)
{ {
QRCodeForm qrCodeForm = new QRCodeForm(controller.GetServerURLForCurrentServer());
//qrCodeForm.Icon = this.Icon;
// TODO
qrCodeForm.Show();
serverSharingWindow = null;
} }
private void ScanQRCodeItem_Click(object sender, EventArgs e) private void ScanQRCodeItem_Click(object sender, EventArgs e)
{ {
foreach (Screen screen in Screen.AllScreens)
var result = Utils.ScanQRCodeFromScreen();
if (result != null)
{ {
using (Bitmap fullImage = new Bitmap(screen.Bounds.Width,
screen.Bounds.Height))
if (result.ToLowerInvariant().StartsWith("http://") || result.ToLowerInvariant().StartsWith("https://"))
{ {
using (Graphics g = Graphics.FromImage(fullImage))
{
g.CopyFromScreen(screen.Bounds.X,
screen.Bounds.Y,
0, 0,
fullImage.Size,
CopyPixelOperation.SourceCopy);
}
int maxTry = 10;
for (int i = 0; i < maxTry; i++)
{
int marginLeft = (int)((double)fullImage.Width * i / 2.5 / maxTry);
int marginTop = (int)((double)fullImage.Height * i / 2.5 / maxTry);
Rectangle cropRect = new Rectangle(marginLeft, marginTop, fullImage.Width - marginLeft * 2, fullImage.Height - marginTop * 2);
Bitmap target = new Bitmap(screen.Bounds.Width, screen.Bounds.Height);
double imageScale = (double)screen.Bounds.Width / (double)cropRect.Width;
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(fullImage, new Rectangle(0, 0, target.Width, target.Height),
cropRect,
GraphicsUnit.Pixel);
}
var source = new BitmapSourceLuminanceSource(null);
var bitmap = new BinaryBitmap(new HybridBinarizer(source));
QRCodeReader reader = new QRCodeReader();
var result = reader.decode(bitmap);
if (result != null)
{
var success = controller.AddServerBySSURL(result.Text);
QRCodeSplashForm splash = new QRCodeSplashForm();
if (success)
{
splash.FormClosed += splash_FormClosed;
}
else if (result.Text.ToLower().StartsWith("http://") || result.Text.ToLower().StartsWith("https://"))
{
_urlToOpen = result.Text;
splash.FormClosed += openURLFromQRCode;
}
else
{
MessageBox.Show(I18N.GetString("Failed to decode QRCode"));
return;
}
double minX = Int32.MaxValue, minY = Int32.MaxValue, maxX = 0, maxY = 0;
foreach (ResultPoint point in result.ResultPoints)
{
minX = Math.Min(minX, point.X);
minY = Math.Min(minY, point.Y);
maxX = Math.Max(maxX, point.X);
maxY = Math.Max(maxY, point.Y);
}
minX /= imageScale;
minY /= imageScale;
maxX /= imageScale;
maxY /= imageScale;
// make it 20% larger
double margin = (maxX - minX) * 0.20f;
minX += -margin + marginLeft;
maxX += margin + marginLeft;
minY += -margin + marginTop;
maxY += margin + marginTop;
splash.Location = new Point(screen.Bounds.X, screen.Bounds.Y);
// we need a panel because a window has a minimal size
// TODO: test on high DPI
splash.TargetRect = new Rectangle((int)minX, (int)minY, (int)maxX - (int)minX, (int)maxY - (int)minY);
splash.Size = new Size(fullImage.Width, fullImage.Height);
splash.Show();
return;
}
}
_urlToOpen = result;
openURLFromQRCode();
} }
else if (controller.AddServerBySSURL(result))
{
ShowConfigForm();
}
else
{
MessageBox.Show(I18N.GetString("Invalid QR Code content: {0}", result));
}
return;
} }
MessageBox.Show(I18N.GetString("No QRCode found. Try to zoom in or move it to the center of the screen."));
else
MessageBox.Show(I18N.GetString("No QRCode found. Try to zoom in or move it to the center of the screen."));
} }
private void ImportURLItem_Click(object sender, EventArgs e) private void ImportURLItem_Click(object sender, EventArgs e)
@@ -881,11 +888,11 @@ namespace Shadowsocks.View
{ {
if (!onlinePACItem.Checked) if (!onlinePACItem.Checked)
{ {
if (controller.GetConfigurationCopy().pacUrl.IsNullOrEmpty())
if (string.IsNullOrEmpty(controller.GetCurrentConfiguration().pacUrl))
{ {
UpdateOnlinePACURLItem_Click(sender, e); UpdateOnlinePACURLItem_Click(sender, e);
} }
if (!controller.GetConfigurationCopy().pacUrl.IsNullOrEmpty())
if (!string.IsNullOrEmpty(controller.GetCurrentConfiguration().pacUrl))
{ {
localPACItem.Checked = false; localPACItem.Checked = false;
onlinePACItem.Checked = true; onlinePACItem.Checked = true;
@@ -897,12 +904,12 @@ namespace Shadowsocks.View
private void UpdateOnlinePACURLItem_Click(object sender, EventArgs e) private void UpdateOnlinePACURLItem_Click(object sender, EventArgs e)
{ {
string origPacUrl = controller.GetConfigurationCopy().pacUrl;
string origPacUrl = controller.GetCurrentConfiguration().pacUrl;
string pacUrl = ViewUtils.InputBox( string pacUrl = ViewUtils.InputBox(
I18N.GetString("Please input PAC Url"), I18N.GetString("Please input PAC Url"),
I18N.GetString("Edit Online PAC URL"), I18N.GetString("Edit Online PAC URL"),
origPacUrl, -1, -1); origPacUrl, -1, -1);
if (!pacUrl.IsNullOrEmpty() && pacUrl != origPacUrl)
if (!string.IsNullOrEmpty(pacUrl) && pacUrl != origPacUrl)
{ {
controller.SavePACUrl(pacUrl); controller.SavePACUrl(pacUrl);
} }
@@ -910,10 +917,16 @@ namespace Shadowsocks.View
private void SecureLocalPacUrlToggleItem_Click(object sender, EventArgs e) private void SecureLocalPacUrlToggleItem_Click(object sender, EventArgs e)
{ {
Configuration configuration = controller.GetConfigurationCopy();
Configuration configuration = controller.GetCurrentConfiguration();
controller.ToggleSecureLocalPac(!configuration.secureLocalPac); controller.ToggleSecureLocalPac(!configuration.secureLocalPac);
} }
private void RegenerateLocalPacOnUpdateItem_Click(object sender, EventArgs e)
{
var config = controller.GetCurrentConfiguration();
controller.ToggleRegeneratePacOnUpdate(!config.regeneratePacOnUpdate);
}
private void CopyLocalPacUrlItem_Click(object sender, EventArgs e) private void CopyLocalPacUrlItem_Click(object sender, EventArgs e)
{ {
controller.CopyPacUrl(); controller.CopyPacUrl();
@@ -937,7 +950,6 @@ namespace Shadowsocks.View
} }
} }
private void EditPACFileItem_Click(object sender, EventArgs e) private void EditPACFileItem_Click(object sender, EventArgs e)
{ {
controller.TouchPACFile(); controller.TouchPACFile();
@@ -980,12 +992,12 @@ namespace Shadowsocks.View
void controller_VerboseLoggingStatusChanged(object sender, EventArgs e) void controller_VerboseLoggingStatusChanged(object sender, EventArgs e)
{ {
VerboseLoggingToggleItem.Checked = controller.GetConfigurationCopy().isVerboseLogging;
VerboseLoggingToggleItem.Checked = controller.GetCurrentConfiguration().isVerboseLogging;
} }
void controller_ShowPluginOutputChanged(object sender, EventArgs e) void controller_ShowPluginOutputChanged(object sender, EventArgs e)
{ {
ShowPluginOutputToggleItem.Checked = controller.GetConfigurationCopy().showPluginOutput;
ShowPluginOutputToggleItem.Checked = controller.GetCurrentConfiguration().showPluginOutput;
} }
private void VerboseLoggingToggleItem_Click(object sender, EventArgs e) private void VerboseLoggingToggleItem_Click(object sender, EventArgs e)
@@ -1016,41 +1028,37 @@ namespace Shadowsocks.View
void updateChecker_CheckUpdateCompleted(object sender, EventArgs e) void updateChecker_CheckUpdateCompleted(object sender, EventArgs e)
{ {
if (updateChecker.NewVersionFound)
{
ShowBalloonTip(I18N.GetString("Shadowsocks {0} Update Found", updateChecker.LatestVersionNumber + updateChecker.LatestVersionSuffix), I18N.GetString("Click here to update"), ToolTipIcon.Info, 5000);
}
else if (!_isStartupChecking)
if (!_isStartupCheck && updateChecker.NewReleaseZipFilename == null)
{ {
ShowBalloonTip(I18N.GetString("Shadowsocks"), I18N.GetString("No update is available"), ToolTipIcon.Info, 5000); ShowBalloonTip(I18N.GetString("Shadowsocks"), I18N.GetString("No update is available"), ToolTipIcon.Info, 5000);
} }
_isStartupChecking = false;
_isStartupCheck = false;
} }
private void UpdateUpdateMenu() private void UpdateUpdateMenu()
{ {
Configuration configuration = controller.GetConfigurationCopy();
Configuration configuration = controller.GetCurrentConfiguration();
autoCheckUpdatesToggleItem.Checked = configuration.autoCheckUpdate; autoCheckUpdatesToggleItem.Checked = configuration.autoCheckUpdate;
checkPreReleaseToggleItem.Checked = configuration.checkPreRelease; checkPreReleaseToggleItem.Checked = configuration.checkPreRelease;
} }
private void autoCheckUpdatesToggleItem_Click(object sender, EventArgs e) private void autoCheckUpdatesToggleItem_Click(object sender, EventArgs e)
{ {
Configuration configuration = controller.GetConfigurationCopy();
Configuration configuration = controller.GetCurrentConfiguration();
controller.ToggleCheckingUpdate(!configuration.autoCheckUpdate); controller.ToggleCheckingUpdate(!configuration.autoCheckUpdate);
UpdateUpdateMenu(); UpdateUpdateMenu();
} }
private void checkPreReleaseToggleItem_Click(object sender, EventArgs e) private void checkPreReleaseToggleItem_Click(object sender, EventArgs e)
{ {
Configuration configuration = controller.GetConfigurationCopy();
Configuration configuration = controller.GetCurrentConfiguration();
controller.ToggleCheckingPreRelease(!configuration.checkPreRelease); controller.ToggleCheckingPreRelease(!configuration.checkPreRelease);
UpdateUpdateMenu(); UpdateUpdateMenu();
} }
private void checkUpdatesItem_Click(object sender, EventArgs e)
private async void checkUpdatesItem_Click(object sender, EventArgs e)
{ {
updateChecker.CheckUpdate(controller.GetConfigurationCopy());
await updateChecker.CheckForVersionUpdate();
} }
private void AboutItem_Click(object sender, EventArgs e) private void AboutItem_Click(object sender, EventArgs e)


+ 0
- 333
shadowsocks-csharp/View/ProxyForm.Designer.cs View File

@@ -1,333 +0,0 @@
namespace Shadowsocks.View
{
partial class ProxyForm
{
/// <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.MyCancelButton = new System.Windows.Forms.Button();
this.OKButton = new System.Windows.Forms.Button();
this.UseProxyCheckBox = new System.Windows.Forms.CheckBox();
this.ProxyAddrLabel = new System.Windows.Forms.Label();
this.ProxyServerTextBox = new System.Windows.Forms.TextBox();
this.ProxyPortLabel = new System.Windows.Forms.Label();
this.ProxyPortTextBox = new System.Windows.Forms.TextBox();
this.ProxyTypeLabel = new System.Windows.Forms.Label();
this.ProxyTypeComboBox = new System.Windows.Forms.ComboBox();
this.ProxyTimeoutTextBox = new System.Windows.Forms.TextBox();
this.ProxyTimeoutLabel = new System.Windows.Forms.Label();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.ProxyNotificationLabel = new System.Windows.Forms.Label();
this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
this.AuthUserLabel = new System.Windows.Forms.Label();
this.AuthPwdLabel = new System.Windows.Forms.Label();
this.UseAuthCheckBox = new System.Windows.Forms.CheckBox();
this.AuthUserTextBox = new System.Windows.Forms.TextBox();
this.AuthPwdTextBox = new System.Windows.Forms.TextBox();
this.tableLayoutPanel1.SuspendLayout();
this.flowLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// MyCancelButton
//
this.MyCancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.MyCancelButton.Location = new System.Drawing.Point(84, 3);
this.MyCancelButton.Margin = new System.Windows.Forms.Padding(3, 3, 0, 0);
this.MyCancelButton.Name = "MyCancelButton";
this.MyCancelButton.Size = new System.Drawing.Size(75, 23);
this.MyCancelButton.TabIndex = 13;
this.MyCancelButton.Text = "Cancel";
this.MyCancelButton.UseVisualStyleBackColor = true;
this.MyCancelButton.Click += new System.EventHandler(this.CancelButton_Click);
//
// OKButton
//
this.OKButton.DialogResult = System.Windows.Forms.DialogResult.OK;
this.OKButton.Location = new System.Drawing.Point(3, 3);
this.OKButton.Margin = new System.Windows.Forms.Padding(3, 3, 3, 0);
this.OKButton.Name = "OKButton";
this.OKButton.Size = new System.Drawing.Size(75, 23);
this.OKButton.TabIndex = 12;
this.OKButton.Text = "OK";
this.OKButton.UseVisualStyleBackColor = true;
this.OKButton.Click += new System.EventHandler(this.OKButton_Click);
//
// UseProxyCheckBox
//
this.UseProxyCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.UseProxyCheckBox.AutoSize = true;
this.tableLayoutPanel1.SetColumnSpan(this.UseProxyCheckBox, 2);
this.UseProxyCheckBox.Location = new System.Drawing.Point(3, 6);
this.UseProxyCheckBox.Name = "UseProxyCheckBox";
this.UseProxyCheckBox.Size = new System.Drawing.Size(78, 16);
this.UseProxyCheckBox.TabIndex = 0;
this.UseProxyCheckBox.Text = "Use Proxy";
this.UseProxyCheckBox.UseVisualStyleBackColor = true;
this.UseProxyCheckBox.CheckedChanged += new System.EventHandler(this.UseProxyCheckBox_CheckedChanged);
//
// ProxyAddrLabel
//
this.ProxyAddrLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.ProxyAddrLabel.AutoSize = true;
this.ProxyAddrLabel.Location = new System.Drawing.Point(3, 64);
this.ProxyAddrLabel.Name = "ProxyAddrLabel";
this.ProxyAddrLabel.Size = new System.Drawing.Size(65, 12);
this.ProxyAddrLabel.TabIndex = 0;
this.ProxyAddrLabel.Text = "Proxy Addr";
//
// ProxyServerTextBox
//
this.ProxyServerTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.ProxyServerTextBox.Location = new System.Drawing.Point(74, 59);
this.ProxyServerTextBox.MaxLength = 512;
this.ProxyServerTextBox.Name = "ProxyServerTextBox";
this.ProxyServerTextBox.Size = new System.Drawing.Size(138, 21);
this.ProxyServerTextBox.TabIndex = 1;
this.ProxyServerTextBox.WordWrap = false;
//
// ProxyPortLabel
//
this.ProxyPortLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.ProxyPortLabel.AutoSize = true;
this.ProxyPortLabel.Location = new System.Drawing.Point(218, 64);
this.ProxyPortLabel.Name = "ProxyPortLabel";
this.ProxyPortLabel.Size = new System.Drawing.Size(77, 12);
this.ProxyPortLabel.TabIndex = 2;
this.ProxyPortLabel.Text = "Proxy Port";
//
// ProxyPortTextBox
//
this.ProxyPortTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.ProxyPortTextBox.Location = new System.Drawing.Point(301, 59);
this.ProxyPortTextBox.MaxLength = 10;
this.ProxyPortTextBox.Name = "ProxyPortTextBox";
this.ProxyPortTextBox.Size = new System.Drawing.Size(91, 21);
this.ProxyPortTextBox.TabIndex = 3;
this.ProxyPortTextBox.WordWrap = false;
//
// ProxyTypeLabel
//
this.ProxyTypeLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.ProxyTypeLabel.AutoSize = true;
this.ProxyTypeLabel.Location = new System.Drawing.Point(3, 36);
this.ProxyTypeLabel.Name = "ProxyTypeLabel";
this.ProxyTypeLabel.Size = new System.Drawing.Size(65, 12);
this.ProxyTypeLabel.TabIndex = 1;
this.ProxyTypeLabel.Text = "Proxy Type";
//
// ProxyTypeComboBox
//
this.ProxyTypeComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.ProxyTypeComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.ProxyTypeComboBox.FormattingEnabled = true;
this.ProxyTypeComboBox.Items.AddRange(new object[] {
"SOCKS5",
"HTTP"});
this.ProxyTypeComboBox.Location = new System.Drawing.Point(74, 33);
this.ProxyTypeComboBox.Margin = new System.Windows.Forms.Padding(3, 5, 3, 5);
this.ProxyTypeComboBox.Name = "ProxyTypeComboBox";
this.ProxyTypeComboBox.Size = new System.Drawing.Size(138, 20);
this.ProxyTypeComboBox.TabIndex = 2;
this.ProxyTypeComboBox.SelectedIndexChanged += new System.EventHandler(this.ProxyTypeComboBox_SelectedIndexChanged);
//
// ProxyTimeoutTextBox
//
this.ProxyTimeoutTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.ProxyTimeoutTextBox.Location = new System.Drawing.Point(301, 31);
this.ProxyTimeoutTextBox.Name = "ProxyTimeoutTextBox";
this.ProxyTimeoutTextBox.Size = new System.Drawing.Size(91, 21);
this.ProxyTimeoutTextBox.TabIndex = 3;
//
// ProxyTimeoutLabel
//
this.ProxyTimeoutLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.ProxyTimeoutLabel.AutoSize = true;
this.ProxyTimeoutLabel.Location = new System.Drawing.Point(218, 36);
this.ProxyTimeoutLabel.Name = "ProxyTimeoutLabel";
this.ProxyTimeoutLabel.Size = new System.Drawing.Size(77, 12);
this.ProxyTimeoutLabel.TabIndex = 4;
this.ProxyTimeoutLabel.Text = "Timeout(Sec)";
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.tableLayoutPanel1.ColumnCount = 4;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 60F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 40F));
this.tableLayoutPanel1.Controls.Add(this.UseProxyCheckBox, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.ProxyTypeLabel, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.ProxyPortTextBox, 3, 2);
this.tableLayoutPanel1.Controls.Add(this.ProxyTypeComboBox, 1, 1);
this.tableLayoutPanel1.Controls.Add(this.ProxyTimeoutLabel, 2, 1);
this.tableLayoutPanel1.Controls.Add(this.ProxyPortLabel, 2, 2);
this.tableLayoutPanel1.Controls.Add(this.ProxyTimeoutTextBox, 3, 1);
this.tableLayoutPanel1.Controls.Add(this.ProxyServerTextBox, 1, 2);
this.tableLayoutPanel1.Controls.Add(this.ProxyAddrLabel, 0, 2);
this.tableLayoutPanel1.Controls.Add(this.ProxyNotificationLabel, 0, 3);
this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel1, 0, 6);
this.tableLayoutPanel1.Controls.Add(this.AuthUserLabel, 0, 5);
this.tableLayoutPanel1.Controls.Add(this.AuthPwdLabel, 2, 5);
this.tableLayoutPanel1.Controls.Add(this.UseAuthCheckBox, 0, 4);
this.tableLayoutPanel1.Controls.Add(this.AuthUserTextBox, 1, 5);
this.tableLayoutPanel1.Controls.Add(this.AuthPwdTextBox, 3, 5);
this.tableLayoutPanel1.Location = new System.Drawing.Point(15, 15);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 7;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66713F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66713F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66713F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.6662F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.6662F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.6662F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.Size = new System.Drawing.Size(395, 204);
this.tableLayoutPanel1.TabIndex = 14;
//
// ProxyNotificationLabel
//
this.ProxyNotificationLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.ProxyNotificationLabel.AutoSize = true;
this.tableLayoutPanel1.SetColumnSpan(this.ProxyNotificationLabel, 4);
this.ProxyNotificationLabel.ForeColor = System.Drawing.Color.Red;
this.ProxyNotificationLabel.Location = new System.Drawing.Point(3, 92);
this.ProxyNotificationLabel.Name = "ProxyNotificationLabel";
this.ProxyNotificationLabel.Size = new System.Drawing.Size(389, 12);
this.ProxyNotificationLabel.TabIndex = 5;
this.ProxyNotificationLabel.Text = "If server has a plugin, proxy will not be used";
//
// flowLayoutPanel1
//
this.flowLayoutPanel1.AutoSize = true;
this.flowLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.tableLayoutPanel1.SetColumnSpan(this.flowLayoutPanel1, 4);
this.flowLayoutPanel1.Controls.Add(this.MyCancelButton);
this.flowLayoutPanel1.Controls.Add(this.OKButton);
this.flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Right;
this.flowLayoutPanel1.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
this.flowLayoutPanel1.Location = new System.Drawing.Point(233, 171);
this.flowLayoutPanel1.Name = "flowLayoutPanel1";
this.flowLayoutPanel1.Size = new System.Drawing.Size(159, 30);
this.flowLayoutPanel1.TabIndex = 6;
//
// AuthUserLabel
//
this.AuthUserLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.AuthUserLabel.AutoSize = true;
this.AuthUserLabel.Location = new System.Drawing.Point(3, 148);
this.AuthUserLabel.Name = "AuthUserLabel";
this.AuthUserLabel.Size = new System.Drawing.Size(65, 12);
this.AuthUserLabel.TabIndex = 7;
this.AuthUserLabel.Text = "User Name";
//
// AuthPwdLabel
//
this.AuthPwdLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.AuthPwdLabel.AutoSize = true;
this.AuthPwdLabel.Location = new System.Drawing.Point(218, 148);
this.AuthPwdLabel.Name = "AuthPwdLabel";
this.AuthPwdLabel.Size = new System.Drawing.Size(77, 12);
this.AuthPwdLabel.TabIndex = 8;
this.AuthPwdLabel.Text = "Password";
//
// UseAuthCheckBox
//
this.UseAuthCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.UseAuthCheckBox.AutoSize = true;
this.tableLayoutPanel1.SetColumnSpan(this.UseAuthCheckBox, 2);
this.UseAuthCheckBox.Location = new System.Drawing.Point(3, 118);
this.UseAuthCheckBox.Name = "UseAuthCheckBox";
this.UseAuthCheckBox.Size = new System.Drawing.Size(72, 16);
this.UseAuthCheckBox.TabIndex = 9;
this.UseAuthCheckBox.Text = "Use Auth";
this.UseAuthCheckBox.UseVisualStyleBackColor = true;
this.UseAuthCheckBox.CheckedChanged += new System.EventHandler(this.UseAuthCheckBox_CheckedChanged);
//
// AuthUserTextBox
//
this.AuthUserTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.AuthUserTextBox.Location = new System.Drawing.Point(74, 143);
this.AuthUserTextBox.Name = "AuthUserTextBox";
this.AuthUserTextBox.Size = new System.Drawing.Size(138, 21);
this.AuthUserTextBox.TabIndex = 10;
//
// AuthPwdTextBox
//
this.AuthPwdTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.AuthPwdTextBox.Location = new System.Drawing.Point(301, 143);
this.AuthPwdTextBox.Name = "AuthPwdTextBox";
this.AuthPwdTextBox.PasswordChar = '*';
this.AuthPwdTextBox.Size = new System.Drawing.Size(91, 21);
this.AuthPwdTextBox.TabIndex = 11;
//
// ProxyForm
//
this.AcceptButton = this.OKButton;
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.AutoSize = true;
this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.CancelButton = this.MyCancelButton;
this.ClientSize = new System.Drawing.Size(448, 231);
this.Controls.Add(this.tableLayoutPanel1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "ProxyForm";
this.Padding = new System.Windows.Forms.Padding(12, 12, 12, 9);
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Edit Proxy";
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.ProxyForm_FormClosed);
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.flowLayoutPanel1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.CheckBox UseProxyCheckBox;
private System.Windows.Forms.Label ProxyAddrLabel;
private System.Windows.Forms.TextBox ProxyServerTextBox;
private System.Windows.Forms.Label ProxyPortLabel;
private System.Windows.Forms.TextBox ProxyPortTextBox;
private System.Windows.Forms.Button MyCancelButton;
private System.Windows.Forms.Button OKButton;
private System.Windows.Forms.Label ProxyTypeLabel;
private System.Windows.Forms.ComboBox ProxyTypeComboBox;
private System.Windows.Forms.TextBox ProxyTimeoutTextBox;
private System.Windows.Forms.Label ProxyTimeoutLabel;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label ProxyNotificationLabel;
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
private System.Windows.Forms.Label AuthUserLabel;
private System.Windows.Forms.Label AuthPwdLabel;
private System.Windows.Forms.CheckBox UseAuthCheckBox;
private System.Windows.Forms.TextBox AuthUserTextBox;
private System.Windows.Forms.TextBox AuthPwdTextBox;
}
}

+ 0
- 176
shadowsocks-csharp/View/ProxyForm.cs View File

@@ -1,176 +0,0 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using Shadowsocks.Controller;
using Shadowsocks.Model;
using Shadowsocks.Properties;
namespace Shadowsocks.View
{
public partial class ProxyForm : Form
{
private ShadowsocksController controller;
// this is a copy of configuration that we are working on
private ProxyConfig _modifiedProxyConfig;
public ProxyForm(ShadowsocksController controller)
{
this.Font = System.Drawing.SystemFonts.MessageBoxFont;
InitializeComponent();
UpdateTexts();
this.Icon = Icon.FromHandle(Resources.ssw128.GetHicon());
this.controller = controller;
controller.ConfigChanged += controller_ConfigChanged;
UpdateEnabled();
LoadCurrentConfiguration();
}
private void UpdateTexts()
{
I18N.TranslateForm(this);
}
private void controller_ConfigChanged(object sender, EventArgs e)
{
LoadCurrentConfiguration();
}
private void LoadCurrentConfiguration()
{
_modifiedProxyConfig = controller.GetConfigurationCopy().proxy;
UseProxyCheckBox.Checked = _modifiedProxyConfig.useProxy;
ProxyServerTextBox.Text = _modifiedProxyConfig.proxyServer;
ProxyPortTextBox.Text = _modifiedProxyConfig.proxyPort.ToString();
ProxyTimeoutTextBox.Text = _modifiedProxyConfig.proxyTimeout.ToString();
ProxyTypeComboBox.SelectedIndex = _modifiedProxyConfig.proxyType;
UseAuthCheckBox.Checked = _modifiedProxyConfig.useAuth;
AuthUserTextBox.Text = _modifiedProxyConfig.authUser;
AuthPwdTextBox.Text = _modifiedProxyConfig.authPwd;
}
private void OKButton_Click(object sender, EventArgs e)
{
_modifiedProxyConfig.useProxy = UseProxyCheckBox.Checked;
if (_modifiedProxyConfig.useProxy)
{
if (!int.TryParse(ProxyPortTextBox.Text, out _modifiedProxyConfig.proxyPort))
{
MessageBox.Show(I18N.GetString("Illegal port number format"));
return;
}
if (!int.TryParse(ProxyTimeoutTextBox.Text, out _modifiedProxyConfig.proxyTimeout))
{
MessageBox.Show(I18N.GetString("Illegal timeout format"));
return;
}
_modifiedProxyConfig.proxyType = ProxyTypeComboBox.SelectedIndex;
try
{
Configuration.CheckServer(_modifiedProxyConfig.proxyServer = ProxyServerTextBox.Text);
Configuration.CheckPort(_modifiedProxyConfig.proxyPort);
Configuration.CheckTimeout(_modifiedProxyConfig.proxyTimeout, ProxyConfig.MaxProxyTimeoutSec);
_modifiedProxyConfig.useAuth = UseAuthCheckBox.Checked;
if (_modifiedProxyConfig.useAuth)
{
Configuration.CheckProxyAuthUser(_modifiedProxyConfig.authUser = AuthUserTextBox.Text);
Configuration.CheckProxyAuthPwd(_modifiedProxyConfig.authPwd = AuthPwdTextBox.Text);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return;
}
}
controller.SaveProxy(_modifiedProxyConfig);
this.Close();
}
private void CancelButton_Click(object sender, EventArgs e)
{
this.Close();
}
private void ProxyForm_FormClosed(object sender, FormClosedEventArgs e)
{
controller.ConfigChanged -= controller_ConfigChanged;
}
private void UseProxyCheckBox_CheckedChanged(object sender, EventArgs e)
{
UpdateEnabled();
}
private void UpdateEnabled()
{
if (UseProxyCheckBox.Checked)
{
ProxyServerTextBox.Enabled =
ProxyPortTextBox.Enabled =
ProxyTimeoutTextBox.Enabled =
ProxyTypeComboBox.Enabled = true;
if (ProxyTypeComboBox.SelectedIndex == ProxyConfig.PROXY_HTTP)
{
UseAuthCheckBox.Enabled = true;
if (UseAuthCheckBox.Checked)
{
AuthUserTextBox.Enabled =
AuthPwdTextBox.Enabled = true;
}
else
{
AuthUserTextBox.Enabled =
AuthPwdTextBox.Enabled = false;
}
}
else
{
// TODO support for SOCK5 auth
UseAuthCheckBox.Enabled =
AuthUserTextBox.Enabled =
AuthPwdTextBox.Enabled = false;
}
}
else
{
ProxyServerTextBox.Enabled =
ProxyPortTextBox.Enabled =
ProxyTimeoutTextBox.Enabled =
ProxyTypeComboBox.Enabled =
UseAuthCheckBox.Enabled =
AuthUserTextBox.Enabled =
AuthPwdTextBox.Enabled = false;
}
}
private void ProxyTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
// TODO support for SOCK5 auth
if (ProxyTypeComboBox.SelectedIndex != ProxyConfig.PROXY_HTTP)
{
UseAuthCheckBox.Checked = false;
AuthUserTextBox.Clear();
AuthPwdTextBox.Clear();
}
UpdateEnabled();
}
private void UseAuthCheckBox_CheckedChanged(object sender, EventArgs e)
{
UpdateEnabled();
}
}
}

+ 0
- 100
shadowsocks-csharp/View/QRCodeForm.Designer.cs View File

@@ -1,100 +0,0 @@
namespace Shadowsocks.View
{
partial class QRCodeForm
{
/// <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.pictureBox1 = new System.Windows.Forms.PictureBox();
this.listBox1 = new System.Windows.Forms.ListBox();
this.textBoxURL = new System.Windows.Forms.TextBox();
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
this.SuspendLayout();
//
// pictureBox1
//
this.pictureBox1.Location = new System.Drawing.Point(10, 10);
this.pictureBox1.Margin = new System.Windows.Forms.Padding(0);
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(210, 210);
this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage;
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);
//
// textBoxURL
//
this.textBoxURL.Location = new System.Drawing.Point(10, 227);
this.textBoxURL.Name = "textBoxURL";
this.textBoxURL.ReadOnly = true;
this.textBoxURL.Size = new System.Drawing.Size(441, 21);
this.textBoxURL.TabIndex = 3;
this.textBoxURL.Click += new System.EventHandler(this.textBoxURL_Click);
//
// QRCodeForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.AutoSize = true;
this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.BackColor = System.Drawing.Color.White;
this.ClientSize = new System.Drawing.Size(457, 257);
this.Controls.Add(this.textBoxURL);
this.Controls.Add(this.listBox1);
this.Controls.Add(this.pictureBox1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "QRCodeForm";
this.Padding = new System.Windows.Forms.Padding(10);
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "QRCode and URL";
this.Load += new System.EventHandler(this.QRCodeForm_Load);
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.PictureBox pictureBox1;
private System.Windows.Forms.ListBox listBox1;
private System.Windows.Forms.TextBox textBoxURL;
}
}

+ 0
- 90
shadowsocks-csharp/View/QRCodeForm.cs View File

@@ -1,90 +0,0 @@
using ZXing.QrCode.Internal;
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.IO.Compression;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Shadowsocks.Model;
namespace Shadowsocks.View
{
public partial class QRCodeForm : Form
{
private string code;
public QRCodeForm(string code)
{
this.code = code;
InitializeComponent();
this.Icon = Icon.FromHandle(Resources.ssw128.GetHicon());
this.Text = I18N.GetString("QRCode and URL");
}
private void GenQR(string ssconfig)
{
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);
var qrWidth = m.Width*blockSize;
var qrHeight = m.Height*blockSize;
var dWidth = pictureBox1.Width - qrWidth;
var dHeight = pictureBox1.Height - qrHeight;
var maxD = Math.Max(dWidth, dHeight);
pictureBox1.SizeMode = maxD >= 7*blockSize ? PictureBoxSizeMode.Zoom : PictureBoxSizeMode.CenterImage;
Bitmap drawArea = new Bitmap((m.Width*blockSize), (m.Height*blockSize));
using (Graphics g = Graphics.FromImage(drawArea))
{
g.Clear(Color.White);
using (Brush b = new SolidBrush(Color.Black))
{
for (int row = 0; row < m.Width; row++)
{
for (int col = 0; col < m.Height; col++)
{
if (m[row, col] != 0)
{
g.FillRectangle(b, blockSize*row, blockSize*col, blockSize, blockSize);
}
}
}
}
}
pictureBox1.Image = drawArea;
}
private void QRCodeForm_Load(object sender, EventArgs e)
{
Configuration config = Configuration.Load();
List<KeyValuePair<string, string>> serverDatas = config.configs.Select(
server =>
new KeyValuePair<string, string>(server.GetURL(config.generateLegacyUrl), server.ToString())
).ToList();
listBox1.DataSource = serverDatas;
int selectIndex = serverDatas.FindIndex(serverData => serverData.Key.StartsWith(code));
if (selectIndex >= 0) listBox1.SetSelected(selectIndex, true);
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
var url = (sender as ListBox)?.SelectedValue.ToString();
GenQR(url);
textBoxURL.Text = url;
}
private void textBoxURL_Click(object sender, EventArgs e)
{
textBoxURL.SelectAll();
}
}
}

+ 0
- 281
shadowsocks-csharp/View/QRCodeSplashForm.cs View File

@@ -1,281 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace Shadowsocks.View
{
public class QRCodeSplashForm : Form
{
public Rectangle TargetRect;
public QRCodeSplashForm()
{
FormBorderStyle = FormBorderStyle.None;
this.Load += QRCodeSplashForm_Load;
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
this.BackColor = System.Drawing.Color.White;
this.ClientSize = new System.Drawing.Size(1, 1);
this.ControlBox = false;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "QRCodeSplashForm";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
this.TopMost = true;
}
private Timer timer;
private int flashStep;
private static double FPS = 1.0 / 15 * 1000; // System.Windows.Forms.Timer resolution is 15ms
private static double ANIMATION_TIME = 0.5;
private static int ANIMATION_STEPS = (int)(ANIMATION_TIME * FPS);
Stopwatch sw;
int x;
int y;
int w;
int h;
Bitmap bitmap;
Graphics g;
Pen pen;
SolidBrush brush;
private void QRCodeSplashForm_Load(object sender, EventArgs e)
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.BackColor = Color.Transparent;
flashStep = 0;
x = 0;
y = 0;
w = Width;
h = Height;
sw = Stopwatch.StartNew();
timer = new Timer();
timer.Interval = (int)(ANIMATION_TIME * 1000 / ANIMATION_STEPS);
timer.Tick += timer_Tick;
timer.Start();
bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb);
g = Graphics.FromImage(bitmap);
pen = new Pen(Color.Red, 3);
brush = new SolidBrush(Color.FromArgb(30, Color.Red));
}
void timer_Tick(object sender, EventArgs e)
{
double percent = (double)sw.ElapsedMilliseconds / 1000.0 / (double)ANIMATION_TIME;
if (percent < 1)
{
// ease out
percent = 1 - Math.Pow((1 - percent), 4);
x = (int)(TargetRect.X * percent);
y = (int)(TargetRect.Y * percent);
w = (int)(TargetRect.Width * percent + this.Size.Width * (1 - percent));
h = (int)(TargetRect.Height * percent + this.Size.Height * (1 - percent));
//codeRectView.Location = new Point(x, y);
//codeRectView.Size = new Size(w, h);
pen.Color = Color.FromArgb((int)(255 * percent), Color.Red);
brush.Color = Color.FromArgb((int)(30 * percent), Color.Red);
g.Clear(Color.Transparent);
g.FillRectangle(brush, x, y, w, h);
g.DrawRectangle(pen, x, y, w, h);
SetBitmap(bitmap);
}
else
{
if (flashStep == 0)
{
timer.Interval = 100;
g.Clear(Color.Transparent);
SetBitmap(bitmap);
}
else if (flashStep == 1)
{
timer.Interval = 50;
g.FillRectangle(brush, x, y, w, h);
g.DrawRectangle(pen, x, y, w, h);
SetBitmap(bitmap);
}
else if (flashStep == 2)
{
g.Clear(Color.Transparent);
SetBitmap(bitmap);
}
else if (flashStep == 3)
{
g.FillRectangle(brush, x, y, w, h);
g.DrawRectangle(pen, x, y, w, h);
SetBitmap(bitmap);
}
else if (flashStep == 4)
{
g.Clear(Color.Transparent);
SetBitmap(bitmap);
}
else if (flashStep == 5)
{
g.FillRectangle(brush, x, y, w, h);
g.DrawRectangle(pen, x, y, w, h);
SetBitmap(bitmap);
}
else
{
sw.Stop();
timer.Stop();
pen.Dispose();
brush.Dispose();
bitmap.Dispose();
this.Close();
}
flashStep++;
}
}
// PerPixelAlphaForm.cs
// http://www.codeproject.com/Articles/1822/Per-Pixel-Alpha-Blend-in-C
// Rui Lopes
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00080000; // This form has to have the WS_EX_LAYERED extended style
return cp;
}
}
public void SetBitmap(Bitmap bitmap)
{
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)
{
if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
throw new ApplicationException("The bitmap must be 32ppp with alpha-channel.");
// The idea of this is very simple,
// 1. Create a compatible DC with screen;
// 2. Select the bitmap with 32bpp with alpha-channel in the compatible DC;
// 3. Call the UpdateLayeredWindow.
IntPtr screenDc = Win32.GetDC(IntPtr.Zero);
IntPtr memDc = Win32.CreateCompatibleDC(screenDc);
IntPtr hBitmap = IntPtr.Zero;
IntPtr oldBitmap = IntPtr.Zero;
try
{
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0)); // grab a GDI handle from this GDI+ bitmap
oldBitmap = Win32.SelectObject(memDc, hBitmap);
Win32.Size size = new Win32.Size(bitmap.Width, bitmap.Height);
Win32.Point pointSource = new Win32.Point(0, 0);
Win32.Point topPos = new Win32.Point(Left, Top);
Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION();
blend.BlendOp = Win32.AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = opacity;
blend.AlphaFormat = Win32.AC_SRC_ALPHA;
Win32.UpdateLayeredWindow(Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, Win32.ULW_ALPHA);
}
finally
{
Win32.ReleaseDC(IntPtr.Zero, screenDc);
if (hBitmap != IntPtr.Zero)
{
Win32.SelectObject(memDc, oldBitmap);
//Windows.DeleteObject(hBitmap); // The documentation says that we have to use the Windows.DeleteObject... but since there is no such method I use the normal DeleteObject from Win32 GDI and it's working fine without any resource leak.
Win32.DeleteObject(hBitmap);
}
Win32.DeleteDC(memDc);
}
}
}
// class that exposes needed win32 gdi functions.
class Win32
{
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public Int32 x;
public Int32 y;
public Point(Int32 x, Int32 y) { this.x = x; this.y = y; }
}
[StructLayout(LayoutKind.Sequential)]
public struct Size
{
public Int32 cx;
public Int32 cy;
public Size(Int32 cx, Int32 cy) { this.cx = cx; this.cy = cy; }
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct ARGB
{
public byte Blue;
public byte Green;
public byte Red;
public byte Alpha;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BLENDFUNCTION
{
public byte BlendOp;
public byte BlendFlags;
public byte SourceConstantAlpha;
public byte AlphaFormat;
}
public const Int32 ULW_COLORKEY = 0x00000001;
public const Int32 ULW_ALPHA = 0x00000002;
public const Int32 ULW_OPAQUE = 0x00000004;
public const byte AC_SRC_OVER = 0x00;
public const byte AC_SRC_ALPHA = 0x01;
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
public static extern int UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll", ExactSpelling = true)]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern int DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern int DeleteObject(IntPtr hObject);
}
}

+ 0
- 537
shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.Designer.cs View File

@@ -1,537 +0,0 @@
namespace Shadowsocks.View
{
partial class StatisticsStrategyConfigurationForm
{
/// <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();
System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend();
System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series();
System.Windows.Forms.DataVisualization.Charting.Series series2 = new System.Windows.Forms.DataVisualization.Charting.Series();
System.Windows.Forms.DataVisualization.Charting.Series series3 = new System.Windows.Forms.DataVisualization.Charting.Series();
this.StatisticsChart = new System.Windows.Forms.DataVisualization.Charting.Chart();
this.PingCheckBox = new System.Windows.Forms.CheckBox();
this.KeepChoiceForLabel = new System.Windows.Forms.Label();
this.MinutesLabel2 = new System.Windows.Forms.Label();
this.chartModeSelector = new System.Windows.Forms.GroupBox();
this.allMode = new System.Windows.Forms.RadioButton();
this.dayMode = new System.Windows.Forms.RadioButton();
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
this.splitContainer2 = new System.Windows.Forms.SplitContainer();
this.CollectDataPerLabel = new System.Windows.Forms.Label();
this.MinutesLabel1 = new System.Windows.Forms.Label();
this.dataCollectionMinutesNum = new System.Windows.Forms.NumericUpDown();
this.StatisticsEnabledCheckBox = new System.Windows.Forms.CheckBox();
this.choiceKeptMinutesNum = new System.Windows.Forms.NumericUpDown();
this.byHourOfDayCheckBox = new System.Windows.Forms.CheckBox();
this.repeatTimesNum = new System.Windows.Forms.NumericUpDown();
this.PackagePerPingLabel = new System.Windows.Forms.Label();
this.splitContainer3 = new System.Windows.Forms.SplitContainer();
this.FinalScoreLabel = new System.Windows.Forms.Label();
this.calculationContainer = new System.Windows.Forms.FlowLayoutPanel();
this.serverSelector = new System.Windows.Forms.ComboBox();
this.CancelButton = new System.Windows.Forms.Button();
this.OKButton = new System.Windows.Forms.Button();
this.CalculatinTip = new System.Windows.Forms.ToolTip(this.components);
this.bindingConfiguration = new System.Windows.Forms.BindingSource(this.components);
((System.ComponentModel.ISupportInitialize)(this.StatisticsChart)).BeginInit();
this.chartModeSelector.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
this.splitContainer1.Panel1.SuspendLayout();
this.splitContainer1.Panel2.SuspendLayout();
this.splitContainer1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit();
this.splitContainer2.Panel1.SuspendLayout();
this.splitContainer2.Panel2.SuspendLayout();
this.splitContainer2.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dataCollectionMinutesNum)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.choiceKeptMinutesNum)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.repeatTimesNum)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).BeginInit();
this.splitContainer3.Panel1.SuspendLayout();
this.splitContainer3.Panel2.SuspendLayout();
this.splitContainer3.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).BeginInit();
this.SuspendLayout();
//
// StatisticsChart
//
this.StatisticsChart.BackColor = System.Drawing.Color.Transparent;
chartArea1.AxisX.MajorGrid.Enabled = false;
chartArea1.AxisY.MajorGrid.Enabled = false;
chartArea1.AxisY2.Enabled = System.Windows.Forms.DataVisualization.Charting.AxisEnabled.False;
chartArea1.AxisY2.MajorGrid.Enabled = false;
chartArea1.BackColor = System.Drawing.Color.Transparent;
chartArea1.Name = "DataArea";
this.StatisticsChart.ChartAreas.Add(chartArea1);
this.StatisticsChart.Dock = System.Windows.Forms.DockStyle.Fill;
legend1.BackColor = System.Drawing.Color.Transparent;
legend1.Name = "ChartLegend";
this.StatisticsChart.Legends.Add(legend1);
this.StatisticsChart.Location = new System.Drawing.Point(0, 0);
this.StatisticsChart.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.StatisticsChart.Name = "StatisticsChart";
this.StatisticsChart.Palette = System.Windows.Forms.DataVisualization.Charting.ChartColorPalette.Pastel;
series1.ChartArea = "DataArea";
series1.Color = System.Drawing.Color.DarkGray;
series1.Legend = "ChartLegend";
series1.Name = "Speed";
series1.ToolTip = "#VALX\\nMax inbound speed\\n#VAL KiB/s";
series1.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Time;
series2.ChartArea = "DataArea";
series2.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Bubble;
series2.Color = System.Drawing.Color.Crimson;
series2.CustomProperties = "EmptyPointValue=Zero";
series2.Legend = "ChartLegend";
series2.Name = "Package Loss";
series2.ToolTip = "#VALX\\n#VAL%";
series2.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Time;
series2.YAxisType = System.Windows.Forms.DataVisualization.Charting.AxisType.Secondary;
series2.YValuesPerPoint = 2;
series3.BorderWidth = 5;
series3.ChartArea = "DataArea";
series3.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line;
series3.Color = System.Drawing.Color.DodgerBlue;
series3.Legend = "ChartLegend";
series3.MarkerSize = 10;
series3.MarkerStyle = System.Windows.Forms.DataVisualization.Charting.MarkerStyle.Circle;
series3.Name = "Ping";
series3.ToolTip = "#VALX\\n#VAL ms";
series3.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Time;
this.StatisticsChart.Series.Add(series1);
this.StatisticsChart.Series.Add(series2);
this.StatisticsChart.Series.Add(series3);
this.StatisticsChart.Size = new System.Drawing.Size(982, 435);
this.StatisticsChart.TabIndex = 2;
//
// PingCheckBox
//
this.PingCheckBox.AutoSize = true;
this.PingCheckBox.DataBindings.Add(new System.Windows.Forms.Binding("Checked", this.bindingConfiguration, "Ping", true));
this.PingCheckBox.Location = new System.Drawing.Point(13, 54);
this.PingCheckBox.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.PingCheckBox.Name = "PingCheckBox";
this.PingCheckBox.Size = new System.Drawing.Size(107, 27);
this.PingCheckBox.TabIndex = 5;
this.PingCheckBox.Text = "Ping Test";
this.PingCheckBox.UseVisualStyleBackColor = true;
this.PingCheckBox.CheckedChanged += new System.EventHandler(this.PingCheckBox_CheckedChanged);
//
// KeepChoiceForLabel
//
this.KeepChoiceForLabel.AutoSize = true;
this.KeepChoiceForLabel.Location = new System.Drawing.Point(9, 206);
this.KeepChoiceForLabel.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.KeepChoiceForLabel.Name = "KeepChoiceForLabel";
this.KeepChoiceForLabel.Size = new System.Drawing.Size(139, 23);
this.KeepChoiceForLabel.TabIndex = 8;
this.KeepChoiceForLabel.Text = "Keep choice for";
//
// MinutesLabel2
//
this.MinutesLabel2.AutoSize = true;
this.MinutesLabel2.Location = new System.Drawing.Point(286, 206);
this.MinutesLabel2.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.MinutesLabel2.Name = "MinutesLabel2";
this.MinutesLabel2.Size = new System.Drawing.Size(75, 23);
this.MinutesLabel2.TabIndex = 9;
this.MinutesLabel2.Text = "minutes";
//
// chartModeSelector
//
this.chartModeSelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.chartModeSelector.Controls.Add(this.allMode);
this.chartModeSelector.Controls.Add(this.dayMode);
this.chartModeSelector.Location = new System.Drawing.Point(733, 182);
this.chartModeSelector.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.chartModeSelector.Name = "chartModeSelector";
this.chartModeSelector.Padding = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.chartModeSelector.Size = new System.Drawing.Size(234, 103);
this.chartModeSelector.TabIndex = 3;
this.chartModeSelector.TabStop = false;
this.chartModeSelector.Text = "Chart Mode";
//
// allMode
//
this.allMode.AutoSize = true;
this.allMode.Location = new System.Drawing.Point(11, 61);
this.allMode.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.allMode.Name = "allMode";
this.allMode.Size = new System.Drawing.Size(50, 27);
this.allMode.TabIndex = 1;
this.allMode.Text = "all";
this.allMode.UseVisualStyleBackColor = true;
this.allMode.CheckedChanged += new System.EventHandler(this.allMode_CheckedChanged);
//
// dayMode
//
this.dayMode.AutoSize = true;
this.dayMode.Checked = true;
this.dayMode.Location = new System.Drawing.Point(11, 29);
this.dayMode.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.dayMode.Name = "dayMode";
this.dayMode.Size = new System.Drawing.Size(61, 27);
this.dayMode.TabIndex = 0;
this.dayMode.TabStop = true;
this.dayMode.Text = "24h";
this.dayMode.UseVisualStyleBackColor = true;
this.dayMode.CheckedChanged += new System.EventHandler(this.dayMode_CheckedChanged);
//
// splitContainer1
//
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer1.IsSplitterFixed = true;
this.splitContainer1.Location = new System.Drawing.Point(0, 0);
this.splitContainer1.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.splitContainer1.Name = "splitContainer1";
this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal;
//
// splitContainer1.Panel1
//
this.splitContainer1.Panel1.Controls.Add(this.splitContainer2);
//
// splitContainer1.Panel2
//
this.splitContainer1.Panel2.Controls.Add(this.serverSelector);
this.splitContainer1.Panel2.Controls.Add(this.CancelButton);
this.splitContainer1.Panel2.Controls.Add(this.OKButton);
this.splitContainer1.Panel2.Controls.Add(this.chartModeSelector);
this.splitContainer1.Panel2.Controls.Add(this.StatisticsChart);
this.splitContainer1.Size = new System.Drawing.Size(982, 753);
this.splitContainer1.SplitterDistance = 308;
this.splitContainer1.SplitterWidth = 10;
this.splitContainer1.TabIndex = 12;
//
// splitContainer2
//
this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer2.FixedPanel = System.Windows.Forms.FixedPanel.Panel1;
this.splitContainer2.IsSplitterFixed = true;
this.splitContainer2.Location = new System.Drawing.Point(0, 0);
this.splitContainer2.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.splitContainer2.Name = "splitContainer2";
//
// splitContainer2.Panel1
//
this.splitContainer2.Panel1.Controls.Add(this.CollectDataPerLabel);
this.splitContainer2.Panel1.Controls.Add(this.MinutesLabel1);
this.splitContainer2.Panel1.Controls.Add(this.dataCollectionMinutesNum);
this.splitContainer2.Panel1.Controls.Add(this.StatisticsEnabledCheckBox);
this.splitContainer2.Panel1.Controls.Add(this.choiceKeptMinutesNum);
this.splitContainer2.Panel1.Controls.Add(this.byHourOfDayCheckBox);
this.splitContainer2.Panel1.Controls.Add(this.repeatTimesNum);
this.splitContainer2.Panel1.Controls.Add(this.PackagePerPingLabel);
this.splitContainer2.Panel1.Controls.Add(this.KeepChoiceForLabel);
this.splitContainer2.Panel1.Controls.Add(this.PingCheckBox);
this.splitContainer2.Panel1.Controls.Add(this.MinutesLabel2);
//
// splitContainer2.Panel2
//
this.splitContainer2.Panel2.Controls.Add(this.splitContainer3);
this.splitContainer2.Size = new System.Drawing.Size(982, 308);
this.splitContainer2.SplitterDistance = 384;
this.splitContainer2.SplitterWidth = 5;
this.splitContainer2.TabIndex = 7;
//
// CollectDataPerLabel
//
this.CollectDataPerLabel.AutoSize = true;
this.CollectDataPerLabel.Location = new System.Drawing.Point(9, 164);
this.CollectDataPerLabel.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.CollectDataPerLabel.Name = "CollectDataPerLabel";
this.CollectDataPerLabel.Size = new System.Drawing.Size(139, 23);
this.CollectDataPerLabel.TabIndex = 20;
this.CollectDataPerLabel.Text = "Collect data per";
//
// MinutesLabel1
//
this.MinutesLabel1.AutoSize = true;
this.MinutesLabel1.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.MinutesLabel1.Location = new System.Drawing.Point(286, 165);
this.MinutesLabel1.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.MinutesLabel1.Name = "MinutesLabel1";
this.MinutesLabel1.Size = new System.Drawing.Size(75, 23);
this.MinutesLabel1.TabIndex = 19;
this.MinutesLabel1.Text = "minutes";
//
// dataCollectionMinutesNum
//
this.dataCollectionMinutesNum.DataBindings.Add(new System.Windows.Forms.Binding("Value", this.bindingConfiguration, "DataCollectionMinutes", true));
this.dataCollectionMinutesNum.Increment = new decimal(new int[] {
10,
0,
0,
0});
this.dataCollectionMinutesNum.Location = new System.Drawing.Point(177, 162);
this.dataCollectionMinutesNum.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.dataCollectionMinutesNum.Maximum = new decimal(new int[] {
120,
0,
0,
0});
this.dataCollectionMinutesNum.Minimum = new decimal(new int[] {
1,
0,
0,
0});
this.dataCollectionMinutesNum.Name = "dataCollectionMinutesNum";
this.dataCollectionMinutesNum.Size = new System.Drawing.Size(100, 29);
this.dataCollectionMinutesNum.TabIndex = 18;
this.dataCollectionMinutesNum.Value = new decimal(new int[] {
10,
0,
0,
0});
//
// StatisticsEnabledCheckBox
//
this.StatisticsEnabledCheckBox.AutoSize = true;
this.StatisticsEnabledCheckBox.DataBindings.Add(new System.Windows.Forms.Binding("Checked", this.bindingConfiguration, "StatisticsEnabled", true));
this.StatisticsEnabledCheckBox.Location = new System.Drawing.Point(13, 12);
this.StatisticsEnabledCheckBox.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.StatisticsEnabledCheckBox.Name = "StatisticsEnabledCheckBox";
this.StatisticsEnabledCheckBox.Size = new System.Drawing.Size(163, 27);
this.StatisticsEnabledCheckBox.TabIndex = 17;
this.StatisticsEnabledCheckBox.Text = "Enable Statistics";
this.StatisticsEnabledCheckBox.UseVisualStyleBackColor = true;
//
// choiceKeptMinutesNum
//
this.choiceKeptMinutesNum.DataBindings.Add(new System.Windows.Forms.Binding("Value", this.bindingConfiguration, "ChoiceKeptMinutes", true));
this.choiceKeptMinutesNum.Increment = new decimal(new int[] {
10,
0,
0,
0});
this.choiceKeptMinutesNum.Location = new System.Drawing.Point(177, 204);
this.choiceKeptMinutesNum.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.choiceKeptMinutesNum.Maximum = new decimal(new int[] {
120,
0,
0,
0});
this.choiceKeptMinutesNum.Minimum = new decimal(new int[] {
1,
0,
0,
0});
this.choiceKeptMinutesNum.Name = "choiceKeptMinutesNum";
this.choiceKeptMinutesNum.Size = new System.Drawing.Size(100, 29);
this.choiceKeptMinutesNum.TabIndex = 16;
this.choiceKeptMinutesNum.Value = new decimal(new int[] {
10,
0,
0,
0});
//
// byHourOfDayCheckBox
//
this.byHourOfDayCheckBox.AutoSize = true;
this.byHourOfDayCheckBox.DataBindings.Add(new System.Windows.Forms.Binding("Checked", this.bindingConfiguration, "ByHourOfDay", true));
this.byHourOfDayCheckBox.Location = new System.Drawing.Point(13, 127);
this.byHourOfDayCheckBox.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.byHourOfDayCheckBox.Name = "byHourOfDayCheckBox";
this.byHourOfDayCheckBox.Size = new System.Drawing.Size(150, 27);
this.byHourOfDayCheckBox.TabIndex = 15;
this.byHourOfDayCheckBox.Text = "By hour of day";
this.byHourOfDayCheckBox.UseVisualStyleBackColor = true;
//
// repeatTimesNum
//
this.repeatTimesNum.DataBindings.Add(new System.Windows.Forms.Binding("Value", this.bindingConfiguration, "RepeatTimesNum", true));
this.repeatTimesNum.Location = new System.Drawing.Point(34, 84);
this.repeatTimesNum.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.repeatTimesNum.Maximum = new decimal(new int[] {
10,
0,
0,
0});
this.repeatTimesNum.Name = "repeatTimesNum";
this.repeatTimesNum.Size = new System.Drawing.Size(99, 29);
this.repeatTimesNum.TabIndex = 14;
this.repeatTimesNum.Value = new decimal(new int[] {
4,
0,
0,
0});
//
// PackagePerPingLabel
//
this.PackagePerPingLabel.AutoSize = true;
this.PackagePerPingLabel.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.PackagePerPingLabel.Location = new System.Drawing.Point(139, 86);
this.PackagePerPingLabel.Name = "PackagePerPingLabel";
this.PackagePerPingLabel.Size = new System.Drawing.Size(172, 23);
this.PackagePerPingLabel.TabIndex = 13;
this.PackagePerPingLabel.Text = "packages everytime";
//
// splitContainer3
//
this.splitContainer3.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer3.FixedPanel = System.Windows.Forms.FixedPanel.Panel1;
this.splitContainer3.IsSplitterFixed = true;
this.splitContainer3.Location = new System.Drawing.Point(0, 0);
this.splitContainer3.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.splitContainer3.Name = "splitContainer3";
this.splitContainer3.Orientation = System.Windows.Forms.Orientation.Horizontal;
//
// splitContainer3.Panel1
//
this.splitContainer3.Panel1.Controls.Add(this.FinalScoreLabel);
//
// splitContainer3.Panel2
//
this.splitContainer3.Panel2.Controls.Add(this.calculationContainer);
this.splitContainer3.Size = new System.Drawing.Size(593, 308);
this.splitContainer3.SplitterDistance = 42;
this.splitContainer3.SplitterWidth = 1;
this.splitContainer3.TabIndex = 6;
//
// FinalScoreLabel
//
this.FinalScoreLabel.AutoSize = true;
this.FinalScoreLabel.Location = new System.Drawing.Point(5, 9);
this.FinalScoreLabel.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.FinalScoreLabel.Name = "FinalScoreLabel";
this.FinalScoreLabel.Size = new System.Drawing.Size(103, 23);
this.FinalScoreLabel.TabIndex = 0;
this.FinalScoreLabel.Text = "Final Score:";
this.CalculatinTip.SetToolTip(this.FinalScoreLabel, "(The server with the highest score would be choosen)");
//
// calculationContainer
//
this.calculationContainer.AutoScroll = true;
this.calculationContainer.Dock = System.Windows.Forms.DockStyle.Fill;
this.calculationContainer.Location = new System.Drawing.Point(0, 0);
this.calculationContainer.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.calculationContainer.Name = "calculationContainer";
this.calculationContainer.Size = new System.Drawing.Size(593, 265);
this.calculationContainer.TabIndex = 1;
//
// serverSelector
//
this.serverSelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.serverSelector.FormattingEnabled = true;
this.serverSelector.Location = new System.Drawing.Point(733, 145);
this.serverSelector.Name = "serverSelector";
this.serverSelector.Size = new System.Drawing.Size(233, 31);
this.serverSelector.TabIndex = 6;
this.serverSelector.SelectionChangeCommitted += new System.EventHandler(this.serverSelector_SelectionChangeCommitted);
//
// CancelButton
//
this.CancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.CancelButton.Location = new System.Drawing.Point(865, 364);
this.CancelButton.Name = "CancelButton";
this.CancelButton.Size = new System.Drawing.Size(101, 41);
this.CancelButton.TabIndex = 5;
this.CancelButton.Text = "Cancel";
this.CancelButton.UseVisualStyleBackColor = true;
this.CancelButton.Click += new System.EventHandler(this.CancelButton_Click);
//
// OKButton
//
this.OKButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.OKButton.Location = new System.Drawing.Point(758, 364);
this.OKButton.Name = "OKButton";
this.OKButton.Size = new System.Drawing.Size(101, 41);
this.OKButton.TabIndex = 4;
this.OKButton.Text = "OK";
this.OKButton.UseVisualStyleBackColor = true;
this.OKButton.Click += new System.EventHandler(this.OKButton_Click);
//
// bindingConfiguration
//
this.bindingConfiguration.DataSource = typeof(Shadowsocks.Model.StatisticsStrategyConfiguration);
//
// StatisticsStrategyConfigurationForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 23F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoSize = true;
this.ClientSize = new System.Drawing.Size(982, 753);
this.Controls.Add(this.splitContainer1);
this.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.MinimumSize = new System.Drawing.Size(1000, 800);
this.Name = "StatisticsStrategyConfigurationForm";
this.Text = "StatisticsStrategyConfigurationForm";
this.Text = "Statistics configuration";
((System.ComponentModel.ISupportInitialize)(this.StatisticsChart)).EndInit();
this.chartModeSelector.ResumeLayout(false);
this.chartModeSelector.PerformLayout();
this.splitContainer1.Panel1.ResumeLayout(false);
this.splitContainer1.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
this.splitContainer1.ResumeLayout(false);
this.splitContainer2.Panel1.ResumeLayout(false);
this.splitContainer2.Panel1.PerformLayout();
this.splitContainer2.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit();
this.splitContainer2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.dataCollectionMinutesNum)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.choiceKeptMinutesNum)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.repeatTimesNum)).EndInit();
this.splitContainer3.Panel1.ResumeLayout(false);
this.splitContainer3.Panel1.PerformLayout();
this.splitContainer3.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).EndInit();
this.splitContainer3.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).EndInit();
this.ResumeLayout(false);

}

#endregion
private System.Windows.Forms.DataVisualization.Charting.Chart StatisticsChart;
private System.Windows.Forms.CheckBox PingCheckBox;
private System.Windows.Forms.Label KeepChoiceForLabel;
private System.Windows.Forms.Label MinutesLabel2;
private System.Windows.Forms.GroupBox chartModeSelector;
private System.Windows.Forms.RadioButton allMode;
private System.Windows.Forms.RadioButton dayMode;
private System.Windows.Forms.SplitContainer splitContainer1;
private System.Windows.Forms.Label FinalScoreLabel;
private System.Windows.Forms.SplitContainer splitContainer2;
private System.Windows.Forms.FlowLayoutPanel calculationContainer;
private System.Windows.Forms.SplitContainer splitContainer3;
private System.Windows.Forms.NumericUpDown repeatTimesNum;
private System.Windows.Forms.Label PackagePerPingLabel;
private System.Windows.Forms.CheckBox byHourOfDayCheckBox;
private System.Windows.Forms.NumericUpDown choiceKeptMinutesNum;
private System.Windows.Forms.CheckBox StatisticsEnabledCheckBox;
private System.Windows.Forms.Label CollectDataPerLabel;
private System.Windows.Forms.Label MinutesLabel1;
private System.Windows.Forms.NumericUpDown dataCollectionMinutesNum;
private System.Windows.Forms.BindingSource bindingConfiguration;
private new System.Windows.Forms.Button CancelButton;
private System.Windows.Forms.Button OKButton;
private System.Windows.Forms.ComboBox serverSelector;
private System.Windows.Forms.ToolTip CalculatinTip;
}
}

+ 0
- 170
shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs View File

@@ -1,170 +0,0 @@
using System;
using System.Drawing;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using Shadowsocks.Controller;
using Shadowsocks.Model;
using Shadowsocks.Properties;

namespace Shadowsocks.View
{
public partial class StatisticsStrategyConfigurationForm : Form
{
private readonly ShadowsocksController _controller;
private StatisticsStrategyConfiguration _configuration;
private readonly DataTable _dataTable = new DataTable();
private List<string> _servers;
private readonly Series _speedSeries;
private readonly Series _packageLossSeries;
private readonly Series _pingSeries;

public StatisticsStrategyConfigurationForm(ShadowsocksController controller)
{
if (controller == null) return;
InitializeComponent();
Icon = Icon.FromHandle(Resources.ssw128.GetHicon());
_speedSeries = StatisticsChart.Series["Speed"];
_packageLossSeries = StatisticsChart.Series["Package Loss"];
_pingSeries = StatisticsChart.Series["Ping"];
_controller = controller;
_controller.ConfigChanged += (sender, args) => LoadConfiguration();
UpdateTexts();
LoadConfiguration();
Load += (sender, args) => InitData();
}

private void UpdateTexts()
{
I18N.TranslateForm(this);

foreach (var item in StatisticsChart.Series)
{
item.Name = I18N.GetString(item.Name);
}

}
private void LoadConfiguration()
{
var configs = _controller.GetCurrentConfiguration().configs;
_servers = configs.Select(server => server.Identifier()).ToList();
_configuration = _controller.StatisticsConfiguration
?? new StatisticsStrategyConfiguration();
if (_configuration.Calculations == null)
{
_configuration = new StatisticsStrategyConfiguration();
}
}

private void InitData()
{
bindingConfiguration.Add(_configuration);
foreach (var kv in _configuration.Calculations)
{
var calculation = new CalculationControl(I18N.GetString(kv.Key), kv.Value);
calculationContainer.Controls.Add(calculation);
}

serverSelector.DataSource = _servers;

_dataTable.Columns.Add("Timestamp", typeof(DateTime));
_dataTable.Columns.Add("Speed", typeof(int));
_speedSeries.XValueMember = "Timestamp";
_speedSeries.YValueMembers = "Speed";

// might be empty
_dataTable.Columns.Add("Package Loss", typeof(int));
_dataTable.Columns.Add("Ping", typeof(int));
_packageLossSeries.XValueMember = "Timestamp";
_packageLossSeries.YValueMembers = "Package Loss";
_pingSeries.XValueMember = "Timestamp";
_pingSeries.YValueMembers = "Ping";

StatisticsChart.DataSource = _dataTable;
LoadChartData();
StatisticsChart.DataBind();
}

private void CancelButton_Click(object sender, EventArgs e)
{
Close();
}

private void OKButton_Click(object sender, EventArgs e)
{
foreach (CalculationControl calculation in calculationContainer.Controls)
{
_configuration.Calculations[calculation.Value] = calculation.Factor;
}
_controller?.SaveStrategyConfigurations(_configuration);
_controller?.UpdateStatisticsConfiguration(StatisticsEnabledCheckBox.Checked);
Close();
}

private void LoadChartData()
{
var serverName = _servers[serverSelector.SelectedIndex];
_dataTable.Rows.Clear();

//return directly when no data is usable
if (_controller.availabilityStatistics?.FilteredStatistics == null) return;
List<StatisticsRecord> statistics;
if (!_controller.availabilityStatistics.FilteredStatistics.TryGetValue(serverName, out statistics)) return;
IEnumerable<IGrouping<int, StatisticsRecord>> dataGroups;
if (allMode.Checked)
{
_pingSeries.XValueType = ChartValueType.DateTime;
_packageLossSeries.XValueType = ChartValueType.DateTime;
_speedSeries.XValueType = ChartValueType.DateTime;
dataGroups = statistics.GroupBy(data => data.Timestamp.DayOfYear);
StatisticsChart.ChartAreas["DataArea"].AxisX.LabelStyle.Format = "g";
StatisticsChart.ChartAreas["DataArea"].AxisX2.LabelStyle.Format = "g";
}
else
{
_pingSeries.XValueType = ChartValueType.Time;
_packageLossSeries.XValueType = ChartValueType.Time;
_speedSeries.XValueType = ChartValueType.Time;
dataGroups = statistics.GroupBy(data => data.Timestamp.Hour);
StatisticsChart.ChartAreas["DataArea"].AxisX.LabelStyle.Format = "HH:00";
StatisticsChart.ChartAreas["DataArea"].AxisX2.LabelStyle.Format = "HH:00";
}
var finalData = from dataGroup in dataGroups
orderby dataGroup.Key
select new
{
dataGroup.First().Timestamp,
Speed = dataGroup.Max(data => data.MaxInboundSpeed) ?? 0,
Ping = (int)(dataGroup.Average(data => data.AverageResponse) ?? 0),
PackageLossPercentage = (int)(dataGroup.Average(data => data.PackageLoss) ?? 0) * 100
};
foreach (var data in finalData.Where(data => data.Speed != 0 || data.PackageLossPercentage != 0 || data.Ping != 0))
{
_dataTable.Rows.Add(data.Timestamp, data.Speed, data.PackageLossPercentage, data.Ping);
}
StatisticsChart.DataBind();
}

private void serverSelector_SelectionChangeCommitted(object sender, EventArgs e)
{
LoadChartData();
}

private void dayMode_CheckedChanged(object sender, EventArgs e)
{
LoadChartData();
}

private void allMode_CheckedChanged(object sender, EventArgs e)
{
LoadChartData();
}

private void PingCheckBox_CheckedChanged(object sender, EventArgs e)
{
repeatTimesNum.ReadOnly = !PingCheckBox.Checked;
}
}
}

+ 131
- 0
shadowsocks-csharp/ViewModels/ForwardProxyViewModel.cs View File

@@ -0,0 +1,131 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Helpers;
using Shadowsocks.Controller;
using Shadowsocks.Model;
using Shadowsocks.View;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;

namespace Shadowsocks.ViewModels
{
public class ForwardProxyViewModel : ReactiveValidationObject
{
public ForwardProxyViewModel()
{
_config = Program.MainController.GetCurrentConfiguration();
_controller = Program.MainController;
_menuViewController = Program.MenuController;

if (!_config.proxy.useProxy)
NoProxy = true;
else if (_config.proxy.proxyType == 0)
UseSocks5Proxy = true;
else
UseHttpProxy = true;

Address = _config.proxy.proxyServer;
Port = _config.proxy.proxyPort;
Timeout = _config.proxy.proxyTimeout;

Username = _config.proxy.authUser;
Password = _config.proxy.authPwd;

this.WhenAnyValue(x => x.NoProxy, x => !x)
.ToPropertyEx(this, x => x.CanModifyDetails);

AddressRule = this.ValidationRule(
viewModel => viewModel.Address,
address => !string.IsNullOrWhiteSpace(address),
"Address can't be empty or whitespaces.");
PortRule = this.ValidationRule(
viewModel => viewModel.Port,
port => port > 0 && port <= 65535,
port => $"{port} is out of range (0, 65535].");
TimeoutRule = this.ValidationRule(
viewModel => viewModel.Timeout,
timeout => timeout > 0 && timeout <= 10,
timeout => $"{timeout} is out of range (0, 10].");

var authValid = this
.WhenAnyValue(x => x.Username, x => x.Password, (username, password) => new { Username = username, Password = password })
.Select(x => string.IsNullOrWhiteSpace(x.Username) == string.IsNullOrWhiteSpace(x.Password));
AuthRule = this.ValidationRule(authValid, "You must provide both username and password.");

var canSave = this.IsValid();

Save = ReactiveCommand.Create(() =>
{
_controller.SaveProxy(GetForwardProxyConfig());
_menuViewController.CloseForwardProxyWindow();
}, canSave);
Cancel = ReactiveCommand.Create(_menuViewController.CloseForwardProxyWindow);
}

private readonly Configuration _config;
private readonly ShadowsocksController _controller;
private readonly MenuViewController _menuViewController;

public ValidationHelper AddressRule { get; }
public ValidationHelper PortRule { get; }
public ValidationHelper TimeoutRule { get; }
public ValidationHelper AuthRule { get; }

public ReactiveCommand<Unit, Unit> Save { get; }
public ReactiveCommand<Unit, Unit> Cancel { get; }

[ObservableAsProperty]
public bool CanModifyDetails { get; }

[Reactive]
public bool NoProxy { get; set; }

[Reactive]
public bool UseSocks5Proxy { get; set; }

[Reactive]
public bool UseHttpProxy { get; set; }

[Reactive]
public string Address { get; set; }

[Reactive]
public int Port { get; set; }

[Reactive]
public int Timeout { get; set; }

[Reactive]
public string Username { get; set; }

[Reactive]
public string Password { get; set; }

private ForwardProxyConfig GetForwardProxyConfig()
{
var forwardProxyConfig = new ForwardProxyConfig()
{
proxyServer = Address,
proxyPort = Port,
proxyTimeout = Timeout,
authUser = Username,
authPwd = Password
};
if (NoProxy)
forwardProxyConfig.useProxy = false;
else if (UseSocks5Proxy)
{
forwardProxyConfig.useProxy = true;
forwardProxyConfig.proxyType = 0;
}
else
{
forwardProxyConfig.useProxy = true;
forwardProxyConfig.proxyType = 1;
}
return forwardProxyConfig;
}
}
}

+ 189
- 0
shadowsocks-csharp/ViewModels/HotkeysViewModel.cs View File

@@ -0,0 +1,189 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Shadowsocks.Controller;
using Shadowsocks.Model;
using Shadowsocks.View;
using System.Reactive;
using System.Text;
using System.Windows.Input;

namespace Shadowsocks.ViewModels
{
public class HotkeysViewModel : ReactiveObject
{
public HotkeysViewModel()
{
_config = Program.MainController.GetCurrentConfiguration();
_controller = Program.MainController;
_menuViewController = Program.MenuController;
HotkeySystemProxy = _config.hotkey.SwitchSystemProxy;
HotkeyProxyMode = _config.hotkey.SwitchSystemProxyMode;
HotkeyAllowLan = _config.hotkey.SwitchAllowLan;
HotkeyOpenLogs = _config.hotkey.ShowLogs;
HotkeySwitchPrev = _config.hotkey.ServerMoveUp;
HotkeySwitchNext = _config.hotkey.ServerMoveDown;
RegisterAtStartup = _config.hotkey.RegHotkeysAtStartup;

HotkeySystemProxyStatus = "✔";
HotkeyProxyModeStatus = "✔";
HotkeyAllowLanStatus = "✔";
HotkeyOpenLogsStatus = "✔";
HotkeySwitchPrevStatus = "✔";
HotkeySwitchNextStatus = "✔";

RegisterAll = ReactiveCommand.Create(() => RegisterAllAndUpdateStatus());
Save = ReactiveCommand.Create(() => RegisterAllAndUpdateStatus(true));
Cancel = ReactiveCommand.Create(_menuViewController.CloseHotkeysWindow);
}

private readonly Configuration _config;
private readonly ShadowsocksController _controller;
private readonly MenuViewController _menuViewController;

public ReactiveCommand<Unit, Unit> RegisterAll { get; }
public ReactiveCommand<Unit, Unit> Save { get; }
public ReactiveCommand<Unit, Unit> Cancel { get; }

[Reactive]
public string HotkeySystemProxy { get; set; }

[Reactive]
public string HotkeyProxyMode { get; set; }

[Reactive]
public string HotkeyAllowLan { get; set; }

[Reactive]
public string HotkeyOpenLogs { get; set; }

[Reactive]
public string HotkeySwitchPrev { get; set; }

[Reactive]
public string HotkeySwitchNext { get; set; }

[Reactive]
public bool RegisterAtStartup { get; set; }

[Reactive]
public string HotkeySystemProxyStatus { get; set; }

[Reactive]
public string HotkeyProxyModeStatus { get; set; }

[Reactive]
public string HotkeyAllowLanStatus { get; set; }

[Reactive]
public string HotkeyOpenLogsStatus { get; set; }

[Reactive]
public string HotkeySwitchPrevStatus { get; set; }

[Reactive]
public string HotkeySwitchNextStatus { get; set; }

public void RecordKeyDown(int hotkeyIndex, KeyEventArgs keyEventArgs)
{
var recordedKeyStringBuilder = new StringBuilder();

// record modifiers
if ((Keyboard.Modifiers & ModifierKeys.Control) > 0)
recordedKeyStringBuilder.Append("Ctrl+");
if ((Keyboard.Modifiers & ModifierKeys.Alt) > 0)
recordedKeyStringBuilder.Append("Alt+");
if ((Keyboard.Modifiers & ModifierKeys.Shift) > 0)
recordedKeyStringBuilder.Append("Shift+");

// record other keys when at least one modifier is pressed
if (recordedKeyStringBuilder.Length > 0 && (keyEventArgs.Key < Key.LeftShift || keyEventArgs.Key > Key.RightAlt))
recordedKeyStringBuilder.Append(keyEventArgs.Key);

switch (hotkeyIndex)
{
case 0:
HotkeySystemProxy = recordedKeyStringBuilder.ToString();
break;
case 1:
HotkeyProxyMode = recordedKeyStringBuilder.ToString();
break;
case 2:
HotkeyAllowLan = recordedKeyStringBuilder.ToString();
break;
case 3:
HotkeyOpenLogs = recordedKeyStringBuilder.ToString();
break;
case 4:
HotkeySwitchPrev = recordedKeyStringBuilder.ToString();
break;
case 5:
HotkeySwitchNext = recordedKeyStringBuilder.ToString();
break;
}
}

public void FinishOnKeyUp(int hotkeyIndex, KeyEventArgs keyEventArgs)
{
switch (hotkeyIndex)
{
case 0:
if (HotkeySystemProxy.EndsWith("+"))
HotkeySystemProxy = "";
break;
case 1:
if (HotkeyProxyMode.EndsWith("+"))
HotkeyProxyMode = "";
break;
case 2:
if (HotkeyAllowLan.EndsWith("+"))
HotkeyAllowLan = "";
break;
case 3:
if (HotkeyOpenLogs.EndsWith("+"))
HotkeyOpenLogs = "";
break;
case 4:
if (HotkeySwitchPrev.EndsWith("+"))
HotkeySwitchPrev = "";
break;
case 5:
if (HotkeySwitchNext.EndsWith("+"))
HotkeySwitchNext = "";
break;
}
}

private void RegisterAllAndUpdateStatus(bool save = false)
{
HotkeySystemProxyStatus = HotkeyReg.RegHotkeyFromString(HotkeySystemProxy, "SwitchSystemProxyCallback") ? "✔" : "❌";
HotkeyProxyModeStatus = HotkeyReg.RegHotkeyFromString(HotkeyProxyMode, "SwitchSystemProxyModeCallback") ? "✔" : "❌";
HotkeyAllowLanStatus = HotkeyReg.RegHotkeyFromString(HotkeyAllowLan, "SwitchAllowLanCallback") ? "✔" : "❌";
HotkeyOpenLogsStatus = HotkeyReg.RegHotkeyFromString(HotkeyOpenLogs, "ShowLogsCallback") ? "✔" : "❌";
HotkeySwitchPrevStatus = HotkeyReg.RegHotkeyFromString(HotkeySwitchPrev, "ServerMoveUpCallback") ? "✔" : "❌";
HotkeySwitchNextStatus = HotkeyReg.RegHotkeyFromString(HotkeySwitchNext, "ServerMoveDownCallback") ? "✔" : "❌";

if (HotkeySystemProxyStatus == "✔" &&
HotkeyProxyModeStatus == "✔" &&
HotkeyAllowLanStatus == "✔" &&
HotkeyOpenLogsStatus == "✔" &&
HotkeySwitchPrevStatus == "✔" &&
HotkeySwitchNextStatus == "✔" && save)
{
_controller.SaveHotkeyConfig(GetHotkeyConfig);
_menuViewController.CloseHotkeysWindow();
}
}

private HotkeyConfig GetHotkeyConfig => new HotkeyConfig()
{
SwitchSystemProxy = HotkeySystemProxy,
SwitchSystemProxyMode = HotkeyProxyMode,
SwitchAllowLan = HotkeyAllowLan,
ShowLogs = HotkeyOpenLogs,
ServerMoveUp = HotkeySwitchPrev,
ServerMoveDown = HotkeySwitchNext,
RegHotkeysAtStartup = RegisterAtStartup
};
}
}

+ 115
- 0
shadowsocks-csharp/ViewModels/OnlineConfigViewModel.cs View File

@@ -0,0 +1,115 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Helpers;
using Shadowsocks.Controller;
using Shadowsocks.Localization;
using Shadowsocks.Model;
using Shadowsocks.View;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Text;
using System.Windows;

namespace Shadowsocks.ViewModels
{
public class OnlineConfigViewModel : ReactiveValidationObject
{
public OnlineConfigViewModel()
{
_config = Program.MainController.GetCurrentConfiguration();
_controller = Program.MainController;
_menuViewController = Program.MenuController;

Sources = new ObservableCollection<string>(_config.onlineConfigSource);
SelectedSource = "";
Address = "";

// TODO in v5: if http:// show warning as materialDesign:HintAssist.HelperText
AddressRule = this.ValidationRule(
viewModel => viewModel.Address,
address => address.StartsWith("http://"),
"Warning: getting online configuration from plain HTTP sources is NOT secure!");

var canUpdateCopyRemove = this.WhenAnyValue(
x => x.SelectedSource,
selectedSource => !string.IsNullOrWhiteSpace(selectedSource));
var canUpdateAll = this.WhenAnyValue(
x => x.Sources.Count,
count => count > 0);
var canAdd = this.WhenAnyValue(
x => x.Address,
address => Uri.IsWellFormedUriString(address, UriKind.Absolute) &&
(address.StartsWith("https://") || address.StartsWith("http://")));

Update = ReactiveCommand.CreateFromTask(() => _controller.UpdateOnlineConfig(SelectedSource), canUpdateCopyRemove);
UpdateAll = ReactiveCommand.CreateFromTask(_controller.UpdateAllOnlineConfig, canUpdateAll);
CopyLink = ReactiveCommand.Create(() => Clipboard.SetText(SelectedSource), canUpdateCopyRemove);
Remove = ReactiveCommand.Create(() =>
{
bool result;
var urlToRemove = SelectedSource; // save it here because SelectedSource is lost once we remove the selection
do
{
result = Sources.Remove(urlToRemove);
} while (result);
_controller.RemoveOnlineConfig(urlToRemove);
}, canUpdateCopyRemove);
Add = ReactiveCommand.Create(() =>
{
Sources.Add(Address);
SelectedSource = Address;
_controller.SaveOnlineConfigSource(Sources.ToList());
Address = "";
}, canAdd);

// TODO in v5: use MaterialDesignThemes snackbar messages
this.WhenAnyObservable(x => x.Update)
.Subscribe(x =>
{
if (x)
MessageBox.Show(LocalizationProvider.GetLocalizedValue<string>("sip008UpdateSuccess"));
else
MessageBox.Show(LocalizationProvider.GetLocalizedValue<string>("sip008UpdateFailure"));
});
this.WhenAnyObservable(x => x.UpdateAll)
.Subscribe(x =>
{
if (x.Count == 0)
MessageBox.Show(LocalizationProvider.GetLocalizedValue<string>("sip008UpdateAllSuccess"));
else
{
var stringBuilder = new StringBuilder(LocalizationProvider.GetLocalizedValue<string>("sip008UpdateAllFailure"));
foreach (var url in x)
stringBuilder.AppendLine(url);
MessageBox.Show(stringBuilder.ToString());
}
});
}

private readonly Configuration _config;
private readonly ShadowsocksController _controller;
private readonly MenuViewController _menuViewController;

public ValidationHelper AddressRule { get; }

public ReactiveCommand<Unit, bool> Update { get; }
public ReactiveCommand<Unit, List<string>> UpdateAll { get; }
public ReactiveCommand<Unit, Unit> CopyLink { get; }
public ReactiveCommand<Unit, Unit> Remove { get; }
public ReactiveCommand<Unit, Unit> Add { get; }

[Reactive]
public ObservableCollection<string> Sources { get; private set; }

[Reactive]
public string SelectedSource { get; set; }

[Reactive]
public string Address { get; set; }
}
}

+ 96
- 0
shadowsocks-csharp/ViewModels/ServerSharingViewModel.cs View File

@@ -0,0 +1,96 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Shadowsocks.Model;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Windows;
using System.Windows.Media.Imaging;

namespace Shadowsocks.ViewModels
{
public class ServerSharingViewModel : ReactiveObject
{
/// <summary>
/// The view model class for the server sharing user control.
/// </summary>
public ServerSharingViewModel()
{
_config = Program.MainController.GetCurrentConfiguration();
Servers = _config.configs;
SelectedServer = Servers[0];

this.WhenAnyValue(x => x.SelectedServer)
.Subscribe(_ => UpdateUrlAndImage());

CopyLink = ReactiveCommand.Create(() => Clipboard.SetText(SelectedServerUrl));
}

private readonly Configuration _config;

public ReactiveCommand<Unit, Unit> CopyLink { get; }

[Reactive]
public List<Server> Servers { get; private set; }

[Reactive]
public Server SelectedServer { get; set; }

[Reactive]
public string SelectedServerUrl { get; private set; }

[Reactive]
public BitmapImage SelectedServerUrlImage { get; private set; }

/// <summary>
/// Called when SelectedServer changed
/// to update SelectedServerUrl and SelectedServerUrlImage
/// </summary>
private void UpdateUrlAndImage()
{
// update SelectedServerUrl
SelectedServerUrl = SelectedServer.GetURL(_config.generateLegacyUrl);

// generate QR code
var qrCode = ZXing.QrCode.Internal.Encoder.encode(SelectedServerUrl, ZXing.QrCode.Internal.ErrorCorrectionLevel.L);
var byteMatrix = qrCode.Matrix;

// paint bitmap
int blockSize = Math.Max(1024 / byteMatrix.Height, 1);
Bitmap drawArea = new Bitmap((byteMatrix.Width * blockSize), (byteMatrix.Height * blockSize));
using (var graphics = Graphics.FromImage(drawArea))
{
graphics.Clear(Color.White);
using (var solidBrush = new SolidBrush(Color.Black))
{
for (int row = 0; row < byteMatrix.Width; row++)
{
for (int column = 0; column < byteMatrix.Height; column++)
{
if (byteMatrix[row, column] != 0)
{
graphics.FillRectangle(solidBrush, blockSize * row, blockSize * column, blockSize, blockSize);
}
}
}
}
}

// transform to BitmapImage for binding
BitmapImage bitmapImage = new BitmapImage();
using (MemoryStream memoryStream = new MemoryStream())
{
drawArea.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp);
memoryStream.Position = 0;
bitmapImage.BeginInit();
bitmapImage.StreamSource = memoryStream;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
}
SelectedServerUrlImage = bitmapImage;
}
}
}

+ 34
- 0
shadowsocks-csharp/ViewModels/VersionUpdatePromptViewModel.cs View File

@@ -0,0 +1,34 @@
using Newtonsoft.Json.Linq;
using ReactiveUI;
using Shadowsocks.Controller;
using System.Reactive;

namespace Shadowsocks.ViewModels
{
public class VersionUpdatePromptViewModel : ReactiveObject
{
public VersionUpdatePromptViewModel(JToken releaseObject)
{
_updateChecker = Program.MenuController.updateChecker;
_releaseObject = releaseObject;
ReleaseNotes = string.Concat(
$"# {((bool)_releaseObject["prerelease"] ? "⚠ Pre-release" : "ℹ Release")} {(string)_releaseObject["tag_name"] ?? "Failed to get tag name"}\r\n",
(string)_releaseObject["body"] ?? "Failed to get release notes");

Update = ReactiveCommand.CreateFromTask(_updateChecker.DoUpdate);
SkipVersion = ReactiveCommand.Create(_updateChecker.SkipUpdate);
NotNow = ReactiveCommand.Create(_updateChecker.CloseVersionUpdatePromptWindow);
}

private readonly UpdateChecker _updateChecker;
private readonly JToken _releaseObject;

public string ReleaseNotes { get; }

public ReactiveCommand<Unit, Unit> Update { get; }

public ReactiveCommand<Unit, Unit> SkipVersion { get; }

public ReactiveCommand<Unit, Unit> NotNow { get; }
}
}

+ 100
- 0
shadowsocks-csharp/Views/ForwardProxyView.xaml View File

@@ -0,0 +1,100 @@
<reactiveui:ReactiveUserControl
x:Class="Shadowsocks.Views.ForwardProxyView"
x:TypeArguments="vms:ForwardProxyViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Shadowsocks.Views"
xmlns:vms="clr-namespace:Shadowsocks.ViewModels"
xmlns:reactiveui="http://reactiveui.net"
xmlns:lex="http://wpflocalizeextension.codeplex.com"
lex:LocalizeDictionary.DesignCulture="en"
lex:ResxLocalizationProvider.DefaultAssembly="Shadowsocks"
lex:ResxLocalizationProvider.DefaultDictionary="Strings"
mc:Ignorable="d"
d:DesignHeight="380" d:DesignWidth="280">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Margin="4 8 4 8"
FontSize="16"
Text="{lex:Loc Type}"/>
<RadioButton x:Name="noProxyRadioButton" Grid.Row="1" Grid.Column="0"
Margin="4"
GroupName="ProxyType"
Content="{lex:Loc NoProxy}"/>
<RadioButton x:Name="socks5RadioButton" Grid.Row="2" Grid.Column="0"
Margin="4"
GroupName="ProxyType"
Content="{lex:Loc SOCKS5}"/>
<RadioButton x:Name="httpRadioButton" Grid.Row="3" Grid.Column="0"
Margin="4"
GroupName="ProxyType"
Content="{lex:Loc HTTP}"/>

<TextBlock Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"
Margin="4 8 4 8"
FontSize="16"
Text="{lex:Loc Details}"/>
<TextBlock Grid.Row="5" Grid.Column="0"
Margin="4"
Text="{lex:Loc Address}"/>
<TextBox x:Name="addressTextBox" Grid.Row="5" Grid.Column="1"
Margin="4"/>
<TextBlock Grid.Row="6" Grid.Column="0"
Margin="4"
Text="{lex:Loc Port}"/>
<TextBox x:Name="portTextBox" Grid.Row="6" Grid.Column="1"
Margin="4"
HorizontalContentAlignment="Right"/>
<TextBlock Grid.Row="7" Grid.Column="0"
Margin="4"
Text="{lex:Loc Timeout}"/>
<TextBox x:Name="timeoutTextBox" Grid.Row="7" Grid.Column="1"
Margin="4"
HorizontalContentAlignment="Right"/>

<TextBlock Grid.Row="8" Grid.Column="0" Grid.ColumnSpan="2"
Margin="4 8 4 8"
FontSize="16"
Text="{lex:Loc CredentialsOptional}"/>

<TextBlock Grid.Row="9" Grid.Column="0"
Margin="4"
Text="{lex:Loc Username}"/>
<TextBox x:Name="usernameTextBox" Grid.Row="9" Grid.Column="1"
Margin="4"/>
<TextBlock Grid.Row="10" Grid.Column="0"
Margin="4"
Text="{lex:Loc Password}"/>
<TextBox x:Name="passwordTextBox" Grid.Row="10" Grid.Column="1"
Margin="4"/>

<StackPanel Grid.Row="11" Grid.Column="0" Grid.ColumnSpan="2"
Orientation="Horizontal"
HorizontalAlignment="Right"
VerticalAlignment="Bottom">
<Button x:Name="saveButton" Margin="4" MinWidth="75" Content="{lex:Loc}"/>
<Button x:Name="cancelButton" Margin="4" MinWidth="75" Content="{lex:Loc}"/>
</StackPanel>
</Grid>
</reactiveui:ReactiveUserControl>

+ 84
- 0
shadowsocks-csharp/Views/ForwardProxyView.xaml.cs View File

@@ -0,0 +1,84 @@
using ReactiveUI;
using Shadowsocks.ViewModels;
using System.Reactive.Disposables;

namespace Shadowsocks.Views
{
/// <summary>
/// Interaction logic for ForwardProxyView.xaml
/// </summary>
public partial class ForwardProxyView : ReactiveUserControl<ForwardProxyViewModel>
{
public ForwardProxyView()
{
InitializeComponent();
ViewModel = new ForwardProxyViewModel();
this.WhenActivated(disposables =>
{
this.Bind(ViewModel,
viewModel => viewModel.NoProxy,
view => view.noProxyRadioButton.IsChecked)
.DisposeWith(disposables);
this.Bind(ViewModel,
viewModel => viewModel.UseSocks5Proxy,
view => view.socks5RadioButton.IsChecked)
.DisposeWith(disposables);
this.Bind(ViewModel,
viewModel => viewModel.UseHttpProxy,
view => view.httpRadioButton.IsChecked)
.DisposeWith(disposables);

this.Bind(ViewModel,
viewModel => viewModel.Address,
view => view.addressTextBox.Text)
.DisposeWith(disposables);
this.Bind(ViewModel,
viewModel => viewModel.Port,
view => view.portTextBox.Text)
.DisposeWith(disposables);
this.Bind(ViewModel,
viewModel => viewModel.Timeout,
view => view.timeoutTextBox.Text)
.DisposeWith(disposables);
this.OneWayBind(ViewModel,
viewModel => viewModel.CanModifyDetails,
view => view.addressTextBox.IsEnabled)
.DisposeWith(disposables);
this.OneWayBind(ViewModel,
viewModel => viewModel.CanModifyDetails,
view => view.portTextBox.IsEnabled)
.DisposeWith(disposables);
this.OneWayBind(ViewModel,
viewModel => viewModel.CanModifyDetails,
view => view.timeoutTextBox.IsEnabled)
.DisposeWith(disposables);

this.Bind(ViewModel,
viewModel => viewModel.Username,
view => view.usernameTextBox.Text)
.DisposeWith(disposables);
this.Bind(ViewModel,
viewModel => viewModel.Password,
view => view.passwordTextBox.Text)
.DisposeWith(disposables);
this.OneWayBind(ViewModel,
viewModel => viewModel.UseHttpProxy,
view => view.usernameTextBox.IsEnabled)
.DisposeWith(disposables);
this.OneWayBind(ViewModel,
viewModel => viewModel.UseHttpProxy,
view => view.passwordTextBox.IsEnabled)
.DisposeWith(disposables);

this.BindCommand(ViewModel,
viewModel => viewModel.Save,
view => view.saveButton)
.DisposeWith(disposables);
this.BindCommand(ViewModel,
viewModel => viewModel.Cancel,
view => view.cancelButton)
.DisposeWith(disposables);
});
}
}
}

+ 115
- 0
shadowsocks-csharp/Views/HotkeysView.xaml View File

@@ -0,0 +1,115 @@
<reactiveui:ReactiveUserControl
x:Class="Shadowsocks.Views.HotkeysView"
x:TypeArguments="vms:HotkeysViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Shadowsocks.Views"
xmlns:vms="clr-namespace:Shadowsocks.ViewModels"
xmlns:reactiveui="http://reactiveui.net"
xmlns:lex="http://wpflocalizeextension.codeplex.com"
lex:LocalizeDictionary.DesignCulture="en"
lex:ResxLocalizationProvider.DefaultAssembly="Shadowsocks"
lex:ResxLocalizationProvider.DefaultDictionary="Strings"
mc:Ignorable="d"
d:DesignHeight="240" d:DesignWidth="280">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

<TextBlock Grid.Row="0" Grid.Column="0"
Margin="4"
Text="{lex:Loc ToggleSystemProxy}"/>
<TextBox x:Name="systemProxyTextBox"
Grid.Row="0" Grid.Column="1"
Margin="4" IsReadOnly="True" />
<TextBlock x:Name="systemProxyStatusTextBlock"
Grid.Row="0" Grid.Column="2"
Margin="4" />

<TextBlock Grid.Row="1" Grid.Column="0"
Margin="4"
Text="{lex:Loc ToggleProxyMode}"/>
<TextBox x:Name="proxyModeTextBox"
Grid.Row="1" Grid.Column="1"
Margin="4" IsReadOnly="True" />
<TextBlock x:Name="proxyModeStatusTextBlock"
Grid.Row="1" Grid.Column="2"
Margin="4" />

<TextBlock Grid.Row="2" Grid.Column="0"
Margin="4"
Text="{lex:Loc AllowClientsFromLAN}"/>
<TextBox x:Name="allowLanTextBox"
Grid.Row="2" Grid.Column="1"
Margin="4" IsReadOnly="True" />
<TextBlock x:Name="allowLanStatusTextBlock"
Grid.Row="2" Grid.Column="2"
Margin="4" />

<TextBlock Grid.Row="3" Grid.Column="0"
Margin="4"
Text="{lex:Loc OpenLogsWindow}"/>
<TextBox x:Name="openLogsTextBox"
Grid.Row="3" Grid.Column="1"
Margin="4" IsReadOnly="True" />
<TextBlock x:Name="openLogsStatusTextBlock"
Grid.Row="3" Grid.Column="2"
Margin="4" />

<TextBlock Grid.Row="4" Grid.Column="0"
Margin="4"
Text="{lex:Loc SwitchToPreviousServer}"/>
<TextBox x:Name="switchPrevTextBox"
Grid.Row="4" Grid.Column="1"
Margin="4" IsReadOnly="True" />
<TextBlock x:Name="switchPrevStatusTextBlock"
Grid.Row="4" Grid.Column="2"
Margin="4" />

<TextBlock Grid.Row="5" Grid.Column="0"
Margin="4"
Text="{lex:Loc SwitchToNextServer}"/>
<TextBox x:Name="switchNextTextBox"
Grid.Row="5" Grid.Column="1"
Margin="4" IsReadOnly="True" />
<TextBlock x:Name="switchNextStatusTextBlock"
Grid.Row="5" Grid.Column="2"
Margin="4" />

<StackPanel Grid.Row="6" Grid.ColumnSpan="2" Orientation="Horizontal">
<CheckBox x:Name="registerAtStartupCheckBox" Margin="4" VerticalAlignment="Center"/>
<TextBlock Margin="4" Text="{lex:Loc RegisterHotkeysAtStartup}"/>
</StackPanel>

<StackPanel Grid.Row="7"
Grid.ColumnSpan="3"
Orientation="Horizontal"
HorizontalAlignment="Left"
VerticalAlignment="Bottom">
<Button x:Name="registerAllButton" Margin="4" MinWidth="75" Content="{lex:Loc}"/>
</StackPanel>
<StackPanel Grid.Row="7"
Grid.ColumnSpan="3"
Orientation="Horizontal"
HorizontalAlignment="Right"
VerticalAlignment="Bottom">
<Button x:Name="saveButton" Margin="4" MinWidth="75" Content="{lex:Loc}"/>
<Button x:Name="cancelButton" Margin="4" MinWidth="75" Content="{lex:Loc}"/>
</StackPanel>
</Grid>
</reactiveui:ReactiveUserControl>

+ 169
- 0
shadowsocks-csharp/Views/HotkeysView.xaml.cs View File

@@ -0,0 +1,169 @@
using ReactiveUI;
using Shadowsocks.ViewModels;
using System;
using System.Reactive.Disposables;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;

namespace Shadowsocks.Views
{
/// <summary>
/// Interaction logic for HotkeysView.xaml
/// </summary>
public partial class HotkeysView : ReactiveUserControl<HotkeysViewModel>
{
public HotkeysView()
{
InitializeComponent();
ViewModel = new HotkeysViewModel();
this.WhenActivated(disposables =>
{
systemProxyTextBox
.Events().KeyDown
.Subscribe(keyEventArgs => ViewModel.RecordKeyDown(0, keyEventArgs))
.DisposeWith(disposables);

systemProxyTextBox
.Events().KeyUp
.Subscribe(keyEventArgs => ViewModel.FinishOnKeyUp(0, keyEventArgs))
.DisposeWith(disposables);

proxyModeTextBox
.Events().KeyDown
.Subscribe(keyEventArgs => ViewModel.RecordKeyDown(1, keyEventArgs))
.DisposeWith(disposables);

proxyModeTextBox
.Events().KeyUp
.Subscribe(keyEventArgs => ViewModel.FinishOnKeyUp(1, keyEventArgs))
.DisposeWith(disposables);

allowLanTextBox
.Events().KeyDown
.Subscribe(keyEventArgs => ViewModel.RecordKeyDown(2, keyEventArgs))
.DisposeWith(disposables);

allowLanTextBox
.Events().KeyUp
.Subscribe(keyEventArgs => ViewModel.FinishOnKeyUp(2, keyEventArgs))
.DisposeWith(disposables);

openLogsTextBox
.Events().KeyDown
.Subscribe(keyEventArgs => ViewModel.RecordKeyDown(3, keyEventArgs))
.DisposeWith(disposables);

openLogsTextBox
.Events().KeyUp
.Subscribe(keyEventArgs => ViewModel.FinishOnKeyUp(3, keyEventArgs))
.DisposeWith(disposables);

switchPrevTextBox
.Events().KeyDown
.Subscribe(keyEventArgs => ViewModel.RecordKeyDown(4, keyEventArgs))
.DisposeWith(disposables);

switchPrevTextBox
.Events().KeyUp
.Subscribe(keyEventArgs => ViewModel.FinishOnKeyUp(4, keyEventArgs))
.DisposeWith(disposables);

switchNextTextBox
.Events().KeyDown
.Subscribe(keyEventArgs => ViewModel.RecordKeyDown(5, keyEventArgs))
.DisposeWith(disposables);

switchNextTextBox
.Events().KeyUp
.Subscribe(keyEventArgs => ViewModel.FinishOnKeyUp(5, keyEventArgs))
.DisposeWith(disposables);

this.OneWayBind(ViewModel,
viewModel => viewModel.HotkeySystemProxy,
view => view.systemProxyTextBox.Text)
.DisposeWith(disposables);

this.OneWayBind(ViewModel,
viewModel => viewModel.HotkeyProxyMode,
view => view.proxyModeTextBox.Text)
.DisposeWith(disposables);

this.OneWayBind(ViewModel,
viewModel => viewModel.HotkeyAllowLan,
view => view.allowLanTextBox.Text)
.DisposeWith(disposables);

this.OneWayBind(ViewModel,
viewModel => viewModel.HotkeyOpenLogs,
view => view.openLogsTextBox.Text)
.DisposeWith(disposables);

this.OneWayBind(ViewModel,
viewModel => viewModel.HotkeySwitchPrev,
view => view.switchPrevTextBox.Text)
.DisposeWith(disposables);

this.OneWayBind(ViewModel,
viewModel => viewModel.HotkeySwitchNext,
view => view.switchNextTextBox.Text)
.DisposeWith(disposables);

this.Bind(ViewModel,
viewModel => viewModel.RegisterAtStartup,
view => view.registerAtStartupCheckBox.IsChecked)
.DisposeWith(disposables);

this.OneWayBind(ViewModel,
viewModel => viewModel.HotkeySystemProxyStatus,
view => view.systemProxyStatusTextBlock.Text)
.DisposeWith(disposables);

this.OneWayBind(ViewModel,
viewModel => viewModel.HotkeyProxyModeStatus,
view => view.proxyModeStatusTextBlock.Text)
.DisposeWith(disposables);

this.OneWayBind(ViewModel,
viewModel => viewModel.HotkeyAllowLanStatus,
view => view.allowLanStatusTextBlock.Text)
.DisposeWith(disposables);

this.OneWayBind(ViewModel,
viewModel => viewModel.HotkeyOpenLogsStatus,
view => view.openLogsStatusTextBlock.Text)
.DisposeWith(disposables);

this.OneWayBind(ViewModel,
viewModel => viewModel.HotkeySwitchPrevStatus,
view => view.switchPrevStatusTextBlock.Text)
.DisposeWith(disposables);

this.OneWayBind(ViewModel,
viewModel => viewModel.HotkeySwitchNextStatus,
view => view.switchNextStatusTextBlock.Text)
.DisposeWith(disposables);

this.BindCommand(ViewModel,
viewModel => viewModel.RegisterAll,
view => view.registerAllButton)
.DisposeWith(disposables);

this.BindCommand(ViewModel,
viewModel => viewModel.Save,
view => view.saveButton)
.DisposeWith(disposables);

this.BindCommand(ViewModel,
viewModel => viewModel.Cancel,
view => view.cancelButton)
.DisposeWith(disposables);
});
}
}
}

+ 61
- 0
shadowsocks-csharp/Views/OnlineConfigView.xaml View File

@@ -0,0 +1,61 @@
<reactiveui:ReactiveUserControl
x:Class="Shadowsocks.Views.OnlineConfigView"
x:TypeArguments="vms:OnlineConfigViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Shadowsocks.Views"
xmlns:vms="clr-namespace:Shadowsocks.ViewModels"
xmlns:reactiveui="http://reactiveui.net"
xmlns:lex="http://wpflocalizeextension.codeplex.com"
lex:LocalizeDictionary.DesignCulture="en"
lex:ResxLocalizationProvider.DefaultAssembly="Shadowsocks"
lex:ResxLocalizationProvider.DefaultDictionary="Strings"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="480">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

<ListBox x:Name="sourcesListBox" Margin="8 8 4 4"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Row="0" Grid.Column="2">
<Button x:Name="updateButton" Margin="4 8 8 4"
MinWidth="75" Content="{lex:Loc}"/>
<Button x:Name="updateAllButton" Margin="4 4 8 4"
MinWidth="75" Content="{lex:Loc}"/>
<Button x:Name="copyLinkButton" Margin="4 4 8 8"
MinWidth="75" Content="{lex:Loc}"/>
<Button x:Name="removeButton" Margin="4 8 8 8"
MinWidth="75" Content="{lex:Loc}"/>
</StackPanel>
<TextBlock Margin="8 4 4 8"
Grid.Row="1"
Grid.Column="0">URL</TextBlock>
<TextBox x:Name="urlTextBox" Margin="4 4 4 8"
Grid.Row="1"
Grid.Column="1"/>
<Button x:Name="addButton" Margin="4 4 8 8"
Grid.Row="1"
Grid.Column="2"
MinWidth="75"
Content="{lex:Loc}"/>
</Grid>
</reactiveui:ReactiveUserControl>

+ 54
- 0
shadowsocks-csharp/Views/OnlineConfigView.xaml.cs View File

@@ -0,0 +1,54 @@
using ReactiveUI;
using Shadowsocks.ViewModels;
using System.Reactive.Disposables;

namespace Shadowsocks.Views
{
/// <summary>
/// Interaction logic for OnlineConfigView.xaml
/// </summary>
public partial class OnlineConfigView : ReactiveUserControl<OnlineConfigViewModel>
{
public OnlineConfigView()
{
InitializeComponent();
ViewModel = new OnlineConfigViewModel();
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel,
viewModel => viewModel.Sources,
view => view.sourcesListBox.ItemsSource)
.DisposeWith(disposables);
this.Bind(ViewModel,
viewModel => viewModel.SelectedSource,
view => view.sourcesListBox.SelectedItem)
.DisposeWith(disposables);
this.Bind(ViewModel,
viewModel => viewModel.Address,
view => view.urlTextBox.Text)
.DisposeWith(disposables);

this.BindCommand(ViewModel,
viewModel => viewModel.Update,
view => view.updateButton)
.DisposeWith(disposables);
this.BindCommand(ViewModel,
viewModel => viewModel.UpdateAll,
view => view.updateAllButton)
.DisposeWith(disposables);
this.BindCommand(ViewModel,
viewModel => viewModel.CopyLink,
view => view.copyLinkButton)
.DisposeWith(disposables);
this.BindCommand(ViewModel,
viewModel => viewModel.Remove,
view => view.removeButton)
.DisposeWith(disposables);
this.BindCommand(ViewModel,
viewModel => viewModel.Add,
view => view.addButton)
.DisposeWith(disposables);
});
}
}
}

+ 53
- 0
shadowsocks-csharp/Views/ServerSharingView.xaml View File

@@ -0,0 +1,53 @@
<reactiveui:ReactiveUserControl
x:Class="Shadowsocks.Views.ServerSharingView"
x:TypeArguments="vms:ServerSharingViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Shadowsocks.Views"
xmlns:vms="clr-namespace:Shadowsocks.ViewModels"
xmlns:reactiveui="http://reactiveui.net"
xmlns:lex="http://wpflocalizeextension.codeplex.com"
lex:LocalizeDictionary.DesignCulture="en"
lex:ResxLocalizationProvider.DefaultAssembly="Shadowsocks"
lex:ResxLocalizationProvider.DefaultDictionary="Strings"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="660">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="qrCodeImage" Grid.Row="0"
Grid.Column="0"
Margin="8"/>
<ListBox x:Name="serversListBox" Grid.Row="0"
Grid.Column="1"
Margin="8">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox x:Name="urlTextBox"
Grid.Column="0"
Margin="8 8 4 8"
IsReadOnly="True"
PreviewMouseDoubleClick="urlTextBox_PreviewMouseDoubleClick"/>
<Button x:Name="copyLinkButton" Grid.Column="1" Margin="4 8 8 8" MinWidth="36" Content="{lex:Loc Copy}"/>
</Grid>
</Grid>
</reactiveui:ReactiveUserControl>

+ 48
- 0
shadowsocks-csharp/Views/ServerSharingView.xaml.cs View File

@@ -0,0 +1,48 @@
using ReactiveUI;
using Shadowsocks.ViewModels;
using System.Reactive.Disposables;
using System.Windows.Input;

namespace Shadowsocks.Views
{
/// <summary>
/// Interaction logic for ServerSharingView.xaml
/// </summary>
public partial class ServerSharingView : ReactiveUserControl<ServerSharingViewModel>
{
public ServerSharingView()
{
InitializeComponent();
ViewModel = new ServerSharingViewModel();
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel,
viewModel => viewModel.SelectedServerUrlImage,
view => view.qrCodeImage.Source)
.DisposeWith(disposables);
this.OneWayBind(ViewModel,
viewModel => viewModel.Servers,
view => view.serversListBox.ItemsSource)
.DisposeWith(disposables);
this.Bind(ViewModel,
viewModel => viewModel.SelectedServer,
view => view.serversListBox.SelectedItem)
.DisposeWith(disposables);
this.OneWayBind(ViewModel,
viewModel => viewModel.SelectedServerUrl,
view => view.urlTextBox.Text)
.DisposeWith(disposables);

this.BindCommand(ViewModel,
viewModel => viewModel.CopyLink,
view => view.copyLinkButton)
.DisposeWith(disposables);
});
}

private void urlTextBox_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
urlTextBox.SelectAll();
}
}
}

+ 43
- 0
shadowsocks-csharp/Views/VersionUpdatePromptView.xaml View File

@@ -0,0 +1,43 @@
<reactiveui:ReactiveUserControl
x:Class="Shadowsocks.Views.VersionUpdatePromptView"
x:TypeArguments="vms:VersionUpdatePromptViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Shadowsocks.Views"
xmlns:vms="clr-namespace:Shadowsocks.ViewModels"
xmlns:reactiveui="http://reactiveui.net"
xmlns:mdxam="clr-namespace:MdXaml;assembly=MdXaml"
xmlns:lex="http://wpflocalizeextension.codeplex.com"
lex:LocalizeDictionary.DesignCulture="en"
lex:ResxLocalizationProvider.DefaultAssembly="Shadowsocks"
lex:ResxLocalizationProvider.DefaultDictionary="Strings"
mc:Ignorable="d"
d:DesignHeight="480" d:DesignWidth="640">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Margin="8 8 8 4" Grid.Row="0"
FontSize="16"
Text="{lex:Loc updatePromptTitle}" />
<TextBlock Margin="8 4 8 8" Grid.Row="1"
Text="{lex:Loc updatePromptBody}" />
<mdxam:MarkdownScrollViewer x:Name="releaseNotesMarkdownScrollViewer"
Margin="8"
Grid.Row="2"
Markdown="{Binding ReleaseNotes}"
MarkdownStyle="{x:Static mdxam:MarkdownStyle.GithubLike}"/>
<StackPanel Orientation="Horizontal" Grid.Row="3">
<Button x:Name="updateButton" Margin="8 8 4 4" Width="75" Height="23" Content="{lex:Loc}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="3" HorizontalAlignment="Right">
<Button x:Name="skipVersionButton" Margin="4 8 4 4" Width="75" Height="23" Content="{lex:Loc}"/>
<Button x:Name="notNowButton" Margin="4 8 8 4" Width="75" Height="23" Content="{lex:Loc}"/>
</StackPanel>
</Grid>
</reactiveui:ReactiveUserControl>

+ 40
- 0
shadowsocks-csharp/Views/VersionUpdatePromptView.xaml.cs View File

@@ -0,0 +1,40 @@
using Newtonsoft.Json.Linq;
using ReactiveUI;
using Shadowsocks.ViewModels;
using System.Reactive.Disposables;

namespace Shadowsocks.Views
{
/// <summary>
/// Interaction logic for VersionUpdatePromptView.xaml
/// </summary>
public partial class VersionUpdatePromptView : ReactiveUserControl<VersionUpdatePromptViewModel>
{
public VersionUpdatePromptView(JToken releaseObject)
{
InitializeComponent();
ViewModel = new VersionUpdatePromptViewModel(releaseObject);
DataContext = ViewModel; // for compatibility with MdXaml
this.WhenActivated(disposables =>
{
/*this.OneWayBind(ViewModel,
viewModel => viewModel.ReleaseNotes,
view => releaseNotesMarkdownScrollViewer.Markdown)
.DisposeWith(disposables);*/

this.BindCommand(ViewModel,
viewModel => viewModel.Update,
view => view.updateButton)
.DisposeWith(disposables);
this.BindCommand(ViewModel,
viewModel => viewModel.SkipVersion,
view => view.skipVersionButton)
.DisposeWith(disposables);
this.BindCommand(ViewModel,
viewModel => viewModel.NotNow,
view => view.notNowButton)
.DisposeWith(disposables);
});
}
}
}

+ 0
- 12
test/UnitTest.cs View File

@@ -14,18 +14,6 @@ namespace Shadowsocks.Test
[TestClass] [TestClass]
public class UnitTest public class UnitTest
{ {
[TestMethod]
public void TestCompareVersion()
{
Assert.IsTrue(UpdateChecker.Asset.CompareVersion("2.3.1.0", "2.3.1") == 0);
Assert.IsTrue(UpdateChecker.Asset.CompareVersion("1.2", "1.3") < 0);
Assert.IsTrue(UpdateChecker.Asset.CompareVersion("1.3", "1.2") > 0);
Assert.IsTrue(UpdateChecker.Asset.CompareVersion("1.3", "1.3") == 0);
Assert.IsTrue(UpdateChecker.Asset.CompareVersion("1.2.1", "1.2") > 0);
Assert.IsTrue(UpdateChecker.Asset.CompareVersion("2.3.1", "2.4") < 0);
Assert.IsTrue(UpdateChecker.Asset.CompareVersion("1.3.2", "1.3.1") > 0);
}
[TestMethod] [TestMethod]
public void TestHotKey2Str() public void TestHotKey2Str()
{ {


Loading…
Cancel
Save