C ා global listening for Windows keyboard events

Posted by riespies on Sun, 05 Apr 2020 10:42:12 +0200

This method only involves how to use the existing tool classes to realize listening. Its specific principle mainly involves calling the underlying API of Windows: defining a hook to hook the keyboard event, but not the specific principle here.

1. Tool code

Quote

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Reflection;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Specific code

    class KeyboardHook
    {
        public event KeyEventHandler KeyDownEvent;
        public event KeyPressEventHandler KeyPressEvent;
        public event KeyEventHandler KeyUpEvent;

        public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
        static int hKeyboardHook = 0; //Declare the initial value of keyboard hook processing
        //Value is queried in Winuser.h of Microsoft SDK
        public const int WH_KEYBOARD_LL = 13;   //Thread keyboard hook listening mouse message is set to 2, global keyboard listening mouse message is set to 13
        HookProc KeyboardHookProcedure; //Declare KeyboardHookProcedure as a HookProc type
        //Keyboard structure
        [StructLayout(LayoutKind.Sequential)]
        public class KeyboardHookStruct
        {
            public int vkCode;  //Set a virtual key code. The code must have a value range of 1 to 254
            public int scanCode; // Key of specified hardware scan code
            public int flags;  // Key sign
            public int time; // This message for the specified time stamp
            public int dwExtraInfo; // Specify information about additional information
        }
        //Using this function, a hook is installed
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);


        //Call this function to unload the hook
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool UnhookWindowsHookEx(int idHook);


        //Use this function to continue to the next hook through the information hook
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);

        // Get the current thread number (needed for thread hook)
        [DllImport("kernel32.dll")]
        static extern int GetCurrentThreadId();

        //Use WINDOWS API function instead of getting the function of current instance to prevent hook failure
        [DllImport("kernel32.dll")]
        public static extern IntPtr GetModuleHandle(string name);

        public void Start()
        {
            // Installing the keyboard hook
            if (hKeyboardHook == 0)
            {
                KeyboardHookProcedure = new HookProc(KeyboardHookProc);
                hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0);
                //hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
                //************************************
                //Keyboard thread hook
                SetWindowsHookEx(13, KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());//Specifies the thread idGetCurrentThreadId() to listen on,
                //Keyboard global hook, need reference space (using System.Reflection;)
                //SetWindowsHookEx( 13,MouseHookProcedure,Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),0);
                //
                //About SetWindowsHookEx (int idHook, HookProc lpfn, IntPtr hInstance, int threadId) function adding hook to the hook list, the following four parameters are explained:
                //idHook hook type, that is, to determine which messages the hook listens to. In the above code, it is set to 2, that is, to listen to keyboard messages and thread hooks. If it is a global hook, it should be set to 13,
                //The thread hook listening mouse message is set to 7, and the global hook listening mouse message is set to 14. Address pointer to the lpfn hook procedure. If dwThreadId parameter is 0 or a process created by another process
                //The ID of the thread, lpfn must point to the hook procedure in the DLL. In addition, lpfn can point to a hook procedure code of the current process. The entry address of the hook function, when the hook reaches any
                //This function is called after the message. Handle to the instance of the hInstance application. Identifies the DLL that contains the child procedure referred to by lpfn. If threadId identifies a thread created by the current process, and
                //The program code is in the current process, and the hInstance must be NULL. It can be simply set as the instance handle of this application. Identifier of the thread associated with the installed hook procedure
                //If it is 0, the hook procedure is associated with all threads, that is, the global hook
                //************************************
                //If SetWindowsHookEx fails
                if (hKeyboardHook == 0)
                {
                    Stop();
                    throw new Exception("Failed to install keyboard hook");
                }
            }
        }
        public void Stop()
        {
            bool retKeyboard = true;


            if (hKeyboardHook != 0)
            {
                retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
                hKeyboardHook = 0;
            }

            if (!(retKeyboard)) throw new Exception("Failed to unload hook!");
        }
        //The conversion of ToAscii function specifies the corresponding characters or characters of virtual key code and keyboard status
        [DllImport("user32")]
        public static extern int ToAscii(int uVirtKey, //[in] Specifies the virtual key code for translation.
                                         int uScanCode, // [in] The key of the designated hardware scan code shall be translated into English. The key of setting this value of high order bit, if it is (no pressure)
                                         byte[] lpbKeyState, // [in] Pointer, in an array of 256 bytes, containing the status of the current keyboard. The array of each element (byte) contains a key to the state. If the higher-order byte is a set, the key is to drop (press). In low bit, if the setting indicates, the key is to switch. In this function, only the elbow position CAPS LOCK The key is related. In the switching state NUM Locks and scroll lock keys are ignored.
                                         byte[] lpwTransKey, // [out] The buffer for the pointer received a translation character or character.
                                         int fuState); // [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise.

        //Get the status of the key
        [DllImport("user32")]
        public static extern int GetKeyboardState(byte[] pbKeyState);


        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern short GetKeyState(int vKey);

        private const int WM_KEYDOWN = 0x100;//KEYDOWN
        private const int WM_KEYUP = 0x101;//KEYUP
        private const int WM_SYSKEYDOWN = 0x104;//SYSKEYDOWN
        private const int WM_SYSKEYUP = 0x105;//SYSKEYUP

        private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {
            // Listen for keyboard events
            if ((nCode >= 0) && (KeyDownEvent != null || KeyUpEvent != null || KeyPressEvent != null))
            {
                KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
                // raise KeyDown
                if (KeyDownEvent != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
                {
                    Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
                    KeyEventArgs e = new KeyEventArgs(keyData);
                    KeyDownEvent(this, e);
                }

                //Keyboard pressed
                if (KeyPressEvent != null && wParam == WM_KEYDOWN)
                {
                    byte[] keyState = new byte[256];
                    GetKeyboardState(keyState);

                    byte[] inBuffer = new byte[2];
                    if (ToAscii(MyKeyboardHookStruct.vkCode, MyKeyboardHookStruct.scanCode, keyState, inBuffer, MyKeyboardHookStruct.flags) == 1)
                    {
                        KeyPressEventArgs e = new KeyPressEventArgs((char)inBuffer[0]);
                        KeyPressEvent(this, e);
                    }
                }

                // Keyboard lifting
                if (KeyUpEvent != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP))
                {
                    Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
                    KeyEventArgs e = new KeyEventArgs(keyData);
                    KeyUpEvent(this, e);
                }

            }
            //If 1 is returned, the message will be ended and will not be delivered.
            //If 0 is returned or the CallNextHookEx function is called, the message will be sent out of this hook and continue to pass, that is, to the real receiver of the message
            return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
        }
        ~KeyboardHook()
        {
            Stop();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153

2. Call method

2.1 start monitoring

First, define the key hook and tool class

    private KeyEventHandler myKeyEventHandeler = null;//Key hook
    private KeyboardHook k_hook = new KeyboardHook();
  • 1
  • 2

Secondly, define the methods to be implemented after listening

    private void hook_KeyDown(object sender, KeyEventArgs e)
       {
            //  Here write the specific implementation
            writeLog("Press button" + e.KeyValue);
       }
  • 1
  • 2
  • 3
  • 4
  • 5

Finally, enable monitoring

    public void startListen()
       {
           myKeyEventHandeler= new KeyEventHandler(hook_KeyDown);
           k_hook.KeyDownEvent += myKeyEventHandeler;//Hook key press
           k_hook.Start();//Installing the keyboard hook
       }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.2 turn off monitoring

     public void stopListen()
       {
           if (myKeyEventHandeler != null)
           {
               k_hook.KeyDownEvent -= myKeyEventHandeler;//Cancel key event
               myKeyEventHandeler = null;
               k_hook.Stop();//Close keyboard hook
           }
       }

Topics: Windows SDK