鍵盤鍵位修改及管理(Windows篇)

不知道大家生活中有沒有遇到這些情況:鍵盤上某個比較重要的鍵損壞或失靈而要重新更換鍵盤,或者在一些遊戲、辦公軟體重的一些默認按鍵不合理,又或者希望自己DIY出一套屬於自己的鍵位布局。那麼我想這篇文章對你會很有幫助。

前幾個月因心血來潮想嘗試一些其他鍵位布局(例如Dvorak、Norman、Colemak等),需要修改鍵盤鍵位。其實,修改鍵位的方法也有很多,比較常見的是安裝第三方輔助軟體修改鍵位映射和功能。但對於我個人來說,出於簡潔安全、自由可控的原因,不太喜歡在自己計算機上安裝一些不必要的軟體,這個功能完全可以在Windows上通過修改底層的註冊表實現。

先說一下這篇文章的大致內容:

  1. 實現原理
  2. 自製管理工具(Python3)

實現原理

在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",右ctrl鍵的掃描碼為"e01d",右alt鍵的掃描碼則表示為"e038",空值的掃描碼為"0000"(可實現無效按鍵或者說是屏蔽按鍵)。具體其他按鍵掃描碼在源碼中貼有。

既然我們知道鍵盤上每一個鍵都具有其對應的掃描碼,那麼我們假設需要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鍵的功能。

再來一個,我們要實現右Alt鍵能實現右Ctrl鍵的功能,並且屏蔽掉右Ctrl鍵,那麼其"Scancode Map"值就為"hex:00,00,00,00,00,00,00,00,03,00,00,00,1d,e0,38,e0,00,00,1d,e0,00,00,00,00"。

那麼問題來了,既然我知道這個鍵位和所謂的鍵盤掃描碼之間的對應關係和Scancode值之後,具體怎麼實現呢?

別急,我們一步步來,先打開Windows註冊表編輯器(在cmd或powershell命令行下鍵入regedit回車即可打開),然後在"HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlKeyboard Layout"這個項下新建一個名為"ScanCode Map"鍵(右鍵新建二進位值),然後再右鍵名稱列下的這個"ScanCode Map"修改,這時就可以依次鍵入編輯那一串hex值了,最後確定就完成了註冊表的修改了!

圖示如下,先新建這個ScanCode Map的鍵(右鍵Keyboard Layout或空白處,然後選擇新建二進位值)

然後修改值(我的這個hex值功能是實現左Ctrl和CapsLock互換,畢竟Emacs黨)

但是,修改完了後並不能立即生效,因為是通過註冊表修改底層鍵位映射所以需要重啟計算機,重啟資源管理器也是沒有用的。

那麼,要還原按鍵怎麼辦?也很簡單,有兩種方法,一是從註冊表中刪除你創建的ScanCode Map這個鍵,或者用"hex:00,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00"覆蓋掉原來的值就行了。

當然,如果修改的鍵位比較多的話,通過查各個鍵位映射值,修改註冊表寫hex值,就麻煩很多了(很佩服曾經的自己硬是這樣寫了一些鍵位布局)。這時我們需要一個工具來管理,可以用第三方軟體。但是既然原理我們已經知道了,也並不難遠未達到工程級的量級,那完全可以自己實現這個管理工具。我們可以用Java,C++,Python等來寫,這不重要,自己喜歡什麼就用什麼吧。下面是我用Python寫的一個布局管理工具。

自製管理工具

這個工具名為layout_manage.py,功能是實現將我們要修改的鍵位關係轉換為hex,並導出bat或reg文件(bat是Windows批處理文件,reg是Windows註冊表腳本文件,在這裡的功能是將hex值寫入前面註冊表中"ScanCode Map"這個鍵中)到layout_bat或layout_reg文件夾中。其本身不帶有修改註冊表功能(python自身的許可權問題),只是生成的文件具有此功能。

即我們想修改以下鍵位:將qwer和asdf調換位置,小鍵盤123和789調換位置,屏蔽右shift鍵。這時我們只需要新建一個文本文件(例如一個名為test.txt的文件),將以下內容寫入:

