Still hooked on Windows, I refactored the hook into a number of interfaces, and I wanted to support 2-step hotkeys, so I started with defining an IHook that can be attached, detached, exposes a property that tells whether a hook is attached or not, and raises an event when the hook receives a message:public interface IHook{    event EventHandler<HookEventArgs> MessageReceived;    void OnMessageReceived();    bool IsAttached { get; }    void Attach();    void Detach();}The HookEventArgs simply carry a Keys enum value:public class HookEventArgs : EventArgs{    private readonly Keys _key;    public HookEventArgs(Keys key)    {        _key = key;    }    public Keys Key { get { return _key; } }    public new static HookEventArgs Empty {get { return new HookEventArgs(Keys.None); }}}Then I extended IHook into a IHotKeyHook, which exposes a HookInfo value and a flag that indicates whether that hotkey is a 2-step hotkey, like Visual Studio's Ctrl+R,M for refactor/extract method:public interface IHotKeyHook : IHook{    HookInfo HookInfo { get; }    bool IsTwoStepHotKey { get; }}I also extracted a TimerHook, and gave it its own interface:public interface ITimerHook{    event EventHandler Tick;    void Attach();    void Detach();}Lastly, I added a level of abstraction for an object that would be responsible for managing all these hooks and passing the events to the Rubberduck App class:public interface IRubberduckHooks : IHook, IDisposable{    IEnumerable<IHook> Hooks { get; }    void AddHook<THook>(THook hook) where THook : IHook;}AppPurely for context, here's how the App class consumes IRubberduckHooks - I have yet to actually handle the hotkeys and map them to actual commands, but the backbone is here:    private async void hooks_MessageReceived(object sender, HookEventArgs e)    {        if (sender is LowLevelKeyboardHook)        {            if (_skipKeyUp)            {                _skipKeyUp = false;                return;            }            if (_isAwaitingTwoStepKey)            {                // todo: use _firstStepHotKey and e.Key to run 2-step hotkey action                AwaitNextKey(false);                return;            }            var component = _vbe.ActiveCodePane.CodeModule.Parent;            await ParseComponentAsync(component);            AwaitNextKey(false);            return;        }        var hotKey = sender as IHotKeyHook;        if (hotKey == null)        {            AwaitNextKey(false);            return;        }        if (hotKey.IsTwoStepHotKey)        {            _firstStepHotKey = hotKey.HookInfo.Key;            AwaitNextKey(true, _firstStepHotKey);        }        else        {            // todo: use e.Key to run 1-step hotkey action            _firstStepHotKey = Keys.None;            AwaitNextKey(false);        }    }    private void AwaitNextKey(bool eatNextKey = true, Keys key = default(Keys))    {        _isAwaitingTwoStepKey = eatNextKey;        foreach (var hook in _hooks.Hooks.OfType<ILowLevelKeyboardHook>())        {            hook.EatNextKey = eatNextKey;        }        // todo: include modifier keys        _stateBar.SetStatusText(eatNextKey ? (hotkey  + key + ) was pressed. Waiting for second key... : Ready.);        _skipKeyUp = eatNextKey;    }RubberduckHooksThis class is responsible for coordinating the hooks, and implements the main Windows message hook that picks up, notably HOTKEY messages. It's this class that the App class calls into, to register hotkeys; it's also this class' events that the App class is listening for.I'm having a problem with the parameterless public void OnMessageReceived() method that the IRubberduckHooks interface inherits from IHook, since the App class isn't supposed to be able to call that method - I solved it by throwing a NotImplementedException, but IMO there's a design smell here.. have a look:public class RubberduckHooks : IRubberduckHooks{    private readonly IntPtr _mainWindowHandle;    private readonly IntPtr _oldWndPointer;    private readonly User32.WndProc _oldWndProc;    private User32.WndProc _newWndProc;    private readonly ITimerHook _timerHook;    private const int WA_INACTIVE = 0;    private const int WA_ACTIVE = 1;    public RubberduckHooks(IntPtr mainWindowHandle, ITimerHook timerHook)    {        _mainWindowHandle = mainWindowHandle;        _oldWndProc = WindowProc;        _newWndProc = WindowProc;        _oldWndPointer = User32.SetWindowLong(_mainWindowHandle, (int)WindowLongFlags.GWL_WNDPROC, _newWndProc);        _oldWndProc = (User32.WndProc)Marshal.GetDelegateForFunctionPointer(_oldWndPointer, typeof(User32.WndProc));        _timerHook = timerHook;        _timerHook.Tick += timerHook_Tick;    }    private readonly IList<IHook> _hooks = new List<IHook>();     public IEnumerable<IHook> Hooks { get { return _hooks; } }    public void AddHook<THook>(THook hook) where THook : IHook    {        _hooks.Add(hook);    }    public event EventHandler<HookEventArgs> MessageReceived;    public void OnMessageReceived()    {        throw new NotImplementedException();    }    private void OnMessageReceived(object sender, HookEventArgs args)    {        var handler = MessageReceived;        if (handler != null)        {            handler.Invoke(sender, args);        }    }    public bool IsAttached { get; private set; }    public void Attach()    {        if (IsAttached)        {            return;        }        foreach (var hook in Hooks)        {            hook.MessageReceived += hook_MessageReceived;            hook.Attach();        }        IsAttached = true;    }    public void Detach()    {        if (!IsAttached)        {            return;        }        foreach (var hook in Hooks)        {            hook.MessageReceived -= hook_MessageReceived;            hook.Detach();        }        IsAttached = false;    }    private void hook_MessageReceived(object sender, HookEventArgs e)    {        OnMessageReceived(sender, e);    }    private void timerHook_Tick(object sender, EventArgs e)    {        if (!IsAttached && User32.GetForegroundWindow() == _mainWindowHandle)        {            Attach();        }        else        {            Detach();        }    }    public void Dispose()    {        _timerHook.Tick -= timerHook_Tick;        _timerHook.Detach();        Detach();    }    private IntPtr WindowProc(IntPtr hWnd, int uMsg, int wParam, int lParam)    {        try        {            var processed = false;            if (hWnd == _mainWindowHandle)            {                switch ((WM)uMsg)                {                    case WM.HOTKEY:                        if (GetWindowThread(User32.GetForegroundWindow()) == GetWindowThread(_mainWindowHandle))                        {                            var hook = Hooks.OfType<IHotKeyHook>().SingleOrDefault(k => k.HookInfo.HookId == (IntPtr)wParam);                            if (hook != null)                            {                                hook.OnMessageReceived();                                processed = true;                            }                        }                        break;                    case WM.ACTIVATEAPP:                        switch (LoWord(wParam))                        {                            case WA_ACTIVE:                                Attach();                                break;                            case WA_INACTIVE:                                Detach();                                break;                        }                        break;                }            }            if (!processed)            {                return User32.CallWindowProc(_oldWndProc, hWnd, uMsg, wParam, lParam);            }        }        catch (Exception exception)        {            Console.WriteLine(exception);        }        return User32.CallWindowProc(_oldWndProc, hWnd, uMsg, wParam, lParam);    }    /// <summary>    /// Gets the integer portion of a word    /// </summary>    private static int LoWord(int dw)    {        return (dw & 0x8000) != 0            ? 0x8000 | (dw & 0x7FFF)            : dw & 0xFFFF;    }    private IntPtr GetWindowThread(IntPtr hWnd)    {        uint hThread;        User32.GetWindowThreadProcessId(hWnd, out hThread);        return (IntPtr)hThread;    }}HotKeyHookThe HotKeyHook class is responsible for attaching and detaching a single hotkey, so that the RubberduckHooks instance can pick it up in the main Windows message hook as a WM_HOTKEY message.I don't like the back-and-forth going on here:User presses a hotkey in the VBE.WM_HOTKEY message gets picked up in RubberduckHooks instance; the OnMessageReceived method is called on the appropriate HotKeyHook instance.RubberduckHooks handles the HotKeyHook instance's MessageReceived event by relaying it to its own OnMessageReceived overload.App class receives a MessageReceived event that conveys a HookEventArgs that tells Rubberduck which hotkey was pressed, and that's where the hotkey gets handled.I'm sure this can be simplified quite a bit.public class HotKeyHook : IHotKeyHook{    private readonly Action _action;    private readonly IntPtr _hWndVbe;    public HookInfo HookInfo { get; private set; }    public HotKeyHook(IntPtr hWndVbe, string key, bool isTwoStepHotKey)    {        _hWndVbe = hWndVbe;        _action = OnMessageReceived;        IsTwoStepHotKey = isTwoStepHotKey;        Key = key;    }    public bool IsTwoStepHotKey { get; private set; }    public bool IsAttached { get; private set; }    public string Key { get; private set; }    public event EventHandler<HookEventArgs> MessageReceived;    public void OnMessageReceived()    {        var handler = MessageReceived;        if (handler != null)        {            var hotKey = Key;            var shift = GetModifierValue(ref hotKey);            var args = new HookEventArgs(GetKey(hotKey));            handler.Invoke(this, args);        }    }    public void Attach()    {        var hotKey = Key;        var lShift = GetModifierValue(ref hotKey);        var lKey = GetKey(hotKey);        if (lKey == Keys.None)        {            throw new InvalidOperationException(Invalid key.);        }        HookKey(lKey, lShift, _action);    }    public void Detach()    {        if (!IsAttached)        {            throw new InvalidOperationException(Hook is already detached.);        }        User32.UnregisterHotKey(_hWndVbe, HookInfo.HookId);        Kernel32.GlobalDeleteAtom(HookInfo.HookId);        IsAttached = false;    }    private void HookKey(Keys key, uint shift, Action action)    {        if (IsAttached)        {            throw new InvalidOperationException(Hook is already attached.);        }        var hookId = (IntPtr)Kernel32.GlobalAddAtom(Guid.NewGuid().ToString());        var success = User32.RegisterHotKey(_hWndVbe, hookId, shift, (uint)key);        if (!success)        {            throw new Win32Exception(HotKey was not registered.);        }        HookInfo = new HookInfo(hookId, key, shift, action);        IsAttached = true;    }    private static readonly IDictionary<char,uint> Modifiers = new Dictionary<char, uint>    {        { '+', (uint)KeyModifier.SHIFT },        { '%', (uint)KeyModifier.ALT },        { '^', (uint)KeyModifier.CONTROL },    };    /// <summary>    /// Gets the <see cref=KeyModifier/> values out of a key combination.    /// </summary>    /// <param name=key>The hotkey string, returned without the modifiers.</param>    private static uint GetModifierValue(ref string key)    {        uint result = 0;        for (var i = 0; i < 3; i++)        {            var firstChar = key[0];            if (Modifiers.ContainsKey(firstChar))            {                result |= Modifiers[firstChar];            }            else            {                // first character isn't a modifier symbol:                break;            }            // truncate first character for next iteration:            key = key.Substring(1);        }        return result;    }    private Keys GetKey(string keyCode)    {        var result = Keys.None;        switch (keyCode.Substring(0, 1))        {            case {:                _keys.TryGetValue(keyCode, out result);                break;            case ~:                result = Keys.Return;                break;            default:                if (!String.IsNullOrEmpty(keyCode))                {                    result = (Keys)Enum.Parse(typeof(Keys), keyCode);                }                break;        }        return result;    }    private static readonly IDictionary<string, Keys> _keys = new Dictionary<string, Keys>    {        { {BACKSPACE}, Keys.Back },        { {BS}, Keys.Back },        { {BKSP}, Keys.Back },        { {CAPSLOCK}, Keys.CapsLock },        { {DELETE}, Keys.Delete },        { {DEL}, Keys.Delete },        { {DOWN}, Keys.Down },        { {END}, Keys.End },        { {ENTER}, Keys.Enter },        { {RETURN}, Keys.Enter },        { {ESC}, Keys.Escape },        { {HELP}, Keys.Help },        { {HOME}, Keys.Home },        { {INSERT}, Keys.Insert },        { {INS}, Keys.Insert },        { {LEFT}, Keys.Left },        { {NUMLOCK}, Keys.NumLock },        { {PGDN}, Keys.PageDown },        { {PGUP}, Keys.PageUp },        { {PRTSC}, Keys.PrintScreen },        { {RIGHT}, Keys.Right },        { {TAB}, Keys.Tab },        { {UP}, Keys.Up },        { {F1}, Keys.F1 },        { {F2}, Keys.F2 },        { {F3}, Keys.F3 },        { {F4}, Keys.F4 },        { {F5}, Keys.F5 },        { {F6}, Keys.F6 },        { {F7}, Keys.F7 },        { {F8}, Keys.F8 },        { {F9}, Keys.F9 },        { {F10}, Keys.F10 },        { {F11}, Keys.F11 },        { {F12}, Keys.F12 },        { {F13}, Keys.F13 },        { {F14}, Keys.F14 },        { {F15}, Keys.F15 },        { {F16}, Keys.F16 },    };}By cheating a bit and hard-coding the Ctrl+ part in the 2-step hotkey handler (I'll get the hotkey modifiers in there, that's a given), I got this screenshot:..which shows that the mechanism works as intended:When a single-step hotkey is pressed, it gets handled immediately.When a two-step hotkey is pressed, the RubberduckHooks enters a gimmeh teh key mode that eats the next key (it won't show up in the code pane, and the low-level keyboard hook will ignore it), and it's up to the App class to determine which action to execute based on what the pressed key was.Typing code in a code pane in the VBE still triggers a parse task that processes the modified code module.My next implementation step will be to tweak my CommandMenuItem objects to include either some hotkey string (e.g. Ctrl+R,R or Ctrl+T), or two Keys properties (e.g. HotKeyStep1 and HotKeyStep2, where step 1 would include the modifier key(s), and step 2 would be Keys.None for a single-step hotkey), so that the App class can cycle through its menus and determine which command needs to be executed, if any.But before I do that, is there anything I need to know about this current implementation?
Step by Step, ooh HotKey
c#;rubberduck;winapi;com
Your bit twiddling is a bit odd:/// <summary>/// Gets the integer portion of a word/// </summary>private static int LoWord(int dw){    return (dw & 0x8000) != 0        ? 0x8000 | (dw & 0x7FFF)        : dw & 0xFFFF;}Is the same as just doing:/// <summary>/// Gets the integer portion of a word/// </summary>private static int LoWord(int dw){    return dw & 0xFFFF;}Which both return the low 16 bit word.According to msdnThe low-order word specifies whether the window is being activated or deactivated. [snipped]. The high-order word specifies the minimized state of the window being activated or deactivated. A nonzero value indicates the window is minimized.So you might also be interested in getting the higher 16 bits and checking if you're active but minimized.Update:I thought I'd a some other thoughts...public interface IRubberduckHooks : IHook, IDisposable{    IEnumerable<IHook> Hooks { get; }    void AddHook<THook>(THook hook) where THook : IHook;}I don't see why you need the AddHook method to be generic... You're only adding the hook to an internal list of IHook. I think you'd minimise noise by removing the generics:public interface IRubberduckHooks : IHook, IDisposable{    IEnumerable<IHook> Hooks { get; }    void AddHook(IHook hook);}This is also a bit odd:switch (keyCode.Substring(0, 1))You could just switch on the char directly:switch (keyCode[0]){    case '{':The default case doesn't need to check String.IsNullOrEmpty(keyCode) either as an exception would have been thrown when you enter the switch if the keycode was null or an empty string.The fact that GetModifierValue has a string parameter passed by reference is less than ideal... Get in a method name normally screams idempotent to me - mutating my parameter is pretty unexpected. I think you have a concept that needs wrapping up in a class here. You're returning something called a shift and stripping characters out of the parameter - I'm sure it makes sense but it's not obvious what that sense is to me.Your Empty property on HookEventArgs would be better backed by a field. Don't forget that classes default to referential equality and so Empty != Empty if you return a new instance each time.