PageRenderTime 39ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/Rusty/Common/Keyboard/KeyboardHook.cs

http://github.com/polyethene/IronAHK
C# | 487 lines | 352 code | 118 blank | 17 comment | 103 complexity | a5ee6c7853fd355a2f12bfecbe68c92c MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Windows.Forms;
  5. using System.Threading;
  6. using System.Diagnostics;
  7. namespace IronAHK.Rusty.Common
  8. {
  9. partial class Keyboard
  10. {
  11. // ToDo: Raise Events
  12. /// <summary>
  13. /// Platform independent keyboard Hook base.
  14. /// This Class is abstract.
  15. /// </summary>
  16. internal abstract class KeyboardHook
  17. {
  18. #region Properties
  19. List<HotkeyDefinition> hotkeys;
  20. List<HotstringDefinition> hotstrings;
  21. Dictionary<Keys, bool> pressed;
  22. StringBuilder history;
  23. const int retention = 1024;
  24. public string CurrentHotkey { get; set; }
  25. public string PriorHotkey { get; set; }
  26. public int CurrentHotkeyTime { get; set; }
  27. public int PriorHotkeyTime { get; set; }
  28. public bool Block { get; set; }
  29. #endregion
  30. #region Events
  31. /// <summary>
  32. /// Raised when a Key is pressed/released
  33. /// </summary>
  34. public event EventHandler<IAKeyEventArgs> IAKeyEvent;
  35. #endregion
  36. #region Constructor/destructor
  37. public KeyboardHook()
  38. {
  39. hotkeys = new List<HotkeyDefinition>();
  40. hotstrings = new List<HotstringDefinition>();
  41. history = new StringBuilder(retention);
  42. pressed = new Dictionary<Keys, bool>();
  43. foreach (int i in Enum.GetValues(typeof(Keys)))
  44. if (!pressed.ContainsKey((Keys)i))
  45. pressed.Add((Keys)i, false);
  46. RegisterHook();
  47. }
  48. ~KeyboardHook()
  49. {
  50. DeregisterHook();
  51. }
  52. #endregion
  53. #region Add/remove
  54. public HotkeyDefinition Add(HotkeyDefinition hotkey)
  55. {
  56. hotkeys.Add(hotkey);
  57. return hotkey;
  58. }
  59. public HotstringDefinition Add(HotstringDefinition hotstring)
  60. {
  61. hotstrings.Add(hotstring);
  62. return hotstring;
  63. }
  64. public void Remove(HotkeyDefinition hotkey)
  65. {
  66. hotkeys.Remove(hotkey);
  67. }
  68. public void Remove(HotstringDefinition hotstring)
  69. {
  70. hotstrings.Remove(hotstring);
  71. }
  72. #endregion
  73. #region Key Status
  74. public bool IsPressed(Keys key)
  75. {
  76. if (pressed.ContainsKey(key))
  77. return pressed[key];
  78. else
  79. {
  80. Debug.Fail("Thre should'nt be any key not in this table...");
  81. return false;
  82. }
  83. }
  84. #endregion
  85. #region Conversions
  86. char Letter(Keys key)
  87. {
  88. // HACK: remove Keys translation (and the overload) since it should be passed from the native handler
  89. bool caps = (key & Keys.Shift) == Keys.Shift || pressed[Keys.ShiftKey] || pressed[Keys.LShiftKey] || pressed[Keys.RShiftKey];
  90. key &= ~Keys.Modifiers;
  91. switch (key)
  92. {
  93. case Keys.Space: return ' ';
  94. case Keys.Enter: return '\n';
  95. }
  96. string letter = key.ToString();
  97. if (!caps)
  98. letter = letter.ToLower();
  99. return letter.Length == 1 ? letter[0] : (char)0;
  100. }
  101. #endregion
  102. #region Hotkey fired
  103. [Obsolete]
  104. protected bool KeyReceived(Keys key, bool down)
  105. {
  106. return KeyReceived(key, Letter(key).ToString(), down);
  107. }
  108. protected bool KeyReceived(Keys key, string typed, bool down)
  109. {
  110. if (Block)
  111. return true;
  112. bool block = false;
  113. var args = new IAKeyEventArgs(key, typed, down);
  114. if (IAKeyEvent != null)
  115. IAKeyEvent(this, args);
  116. if (args.Block)
  117. {
  118. block = true;
  119. }
  120. if (args.Handeled)
  121. {
  122. return block;
  123. }
  124. #region Trigger Hotkey
  125. if (!Core.Suspended)
  126. {
  127. pressed[key] = down;
  128. var exec = new List<HotkeyDefinition>();
  129. foreach (var hotkey in hotkeys)
  130. {
  131. bool match = KeyMatch(hotkey.Keys & ~Keys.Modifiers, key) ||
  132. hotkey.Typed.Length != 0 && hotkey.Typed.Equals(typed, StringComparison.CurrentCultureIgnoreCase);
  133. bool up = (hotkey.EnabledOptions & HotkeyDefinition.Options.Up) == HotkeyDefinition.Options.Up;
  134. if (hotkey.Enabled && match && HasModifiers(hotkey) && up != down)
  135. {
  136. exec.Add(hotkey);
  137. if ((hotkey.EnabledOptions & HotkeyDefinition.Options.PassThrough) != HotkeyDefinition.Options.PassThrough)
  138. block = true;
  139. }
  140. }
  141. new Thread(delegate()
  142. {
  143. foreach (var hotkey in exec)
  144. {
  145. PriorHotkeyTime = CurrentHotkeyTime;
  146. CurrentHotkeyTime = Environment.TickCount;
  147. PriorHotkey = CurrentHotkey;
  148. CurrentHotkey = hotkey.ToString();
  149. if (hotkey.Condition())
  150. hotkey.Proc(new object[] { });
  151. }
  152. }).Start();
  153. }
  154. #endregion
  155. if (!down)
  156. return block;
  157. #region Sequencing
  158. if (hotstrings.Count > 0)
  159. {
  160. if (key == Keys.Back && history.Length > 0)
  161. history.Remove(history.Length - 1, 1);
  162. switch (key)
  163. {
  164. case Keys.Left:
  165. case Keys.Right:
  166. case Keys.Down:
  167. case Keys.Up:
  168. case Keys.Next:
  169. case Keys.Prior:
  170. case Keys.Home:
  171. case Keys.End:
  172. history.Length = 0;
  173. break;
  174. case Keys.Alt:
  175. case Keys.LMenu:
  176. case Keys.RMenu:
  177. case Keys.LControlKey:
  178. case Keys.RControlKey:
  179. case Keys.LShiftKey:
  180. case Keys.RShiftKey:
  181. break;
  182. default:
  183. int d = retention - history.Length;
  184. if (d < 0)
  185. history.Remove(history.Length + d, -d);
  186. history.Append(typed);
  187. break;
  188. }
  189. }
  190. if (Core.Suspended)
  191. return block;
  192. #endregion
  193. #region Hotstring
  194. var expand = new List<HotstringDefinition>();
  195. foreach (var hotstring in hotstrings)
  196. {
  197. if (hotstring.Enabled && HasConditions(hotstring))
  198. {
  199. expand.Add(hotstring);
  200. if ((hotstring.EnabledOptions & HotstringDefinition.Options.Reset) == HotstringDefinition.Options.Reset)
  201. history.Length = 0;
  202. }
  203. }
  204. string trigger = history.Length > 0 ? history[history.Length - 1].ToString() : null;
  205. foreach (var hotstring in expand)
  206. {
  207. block = true;
  208. new Thread(delegate()
  209. {
  210. PriorHotkeyTime = CurrentHotkeyTime;
  211. CurrentHotkeyTime = Environment.TickCount;
  212. PriorHotkey = CurrentHotkey;
  213. CurrentHotkey = hotstring.ToString();
  214. int length = hotstring.Sequence.Length;
  215. bool auto = (hotstring.EnabledOptions & HotstringDefinition.Options.AutoTrigger) == HotstringDefinition.Options.AutoTrigger;
  216. if (auto)
  217. length--;
  218. if ((hotstring.EnabledOptions & HotstringDefinition.Options.Backspace) == HotstringDefinition.Options.Backspace && length > 0)
  219. {
  220. int n = length + 1;
  221. history.Remove(history.Length - n, n);
  222. Backspace(length);
  223. // UNDONE: hook on Windows captures triggering key and blocks it, but X11 allows it through and needs an extra backspace
  224. if (!auto && Environment.OSVersion.Platform != PlatformID.Win32NT)
  225. Backspace(1);
  226. }
  227. hotstring.Proc(new object[] { });
  228. if ((hotstring.EnabledOptions & HotstringDefinition.Options.OmitEnding) == HotstringDefinition.Options.OmitEnding)
  229. {
  230. if ((hotstring.EnabledOptions & HotstringDefinition.Options.Backspace) == HotstringDefinition.Options.Backspace &&
  231. (hotstring.EnabledOptions & HotstringDefinition.Options.AutoTrigger) != HotstringDefinition.Options.AutoTrigger)
  232. {
  233. history.Remove(history.Length - 1, 1);
  234. Backspace(1);
  235. }
  236. }
  237. else if (trigger != null && !auto)
  238. SendMixed(trigger);
  239. }).Start();
  240. }
  241. return block;
  242. #endregion
  243. }
  244. bool KeyMatch(Keys expected, Keys received)
  245. {
  246. expected &= ~Keys.Modifiers;
  247. received &= ~Keys.Modifiers;
  248. if (expected == received)
  249. return true;
  250. switch (expected)
  251. {
  252. case Keys.ControlKey:
  253. return received == Keys.LControlKey || received == Keys.RControlKey;
  254. case Keys.ShiftKey:
  255. return received == Keys.LShiftKey || received == Keys.RShiftKey;
  256. }
  257. return false;
  258. }
  259. bool HasModifiers(HotkeyDefinition hotkey)
  260. {
  261. if (hotkey.Extra != Keys.None && !pressed[hotkey.Extra])
  262. return false;
  263. if ((hotkey.EnabledOptions & HotkeyDefinition.Options.IgnoreModifiers) == HotkeyDefinition.Options.IgnoreModifiers)
  264. return true;
  265. bool[,] modifiers = {
  266. { (hotkey.Keys & Keys.Alt) == Keys.Alt, pressed[Keys.Alt] || pressed[Keys.LMenu] || pressed[Keys.RMenu], (hotkey.Keys & Keys.LMenu) == Keys.LMenu },
  267. { (hotkey.Keys & Keys.Control) == Keys.Control, pressed[Keys.Control] || pressed[Keys.LControlKey] || pressed[Keys.RControlKey], (hotkey.Keys & Keys.ControlKey) == Keys.ControlKey },
  268. { (hotkey.Keys & Keys.Shift) == Keys.Shift, pressed[Keys.Shift] || pressed[Keys.LShiftKey] || pressed[Keys.RShiftKey], (hotkey.Keys & Keys.ShiftKey) == Keys.ShiftKey }
  269. };
  270. for (int i = 0; i < 3; i++)
  271. if ((modifiers[i, 0] && !modifiers[i, 1]) || (modifiers[i, 1] && !modifiers[i, 0] && !modifiers[i, 2]))
  272. return false;
  273. return true;
  274. }
  275. bool HasConditions(HotstringDefinition hotstring)
  276. {
  277. string history = this.history.ToString();
  278. if (history.Length == 0)
  279. return false;
  280. var compare = (hotstring.EnabledOptions & HotstringDefinition.Options.CaseSensitive) == HotstringDefinition.Options.CaseSensitive ?
  281. StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase;
  282. int x = history.Length - hotstring.Sequence.Length - 1;
  283. if ((hotstring.EnabledOptions & HotstringDefinition.Options.AutoTrigger) == HotstringDefinition.Options.AutoTrigger)
  284. {
  285. if (!history.EndsWith(hotstring.Sequence, compare))
  286. return false;
  287. }
  288. else
  289. {
  290. if (history.Length < hotstring.Sequence.Length + 1)
  291. return false;
  292. if (hotstring.EndChars.IndexOf(history[history.Length - 1]) == -1)
  293. return false;
  294. if (!history.Substring(x--, hotstring.Sequence.Length).Equals(hotstring.Sequence, compare))
  295. return false;
  296. }
  297. if ((hotstring.EnabledOptions & HotstringDefinition.Options.Nested) != HotstringDefinition.Options.Nested)
  298. if (x > -1 && char.IsLetterOrDigit(history[x]))
  299. return false;
  300. return true;
  301. }
  302. #endregion
  303. #region Send
  304. public void SendMixed(string sequence)
  305. {
  306. // TODO: modifiers in mixed mode send e.g. ^{a down}
  307. var keys = KeyParser.ParseKeyStream(sequence);
  308. foreach (var key in keys)
  309. if (key != Keys.None)
  310. Send(key);
  311. }
  312. #endregion
  313. #region Abstract methods
  314. protected abstract void RegisterHook();
  315. protected abstract void DeregisterHook();
  316. protected internal abstract void Send(string keys);
  317. protected internal abstract void Send(Keys key);
  318. protected abstract void Backspace(int n);
  319. #endregion
  320. }
  321. internal class IAKeyEventArgs : EventArgs
  322. {
  323. Keys keys;
  324. bool _handeld = false;
  325. bool _block = false;
  326. public IAKeyEventArgs(Keys keys, string typed, bool down)
  327. {
  328. this.keys = keys;
  329. Typed = typed;
  330. Down = down;
  331. }
  332. public Keys Keys
  333. {
  334. get { return keys; }
  335. }
  336. public string Typed { get; private set; }
  337. public bool Down { get; private set; }
  338. /// <summary>
  339. /// Has this Key already processed enought
  340. /// </summary>
  341. public bool Handeled
  342. {
  343. get { return _handeld; }
  344. set { _handeld = value; }
  345. }
  346. /// <summary>
  347. /// Should this Key be blocked from the system
  348. /// </summary>
  349. public bool Block
  350. {
  351. get { return _block; }
  352. set { _block = value; }
  353. }
  354. }
  355. internal class HotstringEventArgs : EventArgs
  356. {
  357. string sequence;
  358. public HotstringEventArgs() : this(string.Empty) { }
  359. public HotstringEventArgs(string sequence)
  360. {
  361. this.sequence = sequence;
  362. }
  363. public string Sequence
  364. {
  365. get { return sequence; }
  366. }
  367. }
  368. }
  369. }