1

This is the class i'm using for hooking the keyboard keys.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

public class Hook : IDisposable
{
    bool Global = false;

    public delegate void LocalKeyEventHandler(Keys key, bool Shift, bool Ctrl, bool Alt);
    public event LocalKeyEventHandler KeyDown;
    public event LocalKeyEventHandler KeyUp;

    public delegate int CallbackDelegate(int Code, int W, int L);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct KBDLLHookStruct
    {
        public Int32 vkCode;
        public Int32 scanCode;
        public Int32 flags;
        public Int32 time;
        public Int32 dwExtraInfo;
    }

    [DllImport("user32", CallingConvention = CallingConvention.StdCall)]
    private static extern int SetWindowsHookEx(HookType idHook, CallbackDelegate lpfn, int hInstance, int threadId);

    [DllImport("user32", CallingConvention = CallingConvention.StdCall)]
    private static extern bool UnhookWindowsHookEx(int idHook);

    [DllImport("user32", CallingConvention = CallingConvention.StdCall)]
    private static extern int CallNextHookEx(int idHook, int nCode, int wParam, int lParam);

    [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern int GetCurrentThreadId();

    public enum HookType : int
    {
        WH_JOURNALRECORD = 0,
        WH_JOURNALPLAYBACK = 1,
        WH_KEYBOARD = 2,
        WH_GETMESSAGE = 3,
        WH_CALLWNDPROC = 4,
        WH_CBT = 5,
        WH_SYSMSGFILTER = 6,
        WH_MOUSE = 7,
        WH_HARDWARE = 8,
        WH_DEBUG = 9,
        WH_SHELL = 10,
        WH_FOREGROUNDIDLE = 11,
        WH_CALLWNDPROCRET = 12,
        WH_KEYBOARD_LL = 13,
        WH_MOUSE_LL = 14
    }

    private int HookID = 0;
    CallbackDelegate TheHookCB = null;

    //Start hook
    public Hook(bool Global)
    {
        this.Global = Global;
        TheHookCB = new CallbackDelegate(KeybHookProc);
        if (Global)
        {
            HookID = SetWindowsHookEx(HookType.WH_KEYBOARD_LL, TheHookCB,
                0, //0 for local hook. eller hwnd til user32 for global
                0); //0 for global hook. eller thread for hooken
        }
        else
        {
            HookID = SetWindowsHookEx(HookType.WH_KEYBOARD, TheHookCB,
                0, //0 for local hook. or hwnd to user32 for global
                GetCurrentThreadId()); //0 for global hook. or thread for the hook
        }
    }

    bool IsFinalized = false;
    ~Hook()
    {
        if (!IsFinalized)
        {
            UnhookWindowsHookEx(HookID);
            IsFinalized = true;
        }
    }
    public void Dispose()
    {
        if (!IsFinalized)
        {
            UnhookWindowsHookEx(HookID);
            IsFinalized = true;
        }
    }

    //The listener that will trigger events
    private int KeybHookProc(int Code, int W, int L)
    {
        KBDLLHookStruct LS = new KBDLLHookStruct();
        if (Code < 0)
        {
            return CallNextHookEx(HookID, Code, W, L);
        }
        try
        {
            if (!Global)
            {
                if (Code == 3)
                {
                    IntPtr ptr = IntPtr.Zero;

                    int keydownup = L >> 30;
                    if (keydownup == 0)
                    {
                        if (KeyDown != null) KeyDown((Keys)W, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
                    }
                    if (keydownup == -1)
                    {
                        if (KeyUp != null) KeyUp((Keys)W, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
                    }
                    //System.Diagnostics.Debug.WriteLine("Down: " + (Keys)W);
                }
            }
            else
            {
                KeyEvents kEvent = (KeyEvents)W;

                Int32 vkCode = Marshal.ReadInt32((IntPtr)L); //Leser vkCode som er de første 32 bits hvor L peker.

                if (kEvent != KeyEvents.KeyDown && kEvent != KeyEvents.KeyUp && kEvent != KeyEvents.SKeyDown && kEvent != KeyEvents.SKeyUp)
                {
                }
                if (kEvent == KeyEvents.KeyDown || kEvent == KeyEvents.SKeyDown)
                {
                    if (KeyDown != null) KeyDown((Keys)vkCode, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
                }
                if (kEvent == KeyEvents.KeyUp || kEvent == KeyEvents.SKeyUp)
                {
                    if (KeyUp != null) KeyUp((Keys)vkCode, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
                }
            }
        }
        catch (Exception)
        {
            //Ignore all errors...
        }

        return CallNextHookEx(HookID, Code, W, L);

    }

    public enum KeyEvents
    {
        KeyDown = 0x0100,
        KeyUp = 0x0101,
        SKeyDown = 0x0104,
        SKeyUp = 0x0105
    }

    [DllImport("user32.dll")]
    static public extern short GetKeyState(System.Windows.Forms.Keys nVirtKey);

    public static bool GetCapslock()
    {
        return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.CapsLock)) & true;
    }
    public static bool GetNumlock()
    {
        return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.NumLock)) & true;
    }
    public static bool GetScrollLock()
    {
        return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.Scroll)) & true;
    }
    public static bool GetShiftPressed()
    {
        int state = GetKeyState(System.Windows.Forms.Keys.ShiftKey);
        if (state > 1 || state < -1) return true;
        return false;
    }
    public static bool GetCtrlPressed()
    {
        int state = GetKeyState(System.Windows.Forms.Keys.ControlKey);
        if (state > 1 || state < -1) return true;
        return false;
    }
    public static bool GetAltPressed()
    {
        int state = GetKeyState(System.Windows.Forms.Keys.Menu);
        if (state > 1 || state < -1) return true;
        return false;
    }
}

