本文将讲述如何使用Python生成bat文件来修改Windows中的键盘键位。

本文参考了《键盘键位修改及管理(Windows篇)》一文。但原文中代码有几处错误,因此有所修改。

在Windows注册表中有个”Scancode Map”(即扫描码映射)的键,我们可以通过修改这个键的值来实现键位映射的更改。

“Scancode Map”的值的格式是”hex:00,00,00,00,00,00,00,00,xx,00,00,00,[yy,yy,yy,yy,…,yy,yy,yy,yy,]00,00,00,00”

前8个16进制的值(即前8组00)表示版本号和头部字节,后4个16进制的值(即最后的4组00)表示结束标志,中间xx表示映射数目,最小值为01(考虑到结束标志的4组00),中括号内为可写项,也是我们修改键位比较关键的部分,每四个代表一组映射。

在我们键盘上每一个按键都有其十六进制扫描码,例如A的扫描码为”1e”,其十六进制扫描码修正形式(为了表示方便就这么说吧)就是”001e”,B的扫描码为”0030”。具体其他按键扫描码在源码中贴有。

既然我们知道键盘上每一个键都具有其对应的扫描码,那么我们假设需要A和B键互换,应该怎么做呢?这个时候就需要我们向中括号中添加我们需要的值,”30,00,1e,00,”就可以实现将B键的功能映射到物理键盘A上(通俗点说就是敲击键盘上的A键会打出B字符,同时要注意值的顺序)。你以为这样就完了吗?不然。”30,00,1e,00,”只能将B键功能映射在物理键盘A键位上,而物理键位B键并未被映射成A!这很危险,相当于键盘上没有一个按键能实现A的功能,所以我们还得添加一项”1e,00,30,00,”。最后我们的”Scancode Map”的完整值就为”hex:00,00,00,00,00,00,00,00,03,00,00,00,30,00,1e,00,1e,00,30,00,00,00,00,00”。就这一串值就可完全调换A,B键的功能。

每次手动修改注册表太麻烦,因此考虑写一个python的脚本key_map.py。但是python无法直接修改注册表,我们的python代码负责根据一个配置文件生成一个.bat的文件。之后以管理员身份运行.bat文件即可。为了方便恢复,还顺便生成了一个用于复原的.bat文件。

配置文件的格式如下。

1
2
Caps Lock: Left Ctrl;
Left Ctrl: Caps Lock;

