diff --git a/Source/GrasscutterTools/GrasscutterTools.csproj b/Source/GrasscutterTools/GrasscutterTools.csproj
index 85a670f..cdffa18 100644
--- a/Source/GrasscutterTools/GrasscutterTools.csproj
+++ b/Source/GrasscutterTools/GrasscutterTools.csproj
@@ -280,9 +280,12 @@
True
True
+
+
+
diff --git a/Source/GrasscutterTools/Utils/AppHotKey.cs b/Source/GrasscutterTools/Utils/AppHotKey.cs
new file mode 100644
index 0000000..4228120
--- /dev/null
+++ b/Source/GrasscutterTools/Utils/AppHotKey.cs
@@ -0,0 +1,61 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace GrasscutterTools.Utils
+{
+ public static class AppHotKey
+ {
+ ///
+ /// 注册热键
+ ///
+ /// 窗口句柄
+ /// 热键ID
+ /// 组合键
+ /// 热键
+ public static void RegKey(IntPtr hwnd, int hotKey_id, KeyModifiers keyModifiers, Keys key)
+ {
+ if (!RegisterHotKey(hwnd, hotKey_id, keyModifiers, key))
+ throw new Win32Exception();
+ }
+
+ ///
+ /// 注销热键
+ ///
+ /// 窗口句柄
+ /// 热键ID
+ public static void UnRegKey(IntPtr hwnd, int hotKey_id)
+ {
+ //注销Id号为hotKey_id的热键设定
+ UnregisterHotKey(hwnd, hotKey_id);
+ }
+
+ //如果函数执行成功,返回值不为0。
+ //如果函数执行失败,返回值为0。要得到扩展错误信息,调用GetLastError。
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern bool RegisterHotKey(
+ IntPtr hWnd, //要定义热键的窗口的句柄
+ int id, //定义热键ID(不能与其它ID重复)
+ KeyModifiers fsModifiers, //标识热键是否在按Alt、Ctrl、Shift、Windows等键时才会生效
+ Keys vk //定义热键的内容
+ );
+
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern bool UnregisterHotKey(
+ IntPtr hWnd, //要取消热键的窗口的句柄
+ int id //要取消热键的ID
+ );
+
+ //定义了辅助键的名称(将数字转变为字符以便于记忆,也可去除此枚举而直接使用数值)
+ [Flags]
+ public enum KeyModifiers
+ {
+ None = 0,
+ Alt = 1,
+ Ctrl = 2,
+ Shift = 4,
+ WindowsKey = 8
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/GrasscutterTools/Utils/HotKey.cs b/Source/GrasscutterTools/Utils/HotKey.cs
new file mode 100644
index 0000000..af68967
--- /dev/null
+++ b/Source/GrasscutterTools/Utils/HotKey.cs
@@ -0,0 +1,298 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Windows.Forms;
+using System.Xml.Serialization;
+
+namespace GrasscutterTools.Utils
+{
+ ///
+ /// KeyGo 核心功能类
+ ///
+ public class KeyGo
+ {
+ #region Member
+
+ private static int _RegMaxID;
+
+ [XmlIgnore]
+ public IntPtr FormHandle { get; set; }
+
+ public List Items { get; set; } = new List();
+
+ #endregion Member
+
+ #region FILE IO
+
+ ///
+ /// Loads the XML.
+ ///
+ /// The file path.
+ ///
+ public static KeyGo LoadXml(string filePath)
+ {
+ KeyGo data = null;
+ if (File.Exists(filePath))
+ {
+ XmlSerializer formatter = new XmlSerializer(typeof(KeyGo));
+ using (var stream = File.OpenRead(filePath))
+ {
+ if (stream.Length > 0)
+ {
+ data = formatter.Deserialize(stream) as KeyGo;
+ }
+ }
+ }
+ return data;
+ }
+
+ ///
+ /// Saves the XML.
+ ///
+ /// The file path.
+ public void SaveXml(string filePath)
+ {
+ if (!File.Exists(filePath))
+ Directory.CreateDirectory(Path.GetDirectoryName(filePath));
+
+ XmlSerializer formatter = new XmlSerializer(typeof(KeyGo));
+ using (var stream = File.Create(filePath))
+ {
+ formatter.Serialize(stream, this);
+ }
+ }
+
+ #endregion FILE IO
+
+ #region HotKey Register
+
+ ///
+ /// Regs all key.
+ ///
+ public void RegAllKey()
+ {
+ foreach (var item in Items)
+ {
+ if (!item.Enabled)
+ continue;
+
+ try
+ {
+ RegKey(item);
+ }
+ catch (Exception)
+ {
+ // 忽视异常,外部通过ID是否被设置判断执行结果
+ }
+ }
+ }
+
+ ///
+ /// Uns the reg all key.
+ ///
+ public void UnRegAllKey()
+ {
+ foreach (var item in Items)
+ {
+ try
+ {
+ UnRegKey(item);
+ }
+ catch (Exception)
+ {
+ // 忽视异常,外部通过ID是否被设置判断执行结果
+ }
+ }
+ }
+
+ ///
+ /// 注册热键 - 成功后,会设置 HotKeyID
+ ///
+ /// The item.
+ ///
+ /// item
+ /// or
+ /// HotKey - 热键不能为空!
+ ///
+ ///
+ /// 功能键不能为空!
+ /// or
+ /// 快捷键不能为空!
+ ///
+ public void RegKey(HotKeyItem item)
+ {
+ if (item is null)
+ throw new ArgumentNullException(nameof(item));
+ if (string.IsNullOrWhiteSpace(item.HotKey))
+ throw new ArgumentNullException(nameof(item.HotKey), "热键不能为空!");
+
+ // 如果注册过该热键,ID不为0。卸载热键会将ID置零。
+ if (item.HotKeyID != 0)
+ return;
+
+ int id = Interlocked.Increment(ref _RegMaxID);
+
+ var keys = item.HotKey.Split('+');
+ Keys keyCode = Keys.None;
+ AppHotKey.KeyModifiers keyModifiers = AppHotKey.KeyModifiers.None;
+ foreach (var key in keys)
+ {
+ switch (key.ToLower())
+ {
+ case "ctrl":
+ keyModifiers |= AppHotKey.KeyModifiers.Ctrl;
+ break;
+
+ case "shift":
+ keyModifiers |= AppHotKey.KeyModifiers.Shift;
+ break;
+
+ case "alt":
+ keyModifiers |= AppHotKey.KeyModifiers.Alt;
+ break;
+
+ case "win":
+ keyModifiers |= AppHotKey.KeyModifiers.WindowsKey;
+ break;
+
+ default:
+ keyCode = (Keys)Enum.Parse(typeof(Keys), key);
+ break;
+ }
+ }
+
+ if (keyModifiers == AppHotKey.KeyModifiers.None)
+ throw new InvalidOperationException("功能键不能为空!");
+ if (keyCode == Keys.None)
+ throw new InvalidOperationException("快捷键不能为空!");
+
+ AppHotKey.RegKey(FormHandle, id, keyModifiers, keyCode);
+ item.HotKeyID = id;
+ }
+
+ ///
+ /// 注销热键 - 完成后,会清零 HotKeyID
+ ///
+ /// The item.
+ /// item
+ public void UnRegKey(HotKeyItem item)
+ {
+ if (item is null)
+ throw new ArgumentNullException(nameof(item));
+ if (item.HotKeyID == 0)
+ return;
+
+ AppHotKey.UnRegKey(FormHandle, item.HotKeyID);
+ item.HotKeyID = 0;
+ }
+
+ #endregion HotKey Register
+
+ #region HotKey Trigger
+
+ ///
+ /// 热键触发时调用
+ ///
+ public event EventHandler HotKeyTriggerEvent;
+
+ private bool OnHotKeyTrigger(HotKeyItem item)
+ {
+ var args = new HotKeyTriggerEventArgs { HotKeyItem = item };
+ HotKeyTriggerEvent?.Invoke(this, args);
+ return args.Handle;
+ }
+
+ ///
+ /// Processes the hotkey.
+ ///
+ /// The hot key identifier.
+ public void ProcessHotkey(int hotKey_id)
+ {
+ var hotkey = Items.Find(k => k.HotKeyID == hotKey_id);
+ if (hotkey != null)
+ {
+ //Console.WriteLine($"ID:{hotkey.HotKeyID} Keys:{hotkey.HotKey} ProcessName:{hotkey.ProcessName}\nStartupPath:{hotkey.StartupPath}");
+ ++hotkey.TriggerCounter;
+ // 触发事件,若被外部处理,则内部不再执行
+ OnHotKeyTrigger(hotkey);
+ }
+ }
+
+ #endregion HotKey Trigger
+
+ #region HotKey Manager
+
+ ///
+ /// 添加一个新热键
+ ///
+ /// The item.
+ ///
+ /// item
+ /// or
+ /// HotKey - 热键不能为空!
+ ///
+ ///
+ /// 功能键不能为空!
+ /// or
+ /// 快捷键不能为空!
+ ///
+ public void AddHotKey(HotKeyItem item)
+ {
+ if (item is null)
+ throw new ArgumentNullException(nameof(item));
+
+ if (item.Enabled)
+ RegKey(item);
+ Items.Add(item);
+ }
+
+ ///
+ /// 删除一个热键
+ ///
+ /// The item.
+ public void DelHotKey(HotKeyItem item)
+ {
+ if (item is null)
+ throw new ArgumentNullException(nameof(item));
+
+ if (item.HotKeyID != 0)
+ UnRegKey(item);
+ Items.Remove(item);
+ }
+
+ ///
+ /// 修改热键
+ ///
+ /// The item.
+ public void ChangeHotKey(HotKeyItem item)
+ {
+ if (item is null)
+ throw new ArgumentNullException(nameof(item));
+
+ // 重新注册
+ if (item.HotKeyID != 0)
+ UnRegKey(item);
+ if (item.Enabled)
+ RegKey(item);
+ }
+
+ #endregion HotKey Manager
+ }
+
+ ///
+ /// 热键触发事件参数
+ ///
+ public class HotKeyTriggerEventArgs
+ {
+ public HotKeyItem HotKeyItem { get; set; }
+
+ ///
+ /// 获取或设置该事件是否已经被处理
+ ///
+ ///
+ /// true if handle; otherwise, false.
+ ///
+ public bool Handle { get; set; }
+ }
+}
diff --git a/Source/GrasscutterTools/Utils/HotKeyItem.cs b/Source/GrasscutterTools/Utils/HotKeyItem.cs
new file mode 100644
index 0000000..5781409
--- /dev/null
+++ b/Source/GrasscutterTools/Utils/HotKeyItem.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Xml.Serialization;
+
+namespace GrasscutterTools.Utils
+{
+ ///
+ /// 热键项
+ ///
+ public class HotKeyItem
+ {
+ ///
+ /// Gets or sets the hot key identifier.
+ ///
+ ///
+ /// The hot key identifier.
+ ///
+ [XmlIgnore]
+ public int HotKeyID { get; set; }
+
+ ///
+ /// Gets or sets the name of the Tag.
+ ///
+ ///
+ /// The name of the Tag.
+ ///
+ public string Tag { get; set; }
+
+ ///
+ /// Gets or sets the Commands.
+ ///
+ ///
+ /// The Commands.
+ ///
+ public string Commands { get; set; }
+
+ ///
+ /// Gets or sets the hot key.
+ /// 组合键之间使用+隔开,例如:"Ctrl+Shift+W"
+ ///
+ ///
+ /// The hot key.
+ ///
+ public string HotKey { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this is enabled.
+ ///
+ ///
+ /// true if enabled; otherwise, false.
+ ///
+ public bool Enabled { get; set; } = true;
+
+ ///
+ /// Gets or sets the trigger counter.
+ ///
+ ///
+ /// The trigger counter.
+ ///
+ public int TriggerCounter { get; set; }
+
+ ///
+ /// Gets or sets the creation time.
+ ///
+ ///
+ /// The creation time.
+ ///
+ public DateTime CreationTime { get; set; } = DateTime.Now;
+
+ ///
+ /// Gets or sets the last modified time.
+ ///
+ ///
+ /// The last modified time.
+ ///
+ public DateTime LastModifiedTime { get; set; } = DateTime.Now;
+ }
+}
\ No newline at end of file