Search code examples
c#pinvokesendkeyshotkeysregisterhotkey

Detecting Ctrl+V with RegisterHotKey but not intercepting it


I need to detect when a user presses Ctrl+V(regardless of window focus - my app will likely be minimised) but I must not stop the actual paste operation.

I have tried a few things: (I am successfully binding to keystrokes with RegisterHotKey)

I have:

protected override void WndProc(ref Message m)
{
  if (m.Msg == 0x312)
    hotKey();
  base.WndProc(ref m);
}

and I've tried the following:

void hotKey()
{
  SendKeys.SendWait("^v"); //just puts 'v' instead of clipboard contents
}

and

void hotKey()
{
  SendKeys.SendWait(ClipBoard.GetText());
  /* This works, but since Ctrl is still down, it triggers
   * all the shortcut keys for the app, e.g. if the keyboard
   * contains 's' then instead of putting 's' in the app, it
   * calls Ctrl+S which makes the app think the user wants
   * to save.
   */
}

Currently the only working solution I have is to bind to something different, e.g. Ctrl+B and then call SendKeys.SendWait("^v"); however this isn't ideal.

An ideal solution would be if my window didn't intercept the keystroke in the first place, just reacted.


Solution

  • You can do this by leveraging hooks using SetWindowsHookEx().

    HHOOK WINAPI SetWindowsHookEx(
      __in  int idHook,
      __in  HOOKPROC lpfn,
      __in  HINSTANCE hMod,
      __in  DWORD dwThreadId
    );
    

    Basically, you can set up a low-level keyboard hook:

    _hookHandle = SetWindowsHookEx(
        WH_KEYBOARD_LL,
        KbHookProc,                   // Your keyboard handler
        (IntPtr)0,
        0);                           // Set up system-wide hook.
    

    to capture system-wide keyboard events. But it also allows you to make those keyboard events pass through to other apps. For your particular case, you can define KbHookProc as:

    private static int KbHookProc(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0) // This means we can intercept the event.
        {
            var hookStruct = (KbLLHookStruct)Marshal.PtrToStructure(
                    lParam,
                    typeof(KbLLHookStruct));
    
            // Quick check if Ctrl key is down. 
            // See GetKeyState() doco for more info about the flags.
            bool ctrlDown = 
                    GetKeyState(VK_LCONTROL) != 0 ||
                    GetKeyState(VK_RCONTROL) != 0;
    
            if (ctrlDown && hookStruct.vkCode == 0x56) // Ctrl+V
            {
                // Replace this with your custom action.
                Clipboard.SetText("Hi");
            }
        }
    
        // Pass to other keyboard handlers. Makes the Ctrl+V pass through.
        return CallNextHookEx(_hookHandle, nCode, wParam, lParam);
    } 
    

    I coded a quick and dirty WinForms app to illustrate this. For the full code listing, see http://pastebin.com/uCSvqwb4.