Fix hotkey unregister issue.
Use singleton mode for hotkey callbacks.
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
using System.Reflection;
|
||||
using Shadowsocks.View;
|
||||
|
||||
namespace Shadowsocks.Controller.Hotkeys
|
||||
{
|
||||
public class HotkeyCallbacks
|
||||
{
|
||||
public static HotkeyCallbacks Instance { get; private set; }
|
||||
|
||||
public static void InitInstance(ShadowsocksController controller)
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = new HotkeyCallbacks(controller);
|
||||
}
|
||||
|
||||
private readonly ShadowsocksController _controller;
|
||||
|
||||
private HotkeyCallbacks(ShadowsocksController controller)
|
||||
{
|
||||
_controller = controller;
|
||||
}
|
||||
|
||||
#region Callbacks
|
||||
|
||||
private void SwitchSystemProxyCallback()
|
||||
{
|
||||
bool enabled = _controller.GetConfigurationCopy().enabled;
|
||||
_controller.ToggleEnable(!enabled);
|
||||
}
|
||||
|
||||
private void SwitchProxyModeCallback()
|
||||
{
|
||||
var config = _controller.GetConfigurationCopy();
|
||||
if (config.enabled == false) return;
|
||||
var currStatus = config.global;
|
||||
_controller.ToggleGlobal(!currStatus);
|
||||
}
|
||||
|
||||
private void SwitchAllowLanCallback()
|
||||
{
|
||||
var status = _controller.GetConfigurationCopy().shareOverLan;
|
||||
_controller.ToggleShareOverLAN(!status);
|
||||
}
|
||||
|
||||
private void ShowLogsCallback()
|
||||
{
|
||||
// Get the current MenuViewController in this program via reflection
|
||||
FieldInfo fi = Assembly.GetExecutingAssembly().GetType("Shadowsocks.Program")
|
||||
.GetField("_viewController",
|
||||
BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.IgnoreCase);
|
||||
// To retrieve the value of a static field, pass null here
|
||||
var mvc = fi.GetValue(null) as MenuViewController;
|
||||
mvc.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
|
||||
}
|
||||
}
|
||||
+180
-178
@@ -1,179 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
using GlobalHotKey;
|
||||
|
||||
namespace Shadowsocks.Util
|
||||
{
|
||||
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()
|
||||
{
|
||||
_hotKeyManager = new HotKeyManager();
|
||||
_hotKeyManager.KeyPressed += HotKeyManagerPressed;
|
||||
}
|
||||
|
||||
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 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));
|
||||
try
|
||||
{
|
||||
var key = _keymap.First(x => x.Value == cb).Key;
|
||||
hotkey = key;
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// not found
|
||||
hotkey = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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 (s.IsNullOrEmpty()) 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;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Regist( 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;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Regist(Key key, ModifierKeys modifiers, HotKeyCallBackHandler callBack)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(Key), key))
|
||||
throw new InvalidEnumArgumentException(nameof(key), (int) key, typeof(Key));
|
||||
try
|
||||
{
|
||||
var hotkey = _hotKeyManager.Register(key, modifiers);
|
||||
_keymap[hotkey] = callBack;
|
||||
return true;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// already called this method with the specific hotkey
|
||||
// return success silently
|
||||
return true;
|
||||
}
|
||||
catch (Win32Exception)
|
||||
{
|
||||
// already registered by other programs
|
||||
// notify user to change key
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnRegist(HotKey key)
|
||||
{
|
||||
if (key == null)
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
_hotKeyManager.Unregister(key);
|
||||
if(_keymap.ContainsKey(key))
|
||||
_keymap.Remove(key);
|
||||
}
|
||||
|
||||
public static IEnumerable<TControl> GetChildControls<TControl>(this Control control) where TControl : Control
|
||||
{
|
||||
var children = control.Controls.Count > 0 ? control.Controls.OfType<TControl>() : Enumerable.Empty<TControl>();
|
||||
return children.SelectMany(c => GetChildControls<TControl>(c)).Concat(children);
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
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 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));
|
||||
try
|
||||
{
|
||||
var key = _keymap.First(x => x.Value == cb).Key;
|
||||
hotkey = key;
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// not found
|
||||
hotkey = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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 (s.IsNullOrEmpty()) 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;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Regist( 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;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Regist(Key key, ModifierKeys modifiers, HotKeyCallBackHandler callBack)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(Key), key))
|
||||
throw new InvalidEnumArgumentException(nameof(key), (int) key, typeof(Key));
|
||||
try
|
||||
{
|
||||
var hotkey = _hotKeyManager.Register(key, modifiers);
|
||||
_keymap[hotkey] = callBack;
|
||||
return true;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// already called this method with the specific hotkey
|
||||
// return success silently
|
||||
return true;
|
||||
}
|
||||
catch (Win32Exception)
|
||||
{
|
||||
// already registered by other programs
|
||||
// notify user to change key
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnRegist(HotKey key)
|
||||
{
|
||||
if (key == null)
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
_hotKeyManager.Unregister(key);
|
||||
if(_keymap.ContainsKey(key))
|
||||
_keymap.Remove(key);
|
||||
}
|
||||
|
||||
public static IEnumerable<TControl> GetChildControls<TControl>(this Control control) where TControl : Control
|
||||
{
|
||||
var children = control.Controls.Count > 0 ? control.Controls.OfType<TControl>() : Enumerable.Empty<TControl>();
|
||||
return children.SelectMany(c => GetChildControls<TControl>(c)).Concat(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Windows.Forms;
|
||||
using Microsoft.Win32;
|
||||
|
||||
using Shadowsocks.Controller;
|
||||
using Shadowsocks.Controller.Hotkeys;
|
||||
using Shadowsocks.Util;
|
||||
using Shadowsocks.View;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Shadowsocks.Controller;
|
||||
using Shadowsocks.Controller.Hotkeys;
|
||||
using Shadowsocks.Model;
|
||||
using Shadowsocks.Properties;
|
||||
using Shadowsocks.Util;
|
||||
@@ -243,81 +244,7 @@ namespace Shadowsocks.View
|
||||
_controller.SaveHotkeyConfig(_modifiedConfig);
|
||||
}
|
||||
|
||||
#region Callbacks
|
||||
|
||||
private void SwitchSystemProxyCallback()
|
||||
{
|
||||
bool enabled = _controller.GetConfigurationCopy().enabled;
|
||||
_controller.ToggleEnable(!enabled);
|
||||
}
|
||||
|
||||
private void SwitchProxyModeCallback()
|
||||
{
|
||||
var config = _controller.GetConfigurationCopy();
|
||||
if (config.enabled == false) return;
|
||||
var currStatus = config.global;
|
||||
_controller.ToggleGlobal(!currStatus);
|
||||
}
|
||||
|
||||
private void SwitchAllowLanCallback()
|
||||
{
|
||||
var status = _controller.GetConfigurationCopy().shareOverLan;
|
||||
_controller.ToggleShareOverLAN(!status);
|
||||
}
|
||||
|
||||
private void ShowLogsCallback()
|
||||
{
|
||||
// Get the current MenuViewController in this program via reflection
|
||||
FieldInfo fi = Assembly.GetExecutingAssembly().GetType("Shadowsocks.Program")
|
||||
.GetField("_viewController",
|
||||
BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.IgnoreCase);
|
||||
// To retrieve the value of a static field, pass null here
|
||||
var mvc = fi.GetValue(null) as MenuViewController;
|
||||
mvc.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
|
||||
|
||||
#region Prepare hotkey
|
||||
|
||||
@@ -343,7 +270,7 @@ namespace Shadowsocks.View
|
||||
var labelName = rawName + "Label";
|
||||
var callbackName = rawName + "Callback";
|
||||
|
||||
var callback = GetDelegateViaMethodName(this.GetType(), callbackName);
|
||||
var callback = GetDelegateViaMethodName(callbackName);
|
||||
if (callback == null)
|
||||
{
|
||||
throw new Exception($"{callbackName} not found");
|
||||
@@ -381,15 +308,12 @@ namespace Shadowsocks.View
|
||||
/// <param name="type"></param>
|
||||
/// <param name="methodname"></param>
|
||||
/// <returns></returns>
|
||||
private Delegate GetDelegateViaMethodName(Type type, string methodname)
|
||||
private Delegate GetDelegateViaMethodName(string methodname)
|
||||
{
|
||||
if (type == null) throw new ArgumentNullException(nameof(type));
|
||||
if (methodname.IsNullOrEmpty()) throw new ArgumentException(nameof(methodname));
|
||||
//HotkeySettingsForm form = new HotkeySettingsForm(_controller);
|
||||
Type delegateType = Type.GetType("Shadowsocks.Util.HotKeys").GetNestedType("HotKeyCallBackHandler");
|
||||
MethodInfo dynMethod = type.GetMethod(methodname,
|
||||
MethodInfo dynMethod = typeof(HotkeyCallbacks).GetMethod(methodname,
|
||||
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
||||
return dynMethod == null ? null : Delegate.CreateDelegate(delegateType, this, dynMethod);
|
||||
return dynMethod == null ? null : Delegate.CreateDelegate(typeof(HotKeys.HotKeyCallBackHandler), HotkeyCallbacks.Instance, dynMethod);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
<Compile Include="3rd\zxing\ResultPoint.cs" />
|
||||
<Compile Include="3rd\zxing\ResultPointCallback.cs" />
|
||||
<Compile Include="3rd\zxing\WriterException.cs" />
|
||||
<Compile Include="Controller\System\Hotkeys\HotkeyCallbacks.cs" />
|
||||
<Compile Include="Model\HotKeyConfig.cs" />
|
||||
<Compile Include="Model\ProxyConfig.cs" />
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
@@ -186,7 +187,7 @@
|
||||
<Compile Include="Proxy\Socks5Proxy.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="StringEx.cs" />
|
||||
<Compile Include="Util\Hotkeys.cs" />
|
||||
<Compile Include="Controller\System\Hotkeys\Hotkeys.cs" />
|
||||
<Compile Include="Util\ProcessManagement\Job.cs" />
|
||||
<Compile Include="Util\ProcessManagement\ThreadUtil.cs" />
|
||||
<Compile Include="Encryption\RNG.cs" />
|
||||
|
||||
+1
-1
@@ -2,11 +2,11 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Shadowsocks.Controller;
|
||||
using Shadowsocks.Encryption;
|
||||
using Shadowsocks.Util;
|
||||
using GlobalHotKey;
|
||||
using System.Windows.Input;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using Shadowsocks.Controller.Hotkeys;
|
||||
|
||||
namespace test
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user