這個文件內容是按鍵值對組成的,"Q: A"的意思是將物理按鍵Q映射為A字元,即按Q鍵敲出A,更容易讓人理解,冒號前為物理按鍵,冒號後為按鍵功能。說一下這個格式規則:

  1. 忽略大小寫
  2. 忽略換行符、冒號、分號前後的空白字元(位於兩單詞間的空格不忽略,如Left Ctrl中間只能有一個空格)
  3. 小鍵盤上的字元前需要加n,例如小鍵盤上的0和.應分別寫為"n0"和n.
  4. 一些功能鍵需正確寫入,如:Tab、Esc、CapsLock、Backspace、Delete、Enter、Left Ctrl、Left Windows等

然後我們將這個文件作為參數,傳遞給layout_manage.py並執行。有兩種方法,第一種是直接拖動這個txt文件到layout_manage.py圖標上,這樣layout_manage.py也能順利執行。第二種方法是在cmd或powershell中執行命令"python layout_manage.py test.txt",注意路徑問題。

此外如果沒有其他參數傳遞給該腳本文件時(即雙擊直接執行),將會生成一個recover文件,即還原初始鍵位的文件,也很方便。

附上源碼,注釋應該很清楚了:

import sysimport ossave_format = "bat" #這個值可以修改,有兩個選項(bat和reg),可選擇生成bat批處理文件或者reg註冊表腳本文件,功能一樣class CountError(Exception): #文本文件格式錯誤異常 passclass FileFormatError(Exception): #save_format值異常 passif 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: if save_format == "bat": with open("layout_bat/recover.bat",w) as f: f.write(@echo off
reg delete "hklm\system\currentcontrolset\control\keyboard layout" /v "ScanCode Map" /f
echo "鍵位已恢復,重啟系統後生效"
pause) input("恢復文件recover.bat已生成至layout_bat文件夾下,以管理員身份右鍵執行該文件後重啟系統生效。
按回車鍵退出程序...") else: with open("layout_reg/recover.reg",w) as f: f.write(Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"ScanCode Map"=hex:00,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00) input("恢復文件recover.reg已生成至layout_reg文件夾下,雙擊執行該文件後重啟系統生效。
按回車鍵退出程序...") 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 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() if save_format == "bat": with open("layout_bat/"+..join(sys.argv[1].split(\)[-1].split(.)[:-1])+.bat,w) as g: g.write(@echo off
reg add "hklm\system\currentcontrolset\control\keyboard layout" /v "ScanCode Map" /t REG_BINARY /d "{}" /f
echo "鍵位已完成修改,重啟系統後生效"
pause.format(content)) input("...
{}文件已生成至layout_bat目錄下,右鍵以管理員身份執行該文件後重啟系統生效。
按回車鍵退出程序...".format(..join(sys.argv[1].split(\)[-1].split(.)[:-1])+.+save_format)) else: with open("layout_reg/"+..join(sys.argv[1].split(\)[-1].split(.)[:-1])+.reg,w) as g: g.write(Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"ScanCode Map"=hex:{}.format(,.join(content.split()))) input("...
{}文件已生成至layout_reg目錄下,雙擊執行該文件後重啟系統生效。
按回車鍵退出程序...".format(..join(sys.argv[1].split(\)[-1].split(.)[:-1])+.+save_format)) else: input("傳入參數錯誤,按回車鍵退出程序...")

另外通過此腳本生成的bat或reg文件可以將其保存好,在其他Windows系統電腦上可以直接執行並修改為你想要的鍵位,這樣可以一次生成,多處適用!切勿用來惡作劇哦。

關於鍵位布局:我用了一個多月的Dvorak和兩個多月的Norman,剛開始練習使用新鍵位的一周非常痛苦和新鮮,適應之後在英文文章輸入的確要快點,但不是很明顯。如果是敲code的話,就沒什麼可比性了。考慮到種種因素(尤其是在用其他人設備時),所以最後還是回歸了QWERTY鍵位。


推薦閱讀:

能否在 Windows 中完整模擬 fork(3)?若能,如何?
微軟內部對於 Windows 在中國市場的盜版情況是否曾經存在過適度縱容的政策?如果有,這樣決定的原因是什麼?
Windows 為什麼不支持多桌面?
Windows 8 在中國銷量不佳,有哪些原因?
怎麼在windows上用keynote演示?

TAG:MicrosoftWindows | Python | 計算機科學 |