And in form1 a sample how to work with it:

using System;
using System.Windows.Forms;

namespace KeyboardHook
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            Hook kh = new Hook(true);
            kh.KeyDown += Kh_KeyDown;
        }

        private void Kh_KeyDown(Keys key, bool Shift, bool Ctrl, bool Alt)
        {
            MessageBox.Show(key.ToString());
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }
    }
}

I wonder if there is a way using the class or some other way to hook also the mouse ? For exmaple when i click the mouse middle button. Or if i roll the mouse wheel then give once a message i rolled the wheel but the wheel is not so important is more the buttons left,right,middle.

testman tm
  • 89
  • 1
  • 11

1 Answers1

7

Yes, i used the following code in a project of mine, it allows direct access to most windows mouse events:

using System;
using System.Runtime.InteropServices;

/// <summary>
///     The CallWndProc hook procedure is an application-defined or library-defined
///     callback function used with the SetWindowsHookEx function. The HOOKPROC type
///     defines a pointer to this callback function. CallWndProc is a placeholder for
///     the application-defined or library-defined function name.
/// </summary>
/// <param name="nCode">
///     Specifies whether the hook procedure must process the message.
/// </param>
/// <param name="wParam">
///     Specifies whether the message was sent by the current thread.
/// </param>
/// <param name="lParam">
///     Pointer to a CWPSTRUCT structure that contains details about the message.
/// </param>
/// <returns>
///     If nCode is less than zero, the hook procedure must return the value returned
///     by CallNextHookEx. If nCode is greater than or equal to zero, it is highly
///     recommended that you call CallNextHookEx and return the value it returns;
///     otherwise, other applications that have installed WH_CALLWNDPROC hooks will
///     not receive hook notifications and may behave incorrectly as a result. If the
///     hook procedure does not call CallNextHookEx, the return value should be zero.
/// </returns>
internal delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

