3
votes

TL;DR: how do I override windows hotkey functionality or at least perform an action BEFORE it occurs? It seems that global hooks aren't 'fast' enough to happen before

I've run into a bit of a problem with some code I'm working on... I'm attempting to override the windows natural behavior when one presses CTRL + V, instead of pasting from clipboard, I'd like for the contents of clipboard to be replaced by those of my program's designation.

NOTE THIS NEEDS TO APPLY TO PASTE ACTION OUTSIDE OF MY PROGRAM AS WELL, HENCE USE OF GLOBAL HOOKS

below is a snippet of how I'm currently attempting to "intercept" the paste hotkeys, and an explanation of why this isn't working to follow:

Here I define my Global hooks:

class globalKeyboardHook {
        #region Constant, Structure and Delegate Definitions
        /// <summary>
        /// defines the callback type for the hook
        /// </summary>
        public delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam);

    public struct keyboardHookStruct {
        public int vkCode;
        public int scanCode;
        public int flags;
        public int time;
        public int dwExtraInfo;
    }

    const int WH_KEYBOARD_LL = 13;
    const int WM_KEYDOWN = 0x100;
    const int WM_KEYUP = 0x101;
    const int WM_SYSKEYDOWN = 0x104;
    const int WM_SYSKEYUP = 0x105;
    #endregion

    #region Instance Variables
    /// <summary>
    /// The collections of keys to watch for
    /// </summary>
    public List<Keys> HookedKeys = new List<Keys>();
    /// <summary>
    /// Handle to the hook, need this to unhook and call the next hook
    /// </summary>
    IntPtr hhook = IntPtr.Zero;
    #endregion

    #region Events
    /// <summary>
    /// Occurs when one of the hooked keys is held
    /// </summary>
    public event KeyEventHandler KeyDown;
    /// <summary>
    /// Occurs when one of the hooked keys is released
    /// </summary>
    public event KeyEventHandler KeyUp;
    #endregion

    #region Constructors and Destructors
    /// <summary>
    /// Initializes a new instance of the <see cref="globalKeyboardHook"/> class and installs the keyboard hook.
    /// </summary>
    public globalKeyboardHook() {
        hook();
    }

    /// <summary>
    /// Releases unmanaged resources and performs other cleanup operations before the
    /// <see cref="globalKeyboardHook"/> is reclaimed by garbage collection and uninstalls the keyboard hook.
    /// </summary>
    ~globalKeyboardHook() {
        unhook();
        Dispose();
    }


    #endregion

    #region Public Methods
    /// <summary>
    /// Installs the global hook
    /// </summary>
    public void hook() {
        IntPtr hInstance = LoadLibrary("User32");
        hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);
    }
    public void Dispose()
    {
        try { unhook(); }
        catch (Exception e)
        { }
    }
    /// <summary>
    /// Uninstalls the global hook
    /// </summary>
    public void unhook() {
        UnhookWindowsHookEx(hhook);
    }
    /// <summary>
    /// The callback for the keyboard hook
    /// </summary>
    /// <param name="code">The hook code, if it isn't >= 0, the function shouldn't do anyting</param>
    /// <param name="wParam">The event type</param>
    /// <param name="lParam">The keyhook event information</param>
    /// <returns></returns>
    public int hookProc(int code, int wParam, ref keyboardHookStruct lParam) {
        if (code >= 0) {
            Keys key = (Keys)lParam.vkCode;
            if (HookedKeys.Contains(key)) {
                KeyEventArgs kea = new KeyEventArgs(key);
                if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && (KeyDown != null)) {
                    KeyDown(this, kea) ;
                } else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) && (KeyUp != null)) {
                    KeyUp(this, kea);
                }
                if (kea.Handled)
                    return 1;
            }
        }
        return CallNextHookEx(hhook, code, wParam, ref lParam);
    }
    #endregion

    #region DLL imports
    /// <summary>
    /// Sets the windows hook, do the desired event, one of hInstance or threadId must be non-null
    /// </summary>
    /// <param name="idHook">The id of the event you want to hook</param>
    /// <param name="callback">The callback.</param>
    /// <param name="hInstance">The handle you want to attach the event to, can be null</param>
    /// <param name="threadId">The thread you want to attach the event to, can be null</param>
    /// <returns>a handle to the desired hook</returns>
    [DllImport("user32.dll")]
    static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId);

    /// <summary>
    /// Unhooks the windows hook.
    /// </summary>
    /// <param name="hInstance">The hook handle that was returned from SetWindowsHookEx</param>
    /// <returns>True if successful, false otherwise</returns>
    [DllImport("user32.dll")]
    static extern bool UnhookWindowsHookEx(IntPtr hInstance);

    /// <summary>
    /// Calls the next hook.
    /// </summary>
    /// <param name="idHook">The hook id</param>
    /// <param name="nCode">The hook code</param>
    /// <param name="wParam">The wparam.</param>
    /// <param name="lParam">The lparam.</param>
    /// <returns></returns>
    [DllImport("user32.dll")]
    static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam);

    /// <summary>
    /// Loads the library.
    /// </summary>
    /// <param name="lpFileName">Name of the library</param>
    /// <returns>A handle to the library</returns>
    [DllImport("kernel32.dll")]
    static extern IntPtr LoadLibrary(string lpFileName);
    #endregion
}

