本文参考了《键盘键位修改及管理(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
文件。
配置文件的格式如下。
Caps Lock: Left Ctrl;
Left Ctrl: Caps Lock;
配置文件可以任意命名,假设命名为key.txt
,该配置文件首行含义为将物理键Caps Lock映射为Left Ctrl,第二行相反。之后只要在命令行中执行python key_map.py key.txt
,就可以生成相应.bat
文件。然后以管理员身份运行.bat
文件后重启即可生效。
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("传入参数错误,按回车键退出程序...")