用C/C++(Win32API)写软件修改键位
用C/C++(Win32API)写软件修改键位
扑克前言
紧接上篇《Windows用注册表修改键盘映射(扫描码)》, 用起来会发现处处不协调,除了需要熟悉新键位以外,最重要的是原本的快捷键也被拆散了,如原本都在左下角的Ctrl+Z/X/C/V。 所以我们应该在保证快捷键相对位置不变的情况下,修改其他字母的位置,在本专栏中使用键盘钩子(Keyboard Hook)。
注:
由于软件很小而且要不安全操作,所以选择C/C++来写,并且暂时不显示窗口页面。
每段代码会分别展示C/C++的写法,C在前、C++在后,两者相同时只会标注为C代码。一般来说C的代码C++也可以用,但写C++时建议用C++的标准。
使用本方法改键位可能会被某些游戏判为作弊!但上篇专栏修改注册表的方法不会。
引入
本次我们以德沃夏克键盘(Dvorak Keyboard)为例,把Qwerty键盘修改为德沃夏克键盘。
Qwerty键盘(Qwerty Keyboard)
1 ! | 2 @ | 3 # | 4 $ | 5 % | 6 ^ | 7 & | 8 * | 9 ( | 0 ) | - _ | + = |
Q | W | E | R | T | Y | U | I | O | P | [ { | ] } |
A | S | D | F | G | H | J | K | L | ; : | ' " | |
Z | X | C | V | B | N | M | , < | . > | / ? |
德沃夏克键盘(Dvorak Keyboard)
1 ! | 2 @ | 3 # | 4 $ | 5 % | 6 ^ | 7 & | 8 * | 9 ( | 0 ) | [ { | ] } |
' " | , < | . > | P | Y | F | G | C | R | L | / ? | + = |
A | O | E | U | I | D | H | T | N | S | - _ | |
; : | Q | J | K | X | B | M | W | V | Z |
此外,根据维基百科1:
钩子编程(Hooking),也称作“挂钩”,是计算机程序设计术语,指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码,被称为钩子(Hook)。
简单来说就是拦截你输入的信息,处理过后再给电脑。
编写方法
以下默认引用头文件
1 |
首先是WinMain()
函数,这里只有两件事要做:安装键盘钩子和进行事件循环。
注:因为keyboardHook
在其他函数里也会用到,所以是全局变量。
1 | // 键盘钩子 |
1 | static HHOOK KeyboardHook = nullptr; |
1 | // 主程序 |
事件循环并不重要,所以可以直接抄网上的代码,安装钩子主体是创建一个新对象,这部分需要重点解释。
修改键位
KeyboardLayoutList
数列规定了各种键盘的布局,其中第一个键盘是Qwerty键盘,第二个以德沃夏克键盘为例(如S对应O,D对应E)。
注:由于后面一个函数keybd_event()
需要BYTE类型的字符,所以我们用BYTE类型定义。
1 | // 某些键盘上符号的虚拟键代码 |
1 |
|
1 | // Qwerty键盘(序号0) |
1 | // 键盘列表 |
1 |
|
首先记录原键盘的键位,代码中一行代表现实中的一行(其实不记录也没关系,但如果以后要搞自定义键盘功能时,就一定要先留一个默认布局)
其中这些宏或常量表达式是为了提高可读性,对应了键盘上的标点符号,可以见WinUser.h
的文件里定义:
1 | // WinUser.h |
当需要用某种布局时,只要改变选择的序号就可以了:
1 | // 目前选择的键盘序号 |
安装钩子
这段是本项目的核心区:
1 | // 某键是否被按下 |
1 | inline auto IsKeyPressed(const int nVirtualKey) { return (GetKeyState(nVirtualKey) & (1 << (sizeof(SHORT) * 8 - 1))) != 0; } |
1 | // 键盘钩子处理程序 |
1 | inline LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) |
从KeyboardProc()
开始看,首先p
储存了键盘事件,即当前按下或抬起了什么键。handled
是一个临时标志,表示有没有对键盘事件进行处理,表示是否对键盘事件进行处理,处理了为true
。
下面就要写除了快捷键以外的键位修改了,一般来说快捷键开头都是Ctrl、Win、Alt、Tab以及它们的组合(Shift一般不会出现在第一个,因为Shift按下会转换符号或者转换大小写),所以当以上四个键(左右Win键算同一个)按下时就不处理,其他才会处理,当确定要处理时,令handled
变为true
。
IsKeyPressed()
:当按键按下时,GetKeyState()
返回值(SHORT类型)的最高位为1
,否则为0
,所以与图中1<<(sizeof(SHORT)*8-1)
按位与结果不为0
就是按下,为0
就是没按下。
下面是一个for
循环,找到原键位的键后映射到新的键,用Kbe()
(keybd_event()
)进行处理。有四个参数,第一个填虚拟键值,之前已经定义好;第二个填扫描码(可见上个专栏),但可以不填;第三填选项标志,键抬起时为KEYEVENTF_KEYUP
,落下时为0
,此处填0
;最后一个是附加信息,要填1<<24
因为根据MSDN2,最后一个ULONG_PTR
类型参数对应了p
中的dwExtraInfo
,可以传递额外的信息,而dwExtraInfo
只有25-28位是保留的,其他都会被其他的信息占据,所以填1<<24
(刚好到25位)。如果不是我们目标的键,进入default
,也不处理。
最后,如果处理了数据就返回1
,表示屏蔽原来的事件并发送已编辑的新事件,第二次再被抓获时会因为dwExtraInfo
的标志而直接不处理跳过;如果不处理数据则直接放行事件,并让下一个钩子再处理。
综上,一个KeyboardCorrector项目就写完了,可以完成预设的任务,并有自定义键位的改进空间。
注:关闭软件时可以用任务管理器,也可以在程序里设置快捷键关闭。
完整代码(Github)
C:https://github.com/Poker-sang/KeyboardCorrector/blob/main/KeyboardCorrector.c
C++(C++/CLI):https://github.com/Poker-sang/KeyboardCorrector/blob/main/KeyboardCorrector/KeyboardCorrector.h