internal class NativeMethods
{
    /// <summary>
    ///     The SetWindowsHookEx function installs an application-defined hook
    ///     procedure into a hook chain. You would install a hook procedure to monitor
    ///     the system for certain types of events. These events are associated either
    ///     with a specific thread or with all threads in the same desktop as the
    ///     calling thread.
    /// </summary>
    /// <param name="hookType">
    ///     Specifies the type of hook procedure to be installed
    /// </param>
    /// <param name="callback">Pointer to the hook procedure.</param>
    /// <param name="hMod">
    ///     Handle to the DLL containing the hook procedure pointed to by the lpfn
    ///     parameter. The hMod parameter must be set to NULL if the dwThreadId
    ///     parameter specifies a thread created by the current process and if the
    ///     hook procedure is within the code associated with the current process.
    /// </param>
    /// <param name="dwThreadId">
    ///     Specifies the identifier of the thread with which the hook procedure is
    ///     to be associated.
    /// </param>
    /// <returns>
    ///     If the function succeeds, the return value is the handle to the hook
    ///     procedure. If the function fails, the return value is 0.
    /// </returns>
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SetWindowsHookEx(HookType hookType,
        HookProc callback, IntPtr hMod, uint dwThreadId);

    /// <summary>
    ///     The UnhookWindowsHookEx function removes a hook procedure installed in
    ///     a hook chain by the SetWindowsHookEx function.
    /// </summary>
    /// <param name="hhk">Handle to the hook to be removed.</param>
    /// <returns>
    ///     If the function succeeds, the return value is true.
    ///     If the function fails, the return value is false.
    /// </returns>
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern bool UnhookWindowsHookEx(IntPtr hhk);

    /// <summary>
    ///     The CallNextHookEx function passes the hook information to the next hook
    ///     procedure in the current hook chain. A hook procedure can call this
    ///     function either before or after processing the hook information.
    /// </summary>
    /// <param name="idHook">Handle to the current hook.</param>
    /// <param name="nCode">
    ///     Specifies the hook code passed to the current hook procedure.
    /// </param>
    /// <param name="wParam">
    ///     Specifies the wParam value passed to the current hook procedure.
    /// </param>
    /// <param name="lParam">
    ///     Specifies the lParam value passed to the current hook procedure.
    /// </param>
    /// <returns>
    ///     This value is returned by the next hook procedure in the chain.
    /// </returns>
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int CallNextHookEx(IntPtr hhk, int nCode,
        IntPtr wParam, IntPtr lParam);
}

internal static class HookCodes
{
    public const int HC_ACTION = 0;
    public const int HC_GETNEXT = 1;
    public const int HC_SKIP = 2;
    public const int HC_NOREMOVE = 3;
    public const int HC_NOREM = HC_NOREMOVE;
    public const int HC_SYSMODALON = 4;
    public const int HC_SYSMODALOFF = 5;
}

internal enum HookType
{
    WH_KEYBOARD = 2,
    WH_MOUSE = 7,
    WH_KEYBOARD_LL = 13,
    WH_MOUSE_LL = 14
}

[StructLayout(LayoutKind.Sequential)]
internal class POINT
{
    public int x;
    public int y;
}

/// <summary>
///     The MSLLHOOKSTRUCT structure contains information about a low-level keyboard
///     input event.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct MOUSEHOOKSTRUCT
{
    public POINT pt; // The x and y coordinates in screen coordinates
    public int hwnd; // Handle to the window that'll receive the mouse message
    public int wHitTestCode;
    public int dwExtraInfo;
}

/// <summary>
///     The MOUSEHOOKSTRUCT structure contains information about a mouse event passed
///     to a WH_MOUSE hook procedure, MouseProc.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct MSLLHOOKSTRUCT
{
    public POINT pt; // The x and y coordinates in screen coordinates. 
    public int mouseData; // The mouse wheel and button info.
    public int flags;
    public int time; // Specifies the time stamp for this message. 
    public IntPtr dwExtraInfo;
}

internal enum MouseMessage
{
    WM_MOUSEMOVE = 0x0200,
    WM_LBUTTONDOWN = 0x0201,
    WM_LBUTTONUP = 0x0202,
    WM_LBUTTONDBLCLK = 0x0203,
    WM_RBUTTONDOWN = 0x0204,
    WM_RBUTTONUP = 0x0205,
    WM_RBUTTONDBLCLK = 0x0206,
    WM_MBUTTONDOWN = 0x0207,
    WM_MBUTTONUP = 0x0208,
    WM_MBUTTONDBLCLK = 0x0209,

