A while back I was researching a way to check if a machine was being used so that it could be used in some computationally expensive processing if the machine was idle. I ended up using some WMI commands, but in the course of researching I wrote some code to hook into the keyboard and mouse at a low level.

This code is based off of the GlobalMouseKeyHook project.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace MinHook
    class KBHook
        public const int WM_KEYDOWN = 0x100;

        internal struct KeyboardHookStruct
            public int VirtualKeyCode;
            public int ScanCode;
            public int Flags;
            public int Time;
            public int ExtraInfo;

        public static int HookHandle;

        public delegate int HookCallback(int nCode, int wParam, IntPtr lParam);

        static void Main(string[] args)
            HookHandle = SetWindowsHookEx(13, new HookCallback(CallBackFunction), Process.GetCurrentProcess().MainModule.BaseAddress, 0);

            while (true)

        public static int CallBackFunction(int nCode, Int32 wParam, IntPtr lParam)
            if (nCode == 0)
                ProcessCallBack(wParam, lParam);

            return CallNextHookEx(HookHandle, nCode, wParam, lParam);

        public static void ProcessCallBack(Int32 wParam, IntPtr lParam)
            if (wParam != WM_KEYDOWN)

            KeyboardHookStruct k = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));

            byte[] keyState = new byte[256];
            byte[] inBuffer = new byte[2];

            bool isSuccesfullyConverted = ToAscii(k.VirtualKeyCode, k.ScanCode, keyState, inBuffer, k.Flags) == 1;

            var ch = (char)inBuffer[0];

            Console.WriteLine(string.Format("VKC={0}, Char={1}", k.VirtualKeyCode, ch));

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        internal static extern int SetWindowsHookEx(int idHook,HookCallback lpfn,IntPtr hMod,int dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        internal static extern int CallNextHookEx(int idHook,int nCode,int wParam,IntPtr lParam);

        public static extern int ToAscii(int uVirtKey,int uScanCode,byte[] lpbKeyState,byte[] lpwTransKey,int fuState);

        public static extern int GetKeyboardState(byte[] pbKeyState);