配置文件可以任意命名,假设命名为key.txt,该配置文件首行含义为将物理键Caps Lock映射为Left Ctrl,第二行相反。之后只要在命令行中执行python key_map.py key.txt,就可以生成相应.bat文件。然后以管理员身份运行.bat文件后重启即可生效。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import sys
import os
save_format = "bat"
class CountError(Exception): #文本文件格式错误异常
pass
class FileFormatError(Exception): #save_format值异常
pass
if save_format not in ["bat","reg"]:
raise FileFormatError("The variable 'save_format'`s value must be 'bat' or 'reg'.")
if __name__ == "__main__":
if not os.path.isdir("layout_"+save_format):
os.mkdir("layout_"+save_format)
if len(sys.argv) == 1:
with open("layout_bat/recover.bat",'w') as f:
f.write('@echo off\nreg delete "hklm\\system\\currentcontrolset\\control\\keyboard layout" /v "ScanCode Map" /f\necho "键位已恢复,重启系统后生效"\npause')
input("恢复文件recover.bat已生成至layout_bat文件夹下,以管理员身份右键执行该文件后重启系统生效。\n按回车键退出程序...")
elif len(sys.argv) == 2:
scan_code_dict = {
"00 00":"None",
"01 00":"Esc", #即Esc键的扫描码是"0001"
"02 00":"1",
"03 00":"2",
"04 00":"3",
"05 00":"4",
"06 00":"5",
"07 00":"6",
"08 00":"7",
"09 00":"8",
"0a 00":"9",
"0b 00":"0",
"0c 00":"-",
"0d 00":"+",
"0e 00":"Backspace",
"0f 00":"Tab",
"10 00":"Q",
"11 00":"W",
"12 00":"E",
"13 00":"R",
"14 00":"T",
"15 00":"Y",
"16 00":"U",
"17 00":"I",
"18 00":"O",
"19 00":"P",
"1a 00":"[",
"1b 00":"]",
"1c 00":"Enter",
"1d 00":"Left Ctrl",
"1e 00":"A",
"1f 00":"S",
"20 00":"D",
"21 00":"F",
"22 00":"G",
"23 00":"H",
"24 00":"J",
"25 00":"K",
"26 00":"L",
"27 00":";",
"28 00":"'",
"29 00":"`",
"2a 00":"Left Shift",
"2b 00":"\\",
"2c 00":"Z",
"2d 00":"X",
"2e 00":"C",
"2f 00":"V",
"30 00":"B",
"31 00":"N",
"32 00":"M",
"33 00":",",
"34 00":".",
"35 00":"/",
"36 00":"Right Shift",
"37 00":"n*",
"38 00":"Left Alt",
"39 00":"Space",
"3a 00":"Caps Lock",
"3b 00":"F1",
"3c 00":"F2",
"3d 00":"F3",
"3e 00":"F4",
"3f 00":"F5",
"40 00":"F6",
"41 00":"F7",
"42 00":"F8",
"43 00":"F9",
"44 00":"F10",
"45 00":"Num Lock",
"46 00":"Scroll Lock",
"47 00":"n7",
"48 00":"n8",
"49 00":"n9",
"4a 00":"n-",
"4b 00":"n4",
"4c 00":"n5",
"4d 00":"n6",
"4e 00":"n+",
"4f 00":"n1",
"50 00":"n2",
"51 00":"n3",
"52 00":"n0",
"53 00":"n.",
"57 00":"F11",
"58 00":"F12",
"1c e0":"nEnter",
"1d e0":"Right Ctrl",
"37 e0":"PrtSc",
"38 e0":"Right Alt",
"47 e0":"Home",
"48 e0":"Up",
"49 e0":"Page Up",
"4b e0":"Left",
"4d e0":"Right",
"4f e0":"End",
"50 e0":"Down",
"51 e0":"Page Down",
"52 e0":"Insert",
"53 e0":"Delete",
"5b e0":"Left Windows",
"5c e0":"Right Windows",
}
fun_key_dict = dict((m.upper(),n) for n,m in scan_code_dict.items()) #键值互换,键值全大写
content = '00 00 00 00 00 00 00 00'
#用于暂时保存映射前后的键位,判断这次键位修改是否有风险
before_map_set = set()
after_map_set = set()
with open(sys.argv[1]) as f:
p = f.read().strip().split(';')
p.remove('')
content += ' {:0>2x} 00 00 00'.format(len(p)+1)
try:
for i in p:
if len(i.strip().split(':')) == 2:
before_map_set.add(i.split(':')[0].strip().upper())
after_map_set.add(i.split(':')[1].strip().upper())
content += ' '+fun_key_dict[i.split(':')[1].strip().upper()]+' '+fun_key_dict[i.split(':')[0].strip().upper()]
else:
raise CountError
content += ' 00 00 00 00'
except KeyError:
print("文件中键名称有误")
except CountError:
print("文件中未按格式书写")
else:
if before_map_set != after_map_set:
run = input("此次键位替换存在风险,{}键功能将在键盘上无对应按键,是否继续?(输入y继续,否则退出程序)".format(str(before_map_set-after_map_set)[1:-1]))
if run != 'y':
sys.exit()
content = content.replace(' ','')
print(content)
with open("layout_bat/"+'.'.join(sys.argv[1].split('\\')[-1].split('.')[:-1])+'.bat','w') as g:
g.write('@echo off\nreg add "hklm\\system\\currentcontrolset\\control\\keyboard layout" /v "ScanCode Map" /t REG_BINARY /d "{}" /f\necho "键位已完成修改,重启系统后生效"\npause'.format(content))
input("...\n{}文件已生成至layout_bat目录下,右键以管理员身份执行该文件后重启系统生效。\n按回车键退出程序...".format('.'.join(sys.argv[1].split('\\')[-1].split('.')[:-1])+'.'+save_format))
else:
input("传入参数错误,按回车键退出程序...")