@@ -23,9 +23,9 @@ namespace Shadowsocks.PAC | |||||
public class GeositeUpdater : IEnableLogger | public class GeositeUpdater : IEnableLogger | ||||
{ | { | ||||
public event EventHandler<GeositeResultEventArgs> UpdateCompleted; | |||||
public event EventHandler<GeositeResultEventArgs>? UpdateCompleted; | |||||
public event ErrorEventHandler Error; | |||||
public event ErrorEventHandler? Error; | |||||
private readonly string DATABASE_PATH; | private readonly string DATABASE_PATH; | ||||
@@ -230,7 +230,7 @@ var __RULES__ = {JsonSerializer.Serialize(ruleLines, jsonSerializerOptions)}; | |||||
List<string> valid_lines = new List<string>(); | List<string> valid_lines = new List<string>(); | ||||
using (var stringReader = new StringReader(content)) | using (var stringReader = new StringReader(content)) | ||||
{ | { | ||||
for (string line = stringReader.ReadLine(); line != null; line = stringReader.ReadLine()) | |||||
for (string? line = stringReader.ReadLine(); line != null; line = stringReader.ReadLine()) | |||||
{ | { | ||||
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("!") || line.StartsWith("[")) | if (string.IsNullOrWhiteSpace(line) || line.StartsWith("!") || line.StartsWith("[")) | ||||
continue; | continue; | ||||
@@ -189,13 +189,14 @@ Connection: Close | |||||
private void SendCallback(IAsyncResult ar) | private void SendCallback(IAsyncResult ar) | ||||
{ | { | ||||
Socket conn = (Socket)ar.AsyncState; | |||||
Socket? conn = ar.AsyncState as Socket; | |||||
try | try | ||||
{ | { | ||||
conn.Shutdown(SocketShutdown.Send); | |||||
conn?.Shutdown(SocketShutdown.Send); | |||||
} | } | ||||
catch | catch | ||||
{ } | |||||
{ | |||||
} | |||||
} | } | ||||
private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks) => $"{(useSocks ? "SOCKS5" : "PROXY")} {localEndPoint};"; | private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks) => $"{(useSocks ? "SOCKS5" : "PROXY")} {localEndPoint};"; | ||||
@@ -2,6 +2,7 @@ | |||||
<PropertyGroup> | <PropertyGroup> | ||||
<TargetFramework>netcoreapp3.1</TargetFramework> | <TargetFramework>netcoreapp3.1</TargetFramework> | ||||
<Nullable>enable</Nullable> | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -1,91 +0,0 @@ | |||||
using NLog; | |||||
using Shadowsocks.Controller.Hotkeys; | |||||
using System; | |||||
using System.Windows.Forms; | |||||
namespace Shadowsocks.WPF.Behaviors | |||||
{ | |||||
static class HotkeyReg | |||||
{ | |||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
public static void RegAllHotkeys() | |||||
{ | |||||
var hotkeyConfig = Program.MainController.GetCurrentConfiguration().hotkey; | |||||
if (hotkeyConfig == null || !hotkeyConfig.RegHotkeysAtStartup) | |||||
return; | |||||
// if any of the hotkey reg fail, undo everything | |||||
if (RegHotkeyFromString(hotkeyConfig.SwitchSystemProxy, "SwitchSystemProxyCallback") | |||||
&& RegHotkeyFromString(hotkeyConfig.SwitchSystemProxyMode, "SwitchSystemProxyModeCallback") | |||||
&& RegHotkeyFromString(hotkeyConfig.SwitchAllowLan, "SwitchAllowLanCallback") | |||||
&& RegHotkeyFromString(hotkeyConfig.ShowLogs, "ShowLogsCallback") | |||||
&& RegHotkeyFromString(hotkeyConfig.ServerMoveUp, "ServerMoveUpCallback") | |||||
&& RegHotkeyFromString(hotkeyConfig.ServerMoveDown, "ServerMoveDownCallback") | |||||
) | |||||
{ | |||||
// success | |||||
} | |||||
else | |||||
{ | |||||
RegHotkeyFromString("", "SwitchSystemProxyCallback"); | |||||
RegHotkeyFromString("", "SwitchSystemProxyModeCallback"); | |||||
RegHotkeyFromString("", "SwitchAllowLanCallback"); | |||||
RegHotkeyFromString("", "ShowLogsCallback"); | |||||
RegHotkeyFromString("", "ServerMoveUpCallback"); | |||||
RegHotkeyFromString("", "ServerMoveDownCallback"); | |||||
MessageBox.Show(I18N.GetString("Register hotkey failed"), I18N.GetString("Shadowsocks")); | |||||
} | |||||
} | |||||
public static bool RegHotkeyFromString(string hotkeyStr, string callbackName, Action<RegResult> onComplete = null) | |||||
{ | |||||
var _callback = HotkeyCallbacks.GetCallback(callbackName); | |||||
if (_callback == null) | |||||
{ | |||||
throw new Exception($"{callbackName} not found"); | |||||
} | |||||
var callback = _callback as HotKeys.HotKeyCallBackHandler; | |||||
if (string.IsNullOrEmpty(hotkeyStr)) | |||||
{ | |||||
HotKeys.UnregExistingHotkey(callback); | |||||
onComplete?.Invoke(RegResult.UnregSuccess); | |||||
return true; | |||||
} | |||||
else | |||||
{ | |||||
var hotkey = HotKeys.Str2HotKey(hotkeyStr); | |||||
if (hotkey == null) | |||||
{ | |||||
logger.Error($"Cannot parse hotkey: {hotkeyStr}"); | |||||
onComplete?.Invoke(RegResult.ParseError); | |||||
return false; | |||||
} | |||||
else | |||||
{ | |||||
bool regResult = (HotKeys.RegHotkey(hotkey, callback)); | |||||
if (regResult) | |||||
{ | |||||
onComplete?.Invoke(RegResult.RegSuccess); | |||||
} | |||||
else | |||||
{ | |||||
onComplete?.Invoke(RegResult.RegFailure); | |||||
} | |||||
return regResult; | |||||
} | |||||
} | |||||
} | |||||
public enum RegResult | |||||
{ | |||||
RegSuccess, | |||||
RegFailure, | |||||
ParseError, | |||||
UnregSuccess, | |||||
//UnregFailure | |||||
} | |||||
} | |||||
} |
@@ -1,114 +0,0 @@ | |||||
using System; | |||||
using System.Reflection; | |||||
namespace Shadowsocks.Controller.Hotkeys | |||||
{ | |||||
public class HotkeyCallbacks | |||||
{ | |||||
public static void InitInstance(ShadowsocksController controller) | |||||
{ | |||||
if (Instance != null) | |||||
{ | |||||
return; | |||||
} | |||||
Instance = new HotkeyCallbacks(controller); | |||||
} | |||||
/// <summary> | |||||
/// Create hotkey callback handler delegate based on callback name | |||||
/// </summary> | |||||
/// <param name="methodname"></param> | |||||
/// <returns></returns> | |||||
public static Delegate GetCallback(string methodname) | |||||
{ | |||||
if (string.IsNullOrEmpty(methodname)) throw new ArgumentException(nameof(methodname)); | |||||
MethodInfo dynMethod = typeof(HotkeyCallbacks).GetMethod(methodname, | |||||
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase); | |||||
return dynMethod == null ? null : Delegate.CreateDelegate(typeof(HotKeys.HotKeyCallBackHandler), Instance, dynMethod); | |||||
} | |||||
#region Singleton | |||||
private static HotkeyCallbacks Instance { get; set; } | |||||
private readonly ShadowsocksController _controller; | |||||
private HotkeyCallbacks(ShadowsocksController controller) | |||||
{ | |||||
_controller = controller; | |||||
} | |||||
#endregion | |||||
#region Callbacks | |||||
private void SwitchSystemProxyCallback() | |||||
{ | |||||
bool enabled = _controller.GetCurrentConfiguration().enabled; | |||||
_controller.ToggleEnable(!enabled); | |||||
} | |||||
private void SwitchSystemProxyModeCallback() | |||||
{ | |||||
var config = _controller.GetCurrentConfiguration(); | |||||
if (config.enabled) | |||||
_controller.ToggleGlobal(!config.global); | |||||
} | |||||
private void SwitchAllowLanCallback() | |||||
{ | |||||
var status = _controller.GetCurrentConfiguration().shareOverLan; | |||||
_controller.ToggleShareOverLAN(!status); | |||||
} | |||||
private void ShowLogsCallback() | |||||
{ | |||||
Program.MenuController.ShowLogForm_HotKey(); | |||||
} | |||||
private void ServerMoveUpCallback() | |||||
{ | |||||
int currIndex; | |||||
int serverCount; | |||||
GetCurrServerInfo(out currIndex, out serverCount); | |||||
if (currIndex - 1 < 0) | |||||
{ | |||||
// revert to last server | |||||
currIndex = serverCount - 1; | |||||
} | |||||
else | |||||
{ | |||||
currIndex -= 1; | |||||
} | |||||
_controller.SelectServerIndex(currIndex); | |||||
} | |||||
private void ServerMoveDownCallback() | |||||
{ | |||||
int currIndex; | |||||
int serverCount; | |||||
GetCurrServerInfo(out currIndex, out serverCount); | |||||
if (currIndex + 1 == serverCount) | |||||
{ | |||||
// revert to first server | |||||
currIndex = 0; | |||||
} | |||||
else | |||||
{ | |||||
currIndex += 1; | |||||
} | |||||
_controller.SelectServerIndex(currIndex); | |||||
} | |||||
private void GetCurrServerInfo(out int currIndex, out int serverCount) | |||||
{ | |||||
var currConfig = _controller.GetCurrentConfiguration(); | |||||
currIndex = currConfig.index; | |||||
serverCount = currConfig.configs.Count; | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -1,174 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.ComponentModel; | |||||
using System.Linq; | |||||
using System.Windows.Input; | |||||
using GlobalHotKey; | |||||
namespace Shadowsocks.Controller.Hotkeys | |||||
{ | |||||
public static class HotKeys | |||||
{ | |||||
private static HotKeyManager _hotKeyManager; | |||||
public delegate void HotKeyCallBackHandler(); | |||||
// map key and corresponding handler function | |||||
private static Dictionary<HotKey, HotKeyCallBackHandler> _keymap = new Dictionary<HotKey, HotKeyCallBackHandler>(); | |||||
public static void Init(ShadowsocksController controller) | |||||
{ | |||||
_hotKeyManager = new HotKeyManager(); | |||||
_hotKeyManager.KeyPressed += HotKeyManagerPressed; | |||||
HotkeyCallbacks.InitInstance(controller); | |||||
} | |||||
public static void Destroy() | |||||
{ | |||||
_hotKeyManager.KeyPressed -= HotKeyManagerPressed; | |||||
_hotKeyManager.Dispose(); | |||||
} | |||||
private static void HotKeyManagerPressed(object sender, KeyPressedEventArgs e) | |||||
{ | |||||
var hotkey = e.HotKey; | |||||
HotKeyCallBackHandler callback; | |||||
if (_keymap.TryGetValue(hotkey, out callback)) | |||||
callback(); | |||||
} | |||||
public static bool RegHotkey(HotKey hotkey, HotKeyCallBackHandler callback) | |||||
{ | |||||
UnregExistingHotkey(callback); | |||||
return Register(hotkey, callback); | |||||
} | |||||
public static bool UnregExistingHotkey(HotKeys.HotKeyCallBackHandler cb) | |||||
{ | |||||
HotKey existingHotKey; | |||||
if (IsCallbackExists(cb, out existingHotKey)) | |||||
{ | |||||
// unregister existing one | |||||
Unregister(existingHotKey); | |||||
return true; | |||||
} | |||||
else | |||||
{ | |||||
return false; | |||||
} | |||||
} | |||||
public static bool IsHotkeyExists( HotKey hotKey ) | |||||
{ | |||||
if (hotKey == null) throw new ArgumentNullException(nameof(hotKey)); | |||||
return _keymap.Any( v => v.Key.Equals( hotKey ) ); | |||||
} | |||||
public static bool IsCallbackExists( HotKeyCallBackHandler cb, out HotKey hotkey) | |||||
{ | |||||
if (cb == null) throw new ArgumentNullException(nameof(cb)); | |||||
if (_keymap.Any(v => v.Value == cb)) | |||||
{ | |||||
hotkey = _keymap.First(v => v.Value == cb).Key; | |||||
return true; | |||||
} | |||||
else | |||||
{ | |||||
hotkey = null; | |||||
return false; | |||||
} | |||||
} | |||||
#region Converters | |||||
public static string HotKey2Str( HotKey key ) | |||||
{ | |||||
if (key == null) throw new ArgumentNullException(nameof(key)); | |||||
return HotKey2Str( key.Key, key.Modifiers ); | |||||
} | |||||
public static string HotKey2Str( Key key, ModifierKeys modifier ) | |||||
{ | |||||
if (!Enum.IsDefined(typeof(Key), key)) | |||||
throw new InvalidEnumArgumentException(nameof(key), (int) key, typeof(Key)); | |||||
try | |||||
{ | |||||
ModifierKeysConverter mkc = new ModifierKeysConverter(); | |||||
var keyStr = Enum.GetName(typeof(Key), key); | |||||
var modifierStr = mkc.ConvertToInvariantString(modifier); | |||||
return $"{modifierStr}+{keyStr}"; | |||||
} | |||||
catch (NotSupportedException) | |||||
{ | |||||
// converter exception | |||||
return null; | |||||
} | |||||
} | |||||
public static HotKey Str2HotKey(string s) | |||||
{ | |||||
try | |||||
{ | |||||
if (string.IsNullOrEmpty(s)) return null; | |||||
int offset = s.LastIndexOf("+", StringComparison.OrdinalIgnoreCase); | |||||
if (offset <= 0) return null; | |||||
string modifierStr = s.Substring(0, offset).Trim(); | |||||
string keyStr = s.Substring(offset + 1).Trim(); | |||||
KeyConverter kc = new KeyConverter(); | |||||
ModifierKeysConverter mkc = new ModifierKeysConverter(); | |||||
Key key = (Key) kc.ConvertFrom(keyStr.ToUpper()); | |||||
ModifierKeys modifier = (ModifierKeys) mkc.ConvertFrom(modifierStr.ToUpper()); | |||||
return new HotKey(key, modifier); | |||||
} | |||||
catch (NotSupportedException) | |||||
{ | |||||
// converter exception | |||||
return null; | |||||
} | |||||
catch (NullReferenceException) | |||||
{ | |||||
return null; | |||||
} | |||||
} | |||||
#endregion | |||||
private static bool Register(HotKey key, HotKeyCallBackHandler callBack) | |||||
{ | |||||
if (key == null) | |||||
throw new ArgumentNullException(nameof(key)); | |||||
if (callBack == null) | |||||
throw new ArgumentNullException(nameof(callBack)); | |||||
try | |||||
{ | |||||
_hotKeyManager.Register(key); | |||||
_keymap[key] = callBack; | |||||
return true; | |||||
} | |||||
catch (ArgumentException) | |||||
{ | |||||
// already called this method with the specific hotkey | |||||
// return success silently | |||||
return true; | |||||
} | |||||
catch (Win32Exception) | |||||
{ | |||||
// this hotkey already registered by other programs | |||||
// notify user to change key | |||||
return false; | |||||
} | |||||
} | |||||
private static void Unregister(HotKey key) | |||||
{ | |||||
if (key == null) | |||||
throw new ArgumentNullException(nameof(key)); | |||||
_hotKeyManager.Unregister(key); | |||||
if(_keymap.ContainsKey(key)) | |||||
_keymap.Remove(key); | |||||
} | |||||
} | |||||
} |
@@ -1,143 +0,0 @@ | |||||
using Shadowsocks.Net.SystemProxy; | |||||
using System; | |||||
using System.ComponentModel; | |||||
using System.Net; | |||||
using System.Net.Sockets; | |||||
namespace NLog | |||||
{ | |||||
public static class LoggerExtension | |||||
{ | |||||
// for key, iv, etc... | |||||
public static void Dump(this Logger logger, string tag, ReadOnlySpan<byte> arr) | |||||
{ | |||||
logger.Dump(tag, arr.ToArray(), arr.Length); | |||||
} | |||||
public static void Dump(this Logger logger, string tag, byte[] arr, int length = -1) | |||||
{ | |||||
if (arr == null) logger.Trace($@" | |||||
{tag}: | |||||
(null) | |||||
"); | |||||
if (length == -1) length = arr.Length; | |||||
if (!logger.IsTraceEnabled) return; | |||||
string hex = BitConverter.ToString(arr.AsSpan(0, Math.Min(arr.Length, length)).ToArray()).Replace("-", ""); | |||||
string content = $@" | |||||
{tag}: | |||||
{hex} | |||||
"; | |||||
logger.Trace(content); | |||||
} | |||||
// for cipher and plain text, so we can use openssl to test | |||||
public static void DumpBase64(this Logger logger, string tag, ReadOnlySpan<byte> arr) | |||||
{ | |||||
logger.DumpBase64(tag, arr.ToArray(), arr.Length); | |||||
} | |||||
public static void DumpBase64(this Logger logger, string tag, byte[] arr, int length = -1) | |||||
{ | |||||
if (arr == null) logger.Trace($@" | |||||
{tag}: | |||||
(null) | |||||
"); | |||||
if (length == -1) length = arr.Length; | |||||
if (!logger.IsTraceEnabled) return; | |||||
string hex = Convert.ToBase64String(arr.AsSpan(0, Math.Min(arr.Length, length)).ToArray()); | |||||
string content = $@" | |||||
{tag}: | |||||
{hex} | |||||
"; | |||||
logger.Trace(content); | |||||
} | |||||
public static void Debug(this Logger logger, EndPoint local, EndPoint remote, int len, string header = null, string tailer = null) | |||||
{ | |||||
if (logger.IsDebugEnabled) | |||||
{ | |||||
if (header == null && tailer == null) | |||||
logger.Debug($"{local} => {remote} (size={len})"); | |||||
else if (header == null && tailer != null) | |||||
logger.Debug($"{local} => {remote} (size={len}), {tailer}"); | |||||
else if (header != null && tailer == null) | |||||
logger.Debug($"{header}: {local} => {remote} (size={len})"); | |||||
else | |||||
logger.Debug($"{header}: {local} => {remote} (size={len}), {tailer}"); | |||||
} | |||||
} | |||||
public static void Debug(this Logger logger, Socket sock, int len, string header = null, string tailer = null) | |||||
{ | |||||
if (logger.IsDebugEnabled) | |||||
logger.Debug(sock.LocalEndPoint, sock.RemoteEndPoint, len, header, tailer); | |||||
} | |||||
public static void LogUsefulException(this Logger logger, Exception e) | |||||
{ | |||||
// just log useful exceptions, not all of them | |||||
if (e is SocketException se) | |||||
{ | |||||
if (se.SocketErrorCode == SocketError.ConnectionAborted) | |||||
{ | |||||
// closed by browser when sending | |||||
// normally happens when download is canceled or a tab is closed before page is loaded | |||||
} | |||||
else if (se.SocketErrorCode == SocketError.ConnectionReset) | |||||
{ | |||||
// received rst | |||||
} | |||||
else if (se.SocketErrorCode == SocketError.NotConnected) | |||||
{ | |||||
// The application tried to send or receive data, and the System.Net.Sockets.Socket is not connected. | |||||
} | |||||
else if (se.SocketErrorCode == SocketError.HostUnreachable) | |||||
{ | |||||
// There is no network route to the specified host. | |||||
} | |||||
else if (se.SocketErrorCode == SocketError.TimedOut) | |||||
{ | |||||
// The connection attempt timed out, or the connected host has failed to respond. | |||||
} | |||||
else | |||||
{ | |||||
logger.Warn(e); | |||||
} | |||||
} | |||||
else if (e is ObjectDisposedException) | |||||
{ | |||||
} | |||||
else if (e is Win32Exception ex) | |||||
{ | |||||
// Win32Exception (0x80004005): A 32 bit processes cannot access modules of a 64 bit process. | |||||
if ((uint)ex.ErrorCode != 0x80004005) | |||||
logger.Warn(e); | |||||
} | |||||
else if (e is ProxyException pe) | |||||
{ | |||||
switch (pe.Type) | |||||
{ | |||||
case ProxyExceptionType.FailToRun: | |||||
case ProxyExceptionType.QueryReturnMalformed: | |||||
case ProxyExceptionType.SysproxyExitError: | |||||
logger.Error($"sysproxy - {pe.Type}:{pe.Message}"); | |||||
break; | |||||
case ProxyExceptionType.QueryReturnEmpty: | |||||
case ProxyExceptionType.Unspecific: | |||||
logger.Error($"sysproxy - {pe.Type}"); | |||||
break; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
logger.Warn(e); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. | |||||
--> | --> | ||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||||
<PropertyGroup> | <PropertyGroup> | ||||
<Configuration>Debug</Configuration> | |||||
<Configuration>Release</Configuration> | |||||
<Platform>Any CPU</Platform> | <Platform>Any CPU</Platform> | ||||
<PublishDir>bin\Release\netcoreapp3.1\publish\</PublishDir> | <PublishDir>bin\Release\netcoreapp3.1\publish\</PublishDir> | ||||
<PublishProtocol>FileSystem</PublishProtocol> | <PublishProtocol>FileSystem</PublishProtocol> | ||||
@@ -0,0 +1,105 @@ | |||||
//------------------------------------------------------------------------------ | |||||
// <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.WPF.Properties { | |||||
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 Resources { | |||||
private static global::System.Resources.ResourceManager resourceMan; | |||||
private static global::System.Globalization.CultureInfo resourceCulture; | |||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | |||||
internal Resources() { | |||||
} | |||||
/// <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.WPF.Properties.Resources", typeof(Resources).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 <?xml version="1.0" encoding="utf-8" ?> | |||||
///<!-- Warning: Configuration may reset after shadowsocks upgrade. --> | |||||
///<!-- If you messed it up, delete this file and Shadowsocks will create a new one. --> | |||||
///<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||||
/// <targets> | |||||
/// <!-- This line is managed by Shadowsocks. Do not modify it unless you know what you are doing.--> | |||||
/// <target name="file" xsi:type="File" fileName="ss_win_temp\shadowsocks [rest of string was truncated]";. | |||||
/// </summary> | |||||
internal static string NLog { | |||||
get { | |||||
return ResourceManager.GetString("NLog", resourceCulture); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Looks up a localized string similar to listen-address __PRIVOXY_BIND_IP__:__PRIVOXY_BIND_PORT__ | |||||
///toggle 0 | |||||
///logfile ss_privoxy.log | |||||
///show-on-task-bar 0 | |||||
///activity-animation 0 | |||||
///forward-socks5 / __SOCKS_HOST__:__SOCKS_PORT__ . | |||||
///max-client-connections 2048 | |||||
///hide-console | |||||
///. | |||||
/// </summary> | |||||
internal static string privoxy_conf { | |||||
get { | |||||
return ResourceManager.GetString("privoxy_conf", resourceCulture); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Looks up a localized resource of type System.Byte[]. | |||||
/// </summary> | |||||
internal static byte[] privoxy_exe { | |||||
get { | |||||
object obj = ResourceManager.GetObject("privoxy_exe", resourceCulture); | |||||
return ((byte[])(obj)); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,130 @@ | |||||
<?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> | |||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> | |||||
<data name="NLog" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||||
<value>..\Resources\NLog.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value> | |||||
</data> | |||||
<data name="privoxy_conf" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||||
<value>..\Resources\privoxy_conf.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value> | |||||
</data> | |||||
<data name="privoxy_exe" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||||
<value>..\Resources\privoxy.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||||
</data> | |||||
</root> |
@@ -11,15 +11,46 @@ | |||||
<Version>1.0.0</Version> | <Version>1.0.0</Version> | ||||
<ApplicationIcon>Assets\shadowsocks.ico</ApplicationIcon> | <ApplicationIcon>Assets\shadowsocks.ico</ApplicationIcon> | ||||
<NoWin32Manifest>true</NoWin32Manifest> | <NoWin32Manifest>true</NoWin32Manifest> | ||||
<Nullable>enable</Nullable> | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | |||||
<None Remove="Resources\NLog.config" /> | |||||
<None Remove="Resources\privoxy.exe.gz" /> | |||||
<None Remove="Resources\privoxy_conf.txt" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-Bold.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-BoldItalic.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-ExtraLight.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-ExtraLightItalic.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-Italic.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-Light.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-LightItalic.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-Medium.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-MediumItalic.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-Regular.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-SemiBold.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-SemiBoldItalic.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-Thin.ttf" /> | |||||
<None Remove="Resources\RobotoMono\RobotoMono-ThinItalic.ttf" /> | |||||
<None Remove="Resources\shadowsocks.ico" /> | |||||
<None Remove="Resources\ss128.pdn" /> | |||||
<None Remove="Resources\ss32.pdn" /> | |||||
<None Remove="Resources\ss32Fill.png" /> | |||||
<None Remove="Resources\ss32In.png" /> | |||||
<None Remove="Resources\ss32Out.png" /> | |||||
<None Remove="Resources\ss32Outline.png" /> | |||||
<None Remove="Resources\ssw128.png" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="MaterialDesignThemes" Version="3.2.0" /> | <PackageReference Include="MaterialDesignThemes" Version="3.2.0" /> | ||||
<PackageReference Include="MdXaml" Version="1.6.0" /> | |||||
<PackageReference Include="OxyPlot.Wpf" Version="2.0.0" /> | <PackageReference Include="OxyPlot.Wpf" Version="2.0.0" /> | ||||
<PackageReference Include="ReactiveUI.Events.WPF" Version="12.1.1" /> | |||||
<PackageReference Include="ReactiveUI.Fody" Version="12.1.1" /> | |||||
<PackageReference Include="ReactiveUI.Events.WPF" Version="12.1.5" /> | |||||
<PackageReference Include="ReactiveUI.Fody" Version="12.1.5" /> | |||||
<PackageReference Include="ReactiveUI.Validation" Version="1.8.6" /> | <PackageReference Include="ReactiveUI.Validation" Version="1.8.6" /> | ||||
<PackageReference Include="ReactiveUI.WPF" Version="12.1.1" /> | |||||
<PackageReference Include="ReactiveUI.WPF" Version="12.1.5" /> | |||||
<PackageReference Include="Splat.NLog" Version="9.6.1" /> | |||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20371.2" /> | <PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20371.2" /> | ||||
<PackageReference Include="WPFLocalizeExtension" Version="3.8.0" /> | <PackageReference Include="WPFLocalizeExtension" Version="3.8.0" /> | ||||
<PackageReference Include="ZXing.Net" Version="0.16.6" /> | <PackageReference Include="ZXing.Net" Version="0.16.6" /> | ||||
@@ -31,6 +62,11 @@ | |||||
<AutoGen>True</AutoGen> | <AutoGen>True</AutoGen> | ||||
<DependentUpon>Strings.resx</DependentUpon> | <DependentUpon>Strings.resx</DependentUpon> | ||||
</Compile> | </Compile> | ||||
<Compile Update="Properties\Resources.Designer.cs"> | |||||
<DesignTime>True</DesignTime> | |||||
<AutoGen>True</AutoGen> | |||||
<DependentUpon>Resources.resx</DependentUpon> | |||||
</Compile> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -38,15 +74,43 @@ | |||||
<Generator>ResXFileCodeGenerator</Generator> | <Generator>ResXFileCodeGenerator</Generator> | ||||
<LastGenOutput>Strings.Designer.cs</LastGenOutput> | <LastGenOutput>Strings.Designer.cs</LastGenOutput> | ||||
</EmbeddedResource> | </EmbeddedResource> | ||||
<EmbeddedResource Update="Properties\Resources.resx"> | |||||
<Generator>ResXFileCodeGenerator</Generator> | |||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> | |||||
</EmbeddedResource> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<Folder Include="Resources\" /> | |||||
<ProjectReference Include="..\Shadowsocks.Net\Shadowsocks.Net.csproj" /> | |||||
<ProjectReference Include="..\Shadowsocks\Shadowsocks.csproj" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<ProjectReference Include="..\Shadowsocks.Net\Shadowsocks.Net.csproj" /> | |||||
<ProjectReference Include="..\Shadowsocks\Shadowsocks.csproj" /> | |||||
<Resource Include="Resources\NLog.config" /> | |||||
<Resource Include="Resources\privoxy.exe.gz" /> | |||||
<Resource Include="Resources\privoxy_conf.txt" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-Bold.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-BoldItalic.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-ExtraLight.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-ExtraLightItalic.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-Italic.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-Light.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-LightItalic.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-Medium.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-MediumItalic.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-Regular.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-SemiBold.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-SemiBoldItalic.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-Thin.ttf" /> | |||||
<Resource Include="Resources\RobotoMono\RobotoMono-ThinItalic.ttf" /> | |||||
<Resource Include="Resources\shadowsocks.ico" /> | |||||
<Resource Include="Resources\ss128.pdn" /> | |||||
<Resource Include="Resources\ss32.pdn" /> | |||||
<Resource Include="Resources\ss32Fill.png" /> | |||||
<Resource Include="Resources\ss32In.png" /> | |||||
<Resource Include="Resources\ss32Out.png" /> | |||||
<Resource Include="Resources\ss32Outline.png" /> | |||||
<Resource Include="Resources\ssw128.png" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
</Project> | </Project> |
@@ -1,189 +0,0 @@ | |||||
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.WPF.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 | |||||
}; | |||||
} | |||||
} |
@@ -1,115 +0,0 @@ | |||||
<reactiveui:ReactiveUserControl | |||||
x:Class="Shadowsocks.WPF.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.WPF.Views" | |||||
xmlns:vms="clr-namespace:Shadowsocks.WPF.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> |
@@ -1,169 +0,0 @@ | |||||
using ReactiveUI; | |||||
using Shadowsocks.WPF.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.WPF.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); | |||||
}); | |||||
} | |||||
} | |||||
} |
@@ -2,6 +2,7 @@ | |||||
<PropertyGroup> | <PropertyGroup> | ||||
<TargetFramework>netcoreapp3.1</TargetFramework> | <TargetFramework>netcoreapp3.1</TargetFramework> | ||||
<Nullable>enable</Nullable> | |||||
</PropertyGroup> | </PropertyGroup> | ||||
</Project> | </Project> |
@@ -85,6 +85,15 @@ cache: | |||||
# "project" is relative to the original build directory and not influenced by directory changes in "before_build". | # "project" is relative to the original build directory and not influenced by directory changes in "before_build". | ||||
build: | build: | ||||
# parallel: true # enable MSBuild parallel builds | # parallel: true # enable MSBuild parallel builds | ||||
# project: MyTestAzureCS.sln # path to Visual Studio solution or project | |||||
# publish_wap: true # package Web Application Projects (WAP) for Web Deploy | |||||
# publish_wap_xcopy: true # package Web Application Projects (WAP) for XCopy deployment | |||||
# publish_wap_beanstalk: true # Package Web Applications for AWS Elastic Beanstalk deployment | |||||
# publish_wap_octopus: true # Package Web Applications for Octopus deployment | |||||
# publish_azure_webjob: true # Package Azure WebJobs for Zip Push deployment | |||||
# publish_azure: true # package Azure Cloud Service projects and push to artifacts | |||||
# publish_aspnet_core: true # Package ASP.NET Core projects | |||||
# publish_core_console: true # Package .NET Core console projects | |||||
# publish_nuget: true # package projects with .nuspec files and push to artifacts | # publish_nuget: true # package projects with .nuspec files and push to artifacts | ||||
# publish_nuget_symbols: true # generate and publish NuGet symbol packages | # publish_nuget_symbols: true # generate and publish NuGet symbol packages | ||||
# include_nuget_references: true # add -IncludeReferencedProjects option while packaging NuGet artifacts | # include_nuget_references: true # add -IncludeReferencedProjects option while packaging NuGet artifacts | ||||
@@ -121,22 +130,24 @@ after_build: | |||||
$WorkingFolder = "$env:APPVEYOR_BUILD_FOLDER\working" | $WorkingFolder = "$env:APPVEYOR_BUILD_FOLDER\working" | ||||
New-Item $WorkingFolder -ItemType Directory -Force | New-Item $WorkingFolder -ItemType Directory -Force | ||||
$HashFile = "all.hash" | $HashFile = "all.hash" | ||||
$TargetProjectFile = "$env:APPVEYOR_BUILD_FOLDER\Shadowsocks.WPF\Shadowsocks.WPF.csproj" | |||||
# Publish and deploy | # Publish and deploy | ||||
# Normal package | # Normal package | ||||
dotnet publish -c $env:CONFIGURATION $env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\shadowsocks-csharp.csproj | |||||
dotnet publish -c $env:CONFIGURATION $TargetProjectFile | |||||
# Package into a self-contained single-file executable | # Package into a self-contained single-file executable | ||||
dotnet publish -r win-x64 -c $env:CONFIGURATION -p:PublishSingleFile=true -p:PublishTrimmed=true $env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\shadowsocks-csharp.csproj | |||||
dotnet publish -r win-x86 -c $env:CONFIGURATION -p:PublishSingleFile=true -p:PublishTrimmed=true $env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\shadowsocks-csharp.csproj | |||||
dotnet publish -r win-x64 -c $env:CONFIGURATION -p:PublishSingleFile=true -p:PublishTrimmed=true $TargetProjectFile | |||||
dotnet publish -r win-x86 -c $env:CONFIGURATION -p:PublishSingleFile=true -p:PublishTrimmed=true $TargetProjectFile | |||||
$ZipDev = "$WorkingFolder\shadowsocks-windows-$env:APPVEYOR_BUILD_VERSION-dev.zip" | $ZipDev = "$WorkingFolder\shadowsocks-windows-$env:APPVEYOR_BUILD_VERSION-dev.zip" | ||||
$ZipMinimal = "$WorkingFolder\shadowsocks-windows-$env:APPVEYOR_BUILD_VERSION-minimal.zip" | $ZipMinimal = "$WorkingFolder\shadowsocks-windows-$env:APPVEYOR_BUILD_VERSION-minimal.zip" | ||||
$ZipSingleExeX64 = "$WorkingFolder\shadowsocks-windows-$env:APPVEYOR_BUILD_VERSION-portable-x64.zip" | $ZipSingleExeX64 = "$WorkingFolder\shadowsocks-windows-$env:APPVEYOR_BUILD_VERSION-portable-x64.zip" | ||||
$ZipSingleExeX86 = "$WorkingFolder\shadowsocks-windows-$env:APPVEYOR_BUILD_VERSION-portable-x86.zip" | $ZipSingleExeX86 = "$WorkingFolder\shadowsocks-windows-$env:APPVEYOR_BUILD_VERSION-portable-x86.zip" | ||||
7z a $ZipDev "$env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\bin\$env:CONFIGURATION\netcoreapp3.1\*" | |||||
7z a $ZipMinimal "$env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\bin\Any CPU\$env:CONFIGURATION\netcoreapp3.1\publish\*" | |||||
7z a $ZipSingleExeX64 "$env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\bin\Any CPU\$env:CONFIGURATION\netcoreapp3.1\win-x64\publish\Shadowsocks.exe" | |||||
7z a $ZipSingleExeX86 "$env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\bin\Any CPU\$env:CONFIGURATION\netcoreapp3.1\win-x86\publish\Shadowsocks.exe" | |||||
7z a $ZipDev "$env:APPVEYOR_BUILD_FOLDER\Shadowsocks.WPF\bin\$env:CONFIGURATION\netcoreapp3.1\*" | |||||
7z a $ZipMinimal "$env:APPVEYOR_BUILD_FOLDER\Shadowsocks.WPF\bin\Any CPU\$env:CONFIGURATION\netcoreapp3.1\publish\*" | |||||
7z a $ZipSingleExeX64 "$env:APPVEYOR_BUILD_FOLDER\Shadowsocks.WPF\bin\Any CPU\$env:CONFIGURATION\netcoreapp3.1\win-x64\publish\Shadowsocks.exe" | |||||
7z a $ZipSingleExeX86 "$env:APPVEYOR_BUILD_FOLDER\Shadowsocks.WPF\bin\Any CPU\$env:CONFIGURATION\netcoreapp3.1\win-x86\publish\Shadowsocks.exe" | |||||
Calculate-Hash -file $ZipDev | Out-File -FilePath $HashFile -Append | Calculate-Hash -file $ZipDev | Out-File -FilePath $HashFile -Append | ||||
Calculate-Hash -file $ZipMinimal | Out-File -FilePath $HashFile -Append | Calculate-Hash -file $ZipMinimal | Out-File -FilePath $HashFile -Append | ||||
Calculate-Hash -file $ZipSingleExeX64 | Out-File -FilePath $HashFile -Append | Calculate-Hash -file $ZipSingleExeX64 | Out-File -FilePath $HashFile -Append | ||||
@@ -1,20 +1,20 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> | |||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> | |||||
<PropertyGroup> | <PropertyGroup> | ||||
<OutputType>WinExe</OutputType> | <OutputType>WinExe</OutputType> | ||||
<TargetFramework>netcoreapp3.1</TargetFramework> | <TargetFramework>netcoreapp3.1</TargetFramework> | ||||
<RootNamespace>Shadowsocks</RootNamespace> | |||||
<UseWPF>true</UseWPF> | <UseWPF>true</UseWPF> | ||||
<UseWindowsForms>true</UseWindowsForms> | <UseWindowsForms>true</UseWindowsForms> | ||||
<Authors>clowwindy & community 2020</Authors> | <Authors>clowwindy & community 2020</Authors> | ||||
<PackageId>Shadowsocks</PackageId> | |||||
<Product>Shadowsocks</Product> | |||||
<PackageId>Shadowsocks.Legacy</PackageId> | |||||
<Product>Shadowsocks Legacy Project</Product> | |||||
<Version>4.3.0.0</Version> | <Version>4.3.0.0</Version> | ||||
<AssemblyName>Shadowsocks</AssemblyName> | <AssemblyName>Shadowsocks</AssemblyName> | ||||
<ApplicationIcon>shadowsocks.ico</ApplicationIcon> | <ApplicationIcon>shadowsocks.ico</ApplicationIcon> | ||||
<StartupObject>Shadowsocks.Program</StartupObject> | <StartupObject>Shadowsocks.Program</StartupObject> | ||||
<Nullable>disable</Nullable> | <Nullable>disable</Nullable> | ||||
<ApplicationManifest>app.manifest</ApplicationManifest> | <ApplicationManifest>app.manifest</ApplicationManifest> | ||||
<RootNamespace>Shadowsocks.Legacy</RootNamespace> | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -55,10 +55,6 @@ | |||||
<PackageReference Include="ZXing.Net" Version="0.16.6" /> | <PackageReference Include="ZXing.Net" Version="0.16.6" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<ProjectReference Include="..\Shadowsocks.WPF\Shadowsocks.WPF.csproj" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | <ItemGroup> | ||||
<Resource Include="Data\i18n.csv" /> | <Resource Include="Data\i18n.csv" /> | ||||
</ItemGroup> | </ItemGroup> | ||||