    WM_MOUSEWHEEL = 0x020A,
    WM_MOUSEHWHEEL = 0x020E,

    WM_NCMOUSEMOVE = 0x00A0,
    WM_NCLBUTTONDOWN = 0x00A1,
    WM_NCLBUTTONUP = 0x00A2,
    WM_NCLBUTTONDBLCLK = 0x00A3,
    WM_NCRBUTTONDOWN = 0x00A4,
    WM_NCRBUTTONUP = 0x00A5,
    WM_NCRBUTTONDBLCLK = 0x00A6,
    WM_NCMBUTTONDOWN = 0x00A7,
    WM_NCMBUTTONUP = 0x00A8,
    WM_NCMBUTTONDBLCLK = 0x00A9
}

/// <summary>
///     The structure contains information about a low-level keyboard input event.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct KBDLLHOOKSTRUCT
{
    public int vkCode; // Specifies a virtual-key code
    public int scanCode; // Specifies a hardware scan code for the key
    public int flags;
    public int time; // Specifies the time stamp for this message
    public int dwExtraInfo;
}

internal enum KeyboardMessage
{
    WM_KEYDOWN = 0x0100,
    WM_KEYUP = 0x0101,
    WM_SYSKEYDOWN = 0x0104,
    WM_SYSKEYUP = 0x0105
}

To use it you have to register the mouse hook. LowLevelMouseProc is the callback. This method is executed every time a new mouse event occures.

private void SetUpHook()
{
    Logger.Debug("Setting up global mouse hook");

    // Create an instance of HookProc.
    _globalLlMouseHookCallback = LowLevelMouseProc;

    _hGlobalLlMouseHook = NativeMethods.SetWindowsHookEx(
        HookType.WH_MOUSE_LL,
        _globalLlMouseHookCallback,
        Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),
        0);

    if (_hGlobalLlMouseHook == IntPtr.Zero)
    {
        Logger.Fatal("Unable to set global mouse hook");
        throw new Win32Exception("Unable to set MouseHook");
    }
}

To clear mouse hook:

private void ClearHook()
{
    Logger.Debug("Deleting global mouse hook");

    if (_hGlobalLlMouseHook != IntPtr.Zero)
    {
        // Unhook the low-level mouse hook
        if (!NativeMethods.UnhookWindowsHookEx(_hGlobalLlMouseHook))
            throw new Win32Exception("Unable to clear MouseHoo;");

        _hGlobalLlMouseHook = IntPtr.Zero;
    }
}

And last but not least an example of the LowLevelMouseProc, the callback you can use to intercept mouse events:

public int LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode >= 0)
    {
        // Get the mouse WM from the wParam parameter
        var wmMouse = (MouseMessage) wParam;
        if (wmMouse == MouseMessage.WM_LBUTTONDOWN && LeftButtonState == ButtonState.Released)
        {
            Logger.Debug("Left Mouse down");
        }
        if (wmMouse == MouseMessage.WM_LBUTTONUP && LeftButtonState == ButtonState.Down)
        {
            Logger.Debug("Left Mouse up");
        }

        if (wmMouse == MouseMessage.WM_RBUTTONDOWN && RightButtonState == ButtonState.Released)
        {
            Logger.Debug("Right Mouse down");
        }
        if (wmMouse == MouseMessage.WM_RBUTTONUP && RightButtonState == ButtonState.Down)
        {
            Logger.Debug("Right Mouse up");
        }
    }

    // Pass the hook information to the next hook procedure in chain
    return NativeMethods.CallNextHookEx(_hGlobalLlMouseHook, nCode, wParam, lParam);
}

As with all direct windows calls, the code gets unnecessarily long. But the only thing you have to do, is to call SetUpHook and provide your own version of LowLevelMouseProc.

EDIT: There are shorter versions to do this. But this method allows you to catch global mouse events. Not just events issued to your window. All mouse events, system wide will be piped into LowLevelMouseProc

Iqon
  • 1,920
  • 12
  • 20