【小工具】Win32API-使用低级键盘钩子屏蔽 NumLock 按键

在使用键盘进行大量文字输入工作时,经常会不小心按到 NumLock 按键(尤其是在笔记本电脑上,Backspace 离它太近了),带来了不小的麻烦。

本次编写的小工具可以帮助你临时禁用 NumLock 按键。

实现原理

实际上就是使用注册一个低级键盘钩子,来监听按键事件。

1
HHOOK hhkLowLevelKybd = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, 0, 0);

其中,回调处理函数 LowLevelKeyboardProc 如下:

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
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
BOOL fEatKeystroke = FALSE;
if (nCode == HC_ACTION)
{
switch (wParam)
{
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
// case WM_KEYUP:
//case WM_SYSKEYUP:
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam;
switch (p->vkCode)
{
case VK_NUMLOCK: // Eat up NumLock
fEatKeystroke = TRUE;
printf("\r\n[#] Eat KeyCode: %lu\r\n\n", p->vkCode);
break;
default:
fEatKeystroke = FALSE;
printf("[+] Current KeyCode: %lu\r\n", p->vkCode);
break;
}
break;
default:
break;
}
}
return (fEatKeystroke ? 1 : CallNextHookEx(NULL, nCode, wParam, lParam));
}

大致流程如下:

  1. 通过检查 wParam 的值,判断当前按键事件是哪一类(普通按键按下、普通按键弹起、系统按键按下、系统按键弹起)

  2. 对于特定类型的事件进行处理。这里是捕获了 WM_KEYDOWNWM_SYSKEYDOWN 这两种,对“按键按下”的事件进行处理。

  3. 在处理过程中,检查 p->vkCode 的值(键码)以确定被按下的是哪个按键。

    如果发现了需要屏蔽的目标键码,就将 fEatKeystroke 标志设置为 TRUE,否则设为 FALSE

  4. 在退出回调函数前,检查 fEatKeystroke 标志。

    如果为 TRUE ,说明此次按键的触发需要被屏蔽,我们就可以通过 return 1 把这个消息截断,实现按键屏蔽;

    否则我们 return CallNextHookEx ,把消息继续传递,从而使此次按键事件能够被操作系统和其它程序继续处理。

这个函数中,下面这段代码是程序的关键,修改时一定要谨慎。

注释掉某一个 case ,或者是在某个 case 下添加了一个 break; ,都会让程序的行为有很大变化。

1
2
3
4
5
6
7
8
9
10
11
12
switch (wParam)
{
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
// case WM_KEYUP:
// case WM_SYSKEYUP:

// do something...

default:
break;
}

此外,为了使程序运行时 hook 钩子保持,在 main 函数中可以使用这样的结构:

1
2
3
4
5
6
7
8
9
10
// Keep this app running until we're told to stop

MSG msg;
while (!GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

UnhookWindowsHookEx(hhkLowLevelKybd);

一旦获取到用户中止等信号,while 循环就能退出,同时执行 UnhookWindowsHookEx

(对于这一点我还是有点疑惑的,循环一退出是不是就开始执行 _exit 了,而不执行 Unhook?或者说,操作系统在进程结束后会自动执行 Unhook?当然,如果要捕获这些 SIGNAL,在程序退出前再进行一下 Unhook 的操作,使用一个叫 SetConsoleCtrlHandler 的函数也能解决。)

开发环境配置

这次试着使用了 CLion 开发环境,配合 MSVC Build Tools。环境配置过程中有些小坑,也一并记录一下。

要想顺利构建,需要进行如下配置:

  • 打开 文件 - 设置 - 构建、执行、部署 - CMake,添加几个配置文件,但要注意设置工具链为 “Visual Studio 20xx”,设置生成器为 ”Visual Studio 1x 20xx”。
  • 默认编译出来的是64位程序,如果要编译 “x86” 目标,可以在配置文件这个页面中的 “CMake 选项” 追加填写 -A "Win32"

源码地址 & 二进制文件下载

链接:Github or Gitee

在相应的 Release 页面就可以下载构建好的可执行程序了。

其它的一些使用事项在上述源码页面中有说明。


2022.11.22 补充说明

突然发现,有时使用键盘钩子的方式,虽然能成功 Hook,键盘上 NumLock 的指示灯也不熄灭,但系统中, NumLock 按键还是被关闭了。

故又写了个无限循环版本,监测按键状态,如果被关闭则程序模拟点击,将 NumLock 开启。

代码也在上述仓库中,名为 loop_ver.c 。同时,在 Release 页面也有构建好的二进制文件。

如需隐藏窗口,将 ShowWindow(cmd_hwnd, SW_MINIMIZE); 改为 ShowWindow(cmd_hwnd, SW_HIDE); 然后再 Build。