Как отловить все клавиши через низкоуровневый хук?

148
24 декабря 2019, 09:50

Как отловить в HookCallback все клавиши с проверкой по шифтам и.т.п? Обычный (Keys) Marshal.ReadInt32 (lParam) просто пишет большими буквами.

public delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
private static readonly LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
  if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
  {
    try
    {
       Console.WriteLine((Keys)Marshal.ReadInt32(lParam));
    }
    catch (AccessViolationException ex) { Console.WriteLine($"Error: {ex.ToString()}"); }
  }
  return NativeMethods.CallNextHookEx(_hookID, nCode, wParam, lParam);
}

У меня есть идея занести список клавиш в List<Keys> и оттуда как-то делать проверку.

Но может у Вас есть идеи по лучше?! ( желательно с примером )

Answer 1

Как-то так:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Windows.Forms;
namespace ConsoleApplication1
{
    class Program
    {
        static KeyLogger k;
        static void Main(string[] args)
        {            
            k = new KeyLogger();
            k.Start();
            Console.CancelKeyPress += Console_CancelKeyPress;
            Console.WriteLine("Started listening for keyboard events...");
            Application.Run();
        }
        private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            k.Stop();            
            Application.Exit();
        }
    }
    sealed class KeyLogger : IDisposable //отслеживает события клавиатуры с помощью низкоуровневого хука Windows
    {
        //объявления типов, констант и функций Windows API
        delegate IntPtr KeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
        const int WH_KEYBOARD_LL = 13;
        const int HC_ACTION = 0;
        const int WM_KEYDOWN = 0x0100;
        const uint VK_CAPITAL = 0x14;        
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        static extern IntPtr SetWindowsHookEx(int idHook, KeyboardProc lpfn, IntPtr hInstance, int threadId);
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);
        [DllImport("kernel32.dll")]
        private static extern IntPtr LoadLibrary(string dllToLoad);
        [DllImport("USER32.dll")]
        public static extern short GetKeyState(int vKey);
        [DllImport("user32.dll")]
        public static extern short GetAsyncKeyState(int vKey);
        [DllImport("user32.dll")]
        public static extern int ToUnicodeEx(
            uint wVirtKey,
            uint wScanCode,
            byte[] lpKeyState,
            [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff,
            int cchBuff,
            uint wFlags,
            IntPtr dwhkl);
        [DllImport("user32.dll")]
        public static extern bool GetKeyboardState(byte[] lpKeyState);
        [DllImport("user32.dll")]
        public static extern uint MapVirtualKey(
            uint uCode,
            uint uMapType);
        [DllImport("user32.dll")]
        public static extern IntPtr GetKeyboardLayout(uint idThread);
        [DllImport("user32.dll")]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();
        //преобразование виртуального кода клавиши в его Юникод-представление
        public static string VKCodeToUnicode(uint vkCode)
        {
            StringBuilder buf = new StringBuilder();
            byte[] keyboardState = new byte[255];
            short x;
            byte y;
            for (int i = 0; i < 255; i++)
            {
                if (i == VK_CAPITAL)
                {
                    x = GetKeyState(i);
                }
                else
                {
                    x = GetAsyncKeyState(i);
                }
                y = 0;
                if ((x & 0x8000) != 0) y = (byte)(y | 0x80);
                if ((x & 0x0001) != 0) y = (byte)(y | 0x01);
                keyboardState[i] = y;
            }
            ToUnicodeEx(vkCode, MapVirtualKey(vkCode, 0), keyboardState, buf, 5, 0,
                GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero)));
            return buf.ToString();
        }
        IntPtr _hookID = IntPtr.Zero;                
        //начинает отслеживание событий клавиатуры
        public void Start()
        {
            if (disposed) throw new ObjectDisposedException("KeyLogger");
            if (_hookID != IntPtr.Zero) return;
            _hookID = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookCallback, IntPtr.Zero, 0);
            if (_hookID == IntPtr.Zero)
            {
                int error = Marshal.GetLastWin32Error();
                throw new Win32Exception(error, "Failed to install hook! Error: "+ error.ToString());
            }            
        }
        //останавливает отслеживание событий клавиатуры
        public void Stop()
        {
            if (disposed) return;
            if (_hookID == IntPtr.Zero) return;
            UnhookWindowsHookEx(_hookID);
            _hookID = IntPtr.Zero;              
        }               
        //вызывается Windows при нажатии клавиши
        IntPtr KeyboardHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
            {
                int vkCode = Marshal.ReadInt32(lParam);
                string s = VKCodeToUnicode((uint)vkCode);
                Console.Out.WriteLine("Key: " + ((Keys)vkCode).ToString() + "; Character: " + s);
            }
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }
        //так как KeyLogger использует неуправляемый ресурс, реализуем Dispose и Finalize...
        bool disposed = false;                  
        public void Dispose()
        {
            if (disposed) return;
            Stop();
            disposed = true;
            GC.SuppressFinalize(this);
        }
        ~KeyLogger()
        {            
            Dispose();
        }  
    }
}

Известная проблема (Windows 10): если активно консольное окно, всегда распознаются символы раскладки, установленной в момент создания консоли, вместо текущей раскладки.

Основано на данных вопросах:

GetKeyboardState: определение состояния клавиши

Некорректная работа перехватчика нажатых клавиш (кейлоггер) C#

READ ALSO
Поиск элементов на странице не имеющих &#171;class&#187; и &#171;id&#187;, но содержащих текст С# , Selenium

Поиск элементов на странице не имеющих «class» и «id», но содержащих текст С# , Selenium

Имеется 5 элементов на вебстранице содержащих разный текст: 1,7,13,25,4Пример: 13

155
C# вопрос по ООП

C# вопрос по ООП

Задумался как сделать логичнее (правильнее)Есть два класса: один ставит лайки в нём два метода лайки по тэгу и лайки по геолокации, второй...

131
Как добавить MouseEventHandler к PictureBox?

Как добавить MouseEventHandler к PictureBox?

Собственно есть такой код, где в цикле создаются PictureBox

147
Обобщения оператор where

Обобщения оператор where

Подскажите как загуглить что бы почитать о операторе/ключевом слове where в такой конструкции например:

151