3

12.1 使用键盘鼠标监控钩子 - lyshark

 11 months ago
source link: https://www.cnblogs.com/LyShark/p/17745433.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

12.1 使用键盘鼠标监控钩子

本节将介绍如何使用Windows API中的SetWindowsHookExRegisterHotKey函数来实现键盘鼠标的监控。这些函数可以用来设置全局钩子,通过对特定热键挂钩实现监控的效果,两者的区别在于SetWindowsHookEx函数可以对所有线程进行监控,包括其他进程中的线程,而RegisterHotKey函数只能对当前线程进行监控。

首先我们来实现注册热键功能,注册热键可以使用RegisterHotKey()函数,该函数可以将一个热键与当前应用程序或线程绑定,使得当用户按下热键时,系统会自动将该热键的消息发送到该应用程序或线程中,该函数原型如下;

BOOL RegisterHotKey(
  HWND hWnd,
  int id,
  UINT fsModifiers,
  UINT vk
);

其中,参数的含义如下:

  • hWnd:热键所属的窗口句柄,通常设置为NULL,表示与当前线程绑定
  • id:热键的ID号,用于区分不同的热键
  • fsModifiers:热键的修饰键,可以使用组合键,例如CtrlAltShift
  • vk:热键的虚拟键码,例如VK_F1表示F1VK_LEFT表示左箭头键等

函数需要传入一个窗口句柄、热键ID、热键组合键等参数来设置热键。当热键被按下时,系统会自动将一个WM_HOTKEY消息发送给注册了该热键的窗口,应用程序需要重载该窗口的消息处理函数来响应该事件,从而实现相应的响应操作。该函数会返回一个BOOL类型的值,表示热键设置是否成功。

当热键被注册后则就需要接收热键消息,通常可以使用GetMessage函数,该函数用于从消息队列中获取一个消息并将其存储在一个结构体中,通常用于在一个循环中不断地获取消息,从而实现对Windows消息的处理。

该函数的原型定义如下所示;

BOOL GetMessage(
  LPMSG lpMsg,
  HWND hWnd,
  UINT wMsgFilterMin,
  UINT wMsgFilterMax
);

其中,参数的含义如下:

  • lpMsg:指向MSG结构体的指针,用于存储获取到的消息
  • hWnd:消息接收者的窗口句柄,通常设置为NULL,表示接收所有窗口的消息
  • wMsgFilterMin:指定获取消息的最小消息值,通常设置为0
  • wMsgFilterMax:指定获取消息的最大消息值,通常设置为0

GetMessage函数需要传入一个指向MSG结构体的指针,该结构体包含了消息的各种信息,例如消息的类型、发送者、接收者、时间戳等等。读者只需要通过判断函数内的WM_HOTKEY消息,并监控是否为我们所需要的即可,如下代码是一段注册热键的实现,分别注册了Ctrl+F1 Ctrl+F2 Ctrl+F3三个热键组;

#include <windows.h>
#include <iostream>

using namespace std;

int main(int argc, char* argv[])
{
  // 分别注册三个热键 Ctrl+F1 , Ctrl+F2 , Ctrl+F3
  if (0 == RegisterHotKey(NULL, 1, MOD_CONTROL, VK_F1))
  {
    cout << GetLastError() << endl;
  }
  if (0 == RegisterHotKey(NULL, 2, MOD_CONTROL, VK_F2))
  {
    cout << GetLastError() << endl;
  }
  if (0 == RegisterHotKey(NULL, 3, MOD_CONTROL, VK_F3))
  {
    cout << GetLastError() << endl;
  }

  // 消息循环
  MSG msg = { 0 };

  // 从消息循环内读出按键码
  while (GetMessage(&msg, NULL, 0, 0))
  {
    switch (msg.message)
    {
    case WM_HOTKEY:
    {
      if (1 == msg.wParam)
      {
        std::cout << "CTRL + F1" << std::endl;
      }

      else if (2 == msg.wParam)
      {
        std::cout << "CTRL + F2" << std::endl;
      }

      else if (3 == msg.wParam)
      {
        std::cout << "CTRL + F3" << std::endl;
      }
      break;
    }
    default:
      break;
    }
  }
  return 0;
}

