using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Reflection; using System.Text; using System.Windows.Forms; using Shadowsocks.Controller; using Shadowsocks.Model; using Shadowsocks.Properties; using Shadowsocks.Util; namespace Shadowsocks.View { public partial class HotkeySettingsForm : Form { private ShadowsocksController _controller; // this is a copy of configuration that we are working on private HotkeyConfig _modifiedConfig; private StringBuilder _sb = new StringBuilder(); private IEnumerable _allTextBoxes; private static Label _lb = null; private static HotKeys.HotKeyCallBackHandler _callBack = null; public HotkeySettingsForm(ShadowsocksController controller) { InitializeComponent(); UpdateTexts(); this.Icon = Icon.FromHandle(Resources.ssw128.GetHicon()); _controller = controller; _controller.ConfigChanged += controller_ConfigChanged; LoadCurrentConfiguration(); // get all textboxes belong to this form _allTextBoxes = HotKeys.GetChildControls(this.tableLayoutPanel1); if (!_allTextBoxes.Any()) throw new Exception("Cannot get all textboxes"); } private void controller_ConfigChanged(object sender, EventArgs e) { LoadCurrentConfiguration(); } private void LoadCurrentConfiguration() { _modifiedConfig = _controller.GetConfigurationCopy().hotkey; LoadConfiguration(_modifiedConfig); } private void LoadConfiguration(HotkeyConfig config) { SwitchSystemProxyTextBox.Text = config.SwitchSystemProxy; ChangeToPacTextBox.Text = config.ChangeToPac; ChangeToGlobalTextBox.Text = config.ChangeToGlobal; SwitchAllowLanTextBox.Text = config.SwitchAllowLan; ShowLogsTextBox.Text = config.ShowLogs; ServerMoveUpTextBox.Text = config.ServerMoveUp; ServerMoveDownTextBox.Text = config.ServerMoveDown; } private void UpdateTexts() { // I18N stuff SwitchSystemProxyLabel.Text = I18N.GetString("Switch system proxy"); ChangeToPacLabel.Text = I18N.GetString("Switch to PAC mode"); ChangeToGlobalLabel.Text = I18N.GetString("Switch to Global mode"); SwitchAllowLanLabel.Text = I18N.GetString("Switch share over LAN"); ShowLogsLabel.Text = I18N.GetString("Show Logs"); ServerMoveUpLabel.Text = I18N.GetString("Switch to prev server"); ServerMoveDownLabel.Text = I18N.GetString("Switch to next server"); btnOK.Text = I18N.GetString("OK"); btnCancel.Text = I18N.GetString("Cancel"); btnRegisterAll.Text = I18N.GetString("Reg All"); this.Text = I18N.GetString("Edit Hotkeys..."); } /// /// Capture hotkey - Press key /// private void HotkeyDown(object sender, KeyEventArgs e) { _sb.Length = 0; //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(); } /// /// Capture hotkey - Release key /// private void HotkeyUp(object sender, KeyEventArgs e) { TextBox tb = sender as TextBox; string content = tb.Text.TrimEnd(); if (content.Length >= 1 && content[content.Length - 1] == '+') { tb.Text = ""; } } private void TextBox_TextChanged(object sender, EventArgs e) { TextBox tb = sender as TextBox; if (tb.Text == "") { // unreg UnregHotkey(tb); } } private void UnregHotkey(TextBox tb) { PrepareForHotkey(tb, out _callBack, out _lb); UnregPrevHotkey(_callBack); } private void CancelButton_Click(object sender, EventArgs e) { this.Close(); } private void OKButton_Click(object sender, EventArgs e) { // try to register, notify to change settings if failed foreach (var tb in _allTextBoxes) { if (tb.Text.IsNullOrEmpty()) { continue; } if (!TryRegHotkey(tb)) { MessageBox.Show(I18N.GetString("Register hotkey failed")); return; } } // All check passed, saving SaveConfig(); this.Close(); } private void RegisterAllButton_Click(object sender, EventArgs e) { foreach (var tb in _allTextBoxes) { if (tb.Text.IsNullOrEmpty()) { continue; } TryRegHotkey(tb); } } private bool TryRegHotkey(TextBox tb) { var hotkey = HotKeys.Str2HotKey(tb.Text); if (hotkey == null) { MessageBox.Show(string.Format(I18N.GetString("Cannot parse hotkey: {0}"), tb.Text)); tb.Clear(); return false; } PrepareForHotkey(tb, out _callBack, out _lb); UnregPrevHotkey(_callBack); // try to register keys // if already registered by other progs // notify to change // use the corresponding label color to indicate // reg result. // Green: not occupied by others and operation succeed // Yellow: already registered by other program and need action: disable by clear the content // or change to another one // Transparent without color: first run or empty config bool regResult = HotKeys.Regist(hotkey, _callBack); _lb.BackColor = regResult ? Color.Green : Color.Yellow; return regResult; } private static void UnregPrevHotkey(HotKeys.HotKeyCallBackHandler cb) { GlobalHotKey.HotKey prevHotKey; if (HotKeys.IsCallbackExists(cb, out prevHotKey)) { // unregister previous one HotKeys.UnRegist(prevHotKey); } } private void SaveConfig() { _modifiedConfig.SwitchSystemProxy = SwitchSystemProxyTextBox.Text; _modifiedConfig.ChangeToPac = ChangeToPacTextBox.Text; _modifiedConfig.ChangeToGlobal = ChangeToGlobalTextBox.Text; _modifiedConfig.SwitchAllowLan = SwitchAllowLanTextBox.Text; _modifiedConfig.ShowLogs = ShowLogsTextBox.Text; _modifiedConfig.ServerMoveUp = ServerMoveUpTextBox.Text; _modifiedConfig.ServerMoveDown = ServerMoveDownTextBox.Text; _controller.SaveHotkeyConfig(_modifiedConfig); } #region Callbacks private void SwitchSystemProxyCallback() { bool enabled = _controller.GetConfigurationCopy().enabled; _controller.ToggleEnable(!enabled); } private void ChangeToPacCallback() { bool enabled = _controller.GetConfigurationCopy().enabled; if (enabled == false) return; _controller.ToggleGlobal(false); } private void ChangeToGlobalCallback() { bool enabled = _controller.GetConfigurationCopy().enabled; if (enabled == false) return; _controller.ToggleGlobal(true); } 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 /// /// Find correct callback and corresponding label /// /// /// /// private void PrepareForHotkey(TextBox tb, out HotKeys.HotKeyCallBackHandler cb, out Label lb) { /* * XXX: The labelName, TextBoxName and callbackName * must follow this rule to make use of reflection * * */ if (tb == null) throw new ArgumentNullException(nameof(tb)); var pos = tb.Name.LastIndexOf("TextBox", StringComparison.OrdinalIgnoreCase); var rawName = tb.Name.Substring(0, pos); var labelName = rawName + "Label"; var callbackName = rawName + "Callback"; var callback = GetDelegateViaMethodName(this.GetType(), callbackName); if (callback == null) { throw new Exception($"{callbackName} not found"); } cb = callback as HotKeys.HotKeyCallBackHandler; object label = GetFieldViaName(this.GetType(), labelName, this); if (label == null) { throw new Exception($"{labelName} not found"); } lb = label as Label; } /// /// /// /// from which type /// field name /// pass null if static field /// private static object GetFieldViaName(Type type, string name, object obj) { if (type == null) throw new ArgumentNullException(nameof(type)); if (name.IsNullOrEmpty()) throw new ArgumentException(nameof(name)); // In general, TextBoxes and Labels are private FieldInfo fi = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Static); return fi == null ? null : fi.GetValue(obj); } /// /// Create hotkey callback handler delegate based on callback name /// /// /// /// private Delegate GetDelegateViaMethodName(Type type, 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, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase); return dynMethod == null ? null : Delegate.CreateDelegate(delegateType, this, dynMethod); } #endregion } }