/Rusty/Common/Keyboard/KeyboardHook.cs
https://github.com/IsNull/IronAHK · C# · 487 lines · 352 code · 118 blank · 17 comment · 103 complexity · 9a17f7b28257623b39a0b9ffa33735ba MD5 · raw file
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Windows.Forms;
- using System.Threading;
- using System.Diagnostics;
- namespace IronAHK.Rusty.Common
- {
- partial class Keyboard
- {
- // ToDo: Raise Events
- /// <summary>
- /// Platform independent keyboard Hook base.
- /// This Class is abstract.
- /// </summary>
- internal abstract class KeyboardHook
- {
- #region Properties
- List<HotkeyDefinition> hotkeys;
- List<HotstringDefinition> hotstrings;
- Dictionary<Keys, bool> pressed;
- StringBuilder history;
- const int retention = 1024;
- public string CurrentHotkey { get; set; }
- public string PriorHotkey { get; set; }
- public int CurrentHotkeyTime { get; set; }
- public int PriorHotkeyTime { get; set; }
- public bool Block { get; set; }
- #endregion
- #region Events
- /// <summary>
- /// Raised when a Key is pressed/released
- /// </summary>
- public event EventHandler<IAKeyEventArgs> IAKeyEvent;
- #endregion
- #region Constructor/destructor
- public KeyboardHook()
- {
- hotkeys = new List<HotkeyDefinition>();
- hotstrings = new List<HotstringDefinition>();
- history = new StringBuilder(retention);
- pressed = new Dictionary<Keys, bool>();
- foreach (int i in Enum.GetValues(typeof(Keys)))
- if (!pressed.ContainsKey((Keys)i))
- pressed.Add((Keys)i, false);
- RegisterHook();
- }
- ~KeyboardHook()
- {
- DeregisterHook();
- }
- #endregion
- #region Add/remove
- public HotkeyDefinition Add(HotkeyDefinition hotkey)
- {
- hotkeys.Add(hotkey);
- return hotkey;
- }
- public HotstringDefinition Add(HotstringDefinition hotstring)
- {
- hotstrings.Add(hotstring);
- return hotstring;
- }
- public void Remove(HotkeyDefinition hotkey)
- {
- hotkeys.Remove(hotkey);
- }
- public void Remove(HotstringDefinition hotstring)
- {
- hotstrings.Remove(hotstring);
- }
- #endregion
- #region Key Status
- public bool IsPressed(Keys key)
- {
- if (pressed.ContainsKey(key))
- return pressed[key];
- else
- {
- Debug.Fail("Thre should'nt be any key not in this table...");
- return false;
- }
- }
- #endregion
- #region Conversions
- char Letter(Keys key)
- {
- // HACK: remove Keys translation (and the overload) since it should be passed from the native handler
- bool caps = (key & Keys.Shift) == Keys.Shift || pressed[Keys.ShiftKey] || pressed[Keys.LShiftKey] || pressed[Keys.RShiftKey];
- key &= ~Keys.Modifiers;
- switch (key)
- {
- case Keys.Space: return ' ';
- case Keys.Enter: return '\n';
- }
- string letter = key.ToString();
- if (!caps)
- letter = letter.ToLower();
- return letter.Length == 1 ? letter[0] : (char)0;
- }
- #endregion
- #region Hotkey fired
- [Obsolete]
- protected bool KeyReceived(Keys key, bool down)
- {
- return KeyReceived(key, Letter(key).ToString(), down);
- }
- protected bool KeyReceived(Keys key, string typed, bool down)
- {
- if (Block)
- return true;
- bool block = false;
- var args = new IAKeyEventArgs(key, typed, down);
- if (IAKeyEvent != null)
- IAKeyEvent(this, args);
- if (args.Block)
- {
- block = true;
- }
- if (args.Handeled)
- {
- return block;
- }
- #region Trigger Hotkey
- if (!Core.Suspended)
- {
- pressed[key] = down;
- var exec = new List<HotkeyDefinition>();
- foreach (var hotkey in hotkeys)
- {
- bool match = KeyMatch(hotkey.Keys & ~Keys.Modifiers, key) ||
- hotkey.Typed.Length != 0 && hotkey.Typed.Equals(typed, StringComparison.CurrentCultureIgnoreCase);
- bool up = (hotkey.EnabledOptions & HotkeyDefinition.Options.Up) == HotkeyDefinition.Options.Up;
- if (hotkey.Enabled && match && HasModifiers(hotkey) && up != down)
- {
- exec.Add(hotkey);
- if ((hotkey.EnabledOptions & HotkeyDefinition.Options.PassThrough) != HotkeyDefinition.Options.PassThrough)
- block = true;
- }
- }
- new Thread(delegate()
- {
- foreach (var hotkey in exec)
- {
- PriorHotkeyTime = CurrentHotkeyTime;
- CurrentHotkeyTime = Environment.TickCount;
- PriorHotkey = CurrentHotkey;
- CurrentHotkey = hotkey.ToString();
- if (hotkey.Condition())
- hotkey.Proc(new object[] { });
- }
- }).Start();
- }
- #endregion
- if (!down)
- return block;
- #region Sequencing
- if (hotstrings.Count > 0)
- {
- if (key == Keys.Back && history.Length > 0)
- history.Remove(history.Length - 1, 1);
- switch (key)
- {
- case Keys.Left:
- case Keys.Right:
- case Keys.Down:
- case Keys.Up:
- case Keys.Next:
- case Keys.Prior:
- case Keys.Home:
- case Keys.End:
- history.Length = 0;
- break;
- case Keys.Alt:
- case Keys.LMenu:
- case Keys.RMenu:
- case Keys.LControlKey:
- case Keys.RControlKey:
- case Keys.LShiftKey:
- case Keys.RShiftKey:
- break;
- default:
- int d = retention - history.Length;
- if (d < 0)
- history.Remove(history.Length + d, -d);
- history.Append(typed);
- break;
- }
- }
- if (Core.Suspended)
- return block;
- #endregion
- #region Hotstring
- var expand = new List<HotstringDefinition>();
- foreach (var hotstring in hotstrings)
- {
- if (hotstring.Enabled && HasConditions(hotstring))
- {
- expand.Add(hotstring);
- if ((hotstring.EnabledOptions & HotstringDefinition.Options.Reset) == HotstringDefinition.Options.Reset)
- history.Length = 0;
- }
- }
- string trigger = history.Length > 0 ? history[history.Length - 1].ToString() : null;
- foreach (var hotstring in expand)
- {
- block = true;
- new Thread(delegate()
- {
- PriorHotkeyTime = CurrentHotkeyTime;
- CurrentHotkeyTime = Environment.TickCount;
- PriorHotkey = CurrentHotkey;
- CurrentHotkey = hotstring.ToString();
- int length = hotstring.Sequence.Length;
- bool auto = (hotstring.EnabledOptions & HotstringDefinition.Options.AutoTrigger) == HotstringDefinition.Options.AutoTrigger;
- if (auto)
- length--;
- if ((hotstring.EnabledOptions & HotstringDefinition.Options.Backspace) == HotstringDefinition.Options.Backspace && length > 0)
- {
- int n = length + 1;
- history.Remove(history.Length - n, n);
- Backspace(length);
- // UNDONE: hook on Windows captures triggering key and blocks it, but X11 allows it through and needs an extra backspace
- if (!auto && Environment.OSVersion.Platform != PlatformID.Win32NT)
- Backspace(1);
- }
- hotstring.Proc(new object[] { });
- if ((hotstring.EnabledOptions & HotstringDefinition.Options.OmitEnding) == HotstringDefinition.Options.OmitEnding)
- {
- if ((hotstring.EnabledOptions & HotstringDefinition.Options.Backspace) == HotstringDefinition.Options.Backspace &&
- (hotstring.EnabledOptions & HotstringDefinition.Options.AutoTrigger) != HotstringDefinition.Options.AutoTrigger)
- {
- history.Remove(history.Length - 1, 1);
- Backspace(1);
- }
- }
- else if (trigger != null && !auto)
- SendMixed(trigger);
- }).Start();
- }
- return block;
- #endregion
- }
- bool KeyMatch(Keys expected, Keys received)
- {
- expected &= ~Keys.Modifiers;
- received &= ~Keys.Modifiers;
- if (expected == received)
- return true;
- switch (expected)
- {
- case Keys.ControlKey:
- return received == Keys.LControlKey || received == Keys.RControlKey;
- case Keys.ShiftKey:
- return received == Keys.LShiftKey || received == Keys.RShiftKey;
- }
- return false;
- }
- bool HasModifiers(HotkeyDefinition hotkey)
- {
- if (hotkey.Extra != Keys.None && !pressed[hotkey.Extra])
- return false;
- if ((hotkey.EnabledOptions & HotkeyDefinition.Options.IgnoreModifiers) == HotkeyDefinition.Options.IgnoreModifiers)
- return true;
- bool[,] modifiers = {
- { (hotkey.Keys & Keys.Alt) == Keys.Alt, pressed[Keys.Alt] || pressed[Keys.LMenu] || pressed[Keys.RMenu], (hotkey.Keys & Keys.LMenu) == Keys.LMenu },
- { (hotkey.Keys & Keys.Control) == Keys.Control, pressed[Keys.Control] || pressed[Keys.LControlKey] || pressed[Keys.RControlKey], (hotkey.Keys & Keys.ControlKey) == Keys.ControlKey },
- { (hotkey.Keys & Keys.Shift) == Keys.Shift, pressed[Keys.Shift] || pressed[Keys.LShiftKey] || pressed[Keys.RShiftKey], (hotkey.Keys & Keys.ShiftKey) == Keys.ShiftKey }
- };
- for (int i = 0; i < 3; i++)
- if ((modifiers[i, 0] && !modifiers[i, 1]) || (modifiers[i, 1] && !modifiers[i, 0] && !modifiers[i, 2]))
- return false;
- return true;
- }
- bool HasConditions(HotstringDefinition hotstring)
- {
- string history = this.history.ToString();
- if (history.Length == 0)
- return false;
- var compare = (hotstring.EnabledOptions & HotstringDefinition.Options.CaseSensitive) == HotstringDefinition.Options.CaseSensitive ?
- StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase;
- int x = history.Length - hotstring.Sequence.Length - 1;
- if ((hotstring.EnabledOptions & HotstringDefinition.Options.AutoTrigger) == HotstringDefinition.Options.AutoTrigger)
- {
- if (!history.EndsWith(hotstring.Sequence, compare))
- return false;
- }
- else
- {
- if (history.Length < hotstring.Sequence.Length + 1)
- return false;
- if (hotstring.EndChars.IndexOf(history[history.Length - 1]) == -1)
- return false;
- if (!history.Substring(x--, hotstring.Sequence.Length).Equals(hotstring.Sequence, compare))
- return false;
- }
- if ((hotstring.EnabledOptions & HotstringDefinition.Options.Nested) != HotstringDefinition.Options.Nested)
- if (x > -1 && char.IsLetterOrDigit(history[x]))
- return false;
- return true;
- }
- #endregion
- #region Send
- public void SendMixed(string sequence)
- {
- // TODO: modifiers in mixed mode send e.g. ^{a down}
- var keys = KeyParser.ParseKeyStream(sequence);
- foreach (var key in keys)
- if (key != Keys.None)
- Send(key);
- }
- #endregion
- #region Abstract methods
- protected abstract void RegisterHook();
- protected abstract void DeregisterHook();
- protected internal abstract void Send(string keys);
- protected internal abstract void Send(Keys key);
- protected abstract void Backspace(int n);
- #endregion
- }
- internal class IAKeyEventArgs : EventArgs
- {
- Keys keys;
- bool _handeld = false;
- bool _block = false;
- public IAKeyEventArgs(Keys keys, string typed, bool down)
- {
- this.keys = keys;
- Typed = typed;
- Down = down;
- }
- public Keys Keys
- {
- get { return keys; }
- }
- public string Typed { get; private set; }
- public bool Down { get; private set; }
- /// <summary>
- /// Has this Key already processed enought
- /// </summary>
- public bool Handeled
- {
- get { return _handeld; }
- set { _handeld = value; }
- }
- /// <summary>
- /// Should this Key be blocked from the system
- /// </summary>
- public bool Block
- {
- get { return _block; }
- set { _block = value; }
- }
- }
- internal class HotstringEventArgs : EventArgs
- {
- string sequence;
- public HotstringEventArgs() : this(string.Empty) { }
- public HotstringEventArgs(string sequence)
- {
- this.sequence = sequence;
- }
- public string Sequence
- {
- get { return sequence; }
- }
- }
- }
- }