读者可自行编译上述代码片段,并运行,分别按下Ctrl+F1 Ctrl+F2 Ctrl+F3即可看到输出效果图;

1379525-20230510101936438-1771246080.png

当然上述方法是局部的,读者只能在当前进程内使用,如果离开了进程窗体则这类热键将会失效,此时我们就需要使用SetWindowsHookEx函数注册全局钩子,该函数可以在系统中安装钩子,以便监视或拦截特定的事件或消息。

以下是SetWindowsHookEx的函数原型:

HHOOK SetWindowsHookEx(
  int       idHook,
  HOOKPROC  lpfn,
  HINSTANCE hMod,
  DWORD     dwThreadId
);

参数说明:

  • idHook:钩子类型,可以是WH_KEYBOARD(键盘钩子)或WH_MOUSE(鼠标钩子)等
  • lpfn:回调函数,当特定事件或消息发生时,操作系统会调用此函数。该函数的返回值由钩子类型和参数决定
  • hMod:包含lpfnDLL句柄。如果lpfn参数在当前进程内,则该参数可以为NULL
  • dwThreadId:线程标识符,指定与钩子相关联的线程。如果dwThreadId参数为0,则钩子将应用于所有线程

函数会返回一个类型为HHOOK的句柄,该句柄可以在卸载钩子时使用,读者需要注意由于全局钩子会影响系统性能,因此在使用SetWindowsHookEx函数时应谨慎,并在使用结束后及时的通过UnhookWindowsHookEx释放钩子句柄。

如下所示代码则是一个键盘钩子监控案例,在该案例中我们通过SetWindowsHookEx注册一个全局钩子,并设置回调函数LowLevelKeyboardProc通过使用PeekMessageA监控键盘事件,当有键盘事件产生时则自动路由到LowLevelKeyboardProc函数内,此时即可得到按键的类型以及按下键位,如下所示;

#include <windows.h>
#include <iostream>
#include <conio.h>

using namespace std;

// 钩子句柄
HHOOK keyboardHook = 0;