And here is the 'main code' in question:

   globalKeyboardHook gkh = new globalKeyboardHook();
   bool Vpressed;
   bool controlIsUp;
   void gkh_KeyUp(object sender, KeyEventArgs e)
    {
        //Control was released, handle any hotkeys that need handling
        if(e.KeyCode.ToString()=="LControlKey" || e.KeyCode.ToString()=="RControlKey")
        {
            //scan paste
            if(Vpressed && !controlIsUp)
            {
                Clipboard.SetDataObject(myTextLine);
                Vpressed=false;
                controlIsUp = true;
            }
        }
        if (e.KeyCode.ToString() == "V")
        {
            if(controlIsUp)Vpressed=false;
            else Vpressed=true;
        }
    }


    void gkh_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.KeyCode.ToString()=="LControlKey" || e.KeyCode.ToString()=="RControlKey") 
        {
            controlIsUp=false;
        }     
    }

So what I'm seeing happen is that the system is pasting what's in the clipboard BEFORE hitting the 'Clipboard.setDataObjec(...)' call, when I want the clipboard contents SET before system pastes them.

Additionally my next step would be to detect # of times V is pressed before CTRL is released and paste a different message based on that (ie CTL+V pastes 'HI FRIEND', CTRL+V+V pastes 'TEST TEST 2', and CTRL+V+V+V pastes 'heres a third snippet')

SO in conclusion my question is: how can I restructure this to operate as expected? is there something more efficient/takes higher priority to hooks I can use that would trigger before the CTRL+V? Is there a way I could 'force' a paste from clipboard (ie I constantly clear out the clipboard and when the hook is called I force the data to clipboard and paste at cursor) or is there a way I can rearrange my current solution to be more robust?

1
Breaking expected default behaviour is so evil that ... well, Darth. End users will probably not like this. In any case, clipboard behaviour isn't tied to keyboard hooks, it's a core operation that gets activated by a lot of different ways, and obviously has higher priority than any hooks. - Panagiotis Kanavos
I'm intentionally trying to remove native cut/copy/paste functionality. the app will only be used by myself in my graphic design projects. The end-result is being able to have 2 or 3 'virtual clipboards' that I'm controlling, that way I can hold onto several images and just CtrlV or CtrlVV to paste one or the other. The point of my post is to find out HOW to hook into those operations if the key hooks are too low of a priority. Cut/Copy are already functional, it's just the Paste giving me troubles - Medic3000
Try overriding ProcessDialogKey. If it hits CTRL-V, use your code, and return Keys.Alt or something that won't do anything. If not, return base.ProcessDialogKey. I do this for alt-f4 in my program, and it works well. - Aaron
please provide as an answer w/ small code/pseudo code example so that I may give you answer credits - Medic3000
I've tried that, currently this wont work as it messes with my override for cut/copy - Medic3000

1 Answers

1
votes

Give this a shot. Since it's a keybinding on your global hook, it should work. Basically, you're doing this instead of your keyup/down. Modify the code to look at ctrl-v instead of alt-f4, and where I have my messagebox, add your custom code..

protected override bool ProcessDialogKey(Keys keyData)
{

if (keyData == Keys.Alt + Keys.F4) {
    string s = HelixMessageBox.ShowInptDlg("Yeahhhhh, right.  You can shut me down like a normal person, tyvm.", CustomMessageBoxButtons.OK);
    return Keys.Alt;
    }
return base.ProcessDialogKey(keyData);
}