// 键盘钩子
LRESULT CALLBACK LowLevelKeyboardProc(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
    KBDLLHOOKSTRUCT* ks = (KBDLLHOOKSTRUCT*)lParam;
    if (ks->flags == 128 || ks->flags == 129)
    {
        // 监控按键状态
        if (nCode >= 0)
        {
            switch (wParam)
            {
            case WM_KEYDOWN:
                cout << "普通按键抬起" << endl;
                break;
            case WM_KEYUP:
                cout << "普通按鍵按下" << endl;
                break;

            case WM_SYSKEYDOWN:
                cout << "系统按键抬起" << endl;
                break;
            case WM_SYSKEYUP:
                cout << "系统按键按下" << endl;
                break;
            }
        }

        // 监控键盘,并判断键
        switch (ks->vkCode)
        {
        case 0x41:
            cout << "检测到按键:" << "A" << endl;
            break;
        case 0x0D:
            cout << "检测到按键:" << "Enter" << endl;
            break;
        case 0xA0: case 0xA1:
            cout << "检测到按键:" << "Shift" << endl;
            break;
        case 0x08:
            cout << "检测到按键:" << "Backspace" << endl;
            break;
        case 0x20:
            cout << "检测到按键:" << "Space" << endl;
            break;
        }

        // 直接返回1可以使按键失效
        //return 1;
    }
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

int main(int argc, char* argv[])
{
    // 安装钩子WH_KEYBOARD_LL为键盘钩子
    keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandleA(NULL), NULL);
    if (keyboardHook == 0)
    {
        cout << "挂钩键盘失败" << endl; return -1;
    }

    MSG msg;
    while (1)
    {
        if (PeekMessageA(&msg, NULL, NULL, NULL, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
        else
            Sleep(0);
    }
    UnhookWindowsHookEx(keyboardHook);
    return 0;
}

编译并运行上述代码,读者可自行按下键盘键位,则可看到如下图所示的输出;

1379525-20230510103424436-1424837633.png

鼠标钩子的挂钩与键盘基本一致,只是在调用SetWindowsHookEx传递参数时设置了WH_MOUSE_LL鼠标事件,当有鼠标消息时则通过MouseProc鼠标回调函数执行,

#include <windows.h>
#include <iostream>
#include <conio.h>

using namespace std;

// 钩子句柄
HHOOK keyboardHook = 0;

// 鼠标钩子
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
  LPMSLLHOOKSTRUCT p = (LPMSLLHOOKSTRUCT)lParam;
  POINT   pt = p->pt;
  DWORD   mouseData = p->time;

  const char* info = NULL;
  char text[60], pData[50], mData[50];

  PAINTSTRUCT ps;
  HDC hdc;

  if (nCode >= 0)
  {
    if (wParam == WM_MOUSEMOVE)
    {
      info = "鼠标 [移动]";
    }
    else if (wParam == WM_LBUTTONDOWN)
    {
      info = "鼠标 [左键] 按下";
    }
    else if (wParam == WM_LBUTTONUP)
    {
      info = "鼠标 [左键] 抬起";
    }
    else if (wParam == WM_LBUTTONDBLCLK)
    {
      info = "鼠标 [左键] 双击";
    }
    else if (wParam == WM_RBUTTONDOWN)
    {
      info = "鼠标 [右键] 按下";
    }
    else if (wParam == WM_RBUTTONUP)
    {
      info = "鼠标 [右键] 抬起";
    }
    else if (wParam == WM_RBUTTONDBLCLK)
    {
      info = "鼠标 [右键] 双击";
    }
    else if (wParam == WM_MBUTTONDOWN)
    {
      info = "鼠标 [滚轮] 按下";
    }
    else if (wParam == WM_MBUTTONUP)
    {
      info = "鼠标 [滚轮] 抬起";
    }
    else if (wParam == WM_MBUTTONDBLCLK)
    {
      info = "鼠标 [滚轮] 双击";
    }
    else if (wParam == WM_MOUSEWHEEL)
    {
      info = "鼠标 [滚轮] 滚动";
    }

    ZeroMemory(text, sizeof(text));
    ZeroMemory(pData, sizeof(pData));
    ZeroMemory(mData, sizeof(mData));

    std::cout << "鼠标状态: " << info << std::endl;
    std::cout << "X: " << pt.x << " Y: " << pt.y << std::endl;
    std::cout << "附加数据: " << mouseData << std::endl;
  }

  return CallNextHookEx(NULL, nCode, wParam, lParam);
}

int main(int argc, char* argv[])
{
  // 安装钩子
  keyboardHook = SetWindowsHookEx(WH_MOUSE_LL,MouseProc,GetModuleHandleA(NULL),NULL);
  if (keyboardHook == 0)
  {
    cout << "挂钩鼠标失败" << endl; return -1;
  }

  MSG msg;
  while (1)
  {
    // 如果消息队列中有消息
    if (PeekMessageA(&msg,NULL,NULL,NULL,PM_REMOVE))
    {
      // 把按键消息传递给字符消息
      TranslateMessage(&msg);

      // 将消息分派给窗口程序
      DispatchMessageW(&msg);
    }
    else
      Sleep(0);
  }
  UnhookWindowsHookEx(keyboardHook);

  return 0;
}

读者可自行编译并运行上述代码片段,当挂钩后我们就可以看到鼠标的移动位置以及鼠标击键情况,如下图所示;

1379525-20230510122545503-185652063.png

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/bdb59f93.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK